diff --git a/.eslintrc.js b/.eslintrc.js index 8d72d2e2dacf9..c328cb1618bb6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -572,7 +572,6 @@ module.exports = { { files: [ 'test/functional/services/lib/web_element_wrapper/scroll_into_view_if_necessary.js', - 'src/legacy/ui/ui_render/bootstrap/kbn_bundles_loader_source.js', '**/browser_exec_scripts/**/*.js', ], rules: { diff --git a/.stylelintrc b/.stylelintrc index 26431cfee6f1d..bc0e4a6c6c04c 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -33,6 +33,7 @@ rules: - return - for - at-root + - warn comment-no-empty: true no-duplicate-at-import-rules: true no-duplicate-selectors: true diff --git a/api_docs/actions.json b/api_docs/actions.json index fb9bafd6def94..ec2bd86581f32 100644 --- a/api_docs/actions.json +++ b/api_docs/actions.json @@ -127,7 +127,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 70 + "lineNumber": 63 } }, { @@ -138,7 +138,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 71 + "lineNumber": 64 } }, { @@ -149,7 +149,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 72 + "lineNumber": 65 } }, { @@ -160,7 +160,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 73 + "lineNumber": 66 }, "signature": [ "Config | undefined" @@ -174,13 +174,13 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 74 + "lineNumber": 67 } } ], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 69 + "lineNumber": 62 }, "initialIsOpen": false }, @@ -199,7 +199,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 47 + "lineNumber": 40 }, "signature": [ "() => ", @@ -220,7 +220,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 48 + "lineNumber": 41 }, "signature": [ "() => ", @@ -237,7 +237,7 @@ ], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 46 + "lineNumber": 39 }, "initialIsOpen": false }, @@ -256,7 +256,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 56 + "lineNumber": 49 }, "signature": [ { @@ -276,7 +276,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 57 + "lineNumber": 50 }, "signature": [ { @@ -291,7 +291,7 @@ ], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 55 + "lineNumber": 48 }, "initialIsOpen": false }, @@ -320,7 +320,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 108 + "lineNumber": 101 } }, { @@ -331,7 +331,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 109 + "lineNumber": 102 } }, { @@ -342,7 +342,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 110 + "lineNumber": 103 }, "signature": [ "number | undefined" @@ -356,7 +356,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 111 + "lineNumber": 104 }, "signature": [ "\"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\"" @@ -370,7 +370,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 112 + "lineNumber": 105 }, "signature": [ "{ params?: ValidatorType | undefined; config?: ValidatorType | undefined; secrets?: ValidatorType | undefined; } | undefined" @@ -395,7 +395,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 117 + "lineNumber": 110 } }, { @@ -408,7 +408,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 117 + "lineNumber": 110 } } ], @@ -416,7 +416,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 117 + "lineNumber": 110 } }, { @@ -427,7 +427,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 118 + "lineNumber": 111 }, "signature": [ { @@ -443,7 +443,7 @@ ], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 102 + "lineNumber": 95 }, "initialIsOpen": false }, @@ -472,7 +472,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 62 + "lineNumber": 55 } }, { @@ -483,7 +483,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 63 + "lineNumber": 56 }, "signature": [ { @@ -503,7 +503,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 64 + "lineNumber": 57 }, "signature": [ "Config" @@ -517,7 +517,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 65 + "lineNumber": 58 }, "signature": [ "Secrets" @@ -531,7 +531,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 66 + "lineNumber": 59 }, "signature": [ "Params" @@ -540,7 +540,7 @@ ], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 61 + "lineNumber": 54 }, "initialIsOpen": false }, @@ -577,7 +577,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 81 + "lineNumber": 74 }, "signature": [ "Secrets" @@ -586,7 +586,7 @@ ], "source": { "path": "x-pack/plugins/actions/server/types.ts", - "lineNumber": 77 + "lineNumber": 70 }, "initialIsOpen": false } @@ -1008,7 +1008,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 86 + "lineNumber": 85 } } ], @@ -1016,13 +1016,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 80 + "lineNumber": 79 } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 79 + "lineNumber": 78 }, "lifecycle": "setup", "initialIsOpen": true @@ -1053,7 +1053,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 91 + "lineNumber": 90 } }, { @@ -1071,13 +1071,13 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 91 + "lineNumber": 90 } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 91 + "lineNumber": 90 } } ], @@ -1085,7 +1085,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 91 + "lineNumber": 90 } }, { @@ -1107,7 +1107,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 93 + "lineNumber": 92 } }, { @@ -1120,7 +1120,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 94 + "lineNumber": 93 } }, { @@ -1138,13 +1138,13 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 95 + "lineNumber": 94 } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 95 + "lineNumber": 94 } } ], @@ -1152,7 +1152,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 92 + "lineNumber": 91 } }, { @@ -1197,7 +1197,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 97 + "lineNumber": 96 } } ], @@ -1205,7 +1205,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 97 + "lineNumber": 96 } }, { @@ -1250,7 +1250,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 98 + "lineNumber": 97 } } ], @@ -1258,7 +1258,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 98 + "lineNumber": 97 } }, { @@ -1269,7 +1269,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 99 + "lineNumber": 98 }, "signature": [ { @@ -1301,7 +1301,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 101 + "lineNumber": 100 } }, { @@ -1314,7 +1314,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 102 + "lineNumber": 101 } }, { @@ -1327,7 +1327,7 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 103 + "lineNumber": 102 } } ], @@ -1335,13 +1335,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 100 + "lineNumber": 99 } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 90 + "lineNumber": 89 }, "lifecycle": "start", "initialIsOpen": true diff --git a/api_docs/case.mdx b/api_docs/case.mdx deleted file mode 100644 index 00a5d3c04060b..0000000000000 --- a/api_docs/case.mdx +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: kibCasePluginApi -slug: /kibana-dev-docs/casePluginApi -title: case -image: https://source.unsplash.com/400x175/?github -summary: API docs for the case plugin -date: 2020-11-16 -tags: ['contributor', 'dev', 'apidocs', 'kibana', 'case'] -warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. ---- - -import caseObj from './case.json'; - -## Server - -### Interfaces - - diff --git a/api_docs/case.json b/api_docs/cases.json similarity index 70% rename from api_docs/case.json rename to api_docs/cases.json index 24f49c97e61ba..8ac2fb86061bd 100644 --- a/api_docs/case.json +++ b/api_docs/cases.json @@ -1,5 +1,5 @@ { - "id": "case", + "id": "cases", "client": { "classes": [], "functions": [], @@ -21,28 +21,28 @@ "children": [ { "tags": [], - "id": "def-server.CaseRequestContext.getCaseClient", + "id": "def-server.CaseRequestContext.getCasesClient", "type": "Function", - "label": "getCaseClient", + "label": "getCasesClient", "description": [], "source": { - "path": "x-pack/plugins/case/server/types.ts", + "path": "x-pack/plugins/cases/server/types.ts", "lineNumber": 14 }, "signature": [ "() => ", { - "pluginId": "case", + "pluginId": "cases", "scope": "server", - "docId": "kibCasePluginApi", - "section": "def-server.CaseClient", - "text": "CaseClient" + "docId": "kibCasesPluginApi", + "section": "def-server.CasesClient", + "text": "CasesClient" } ] } ], "source": { - "path": "x-pack/plugins/case/server/types.ts", + "path": "x-pack/plugins/cases/server/types.ts", "lineNumber": 13 }, "initialIsOpen": false diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx new file mode 100644 index 0000000000000..36429f257d357 --- /dev/null +++ b/api_docs/cases.mdx @@ -0,0 +1,18 @@ +--- +id: kibCasesPluginApi +slug: /kibana-dev-docs/casesPluginApi +title: cases +image: https://source.unsplash.com/400x175/?github +summary: API docs for the cases plugin +date: 2020-11-16 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] +warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. +--- + +import casesObj from './cases.json'; + +## Server + +### Interfaces + + diff --git a/api_docs/core.json b/api_docs/core.json index 3554fe920eb8b..ba8f27d50d13c 100644 --- a/api_docs/core.json +++ b/api_docs/core.json @@ -4242,7 +4242,7 @@ "type": "string", "label": "actionPath", "description": [ - "The path (without the basePath) that the user should be redirect to to address this warning." + "The path (without the basePath) that the user should be redirect to address this warning." ], "source": { "path": "src/core/server/saved_objects/import/types.ts", @@ -6622,14 +6622,6 @@ "label": "asScoped", "signature": [ "(request?: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.FakeRequest", - "text": "FakeRequest" - }, - " | ", { "pluginId": "core", "scope": "server", @@ -6645,6 +6637,14 @@ "section": "def-server.LegacyRequest", "text": "LegacyRequest" }, + " | ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.FakeRequest", + "text": "FakeRequest" + }, " | undefined) => Pick<", { "pluginId": "core", @@ -6664,14 +6664,6 @@ "label": "request", "isRequired": false, "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.FakeRequest", - "text": "FakeRequest" - }, - " | ", { "pluginId": "core", "scope": "server", @@ -6687,6 +6679,14 @@ "section": "def-server.LegacyRequest", "text": "LegacyRequest" }, + " | ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.FakeRequest", + "text": "FakeRequest" + }, " | undefined" ], "description": [ @@ -10346,7 +10346,7 @@ ], "source": { "path": "src/core/server/rendering/types.ts", - "lineNumber": 72 + "lineNumber": 73 }, "signature": [ "boolean | undefined" @@ -10355,7 +10355,7 @@ ], "source": { "path": "src/core/server/rendering/types.ts", - "lineNumber": 67 + "lineNumber": 68 }, "initialIsOpen": false }, @@ -16174,14 +16174,6 @@ }, "signature": [ "{ callAsInternalUser: LegacyAPICaller; asScoped: (request?: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.FakeRequest", - "text": "FakeRequest" - }, - " | ", { "pluginId": "core", "scope": "server", @@ -16197,6 +16189,14 @@ "section": "def-server.LegacyRequest", "text": "LegacyRequest" }, + " | ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.FakeRequest", + "text": "FakeRequest" + }, " | undefined) => Pick; }" ], "initialIsOpen": false @@ -16218,14 +16218,6 @@ }, "signature": [ "{ close: () => void; callAsInternalUser: LegacyAPICaller; asScoped: (request?: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.FakeRequest", - "text": "FakeRequest" - }, - " | ", { "pluginId": "core", "scope": "server", @@ -16241,6 +16233,14 @@ "section": "def-server.LegacyRequest", "text": "LegacyRequest" }, + " | ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.FakeRequest", + "text": "FakeRequest" + }, " | undefined) => Pick; }" ], "initialIsOpen": false @@ -16533,14 +16533,6 @@ "lineNumber": 192 }, "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.FakeRequest", - "text": "FakeRequest" - }, - " | ", { "pluginId": "core", "scope": "server", @@ -16555,6 +16547,14 @@ "docId": "kibCoreHttpPluginApi", "section": "def-server.LegacyRequest", "text": "LegacyRequest" + }, + " | ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.FakeRequest", + "text": "FakeRequest" } ], "initialIsOpen": false diff --git a/api_docs/core_application.json b/api_docs/core_application.json index 9a61c915614a5..e5bbbf1ca3b58 100644 --- a/api_docs/core_application.json +++ b/api_docs/core_application.json @@ -1638,7 +1638,7 @@ "deprecated" ], "description": [ - "\nA handler that will be executed before leaving the application, either when\ngoing to another application or when closing the browser tab or manually changing\nthe url.\nShould return `confirm` to to prompt a message to the user before leaving the page, or `default`\nto keep the default behavior (doing nothing).\n\nSee {@link AppMountParameters} for detailed usage examples.\n" + "\nA handler that will be executed before leaving the application, either when\ngoing to another application or when closing the browser tab or manually changing\nthe url.\nShould return `confirm` to prompt a message to the user before leaving the page, or `default`\nto keep the default behavior (doing nothing).\n\nSee {@link AppMountParameters} for detailed usage examples.\n" ], "source": { "path": "src/core/public/application/types.ts", diff --git a/api_docs/core_http.json b/api_docs/core_http.json index 39943b40a2fd8..8053550cc0e80 100644 --- a/api_docs/core_http.json +++ b/api_docs/core_http.json @@ -2887,7 +2887,7 @@ "type": "Function", "label": "notHandled", "description": [ - "\nUser has no credentials.\nAllows user to access a resource when authRequired: 'optional'\nRejects a request when authRequired: true" + "\nUser has no credentials.\nAllows user to access a resource when authRequired is 'optional'\nRejects a request when authRequired: true" ], "source": { "path": "src/core/server/http/lifecycle/auth.ts", @@ -4732,7 +4732,7 @@ "type": "CompoundType", "label": "authRequired", "description": [ - "\nDefines authentication mode for a route:\n- true. A user has to have valid credentials to access a resource\n- false. A user can access a resource without any credentials.\n- 'optional'. A user can access a resource if has valid credentials or no credentials at all.\nCan be useful when we grant access to a resource but want to identify a user if possible.\n\nDefaults to `true` if an auth mechanism is registered." + "\nDefines authentication mode for a route:\n- true. A user has to have valid credentials to access a resource\n- false. A user can access a resource without any credentials.\n- 'optional'. A user can access a resource, and will be authenticated if provided credentials are valid.\n Can be useful when we grant access to a resource but want to identify a user if possible.\n\nDefaults to `true` if an auth mechanism is registered." ], "source": { "path": "src/core/server/http/router/route.ts", diff --git a/api_docs/core_saved_objects.json b/api_docs/core_saved_objects.json index f5d60d05094f1..d862df7ef10bb 100644 --- a/api_docs/core_saved_objects.json +++ b/api_docs/core_saved_objects.json @@ -10080,7 +10080,7 @@ "type": "string", "label": "actionPath", "description": [ - "The path (without the basePath) that the user should be redirect to to address this warning." + "The path (without the basePath) that the user should be redirect to address this warning." ], "source": { "path": "src/core/server/saved_objects/import/types.ts", diff --git a/api_docs/data.json b/api_docs/data.json index 612b911915f9d..7989768e180ce 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -23950,37 +23950,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "id": "def-common.doesKueryExpressionHaveLuceneSyntaxError", - "type": "Function", - "children": [ - { - "type": "Any", - "label": "expression", - "isRequired": true, - "signature": [ - "any" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 60 - } - } - ], - "signature": [ - "(expression: any) => boolean" - ], - "description": [], - "label": "doesKueryExpressionHaveLuceneSyntaxError", - "source": { - "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 59 - }, - "tags": [], - "returnComment": [], - "initialIsOpen": false - }, { "id": "def-common.enableFilter", "type": "Function", @@ -25761,7 +25730,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 78 + "lineNumber": 67 } }, { @@ -25781,7 +25750,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 79 + "lineNumber": 68 } }, { @@ -25794,7 +25763,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 80 + "lineNumber": 69 } }, { @@ -25807,7 +25776,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 81 + "lineNumber": 70 } } ], @@ -25841,7 +25810,7 @@ "label": "toElasticsearchQuery", "source": { "path": "src/plugins/data/common/es_query/kuery/ast/ast.ts", - "lineNumber": 77 + "lineNumber": 66 }, "tags": [ "params" @@ -26395,17 +26364,6 @@ "lineNumber": 23 } }, - { - "tags": [], - "id": "def-common.KueryParseOptions.errorOnLuceneSyntax", - "type": "boolean", - "label": "errorOnLuceneSyntax", - "description": [], - "source": { - "path": "src/plugins/data/common/es_query/kuery/types.ts", - "lineNumber": 24 - } - }, { "tags": [], "id": "def-common.KueryParseOptions.cursorSymbol", @@ -26414,7 +26372,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/kuery/types.ts", - "lineNumber": 25 + "lineNumber": 24 }, "signature": [ "string | undefined" @@ -26428,7 +26386,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/kuery/types.ts", - "lineNumber": 26 + "lineNumber": 25 }, "signature": [ "boolean | undefined" diff --git a/api_docs/data_search.json b/api_docs/data_search.json index bcf9e8819f347..0bdfcadd338ea 100644 --- a/api_docs/data_search.json +++ b/api_docs/data_search.json @@ -1435,7 +1435,15 @@ "section": "def-server.SearchUsage", "text": "SearchUsage" }, - " | undefined) => { next(response: ", + " | undefined, { isRestore }: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ISearchOptions", + "text": "ISearchOptions" + }, + ") => { next(response: ", { "pluginId": "data", "scope": "common", @@ -1459,7 +1467,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/collectors/usage.ts", - "lineNumber": 64 + "lineNumber": 83 } }, { @@ -1479,7 +1487,26 @@ "description": [], "source": { "path": "src/plugins/data/server/search/collectors/usage.ts", - "lineNumber": 64 + "lineNumber": 84 + } + }, + { + "type": "Object", + "label": "{ isRestore }", + "isRequired": true, + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ISearchOptions", + "text": "ISearchOptions" + } + ], + "description": [], + "source": { + "path": "src/plugins/data/server/search/collectors/usage.ts", + "lineNumber": 85 } } ], @@ -1487,7 +1514,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/search/collectors/usage.ts", - "lineNumber": 64 + "lineNumber": 82 }, "initialIsOpen": false }, diff --git a/api_docs/lens.json b/api_docs/lens.json index 7e30ec6a15c3e..1c7581a8a1db6 100644 --- a/api_docs/lens.json +++ b/api_docs/lens.json @@ -107,7 +107,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/datatable_visualization/visualization.tsx", - "lineNumber": 35 + "lineNumber": 43 }, "signature": [ { @@ -128,7 +128,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/datatable_visualization/visualization.tsx", - "lineNumber": 36 + "lineNumber": 44 } }, { @@ -139,7 +139,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/datatable_visualization/visualization.tsx", - "lineNumber": 37 + "lineNumber": 45 }, "signature": [ { @@ -155,7 +155,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/datatable_visualization/visualization.tsx", - "lineNumber": 34 + "lineNumber": 42 }, "initialIsOpen": false }, diff --git a/api_docs/lists.json b/api_docs/lists.json index 077ea74fdff28..3e6a22c538504 100644 --- a/api_docs/lists.json +++ b/api_docs/lists.json @@ -3706,7 +3706,83 @@ }, "common": { "classes": [], - "functions": [], + "functions": [ + { + "id": "def-common.buildExceptionFilter", + "type": "Function", + "children": [ + { + "id": "def-common.buildExceptionFilter.{\n- lists,\n excludeExceptions,\n chunkSize,\n}", + "type": "Object", + "label": "{\n lists,\n excludeExceptions,\n chunkSize,\n}", + "tags": [], + "description": [], + "children": [ + { + "tags": [], + "id": "def-common.buildExceptionFilter.{\n- lists,\n excludeExceptions,\n chunkSize,\n}.lists", + "type": "Array", + "label": "lists", + "description": [], + "source": { + "path": "x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts", + "lineNumber": 74 + }, + "signature": [ + "(({ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"text\" | \"keyword\" | \"ip\" | \"long\" | \"double\" | \"date_nanos\" | \"geo_point\" | \"geo_shape\" | \"short\" | \"binary\" | \"date_range\" | \"ip_range\" | \"shape\" | \"integer\" | \"byte\" | \"float\" | \"double_range\" | \"float_range\" | \"half_float\" | \"integer_range\" | \"long_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; })[]; list_id: string; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }) | { _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"text\" | \"keyword\" | \"ip\" | \"long\" | \"double\" | \"date_nanos\" | \"geo_point\" | \"geo_shape\" | \"short\" | \"binary\" | \"date_range\" | \"ip_range\" | \"shape\" | \"integer\" | \"byte\" | \"float\" | \"double_range\" | \"float_range\" | \"half_float\" | \"integer_range\" | \"long_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; })[]" + ] + }, + { + "tags": [], + "id": "def-common.buildExceptionFilter.{\n- lists,\n excludeExceptions,\n chunkSize,\n}.excludeExceptions", + "type": "boolean", + "label": "excludeExceptions", + "description": [], + "source": { + "path": "x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts", + "lineNumber": 75 + } + }, + { + "tags": [], + "id": "def-common.buildExceptionFilter.{\n- lists,\n excludeExceptions,\n chunkSize,\n}.chunkSize", + "type": "number", + "label": "chunkSize", + "description": [], + "source": { + "path": "x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts", + "lineNumber": 76 + } + } + ], + "source": { + "path": "x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts", + "lineNumber": 73 + } + } + ], + "signature": [ + "({ lists, excludeExceptions, chunkSize, }: { lists: (({ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"text\" | \"keyword\" | \"ip\" | \"long\" | \"double\" | \"date_nanos\" | \"geo_point\" | \"geo_shape\" | \"short\" | \"binary\" | \"date_range\" | \"ip_range\" | \"shape\" | \"integer\" | \"byte\" | \"float\" | \"double_range\" | \"float_range\" | \"half_float\" | \"integer_range\" | \"long_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; })[]; list_id: string; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }) | { _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"text\" | \"keyword\" | \"ip\" | \"long\" | \"double\" | \"date_nanos\" | \"geo_point\" | \"geo_shape\" | \"short\" | \"binary\" | \"date_range\" | \"ip_range\" | \"shape\" | \"integer\" | \"byte\" | \"float\" | \"double_range\" | \"float_range\" | \"half_float\" | \"integer_range\" | \"long_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; })[]; excludeExceptions: boolean; chunkSize: number; }) => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | undefined" + ], + "description": [], + "label": "buildExceptionFilter", + "source": { + "path": "x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts", + "lineNumber": 69 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [], "enums": [ { @@ -4413,6 +4489,22 @@ ], "initialIsOpen": false }, + { + "tags": [], + "id": "def-common.osType", + "type": "Object", + "label": "osType", + "description": [], + "source": { + "path": "x-pack/plugins/lists/common/schemas/common/schemas.ts", + "lineNumber": 317 + }, + "signature": [ + "KeyofC", + "<{ linux: null; macos: null; windows: null; }>" + ], + "initialIsOpen": false + }, { "tags": [], "id": "def-common.osTypeArray", diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 8e4a8efb7c24e..5d5f771548355 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -41,6 +41,9 @@ import listsObj from './lists.json'; ### Objects +### Functions + + ### Enums diff --git a/api_docs/ml.json b/api_docs/ml.json index f626cf8dc3ba0..3a3463b7397e2 100644 --- a/api_docs/ml.json +++ b/api_docs/ml.json @@ -546,7 +546,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/modules.ts", - "lineNumber": 80 + "lineNumber": 94 }, "signature": [ { @@ -567,7 +567,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/modules.ts", - "lineNumber": 81 + "lineNumber": 95 }, "signature": [ { @@ -588,7 +588,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/modules.ts", - "lineNumber": 82 + "lineNumber": 96 }, "signature": [ "{ search: ", @@ -621,7 +621,7 @@ ], "source": { "path": "x-pack/plugins/ml/common/types/modules.ts", - "lineNumber": 79 + "lineNumber": 93 }, "initialIsOpen": false }, diff --git a/api_docs/saved_objects.json b/api_docs/saved_objects.json index e4b71b23a047e..a3bc4b059c712 100644 --- a/api_docs/saved_objects.json +++ b/api_docs/saved_objects.json @@ -220,11 +220,11 @@ { "id": "def-public.SavedObjectLoader", "type": "Class", - "tags": [], - "label": "SavedObjectLoader", - "description": [ - "\nThe SavedObjectLoader class provides some convenience functions\nto load and save one kind of saved objects (specified in the constructor).\n\nIt is based on the SavedObjectClient which implements loading and saving\nin an abstract, type-agnostic way. If possible, use SavedObjectClient directly\nto avoid pulling in extra functionality which isn't used." + "tags": [ + "deprecated" ], + "label": "SavedObjectLoader", + "description": [], "children": [ { "tags": [], @@ -234,7 +234,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 34 + "lineNumber": 35 } }, { @@ -245,7 +245,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 35 + "lineNumber": 36 } }, { @@ -256,7 +256,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 36 + "lineNumber": 37 }, "signature": [ "Record" @@ -281,7 +281,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 39 + "lineNumber": 40 } }, { @@ -302,7 +302,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 40 + "lineNumber": 41 } } ], @@ -310,7 +310,7 @@ "returnComment": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 38 + "lineNumber": 39 } }, { @@ -334,7 +334,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 60 + "lineNumber": 61 } } ], @@ -342,7 +342,7 @@ "returnComment": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 60 + "lineNumber": 61 } }, { @@ -364,7 +364,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 68 + "lineNumber": 69 } } ], @@ -372,7 +372,7 @@ "returnComment": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 68 + "lineNumber": 69 } }, { @@ -394,7 +394,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 72 + "lineNumber": 73 } } ], @@ -402,7 +402,7 @@ "returnComment": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 72 + "lineNumber": 73 } }, { @@ -434,7 +434,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 92 + "lineNumber": 93 } }, { @@ -447,7 +447,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 93 + "lineNumber": 94 } }, { @@ -467,7 +467,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 94 + "lineNumber": 95 } } ], @@ -477,7 +477,7 @@ ], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 91 + "lineNumber": 92 } }, { @@ -514,7 +514,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 113 + "lineNumber": 114 }, "signature": [ "Record" @@ -528,7 +528,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 114 + "lineNumber": 115 } }, { @@ -539,7 +539,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 115 + "lineNumber": 116 }, "signature": [ { @@ -555,7 +555,7 @@ ], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 112 + "lineNumber": 113 } } ], @@ -565,7 +565,7 @@ ], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 108 + "lineNumber": 109 } }, { @@ -595,7 +595,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 152 + "lineNumber": 153 } }, { @@ -615,7 +615,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 152 + "lineNumber": 153 } } ], @@ -623,20 +623,22 @@ "returnComment": [], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 152 + "lineNumber": 153 } } ], "source": { "path": "src/plugins/saved_objects/public/saved_object/saved_object_loader.ts", - "lineNumber": 32 + "lineNumber": 33 }, "initialIsOpen": false }, { "id": "def-public.SavedObjectSaveModal", "type": "Class", - "tags": [], + "tags": [ + "deprecated" + ], "label": "SavedObjectSaveModal", "description": [], "signature": [ @@ -671,7 +673,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 72 + "lineNumber": 73 } }, { @@ -682,7 +684,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 73 + "lineNumber": 74 } }, { @@ -693,7 +695,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 74 + "lineNumber": 75 } }, { @@ -704,7 +706,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 75 + "lineNumber": 76 } }, { @@ -715,7 +717,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 76 + "lineNumber": 77 } }, { @@ -726,7 +728,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 77 + "lineNumber": 78 } } ], @@ -734,7 +736,7 @@ "label": "state", "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 71 + "lineNumber": 72 } }, { @@ -750,13 +752,13 @@ "returnComment": [], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 80 + "lineNumber": 81 } } ], "source": { "path": "src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx", - "lineNumber": 69 + "lineNumber": 70 }, "initialIsOpen": false } @@ -1517,7 +1519,9 @@ "type": "Interface", "label": "SavedObject", "description": [], - "tags": [], + "tags": [ + "deprecated" + ], "children": [ { "tags": [], @@ -1527,7 +1531,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 25 + "lineNumber": 26 }, "signature": [ "() => { attributes: ", @@ -1557,7 +1561,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 26 + "lineNumber": 27 }, "signature": [ "Record" @@ -1571,7 +1575,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 27 + "lineNumber": 28 }, "signature": [ "(resp: Record) => Promise<", @@ -1593,7 +1597,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 28 + "lineNumber": 29 } }, { @@ -1604,7 +1608,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 29 + "lineNumber": 30 }, "signature": [ "(opts: ", @@ -1626,7 +1630,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 30 + "lineNumber": 31 }, "signature": [ "any" @@ -1640,7 +1644,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 31 + "lineNumber": 32 }, "signature": [ "(() => Promise<{}>) | undefined" @@ -1654,7 +1658,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 32 + "lineNumber": 33 }, "signature": [ "() => void" @@ -1668,7 +1672,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 33 + "lineNumber": 34 }, "signature": [ "() => string" @@ -1682,7 +1686,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 34 + "lineNumber": 35 }, "signature": [ "() => string" @@ -1696,7 +1700,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 35 + "lineNumber": 36 }, "signature": [ "() => string" @@ -1710,7 +1714,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 36 + "lineNumber": 37 }, "signature": [ "((id?: string | undefined) => Promise<", @@ -1732,7 +1736,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 37 + "lineNumber": 38 }, "signature": [ "string | undefined" @@ -1746,7 +1750,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 38 + "lineNumber": 39 }, "signature": [ "(() => Promise<", @@ -1768,7 +1772,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 39 + "lineNumber": 40 } }, { @@ -1779,7 +1783,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 40 + "lineNumber": 41 }, "signature": [ "() => boolean" @@ -1793,7 +1797,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 41 + "lineNumber": 42 } }, { @@ -1804,7 +1808,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 42 + "lineNumber": 43 }, "signature": [ "Record | undefined" @@ -1818,7 +1822,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 43 + "lineNumber": 44 }, "signature": [ "(saveOptions: ", @@ -1840,7 +1844,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 44 + "lineNumber": 45 }, "signature": [ "Pick<", @@ -1862,7 +1866,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 45 + "lineNumber": 46 }, "signature": [ { @@ -1883,7 +1887,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 46 + "lineNumber": 47 } }, { @@ -1894,7 +1898,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 47 + "lineNumber": 48 } }, { @@ -1905,7 +1909,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 48 + "lineNumber": 49 }, "signature": [ { @@ -1921,7 +1925,7 @@ ], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 24 + "lineNumber": 25 }, "initialIsOpen": false }, @@ -1940,7 +1944,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 78 + "lineNumber": 79 }, "signature": [ "((savedObject: ", @@ -1970,7 +1974,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 79 + "lineNumber": 80 }, "signature": [ "any" @@ -1984,7 +1988,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 80 + "lineNumber": 81 }, "signature": [ "((opts: ", @@ -2014,7 +2018,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 81 + "lineNumber": 82 }, "signature": [ "( void) | undefined" @@ -2072,7 +2076,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 84 + "lineNumber": 85 }, "signature": [ { @@ -2093,7 +2097,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 85 + "lineNumber": 86 }, "signature": [ "Record | undefined" @@ -2107,7 +2111,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 86 + "lineNumber": 87 }, "signature": [ "Record | undefined" @@ -2121,7 +2125,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 87 + "lineNumber": 88 }, "signature": [ "string | undefined" @@ -2135,7 +2139,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 88 + "lineNumber": 89 }, "signature": [ "boolean | Pick<", @@ -2157,7 +2161,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 89 + "lineNumber": 90 }, "signature": [ "string | undefined" @@ -2166,7 +2170,7 @@ ], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 76 + "lineNumber": 77 }, "initialIsOpen": false }, @@ -2599,7 +2603,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 52 + "lineNumber": 53 }, "signature": [ "boolean | undefined" @@ -2613,7 +2617,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 53 + "lineNumber": 54 }, "signature": [ "boolean | undefined" @@ -2627,7 +2631,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 54 + "lineNumber": 55 }, "signature": [ "(() => void) | undefined" @@ -2641,7 +2645,7 @@ "description": [], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 55 + "lineNumber": 56 }, "signature": [ "boolean | undefined" @@ -2650,7 +2654,7 @@ ], "source": { "path": "src/plugins/saved_objects/public/types.ts", - "lineNumber": 51 + "lineNumber": 52 }, "initialIsOpen": false }, @@ -2819,14 +2823,16 @@ "tags": [], "children": [ { - "tags": [], + "tags": [ + "deprecated" + ], "id": "def-public.SavedObjectsStart.SavedObjectClass", "type": "Object", "label": "SavedObjectClass", "description": [], "source": { "path": "src/plugins/saved_objects/public/plugin.ts", - "lineNumber": 26 + "lineNumber": 27 }, "signature": [ "new (raw: Record) => ", @@ -2840,14 +2846,16 @@ ] }, { - "tags": [], + "tags": [ + "deprecated" + ], "id": "def-public.SavedObjectsStart.settings", "type": "Object", "label": "settings", "description": [], "source": { "path": "src/plugins/saved_objects/public/plugin.ts", - "lineNumber": 27 + "lineNumber": 29 }, "signature": [ "{ getPerPage: () => number; getListingLimit: () => number; }" diff --git a/api_docs/saved_objects_tagging.json b/api_docs/saved_objects_tagging.json index 421b6789ec7dd..0372b39ba242d 100644 --- a/api_docs/saved_objects_tagging.json +++ b/api_docs/saved_objects_tagging.json @@ -751,7 +751,7 @@ "type": "string", "label": "tagFeatureId", "description": [ - "\nThe id of the tagging feature as registered to to `features` plugin" + "\nThe id of the tagging feature as registered to `features` plugin" ], "source": { "path": "x-pack/plugins/saved_objects_tagging/common/constants.ts", diff --git a/api_docs/security.json b/api_docs/security.json index 5c605debf1908..8e1214654a82f 100644 --- a/api_docs/security.json +++ b/api_docs/security.json @@ -445,6 +445,20 @@ "signature": [ "number | undefined" ] + }, + { + "tags": [], + "id": "def-public.UserMenuLink.setAsProfile", + "type": "CompoundType", + "label": "setAsProfile", + "description": [], + "source": { + "path": "x-pack/plugins/security/public/nav_control/nav_control_component.tsx", + "lineNumber": 33 + }, + "signature": [ + "boolean | undefined" + ] } ], "source": { diff --git a/api_docs/security_solution.json b/api_docs/security_solution.json index 01fef476278b9..cbcd660749f2d 100644 --- a/api_docs/security_solution.json +++ b/api_docs/security_solution.json @@ -607,7 +607,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 336 + "lineNumber": 337 } }, { @@ -626,7 +626,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 336 + "lineNumber": 337 } } ], @@ -634,7 +634,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 336 + "lineNumber": 337 } }, { @@ -650,7 +650,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 397 + "lineNumber": 398 } } ], diff --git a/api_docs/vis_type_timeseries.json b/api_docs/vis_type_timeseries.json index 657e9a560060e..907ced500294a 100644 --- a/api_docs/vis_type_timeseries.json +++ b/api_docs/vis_type_timeseries.json @@ -30,7 +30,7 @@ "description": [], "source": { "path": "src/plugins/vis_type_timeseries/server/plugin.ts", - "lineNumber": 48 + "lineNumber": 53 }, "signature": [ "(requestContext: ", @@ -45,19 +45,11 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.FakeRequest", - "text": "FakeRequest" + "docId": "kibCoreHttpPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" }, - ", options: ", - { - "pluginId": "visTypeTimeseries", - "scope": "server", - "docId": "kibVisTypeTimeseriesPluginApi", - "section": "def-server.GetVisDataOptions", - "text": "GetVisDataOptions" - }, - ") => Promise<", + ", options: any) => Promise<", { "pluginId": "visTypeTimeseries", "scope": "common", @@ -71,7 +63,7 @@ ], "source": { "path": "src/plugins/vis_type_timeseries/server/plugin.ts", - "lineNumber": 47 + "lineNumber": 52 }, "lifecycle": "setup", "initialIsOpen": true diff --git a/api_docs/visualizations.json b/api_docs/visualizations.json index 2357e8b12608b..d66aa8b8cbe67 100644 --- a/api_docs/visualizations.json +++ b/api_docs/visualizations.json @@ -476,7 +476,8 @@ "docId": "kibVisualizationsPluginApi", "section": "def-public.SerializedVis", "text": "SerializedVis" - } + }, + "" ], "description": [], "source": { @@ -505,7 +506,15 @@ "section": "def-public.SerializedVis", "text": "SerializedVis" }, - ", \"type\" | \"id\" | \"description\" | \"title\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + ">, \"type\" | \"id\" | \"description\" | \"title\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", { "pluginId": "visualizations", "scope": "public", @@ -538,7 +547,15 @@ "section": "def-public.SerializedVis", "text": "SerializedVis" }, - ", \"type\" | \"id\" | \"description\" | \"title\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + ">, \"type\" | \"id\" | \"description\" | \"title\" | \"params\" | \"uiState\"> & Pick<{ data: Partial<", { "pluginId": "visualizations", "scope": "public", @@ -583,15 +600,7 @@ "section": "def-public.Vis", "text": "Vis" }, - "<", - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisParams", - "text": "VisParams" - }, - ">" + "" ], "description": [], "children": [], @@ -614,7 +623,16 @@ "docId": "kibVisualizationsPluginApi", "section": "def-public.SerializedVis", "text": "SerializedVis" - } + }, + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + ">" ], "description": [], "children": [], @@ -1393,6 +1411,16 @@ "id": "def-public.SerializedVis", "type": "Interface", "label": "SerializedVis", + "signature": [ + { + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.SerializedVis", + "text": "SerializedVis" + }, + "" + ], "description": [], "tags": [], "children": [ @@ -1449,7 +1477,7 @@ { "tags": [], "id": "def-public.SerializedVis.params", - "type": "Object", + "type": "Uncategorized", "label": "params", "description": [], "source": { @@ -1457,13 +1485,7 @@ "lineNumber": 47 }, "signature": [ - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisParams", - "text": "VisParams" - } + "T" ] }, { @@ -2922,7 +2944,15 @@ "section": "def-public.SerializedVis", "text": "SerializedVis" }, - " | undefined" + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + "> | undefined" ] }, { @@ -3213,7 +3243,15 @@ "section": "def-public.SerializedVis", "text": "SerializedVis" }, - " | undefined; }, parent?: ", + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + "> | undefined; }, parent?: ", { "pluginId": "embeddable", "scope": "public", @@ -3228,14 +3266,6 @@ "docId": "kibEmbeddablePluginApi", "section": "def-public.ContainerInput", "text": "ContainerInput" - }, - "<{}>, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" } ], "initialIsOpen": false @@ -3433,7 +3463,15 @@ "section": "def-public.SerializedVis", "text": "SerializedVis" }, - ") => Promise<", + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + ">) => Promise<", { "pluginId": "visualizations", "scope": "public", @@ -3478,7 +3516,16 @@ "docId": "kibVisualizationsPluginApi", "section": "def-public.SerializedVis", "text": "SerializedVis" - } + }, + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + ">" ] }, { @@ -3500,7 +3547,15 @@ "section": "def-public.SerializedVis", "text": "SerializedVis" }, - ") => ", + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + ">) => ", { "pluginId": "visualizations", "scope": "public", diff --git a/docs/developer/getting-started/debugging.asciidoc b/docs/developer/getting-started/debugging.asciidoc index 5ddc5dbb861b7..1254462d2e4ea 100644 --- a/docs/developer/getting-started/debugging.asciidoc +++ b/docs/developer/getting-started/debugging.asciidoc @@ -96,7 +96,7 @@ cd ../kibana ---- . Change the elasticsearch credentials in your `kibana.yml` configuration file to match those needed by elasticsearch and the APM server (see the apm-integration-testing repo's https://github.com/elastic/apm-integration-testing#logging-in[README] for users provided to test different scenarios). -. Make sure that the APM agent is active and points to the local APM server by adding the following configuration settings to to a config file under `config/apm.dev.js`: +. Make sure that the APM agent is active and points to the local APM server by adding the following configuration settings to a config file under `config/apm.dev.js`: + Example `config/apm.dev.js` file: + diff --git a/docs/developer/index.asciidoc b/docs/developer/index.asciidoc index 9e349a38557f2..86d1d32e75e36 100644 --- a/docs/developer/index.asciidoc +++ b/docs/developer/index.asciidoc @@ -12,6 +12,7 @@ running in no time. If you have any problems, file an issue in the https://githu * <> * <> * <> +* <> -- @@ -29,3 +30,5 @@ include::advanced/index.asciidoc[] include::plugin-list.asciidoc[] +include::telemetry.asciidoc[] + diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 877026ef313ba..c820c143bdcdf 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -165,7 +165,7 @@ Content is fetched from the remote (https://feeds.elastic.co and https://feeds-s |{kib-repo}blob/{branch}/src/plugins/saved_objects/README.md[savedObjects] -|The savedObjects plugin exposes utilities to manipulate saved objects on the client side. +|NOTE: This plugin is deprecated and will be removed in 8.0. See https://github.com/elastic/kibana/issues/46435 for more information. |{kib-repo}blob/{branch}/src/plugins/saved_objects_management/README.md[savedObjectsManagement] @@ -320,7 +320,7 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error |"Never look back. The past is done. The future is a blank canvas." ― Suzy Kassem, Rise Up and Salute the Sun -|{kib-repo}blob/{branch}/x-pack/plugins/case/README.md[case] +|{kib-repo}blob/{branch}/x-pack/plugins/cases/README.md[cases] |Experimental Feature diff --git a/docs/developer/telemetry.asciidoc b/docs/developer/telemetry.asciidoc new file mode 100644 index 0000000000000..75f42a860624b --- /dev/null +++ b/docs/developer/telemetry.asciidoc @@ -0,0 +1,18 @@ +[[development-telemetry]] +== Development Telemetry + +To help us provide a good developer experience, we track some straightforward metrics when running certain tasks locally and ship them to a service that we run. To disable this functionality, specify `CI_STATS_DISABLED=true` in your environment. + +The operations we current report timing data for: + +* Total execution time of `yarn kbn bootstrap` + +Along with the execution time of each execution, we ship the following information about your machine to the service: + +* The `branch` property from the package.json file +* The value of the `data/uuid` file +* https://nodejs.org/docs/latest/api/os.html#os_os_platform[Operating system platform] +* https://nodejs.org/docs/latest/api/os.html#os_os_release[Operating system release] +* https://nodejs.org/docs/latest/api/os.html#os_os_cpus[Count, model, and speed of the CPUs] +* https://nodejs.org/docs/latest/api/os.html#os_os_arch[CPU architecture] +* https://nodejs.org/docs/latest/api/os.html#os_os_totalmem[Total memory] and https://nodejs.org/docs/latest/api/os.html#os_os_freemem[Free memory] \ No newline at end of file diff --git a/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md b/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md index 2eacdd811f438..7028b3cd36691 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md +++ b/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md @@ -9,7 +9,7 @@ > [AppMountParameters.onAppLeave](./kibana-plugin-core-public.appmountparameters.onappleave.md) has been deprecated in favor of [ScopedHistory.block](./kibana-plugin-core-public.scopedhistory.block.md) > -A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return `confirm` to to prompt a message to the user before leaving the page, or `default` to keep the default behavior (doing nothing). +A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return `confirm` to prompt a message to the user before leaving the page, or `default` to keep the default behavior (doing nothing). See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for detailed usage examples. diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index ba48011ef84e0..e9d08dcd3bf4c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -143,7 +143,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | | [AppLeaveAction](./kibana-plugin-core-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md)See [AppLeaveConfirmAction](./kibana-plugin-core-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | -| [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for detailed usage examples. | +| [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for detailed usage examples. | | [AppMount](./kibana-plugin-core-public.appmount.md) | A mount function called when the user navigates to this app's route. | | [AppSearchDeepLink](./kibana-plugin-core-public.appsearchdeeplink.md) | Input type for registering secondary in-app locations for an application.Deep links must include at least one of path or searchDeepLinks. A deep link that does not have a path represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. | | [AppUnmount](./kibana-plugin-core-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md index 120a9d5f3386c..8752120b27c87 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md @@ -4,7 +4,7 @@ ## SavedObjectsImportActionRequiredWarning.actionPath property -The path (without the basePath) that the user should be redirect to to address this warning. +The path (without the basePath) that the user should be redirect to address this warning. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md index 26d734c39c918..2ecce7233aa57 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md @@ -18,7 +18,7 @@ export interface SavedObjectsImportActionRequiredWarning | Property | Type | Description | | --- | --- | --- | -| [actionPath](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md) | string | The path (without the basePath) that the user should be redirect to to address this warning. | +| [actionPath](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md) | string | The path (without the basePath) that the user should be redirect to address this warning. | | [buttonLabel](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md) | string | An optional label to use for the link button. If unspecified, a default label will be used. | | [message](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md) | string | The translated message to display to the user. | | [type](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md) | 'action_required' | | diff --git a/docs/development/core/server/kibana-plugin-core-server.authtoolkit.md b/docs/development/core/server/kibana-plugin-core-server.authtoolkit.md index 11cbe3d75dc8e..5f8b98ab2e894 100644 --- a/docs/development/core/server/kibana-plugin-core-server.authtoolkit.md +++ b/docs/development/core/server/kibana-plugin-core-server.authtoolkit.md @@ -17,6 +17,6 @@ export interface AuthToolkit | Property | Type | Description | | --- | --- | --- | | [authenticated](./kibana-plugin-core-server.authtoolkit.authenticated.md) | (data?: AuthResultParams) => AuthResult | Authentication is successful with given credentials, allow request to pass through | -| [notHandled](./kibana-plugin-core-server.authtoolkit.nothandled.md) | () => AuthResult | User has no credentials. Allows user to access a resource when authRequired: 'optional' Rejects a request when authRequired: true | +| [notHandled](./kibana-plugin-core-server.authtoolkit.nothandled.md) | () => AuthResult | User has no credentials. Allows user to access a resource when authRequired is 'optional' Rejects a request when authRequired: true | | [redirected](./kibana-plugin-core-server.authtoolkit.redirected.md) | (headers: {
location: string;
} & ResponseHeaders) => AuthResult | Redirects user to another location to complete authentication when authRequired: true Allows user to access a resource without redirection when authRequired: 'optional' | diff --git a/docs/development/core/server/kibana-plugin-core-server.authtoolkit.nothandled.md b/docs/development/core/server/kibana-plugin-core-server.authtoolkit.nothandled.md index b2578ad127f01..577faa6562558 100644 --- a/docs/development/core/server/kibana-plugin-core-server.authtoolkit.nothandled.md +++ b/docs/development/core/server/kibana-plugin-core-server.authtoolkit.nothandled.md @@ -4,7 +4,7 @@ ## AuthToolkit.notHandled property -User has no credentials. Allows user to access a resource when authRequired: 'optional' Rejects a request when authRequired: true +User has no credentials. Allows user to access a resource when authRequired is 'optional' Rejects a request when authRequired: true Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 8dd4667002ead..b4faa4299a929 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -300,7 +300,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttributeSingle](./kibana-plugin-core-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-server.savedobjectattribute.md) | | [SavedObjectMigrationFn](./kibana-plugin-core-server.savedobjectmigrationfn.md) | A migration function for a [saved object type](./kibana-plugin-core-server.savedobjectstype.md) used to migrate it to a given version | | [SavedObjectSanitizedDoc](./kibana-plugin-core-server.savedobjectsanitizeddoc.md) | Describes Saved Object documents that have passed through the migration framework and are guaranteed to have a references root property. | -| [SavedObjectsClientContract](./kibana-plugin-core-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.See [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientContract](./kibana-plugin-core-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.See [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.authrequired.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.authrequired.md index 17e3188a354a1..28f712316bc36 100644 --- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.authrequired.md +++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.authrequired.md @@ -4,7 +4,7 @@ ## RouteConfigOptions.authRequired property -Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource if has valid credentials or no credentials at all. Can be useful when we grant access to a resource but want to identify a user if possible. +Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource, and will be authenticated if provided credentials are valid. Can be useful when we grant access to a resource but want to identify a user if possible. Defaults to `true` if an auth mechanism is registered. diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md index 17fafe2af0de1..cf0fe32c14d1d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md @@ -16,7 +16,7 @@ export interface RouteConfigOptions | Property | Type | Description | | --- | --- | --- | -| [authRequired](./kibana-plugin-core-server.routeconfigoptions.authrequired.md) | boolean | 'optional' | Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource if has valid credentials or no credentials at all. Can be useful when we grant access to a resource but want to identify a user if possible.Defaults to true if an auth mechanism is registered. | +| [authRequired](./kibana-plugin-core-server.routeconfigoptions.authrequired.md) | boolean | 'optional' | Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource, and will be authenticated if provided credentials are valid. Can be useful when we grant access to a resource but want to identify a user if possible.Defaults to true if an auth mechanism is registered. | | [body](./kibana-plugin-core-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody | Additional body options [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md). | | [tags](./kibana-plugin-core-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | | [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | {
payload?: Method extends 'get' | 'options' ? undefined : number;
idleSocket?: number;
} | Defines per-route timeouts. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md index 610356a733126..537cfbc175671 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md @@ -8,7 +8,7 @@ Saved Objects is Kibana's data persisentence mechanism allowing plugins to use E \#\# SavedObjectsClient errors -Since the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either: +Since the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to application code. Ideally, all errors will be either: 1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md index 4ec70301d2ebe..f52fd9057d9d0 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md @@ -4,7 +4,7 @@ ## SavedObjectsImportActionRequiredWarning.actionPath property -The path (without the basePath) that the user should be redirect to to address this warning. +The path (without the basePath) that the user should be redirect to address this warning. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md index ba1e905344af1..c8b96004662d4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md @@ -18,7 +18,7 @@ export interface SavedObjectsImportActionRequiredWarning | Property | Type | Description | | --- | --- | --- | -| [actionPath](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md) | string | The path (without the basePath) that the user should be redirect to to address this warning. | +| [actionPath](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md) | string | The path (without the basePath) that the user should be redirect to address this warning. | | [buttonLabel](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md) | string | An optional label to use for the link button. If unspecified, a default label will be used. | | [message](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md) | string | The translated message to display to the user. | | [type](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md) | 'action_required' | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 491babcdfdecf..fd9ed1e8f635c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -35,7 +35,7 @@ | [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-server.gettime.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | -| [searchUsageObserver(logger, usage)](./kibana-plugin-plugins-data-server.searchusageobserver.md) | Rxjs observer for easily doing tap(searchUsageObserver(logger, usage)) in an rxjs chain. | +| [searchUsageObserver(logger, usage, { isRestore })](./kibana-plugin-plugins-data-server.searchusageobserver.md) | Rxjs observer for easily doing tap(searchUsageObserver(logger, usage)) in an rxjs chain. | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | | [usageProvider(core)](./kibana-plugin-plugins-data-server.usageprovider.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md index 5e03bb381527e..e9c7b33766e56 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md @@ -9,7 +9,7 @@ Rxjs observer for easily doing `tap(searchUsageObserver(logger, usage))` in an r Signature: ```typescript -export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage): { +export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage, { isRestore }?: ISearchOptions): { next(response: IEsSearchResponse): void; error(): void; }; @@ -21,6 +21,7 @@ export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage) | --- | --- | --- | | logger | Logger | | | usage | SearchUsage | | +| { isRestore } | ISearchOptions | | Returns: diff --git a/docs/management/upgrade-assistant/index.asciidoc b/docs/management/upgrade-assistant/index.asciidoc index 61df6457a9bde..209b55faf4f56 100644 --- a/docs/management/upgrade-assistant/index.asciidoc +++ b/docs/management/upgrade-assistant/index.asciidoc @@ -11,7 +11,7 @@ and guides you through the process of resolving issues, including reindexing. Before you upgrade, make sure that you are using the latest released minor version of {es} to see the most up-to-date deprecation issues. -For example, if you want to upgrade to to 7.0, make sure that you are using 6.8. +For example, if you want to upgrade to 7.0, make sure that you are using 6.8. [float] === Required permissions diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 8c16c76c62569..cef5a953fded4 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -88,7 +88,7 @@ reports, you might need to change the following settings. Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`. | `xpack.reporting.queue.pollEnabled` {ess-icon} - | Set to `true` (default) to enable the {kib} instance to to poll the index for + | Set to `true` (default) to enable the {kib} instance to poll the index for pending jobs and claim them for execution. Setting this to `false` allows the {kib} instance to only add new jobs to the reporting queue, list jobs, and provide the downloads to completed report through the UI. diff --git a/docs/user/graph/troubleshooting.asciidoc b/docs/user/graph/troubleshooting.asciidoc index 3819d99036f98..eaac105c57358 100644 --- a/docs/user/graph/troubleshooting.asciidoc +++ b/docs/user/graph/troubleshooting.asciidoc @@ -30,7 +30,7 @@ of any statistical correlation with the sample. required to assert a relationship. [discrete] -=== What can I do to to improve performance? +=== What can I do to improve performance? With the default setting of `use_significance` set to `true`, the Graph API performs a background frequency check of the terms it discovers as part of diff --git a/docs/user/production-considerations/task-manager-production-considerations.asciidoc b/docs/user/production-considerations/task-manager-production-considerations.asciidoc index 39835919a7fd4..606f113b2274f 100644 --- a/docs/user/production-considerations/task-manager-production-considerations.asciidoc +++ b/docs/user/production-considerations/task-manager-production-considerations.asciidoc @@ -12,11 +12,11 @@ This has three major benefits: [IMPORTANT] ============================================== - Task definitions for alerts and actions are stored in the index specified by <>. - The default is `.kibana_task_manager`. +Task definitions for alerts and actions are stored in the index specified by <>. The default is `.kibana_task_manager`. - You must have at least one replica of this index for production deployments. - If you lose this index, all scheduled alerts and actions are lost. +You must have at least one replica of this index for production deployments. + +If you lose this index, all scheduled alerts and actions are lost. ============================================== [float] diff --git a/package.json b/package.json index 12eae1b256570..2d96766952347 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,6 @@ "@hapi/hoek": "^9.1.1", "@hapi/inert": "^6.0.3", "@hapi/podium": "^4.1.1", - "@hapi/vision": "^6.0.1", "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:packages/kbn-ace", "@kbn/analytics": "link:packages/kbn-analytics", @@ -658,7 +657,7 @@ "file-saver": "^1.3.8", "form-data": "^4.0.0", "formsy-react": "^1.1.5", - "geckodriver": "^1.21.2", + "geckodriver": "^1.22.2", "glob-watcher": "5.0.3", "graphql-code-generator": "^0.18.2", "graphql-codegen-add": "^0.18.2", @@ -712,7 +711,7 @@ "json5": "^1.0.1", "jsondiffpatch": "0.4.1", "jsts": "^1.6.2", - "kea": "^2.2.0", + "kea": "^2.3.0", "keymirror": "0.1.1", "leaflet": "1.5.1", "leaflet-draw": "0.4.14", diff --git a/packages/kbn-dev-utils/ci_stats_reporter/package.json b/packages/kbn-dev-utils/ci_stats_reporter/package.json new file mode 100644 index 0000000000000..a4f86a9239e31 --- /dev/null +++ b/packages/kbn-dev-utils/ci_stats_reporter/package.json @@ -0,0 +1,3 @@ +{ + "main": "../target/ci_stats_reporter/ci_stats_reporter" +} \ No newline at end of file diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_config.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_config.ts new file mode 100644 index 0000000000000..9af52ae8d2df0 --- /dev/null +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_config.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ToolingLog } from '../tooling_log'; + +export interface Config { + apiToken: string; + buildId: string; +} + +function validateConfig(log: ToolingLog, config: { [k in keyof Config]: unknown }) { + const validApiToken = typeof config.apiToken === 'string' && config.apiToken.length !== 0; + if (!validApiToken) { + log.warning('KIBANA_CI_STATS_CONFIG is missing a valid api token, stats will not be reported'); + return; + } + + const validId = typeof config.buildId === 'string' && config.buildId.length !== 0; + if (!validId) { + log.warning('KIBANA_CI_STATS_CONFIG is missing a valid build id, stats will not be reported'); + return; + } + + return config as Config; +} + +export function parseConfig(log: ToolingLog) { + const configJson = process.env.KIBANA_CI_STATS_CONFIG; + if (!configJson) { + log.debug('KIBANA_CI_STATS_CONFIG environment variable not found, disabling CiStatsReporter'); + return; + } + + let config: unknown; + try { + config = JSON.parse(configJson); + } catch (_) { + // handled below + } + + if (typeof config === 'object' && config !== null) { + return validateConfig(log, config as { [k in keyof Config]: unknown }); + } + + log.warning('KIBANA_CI_STATS_CONFIG is invalid, stats will not be reported'); + return; +} diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts index 93826cf3add80..7847cad0fd5e7 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts @@ -7,90 +7,178 @@ */ import { inspect } from 'util'; +import Os from 'os'; +import Fs from 'fs'; +import Path from 'path'; import Axios from 'axios'; import { ToolingLog } from '../tooling_log'; +import { parseConfig, Config } from './ci_stats_config'; -interface Config { - apiUrl: string; - apiToken: string; - buildId: string; -} +const BASE_URL = 'https://ci-stats.kibana.dev'; -export type CiStatsMetrics = Array<{ +export interface CiStatsMetric { group: string; id: string; value: number; limit?: number; limitConfigPath?: string; -}>; +} -function parseConfig(log: ToolingLog) { - const configJson = process.env.KIBANA_CI_STATS_CONFIG; - if (!configJson) { - log.debug('KIBANA_CI_STATS_CONFIG environment variable not found, disabling CiStatsReporter'); - return; - } +export interface CiStatsTimingMetadata { + [key: string]: string | string[] | number | boolean | undefined; +} +export interface CiStatsTiming { + group: string; + id: string; + ms: number; + meta?: CiStatsTimingMetadata; +} - let config: unknown; - try { - config = JSON.parse(configJson); - } catch (_) { - // handled below - } +export interface ReqOptions { + auth: boolean; + path: string; + body: any; + bodyDesc: string; +} - if (typeof config === 'object' && config !== null) { - return validateConfig(log, config as { [k in keyof Config]: unknown }); +export interface TimingsOptions { + /** list of timings to record */ + timings: CiStatsTiming[]; + /** master, 7.x, etc, automatically detected from package.json if not specified */ + upstreamBranch?: string; + /** value of data/uuid, automatically loaded if not specified */ + kibanaUuid?: string | null; +} +export class CiStatsReporter { + static fromEnv(log: ToolingLog) { + return new CiStatsReporter(parseConfig(log), log); } - log.warning('KIBANA_CI_STATS_CONFIG is invalid, stats will not be reported'); - return; -} + constructor(private config: Config | undefined, private log: ToolingLog) {} -function validateConfig(log: ToolingLog, config: { [k in keyof Config]: unknown }) { - const validApiUrl = typeof config.apiUrl === 'string' && config.apiUrl.length !== 0; - if (!validApiUrl) { - log.warning('KIBANA_CI_STATS_CONFIG is missing a valid api url, stats will not be reported'); - return; + isEnabled() { + return process.env.CI_STATS_DISABLED !== 'true'; } - const validApiToken = typeof config.apiToken === 'string' && config.apiToken.length !== 0; - if (!validApiToken) { - log.warning('KIBANA_CI_STATS_CONFIG is missing a valid api token, stats will not be reported'); - return; + hasBuildConfig() { + return this.isEnabled() && !!this.config?.apiToken && !!this.config?.buildId; } - const validId = typeof config.buildId === 'string' && config.buildId.length !== 0; - if (!validId) { - log.warning('KIBANA_CI_STATS_CONFIG is missing a valid build id, stats will not be reported'); - return; + /** + * Report timings data to the ci-stats service. If running in CI then the reporter + * will include the buildId in the report with the access token, otherwise the timings + * data will be recorded as anonymous timing data. + */ + async timings(options: TimingsOptions) { + if (!this.isEnabled()) { + return; + } + + const buildId = this.config?.buildId; + const timings = options.timings; + const upstreamBranch = options.upstreamBranch ?? this.getUpstreamBranch(); + const kibanaUuid = options.kibanaUuid === undefined ? this.getKibanaUuid() : options.kibanaUuid; + const defaultMetadata = { + osPlatform: Os.platform(), + osRelease: Os.release(), + osArch: Os.arch(), + cpuCount: Os.cpus()?.length, + cpuModel: Os.cpus()[0]?.model, + cpuSpeed: Os.cpus()[0]?.speed, + freeMem: Os.freemem(), + totalMem: Os.totalmem(), + kibanaUuid, + }; + + return await this.req({ + auth: !!buildId, + path: '/v1/timings', + body: { + buildId, + upstreamBranch, + timings, + defaultMetadata, + }, + bodyDesc: timings.length === 1 ? `${timings.length} timing` : `${timings.length} timings`, + }); } - return config as Config; -} + /** + * Report metrics data to the ci-stats service. If running outside of CI this method + * does nothing as metrics can only be reported when associated with a specific CI build. + */ + async metrics(metrics: CiStatsMetric[]) { + if (!this.hasBuildConfig()) { + return; + } -export class CiStatsReporter { - static fromEnv(log: ToolingLog) { - return new CiStatsReporter(parseConfig(log), log); - } + const buildId = this.config?.buildId; - constructor(private config: Config | undefined, private log: ToolingLog) {} + if (!buildId) { + throw new Error(`CiStatsReporter can't be authorized without a buildId`); + } - isEnabled() { - return !!this.config; + return await this.req({ + auth: true, + path: '/v1/metrics', + body: { + buildId, + metrics, + }, + bodyDesc: `metrics: ${metrics + .map(({ group, id, value }) => `[${group}/${id}=${value}]`) + .join(' ')}`, + }); } - async metrics(metrics: CiStatsMetrics) { - if (!this.config) { - return; + /** + * In order to allow this code to run before @kbn/utils is built, @kbn/pm will pass + * in the upstreamBranch when calling the timings() method. Outside of @kbn/pm + * we rely on @kbn/utils to find the package.json file. + */ + private getUpstreamBranch() { + // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm + const hideFromWebpack = ['@', 'kbn/utils']; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { kibanaPackageJson } = require(hideFromWebpack.join('')); + return kibanaPackageJson.branch; + } + + /** + * In order to allow this code to run before @kbn/utils is built, @kbn/pm will pass + * in the kibanaUuid when calling the timings() method. Outside of @kbn/pm + * we rely on @kbn/utils to find the repo root. + */ + private getKibanaUuid() { + // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm + const hideFromWebpack = ['@', 'kbn/utils']; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { REPO_ROOT } = require(hideFromWebpack.join('')); + try { + return Fs.readFileSync(Path.resolve(REPO_ROOT, 'data/uuid'), 'utf-8').trim(); + } catch (error) { + if (error.code === 'ENOENT') { + return undefined; + } + + throw error; } + } + private async req({ auth, body, bodyDesc, path }: ReqOptions) { let attempt = 0; const maxAttempts = 5; - const bodySummary = metrics - .map(({ group, id, value }) => `[${group}/${id}=${value}]`) - .join(' '); + + let headers; + if (auth && this.config) { + headers = { + Authorization: `token ${this.config.apiToken}`, + }; + } else if (auth) { + throw new Error('this.req() shouldnt be called with auth=true if this.config is defined'); + } while (true) { attempt += 1; @@ -98,15 +186,10 @@ export class CiStatsReporter { try { await Axios.request({ method: 'POST', - url: '/v1/metrics', - baseURL: this.config.apiUrl, - headers: { - Authorization: `token ${this.config.apiToken}`, - }, - data: { - buildId: this.config.buildId, - metrics, - }, + url: path, + baseURL: BASE_URL, + headers, + data: body, }); return true; @@ -116,19 +199,19 @@ export class CiStatsReporter { throw error; } - if (error?.response && error.response.status !== 502) { + if (error?.response && error.response.status < 502) { // error response from service was received so warn the user and move on this.log.warning( - `error recording metric [status=${error.response.status}] [resp=${inspect( + `error reporting ${bodyDesc} [status=${error.response.status}] [resp=${inspect( error.response.data - )}] ${bodySummary}` + )}]` ); return; } if (attempt === maxAttempts) { this.log.warning( - `failed to reach kibana-ci-stats service too many times, unable to record metric ${bodySummary}` + `unable to report ${bodyDesc}, failed to reach ci-stats service too many times` ); return; } @@ -139,7 +222,7 @@ export class CiStatsReporter { : 'no response'; this.log.warning( - `failed to reach kibana-ci-stats service [reason=${reason}], retrying in ${attempt} seconds` + `failed to reach ci-stats service [reason=${reason}], retrying in ${attempt} seconds` ); await new Promise((resolve) => setTimeout(resolve, attempt * 1000)); diff --git a/packages/kbn-optimizer/src/limits.ts b/packages/kbn-optimizer/src/limits.ts index 077fe38ddc7f6..4479e0acc097e 100644 --- a/packages/kbn-optimizer/src/limits.ts +++ b/packages/kbn-optimizer/src/limits.ts @@ -11,7 +11,7 @@ import Path from 'path'; import dedent from 'dedent'; import Yaml from 'js-yaml'; -import { createFailError, ToolingLog, CiStatsMetrics } from '@kbn/dev-utils'; +import { createFailError, ToolingLog, CiStatsMetric } from '@kbn/dev-utils'; import { OptimizerConfig, Limits } from './optimizer'; @@ -86,7 +86,7 @@ export function updateBundleLimits({ limitsPath, }: UpdateBundleLimitsOptions) { const limits = readLimits(limitsPath); - const metrics: CiStatsMetrics = config.bundles + const metrics: CiStatsMetric[] = config.bundles .map((bundle) => JSON.parse(Fs.readFileSync(Path.resolve(bundle.outputDir, 'metrics.json'), 'utf-8')) ) diff --git a/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts index 909a97a3e11c7..92875d3f69e46 100644 --- a/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts +++ b/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts @@ -10,7 +10,7 @@ import Path from 'path'; import webpack from 'webpack'; import { RawSource } from 'webpack-sources'; -import { CiStatsMetrics } from '@kbn/dev-utils'; +import { CiStatsMetric } from '@kbn/dev-utils'; import { Bundle } from '../common'; @@ -68,7 +68,7 @@ export class BundleMetricsPlugin { throw new Error(`moduleCount wasn't populated by PopulateBundleCachePlugin`); } - const bundleMetrics: CiStatsMetrics = [ + const bundleMetrics: CiStatsMetric[] = [ { group: `@kbn/optimizer bundle module count`, id: bundle.id, diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index e4795249d8cb9..206c5c62d2472 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(520); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(563); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildBazelProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); @@ -108,7 +108,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(519); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(562); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -8895,6 +8895,10 @@ __webpack_require__.r(__webpack_exports__); const BootstrapCommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', + reportTiming: { + group: 'bootstrap', + id: 'overall time' + }, async run(projects, projectGraph, { options, @@ -14759,7 +14763,7 @@ var isWin32 = __webpack_require__(121).platform() === 'win32'; var slash = '/'; var backslash = /\\/g; -var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; +var enclosure = /[\{\[].*[\}\]]$/; var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; var escaped = /\\([\!\*\?\|\[\]\(\)\{\}])/g; @@ -14767,6 +14771,7 @@ var escaped = /\\([\!\*\?\|\[\]\(\)\{\}])/g; * @param {string} str * @param {Object} opts * @param {boolean} [opts.flipBackslashes=true] + * @returns {string} */ module.exports = function globParent(str, opts) { var options = Object.assign({ flipBackslashes: true }, opts); @@ -59489,11 +59494,13 @@ function waitUntilWatchIsReady(stream, opts = {}) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(371); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(515); +/* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(515); +/* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(371); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(558); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -59512,10 +59519,14 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope + async function runCommand(command, config) { + const runStartTime = Date.now(); + let kbn; + try { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].debug(`Running [${command.name}] command from [${config.rootPath}]`); - const kbn = await _utils_kibana__WEBPACK_IMPORTED_MODULE_4__["Kibana"].loadFrom(config.rootPath); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].debug(`Running [${command.name}] command from [${config.rootPath}]`); + kbn = await _utils_kibana__WEBPACK_IMPORTED_MODULE_5__["Kibana"].loadFrom(config.rootPath); const projects = kbn.getFilteredProjects({ skipKibanaPlugins: Boolean(config.options['skip-kibana-plugins']), ossOnly: Boolean(config.options.oss), @@ -59524,31 +59535,66 @@ async function runCommand(command, config) { }); if (projects.size === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].error(`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].error(`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.`); return process.exit(1); } - const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_2__["buildProjectGraph"])(projects); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].debug(`Found ${projects.size.toString()} projects`); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].debug(Object(_utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__["renderProjectsTree"])(config.rootPath, projects)); + const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["buildProjectGraph"])(projects); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].debug(`Found ${projects.size.toString()} projects`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].debug(Object(_utils_projects_tree__WEBPACK_IMPORTED_MODULE_4__["renderProjectsTree"])(config.rootPath, projects)); await command.run(projects, projectGraph, _objectSpread(_objectSpread({}, config), {}, { kbn })); + + if (command.reportTiming) { + const reporter = _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__["CiStatsReporter"].fromEnv(_utils_log__WEBPACK_IMPORTED_MODULE_2__["log"]); + await reporter.timings({ + upstreamBranch: kbn.kibanaProject.json.branch, + // prevent loading @kbn/utils by passing null + kibanaUuid: kbn.getUuid() || null, + timings: [{ + group: command.reportTiming.group, + id: command.reportTiming.id, + ms: Date.now() - runStartTime, + meta: { + success: true + } + }] + }); + } } catch (error) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].error(`[${command.name}] failed:`); + if (command.reportTiming) { + // if we don't have a kbn object then things are too broken to report on + if (kbn) { + const reporter = _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__["CiStatsReporter"].fromEnv(_utils_log__WEBPACK_IMPORTED_MODULE_2__["log"]); + await reporter.timings({ + upstreamBranch: kbn.kibanaProject.json.branch, + timings: [{ + group: command.reportTiming.group, + id: command.reportTiming.id, + ms: Date.now() - runStartTime, + meta: { + success: false + } + }] + }); + } + } - if (error instanceof _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"]) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].error(error.message); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].error(`[${command.name}] failed:`); + + if (error instanceof _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"]) { + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].error(error.message); const metaOutput = Object.entries(error.meta).map(([key, value]) => `${key}: ${value}`).join('\n'); if (metaOutput) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info('Additional debugging info:\n'); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].indent(2); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(metaOutput); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].indent(-2); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].info('Additional debugging info:\n'); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].indent(2); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].info(metaOutput); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].indent(-2); } } else { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].error(error); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].error(error); } process.exit(1); @@ -59565,25 +59611,9 @@ function toArray(value) { /***/ }), /* 515 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(516); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); -/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(366); -/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(519); -function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } - -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -59592,834 +59622,4522 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CiStatsReporter = void 0; +const tslib_1 = __webpack_require__(7); +const util_1 = __webpack_require__(112); +const os_1 = tslib_1.__importDefault(__webpack_require__(121)); +const fs_1 = tslib_1.__importDefault(__webpack_require__(134)); +const path_1 = tslib_1.__importDefault(__webpack_require__(4)); +const axios_1 = tslib_1.__importDefault(__webpack_require__(516)); +const ci_stats_config_1 = __webpack_require__(556); +const BASE_URL = 'https://ci-stats.kibana.dev'; +class CiStatsReporter { + constructor(config, log) { + this.config = config; + this.log = log; + } + static fromEnv(log) { + return new CiStatsReporter(ci_stats_config_1.parseConfig(log), log); + } + isEnabled() { + return process.env.CI_STATS_DISABLED !== 'true'; + } + hasBuildConfig() { + var _a, _b; + return this.isEnabled() && !!((_a = this.config) === null || _a === void 0 ? void 0 : _a.apiToken) && !!((_b = this.config) === null || _b === void 0 ? void 0 : _b.buildId); + } + /** + * Report timings data to the ci-stats service. If running in CI then the reporter + * will include the buildId in the report with the access token, otherwise the timings + * data will be recorded as anonymous timing data. + */ + async timings(options) { + var _a, _b, _c, _d, _e; + if (!this.isEnabled()) { + return; + } + const buildId = (_a = this.config) === null || _a === void 0 ? void 0 : _a.buildId; + const timings = options.timings; + const upstreamBranch = (_b = options.upstreamBranch) !== null && _b !== void 0 ? _b : this.getUpstreamBranch(); + const kibanaUuid = options.kibanaUuid === undefined ? this.getKibanaUuid() : options.kibanaUuid; + const defaultMetadata = { + osPlatform: os_1.default.platform(), + osRelease: os_1.default.release(), + osArch: os_1.default.arch(), + cpuCount: (_c = os_1.default.cpus()) === null || _c === void 0 ? void 0 : _c.length, + cpuModel: (_d = os_1.default.cpus()[0]) === null || _d === void 0 ? void 0 : _d.model, + cpuSpeed: (_e = os_1.default.cpus()[0]) === null || _e === void 0 ? void 0 : _e.speed, + freeMem: os_1.default.freemem(), + totalMem: os_1.default.totalmem(), + kibanaUuid, + }; + return await this.req({ + auth: !!buildId, + path: '/v1/timings', + body: { + buildId, + upstreamBranch, + timings, + defaultMetadata, + }, + bodyDesc: timings.length === 1 ? `${timings.length} timing` : `${timings.length} timings`, + }); + } + /** + * Report metrics data to the ci-stats service. If running outside of CI this method + * does nothing as metrics can only be reported when associated with a specific CI build. + */ + async metrics(metrics) { + var _a; + if (!this.hasBuildConfig()) { + return; + } + const buildId = (_a = this.config) === null || _a === void 0 ? void 0 : _a.buildId; + if (!buildId) { + throw new Error(`CiStatsReporter can't be authorized without a buildId`); + } + return await this.req({ + auth: true, + path: '/v1/metrics', + body: { + buildId, + metrics, + }, + bodyDesc: `metrics: ${metrics + .map(({ group, id, value }) => `[${group}/${id}=${value}]`) + .join(' ')}`, + }); + } + /** + * In order to allow this code to run before @kbn/utils is built, @kbn/pm will pass + * in the upstreamBranch when calling the timings() method. Outside of @kbn/pm + * we rely on @kbn/utils to find the package.json file. + */ + getUpstreamBranch() { + // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm + const hideFromWebpack = ['@', 'kbn/utils']; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { kibanaPackageJson } = __webpack_require__(557)(hideFromWebpack.join('')); + return kibanaPackageJson.branch; + } + /** + * In order to allow this code to run before @kbn/utils is built, @kbn/pm will pass + * in the kibanaUuid when calling the timings() method. Outside of @kbn/pm + * we rely on @kbn/utils to find the repo root. + */ + getKibanaUuid() { + // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm + const hideFromWebpack = ['@', 'kbn/utils']; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { REPO_ROOT } = __webpack_require__(557)(hideFromWebpack.join('')); + try { + return fs_1.default.readFileSync(path_1.default.resolve(REPO_ROOT, 'data/uuid'), 'utf-8').trim(); + } + catch (error) { + if (error.code === 'ENOENT') { + return undefined; + } + throw error; + } + } + async req({ auth, body, bodyDesc, path }) { + var _a; + let attempt = 0; + const maxAttempts = 5; + let headers; + if (auth && this.config) { + headers = { + Authorization: `token ${this.config.apiToken}`, + }; + } + else if (auth) { + throw new Error('this.req() shouldnt be called with auth=true if this.config is defined'); + } + while (true) { + attempt += 1; + try { + await axios_1.default.request({ + method: 'POST', + url: path, + baseURL: BASE_URL, + headers, + data: body, + }); + return true; + } + catch (error) { + if (!(error === null || error === void 0 ? void 0 : error.request)) { + // not an axios error, must be a usage error that we should notify user about + throw error; + } + if ((error === null || error === void 0 ? void 0 : error.response) && error.response.status < 502) { + // error response from service was received so warn the user and move on + this.log.warning(`error reporting ${bodyDesc} [status=${error.response.status}] [resp=${util_1.inspect(error.response.data)}]`); + return; + } + if (attempt === maxAttempts) { + this.log.warning(`unable to report ${bodyDesc}, failed to reach ci-stats service too many times`); + return; + } + // we failed to reach the backend and we have remaining attempts, lets retry after a short delay + const reason = ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) ? `${error.response.status} response` + : 'no response'; + this.log.warning(`failed to reach ci-stats service [reason=${reason}], retrying in ${attempt} seconds`); + await new Promise((resolve) => setTimeout(resolve, attempt * 1000)); + } + } + } +} +exports.CiStatsReporter = CiStatsReporter; + + +/***/ }), +/* 516 */ +/***/ (function(module, exports, __webpack_require__) { +module.exports = __webpack_require__(517); +/***/ }), +/* 517 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +var utils = __webpack_require__(518); +var bind = __webpack_require__(519); +var Axios = __webpack_require__(520); +var mergeConfig = __webpack_require__(551); +var defaults = __webpack_require__(526); /** - * Helper class for dealing with a set of projects as children of - * the Kibana project. The kbn/pm is currently implemented to be - * more generic, where everything is an operation of generic projects, - * but that leads to exceptions where we need the kibana project and - * do things like `project.get('kibana')!`. + * Create an instance of Axios * - * Using this helper we can restructre the generic list of projects - * as a Kibana object which encapulates all the projects in the - * workspace and knows about the root Kibana project. + * @param {Object} defaultConfig The default config for the instance + * @return {Axios} A new instance of Axios */ +function createInstance(defaultConfig) { + var context = new Axios(defaultConfig); + var instance = bind(Axios.prototype.request, context); -class Kibana { - static async loadFrom(rootPath) { - return new Kibana(await Object(_projects__WEBPACK_IMPORTED_MODULE_4__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"])({ - rootPath - }))); - } + // Copy axios.prototype to instance + utils.extend(instance, Axios.prototype, context); - constructor(allWorkspaceProjects) { - this.allWorkspaceProjects = allWorkspaceProjects; + // Copy context to instance + utils.extend(instance, context); - _defineProperty(this, "kibanaProject", void 0); + return instance; +} - const kibanaProject = allWorkspaceProjects.get('kibana'); +// Create the default instance to be exported +var axios = createInstance(defaults); - if (!kibanaProject) { - throw new TypeError('Unable to create Kibana object without all projects, including the Kibana project.'); - } +// Expose Axios class to allow class inheritance +axios.Axios = Axios; - this.kibanaProject = kibanaProject; - } - /** make an absolute path by resolving subPath relative to the kibana repo */ +// Factory for creating new instances +axios.create = function create(instanceConfig) { + return createInstance(mergeConfig(axios.defaults, instanceConfig)); +}; +// Expose Cancel & CancelToken +axios.Cancel = __webpack_require__(552); +axios.CancelToken = __webpack_require__(553); +axios.isCancel = __webpack_require__(525); - getAbsolute(...subPath) { - return path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(this.kibanaProject.path, ...subPath); - } - /** convert an absolute path to a relative path, relative to the kibana repo */ +// Expose all/spread +axios.all = function all(promises) { + return Promise.all(promises); +}; +axios.spread = __webpack_require__(554); +// Expose isAxiosError +axios.isAxiosError = __webpack_require__(555); - getRelative(absolute) { - return path__WEBPACK_IMPORTED_MODULE_0___default.a.relative(this.kibanaProject.path, absolute); - } - /** get a copy of the map of all projects in the kibana workspace */ +module.exports = axios; +// Allow use of default import syntax in TypeScript +module.exports.default = axios; - getAllProjects() { - return new Map(this.allWorkspaceProjects); - } - /** determine if a project with the given name exists */ +/***/ }), +/* 518 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; - hasProject(name) { - return this.allWorkspaceProjects.has(name); - } - /** get a specific project, throws if the name is not known (use hasProject() first) */ +var bind = __webpack_require__(519); - getProject(name) { - const project = this.allWorkspaceProjects.get(name); +/*global toString:true*/ - if (!project) { - throw new Error(`No package with name "${name}" in the workspace`); - } +// utils is a library of generic helper functions non-specific to axios - return project; +var toString = Object.prototype.toString; + +/** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Array, otherwise false + */ +function isArray(val) { + return toString.call(val) === '[object Array]'; +} + +/** + * Determine if a value is undefined + * + * @param {Object} val The value to test + * @returns {boolean} True if the value is undefined, otherwise false + */ +function isUndefined(val) { + return typeof val === 'undefined'; +} + +/** + * Determine if a value is a Buffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Buffer, otherwise false + */ +function isBuffer(val) { + return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) + && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val); +} + +/** + * Determine if a value is an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an ArrayBuffer, otherwise false + */ +function isArrayBuffer(val) { + return toString.call(val) === '[object ArrayBuffer]'; +} + +/** + * Determine if a value is a FormData + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an FormData, otherwise false + */ +function isFormData(val) { + return (typeof FormData !== 'undefined') && (val instanceof FormData); +} + +/** + * Determine if a value is a view on an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false + */ +function isArrayBufferView(val) { + var result; + if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { + result = ArrayBuffer.isView(val); + } else { + result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); } - /** get a project and all of the projects it depends on in a ProjectMap */ + return result; +} +/** + * Determine if a value is a String + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a String, otherwise false + */ +function isString(val) { + return typeof val === 'string'; +} - getProjectAndDeps(name) { - const project = this.getProject(name); - return Object(_projects__WEBPACK_IMPORTED_MODULE_4__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); +/** + * Determine if a value is a Number + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Number, otherwise false + */ +function isNumber(val) { + return typeof val === 'number'; +} + +/** + * Determine if a value is an Object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Object, otherwise false + */ +function isObject(val) { + return val !== null && typeof val === 'object'; +} + +/** + * Determine if a value is a plain Object + * + * @param {Object} val The value to test + * @return {boolean} True if value is a plain Object, otherwise false + */ +function isPlainObject(val) { + if (toString.call(val) !== '[object Object]') { + return false; } - /** filter the projects to just those matching certain paths/include/exclude tags */ + var prototype = Object.getPrototypeOf(val); + return prototype === null || prototype === Object.prototype; +} - getFilteredProjects(options) { - const allProjects = this.getAllProjects(); - const filteredProjects = new Map(); - const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); - const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"])(_objectSpread(_objectSpread({}, options), {}, { - rootPath: this.kibanaProject.path - })).map(g => path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(g, 'package.json')); - const matchingPkgJsonPaths = multimatch__WEBPACK_IMPORTED_MODULE_1___default()(pkgJsonPaths, filteredPkgJsonGlobs); +/** + * Determine if a value is a Date + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Date, otherwise false + */ +function isDate(val) { + return toString.call(val) === '[object Date]'; +} - for (const project of allProjects.values()) { - const pathMatches = matchingPkgJsonPaths.includes(project.packageJsonLocation); - const notExcluded = !options.exclude.includes(project.name); - const isIncluded = !options.include.length || options.include.includes(project.name); +/** + * Determine if a value is a File + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a File, otherwise false + */ +function isFile(val) { + return toString.call(val) === '[object File]'; +} - if (pathMatches && notExcluded && isIncluded) { - filteredProjects.set(project.name, project); - } - } +/** + * Determine if a value is a Blob + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Blob, otherwise false + */ +function isBlob(val) { + return toString.call(val) === '[object Blob]'; +} - return filteredProjects; +/** + * Determine if a value is a Function + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Function, otherwise false + */ +function isFunction(val) { + return toString.call(val) === '[object Function]'; +} + +/** + * Determine if a value is a Stream + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Stream, otherwise false + */ +function isStream(val) { + return isObject(val) && isFunction(val.pipe); +} + +/** + * Determine if a value is a URLSearchParams object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ +function isURLSearchParams(val) { + return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; +} + +/** + * Trim excess whitespace off the beginning and end of a string + * + * @param {String} str The String to trim + * @returns {String} The String freed of excess whitespace + */ +function trim(str) { + return str.replace(/^\s*/, '').replace(/\s*$/, ''); +} + +/** + * Determine if we're running in a standard browser environment + * + * This allows axios to run in a web worker, and react-native. + * Both environments support XMLHttpRequest, but not fully standard globals. + * + * web workers: + * typeof window -> undefined + * typeof document -> undefined + * + * react-native: + * navigator.product -> 'ReactNative' + * nativescript + * navigator.product -> 'NativeScript' or 'NS' + */ +function isStandardBrowserEnv() { + if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' || + navigator.product === 'NativeScript' || + navigator.product === 'NS')) { + return false; } + return ( + typeof window !== 'undefined' && + typeof document !== 'undefined' + ); +} - isPartOfRepo(project) { - return project.path === this.kibanaProject.path || is_path_inside__WEBPACK_IMPORTED_MODULE_2___default()(project.path, this.kibanaProject.path); +/** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + */ +function forEach(obj, fn) { + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return; } - isOutsideRepo(project) { - return !this.isPartOfRepo(project); + // Force an array if not already something iterable + if (typeof obj !== 'object') { + /*eslint no-param-reassign:0*/ + obj = [obj]; } - resolveAllProductionDependencies(yarnLock, log) { - const kibanaDeps = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_3__["resolveDepsForProject"])({ - project: this.kibanaProject, - yarnLock, - kbn: this, - includeDependentProject: true, - productionDepsOnly: true, - log - }); - const xpackDeps = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_3__["resolveDepsForProject"])({ - project: this.getProject('x-pack'), - yarnLock, - kbn: this, - includeDependentProject: true, - productionDepsOnly: true, - log - }); - return new Map([...kibanaDeps.entries(), ...xpackDeps.entries()]); + if (isArray(obj)) { + // Iterate over array values + for (var i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj); + } + } else { + // Iterate over object keys + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + fn.call(null, obj[key], key, obj); + } + } + } +} + +/** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * Example: + * + * ```js + * var result = merge({foo: 123}, {foo: 456}); + * console.log(result.foo); // outputs 456 + * ``` + * + * @param {Object} obj1 Object to merge + * @returns {Object} Result of all merge properties + */ +function merge(/* obj1, obj2, obj3, ... */) { + var result = {}; + function assignValue(val, key) { + if (isPlainObject(result[key]) && isPlainObject(val)) { + result[key] = merge(result[key], val); + } else if (isPlainObject(val)) { + result[key] = merge({}, val); + } else if (isArray(val)) { + result[key] = val.slice(); + } else { + result[key] = val; + } } + for (var i = 0, l = arguments.length; i < l; i++) { + forEach(arguments[i], assignValue); + } + return result; } -/***/ }), -/* 516 */ -/***/ (function(module, exports, __webpack_require__) { +/** + * Extends object a by mutably adding to it the properties of object b. + * + * @param {Object} a The object to be extended + * @param {Object} b The object to copy properties from + * @param {Object} thisArg The object to bind function to + * @return {Object} The resulting value of object a + */ +function extend(a, b, thisArg) { + forEach(b, function assignValue(val, key) { + if (thisArg && typeof val === 'function') { + a[key] = bind(val, thisArg); + } else { + a[key] = val; + } + }); + return a; +} -"use strict"; +/** + * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) + * + * @param {string} content with BOM + * @return {string} content value without BOM + */ +function stripBOM(content) { + if (content.charCodeAt(0) === 0xFEFF) { + content = content.slice(1); + } + return content; +} -const minimatch = __webpack_require__(150); -const arrayUnion = __webpack_require__(145); -const arrayDiffer = __webpack_require__(517); -const arrify = __webpack_require__(518); +module.exports = { + isArray: isArray, + isArrayBuffer: isArrayBuffer, + isBuffer: isBuffer, + isFormData: isFormData, + isArrayBufferView: isArrayBufferView, + isString: isString, + isNumber: isNumber, + isObject: isObject, + isPlainObject: isPlainObject, + isUndefined: isUndefined, + isDate: isDate, + isFile: isFile, + isBlob: isBlob, + isFunction: isFunction, + isStream: isStream, + isURLSearchParams: isURLSearchParams, + isStandardBrowserEnv: isStandardBrowserEnv, + forEach: forEach, + merge: merge, + extend: extend, + trim: trim, + stripBOM: stripBOM +}; -module.exports = (list, patterns, options = {}) => { - list = arrify(list); - patterns = arrify(patterns); - if (list.length === 0 || patterns.length === 0) { - return []; - } +/***/ }), +/* 519 */ +/***/ (function(module, exports, __webpack_require__) { - return patterns.reduce((result, pattern) => { - let process = arrayUnion; +"use strict"; - if (pattern[0] === '!') { - pattern = pattern.slice(1); - process = arrayDiffer; - } - return process(result, minimatch.match(list, pattern, options)); - }, []); +module.exports = function bind(fn, thisArg) { + return function wrap() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + return fn.apply(thisArg, args); + }; }; /***/ }), -/* 517 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const arrayDiffer = (array, ...values) => { - const rest = new Set([].concat(...values)); - return array.filter(element => !rest.has(element)); -}; +var utils = __webpack_require__(518); +var buildURL = __webpack_require__(521); +var InterceptorManager = __webpack_require__(522); +var dispatchRequest = __webpack_require__(523); +var mergeConfig = __webpack_require__(551); -module.exports = arrayDiffer; +/** + * Create a new instance of Axios + * + * @param {Object} instanceConfig The default config for the instance + */ +function Axios(instanceConfig) { + this.defaults = instanceConfig; + this.interceptors = { + request: new InterceptorManager(), + response: new InterceptorManager() + }; +} +/** + * Dispatch a request + * + * @param {Object} config The config specific for this request (merged with this.defaults) + */ +Axios.prototype.request = function request(config) { + /*eslint no-param-reassign:0*/ + // Allow for axios('example/url'[, config]) a la fetch API + if (typeof config === 'string') { + config = arguments[1] || {}; + config.url = arguments[0]; + } else { + config = config || {}; + } -/***/ }), -/* 518 */ -/***/ (function(module, exports, __webpack_require__) { + config = mergeConfig(this.defaults, config); -"use strict"; + // Set config.method + if (config.method) { + config.method = config.method.toLowerCase(); + } else if (this.defaults.method) { + config.method = this.defaults.method.toLowerCase(); + } else { + config.method = 'get'; + } + // Hook up interceptors middleware + var chain = [dispatchRequest, undefined]; + var promise = Promise.resolve(config); -const arrify = value => { - if (value === null || value === undefined) { - return []; - } + this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { + chain.unshift(interceptor.fulfilled, interceptor.rejected); + }); - if (Array.isArray(value)) { - return value; - } + this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { + chain.push(interceptor.fulfilled, interceptor.rejected); + }); - if (typeof value === 'string') { - return [value]; - } + while (chain.length) { + promise = promise.then(chain.shift(), chain.shift()); + } - if (typeof value[Symbol.iterator] === 'function') { - return [...value]; - } + return promise; +}; - return [value]; +Axios.prototype.getUri = function getUri(config) { + config = mergeConfig(this.defaults, config); + return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, ''); }; -module.exports = arrify; +// Provide aliases for supported request methods +utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, config) { + return this.request(mergeConfig(config || {}, { + method: method, + url: url, + data: (config || {}).data + })); + }; +}); + +utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, data, config) { + return this.request(mergeConfig(config || {}, { + method: method, + url: url, + data: data + })); + }; +}); + +module.exports = Axios; /***/ }), -/* 519 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 521 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ +var utils = __webpack_require__(518); + +function encode(val) { + return encodeURIComponent(val). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, '+'). + replace(/%5B/gi, '['). + replace(/%5D/gi, ']'); +} + /** - * Returns all the paths where plugins are located + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @returns {string} The formatted url */ -function getProjectPaths({ - rootPath, - ossOnly, - skipKibanaPlugins -}) { - const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared - // plugin functional used in the selenium functional tests. - // As we are now using the webpack dll for the client vendors dependencies - // when we run the plugin functional tests against the distributable - // dependencies used by such plugins like @eui, react and react-dom can't - // be loaded from the dll as the context is different from the one declared - // into the webpack dll reference plugin. - // In anyway, have a plugin declaring their own dependencies is the - // correct and the expect behavior. +module.exports = function buildURL(url, params, paramsSerializer) { + /*eslint no-param-reassign:0*/ + if (!params) { + return url; + } - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*')); + var serializedParams; + if (paramsSerializer) { + serializedParams = paramsSerializer(params); + } else if (utils.isURLSearchParams(params)) { + serializedParams = params.toString(); + } else { + var parts = []; - if (!ossOnly) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/test/functional_with_es_ssl/fixtures/plugins/*')); + utils.forEach(params, function serialize(val, key) { + if (val === null || typeof val === 'undefined') { + return; + } + + if (utils.isArray(val)) { + key = key + '[]'; + } else { + val = [val]; + } + + utils.forEach(val, function parseValue(v) { + if (utils.isDate(v)) { + v = v.toISOString(); + } else if (utils.isObject(v)) { + v = JSON.stringify(v); + } + parts.push(encode(key) + '=' + encode(v)); + }); + }); + + serializedParams = parts.join('&'); } - if (!skipKibanaPlugins) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); + if (serializedParams) { + var hashmarkIndex = url.indexOf('#'); + if (hashmarkIndex !== -1) { + url = url.slice(0, hashmarkIndex); + } + + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; } - return projectPaths; -} + return url; +}; + /***/ }), -/* 520 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 522 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(521); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildBazelProductionProjects"]; }); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(746); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. +var utils = __webpack_require__(518); + +function InterceptorManager() { + this.handlers = []; +} + +/** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ +InterceptorManager.prototype.use = function use(fulfilled, rejected) { + this.handlers.push({ + fulfilled: fulfilled, + rejected: rejected + }); + return this.handlers.length - 1; +}; + +/** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + */ +InterceptorManager.prototype.eject = function eject(id) { + if (this.handlers[id]) { + this.handlers[id] = null; + } +}; + +/** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `eject`. + * + * @param {Function} fn The function to call for each interceptor */ +InterceptorManager.prototype.forEach = function forEach(fn) { + utils.forEach(this.handlers, function forEachHandler(h) { + if (h !== null) { + fn(h); + } + }); +}; +module.exports = InterceptorManager; /***/ }), -/* 521 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 523 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return buildBazelProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(522); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(737); -/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(globby__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(746); -/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(372); -/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(131); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); -/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(251); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(248); -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - - - - - +var utils = __webpack_require__(518); +var transformData = __webpack_require__(524); +var isCancel = __webpack_require__(525); +var defaults = __webpack_require__(526); - -async function buildBazelProductionProjects({ - kibanaRoot, - buildRoot, - onlyOSS -}) { - const projects = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_8__["getBazelProjectsOnly"])(await Object(_build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__["getProductionProjects"])(kibanaRoot, onlyOSS)); - const projectNames = [...projects.values()].map(project => project.name); - _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`Preparing Bazel projects production build for [${projectNames.join(', ')}]`); - await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['build', '//packages:build']); - _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`All Bazel projects production builds for [${projectNames.join(', ')}] are complete}]`); - - for (const project of projects.values()) { - await copyToBuild(project, kibanaRoot, buildRoot); - await applyCorrectPermissions(project, kibanaRoot, buildRoot); +/** + * Throws a `Cancel` if cancellation has been requested. + */ +function throwIfCancellationRequested(config) { + if (config.cancelToken) { + config.cancelToken.throwIfRequested(); } } + /** - * Copy all the project's files from its Bazel dist directory into the - * project build folder. + * Dispatch a request to the server using the configured adapter. * - * When copying all the files into the build, we exclude `node_modules` because - * we want the Kibana build to be responsible for actually installing all - * dependencies. The primary reason for allowing the Kibana build process to - * manage dependencies is that it will "dedupe" them, so we don't include - * unnecessary copies of dependencies. We also exclude every related Bazel build - * files in order to get the most cleaner package module we can in the final distributable. + * @param {object} config The config that is to be used for the request + * @returns {Promise} The Promise to be fulfilled */ +module.exports = function dispatchRequest(config) { + throwIfCancellationRequested(config); -async function copyToBuild(project, kibanaRoot, buildRoot) { - // We want the package to have the same relative location within the build - const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); - const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); - await cpy__WEBPACK_IMPORTED_MODULE_0___default()(['**/*'], buildProjectPath, { - cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel', 'bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), - dot: true, - onlyFiles: true, - parents: true - }); // If a project is using an intermediate build directory, we special-case our - // handling of `package.json`, as the project build process might have copied - // (a potentially modified) `package.json` into the intermediate build - // directory already. If so, we want to use that `package.json` as the basis - // for creating the production-ready `package.json`. If it's not present in - // the intermediate build, we fall back to using the project's already defined - // `package.json`. - - const packageJson = (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isFile"])(Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(buildProjectPath, 'package.json'))) ? await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_7__["readPackageJson"])(buildProjectPath) : project.json; - const preparedPackageJson = Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_7__["createProductionPackageJson"])(packageJson); - await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_7__["writePackageJson"])(buildProjectPath, preparedPackageJson); -} + // Ensure headers exist + config.headers = config.headers || {}; -async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { - const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); - const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); - const allPluginPaths = await globby__WEBPACK_IMPORTED_MODULE_1___default()([`**/*`], { - onlyFiles: false, - cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel', 'bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), - dot: true - }); + // Transform request data + config.data = transformData( + config.data, + config.headers, + config.transformRequest + ); - for (const pluginPath of allPluginPaths) { - const resolvedPluginPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, pluginPath); + // Flatten headers + config.headers = utils.merge( + config.headers.common || {}, + config.headers[config.method] || {}, + config.headers + ); - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isFile"])(resolvedPluginPath)) { - await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["chmod"])(resolvedPluginPath, 0o644); + utils.forEach( + ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], + function cleanHeaderConfig(method) { + delete config.headers[method]; } + ); - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isDirectory"])(resolvedPluginPath)) { - await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["chmod"])(resolvedPluginPath, 0o755); + var adapter = config.adapter || defaults.adapter; + + return adapter(config).then(function onAdapterResolution(response) { + throwIfCancellationRequested(config); + + // Transform response data + response.data = transformData( + response.data, + response.headers, + config.transformResponse + ); + + return response; + }, function onAdapterRejection(reason) { + if (!isCancel(reason)) { + throwIfCancellationRequested(config); + + // Transform response data + if (reason && reason.response) { + reason.response.data = transformData( + reason.response.data, + reason.response.headers, + config.transformResponse + ); + } } - } -} + + return Promise.reject(reason); + }); +}; + /***/ }), -/* 522 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const EventEmitter = __webpack_require__(156); -const path = __webpack_require__(4); -const os = __webpack_require__(121); -const pMap = __webpack_require__(523); -const arrify = __webpack_require__(518); -const globby = __webpack_require__(526); -const hasGlob = __webpack_require__(721); -const cpFile = __webpack_require__(723); -const junk = __webpack_require__(733); -const pFilter = __webpack_require__(734); -const CpyError = __webpack_require__(736); -const defaultOptions = { - ignoreJunk: true -}; +var utils = __webpack_require__(518); -class SourceFile { - constructor(relativePath, path) { - this.path = path; - this.relativePath = relativePath; - Object.freeze(this); - } +/** + * Transform the data for a request or a response + * + * @param {Object|String} data The data to be transformed + * @param {Array} headers The headers for the request or response + * @param {Array|Function} fns A single function or Array of functions + * @returns {*} The resulting transformed data + */ +module.exports = function transformData(data, headers, fns) { + /*eslint no-param-reassign:0*/ + utils.forEach(fns, function transform(fn) { + data = fn(data, headers); + }); - get name() { - return path.basename(this.relativePath); - } + return data; +}; - get nameWithoutExtension() { - return path.basename(this.relativePath, path.extname(this.relativePath)); - } - get extension() { - return path.extname(this.relativePath).slice(1); - } -} +/***/ }), +/* 525 */ +/***/ (function(module, exports, __webpack_require__) { -const preprocessSourcePath = (source, options) => path.resolve(options.cwd ? options.cwd : process.cwd(), source); +"use strict"; -const preprocessDestinationPath = (source, destination, options) => { - let basename = path.basename(source); - if (typeof options.rename === 'string') { - basename = options.rename; - } else if (typeof options.rename === 'function') { - basename = options.rename(basename); - } +module.exports = function isCancel(value) { + return !!(value && value.__CANCEL__); +}; - if (options.cwd) { - destination = path.resolve(options.cwd, destination); - } - if (options.parents) { - const dirname = path.dirname(source); - const parsedDirectory = path.parse(dirname); - return path.join(destination, dirname.replace(parsedDirectory.root, path.sep), basename); - } +/***/ }), +/* 526 */ +/***/ (function(module, exports, __webpack_require__) { - return path.join(destination, basename); -}; +"use strict"; -module.exports = (source, destination, { - concurrency = (os.cpus().length || 1) * 2, - ...options -} = {}) => { - const progressEmitter = new EventEmitter(); - options = { - ...defaultOptions, - ...options - }; +var utils = __webpack_require__(518); +var normalizeHeaderName = __webpack_require__(527); - const promise = (async () => { - source = arrify(source); +var DEFAULT_CONTENT_TYPE = { + 'Content-Type': 'application/x-www-form-urlencoded' +}; - if (source.length === 0 || !destination) { - throw new CpyError('`source` and `destination` required'); - } +function setContentTypeIfUnset(headers, value) { + if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { + headers['Content-Type'] = value; + } +} - const copyStatus = new Map(); - let completedFiles = 0; - let completedSize = 0; +function getDefaultAdapter() { + var adapter; + if (typeof XMLHttpRequest !== 'undefined') { + // For browsers use XHR adapter + adapter = __webpack_require__(528); + } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { + // For node use HTTP adapter + adapter = __webpack_require__(538); + } + return adapter; +} - let files; - try { - files = await globby(source, options); +var defaults = { + adapter: getDefaultAdapter(), - if (options.ignoreJunk) { - files = files.filter(file => junk.not(path.basename(file))); - } - } catch (error) { - throw new CpyError(`Cannot glob \`${source}\`: ${error.message}`, error); - } + transformRequest: [function transformRequest(data, headers) { + normalizeHeaderName(headers, 'Accept'); + normalizeHeaderName(headers, 'Content-Type'); + if (utils.isFormData(data) || + utils.isArrayBuffer(data) || + utils.isBuffer(data) || + utils.isStream(data) || + utils.isFile(data) || + utils.isBlob(data) + ) { + return data; + } + if (utils.isArrayBufferView(data)) { + return data.buffer; + } + if (utils.isURLSearchParams(data)) { + setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); + return data.toString(); + } + if (utils.isObject(data)) { + setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); + return JSON.stringify(data); + } + return data; + }], - if (files.length === 0 && !hasGlob(source)) { - throw new CpyError(`Cannot copy \`${source}\`: the file doesn't exist`); - } + transformResponse: [function transformResponse(data) { + /*eslint no-param-reassign:0*/ + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { /* Ignore */ } + } + return data; + }], - let sources = files.map(sourcePath => new SourceFile(sourcePath, preprocessSourcePath(sourcePath, options))); + /** + * A timeout in milliseconds to abort a request. If set to 0 (default) a + * timeout is not created. + */ + timeout: 0, - if (options.filter !== undefined) { - const filteredSources = await pFilter(sources, options.filter, {concurrency: 1024}); - sources = filteredSources; - } + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', - if (sources.length === 0) { - progressEmitter.emit('progress', { - totalFiles: 0, - percent: 1, - completedFiles: 0, - completedSize: 0 - }); - } + maxContentLength: -1, + maxBodyLength: -1, - const fileProgressHandler = event => { - const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; + } +}; - if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { - completedSize -= fileStatus.written; - completedSize += event.written; +defaults.headers = { + common: { + 'Accept': 'application/json, text/plain, */*' + } +}; - if (event.percent === 1 && fileStatus.percent !== 1) { - completedFiles++; - } +utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { + defaults.headers[method] = {}; +}); - copyStatus.set(event.src, { - written: event.written, - percent: event.percent - }); +utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); +}); - progressEmitter.emit('progress', { - totalFiles: files.length, - percent: completedFiles / files.length, - completedFiles, - completedSize - }); - } - }; +module.exports = defaults; - return pMap(sources, async source => { - const to = preprocessDestinationPath(source.relativePath, destination, options); - try { - await cpFile(source.path, to, options).on('progress', fileProgressHandler); - } catch (error) { - throw new CpyError(`Cannot copy from \`${source.relativePath}\` to \`${to}\`: ${error.message}`, error); - } +/***/ }), +/* 527 */ +/***/ (function(module, exports, __webpack_require__) { - return to; - }, {concurrency}); - })(); +"use strict"; - promise.on = (...arguments_) => { - progressEmitter.on(...arguments_); - return promise; - }; - return promise; +var utils = __webpack_require__(518); + +module.exports = function normalizeHeaderName(headers, normalizedName) { + utils.forEach(headers, function processHeader(value, name) { + if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { + headers[normalizedName] = value; + delete headers[name]; + } + }); }; /***/ }), -/* 523 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(524); -module.exports = async ( - iterable, - mapper, - { - concurrency = Infinity, - stopOnError = true - } = {} -) => { - return new Promise((resolve, reject) => { - if (typeof mapper !== 'function') { - throw new TypeError('Mapper function is required'); - } +var utils = __webpack_require__(518); +var settle = __webpack_require__(529); +var cookies = __webpack_require__(532); +var buildURL = __webpack_require__(521); +var buildFullPath = __webpack_require__(533); +var parseHeaders = __webpack_require__(536); +var isURLSameOrigin = __webpack_require__(537); +var createError = __webpack_require__(530); - if (!(typeof concurrency === 'number' && concurrency >= 1)) { - throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); - } +module.exports = function xhrAdapter(config) { + return new Promise(function dispatchXhrRequest(resolve, reject) { + var requestData = config.data; + var requestHeaders = config.headers; - const ret = []; - const errors = []; - const iterator = iterable[Symbol.iterator](); - let isRejected = false; - let isIterableDone = false; - let resolvingCount = 0; - let currentIndex = 0; + if (utils.isFormData(requestData)) { + delete requestHeaders['Content-Type']; // Let the browser set it + } - const next = () => { - if (isRejected) { - return; - } + var request = new XMLHttpRequest(); - const nextItem = iterator.next(); - const i = currentIndex; - currentIndex++; + // HTTP basic authentication + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ''; + requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); + } - if (nextItem.done) { - isIterableDone = true; + var fullPath = buildFullPath(config.baseURL, config.url); + request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); - if (resolvingCount === 0) { - if (!stopOnError && errors.length !== 0) { - reject(new AggregateError(errors)); - } else { - resolve(ret); - } - } + // Set the request timeout in MS + request.timeout = config.timeout; - return; - } + // Listen for ready state + request.onreadystatechange = function handleLoad() { + if (!request || request.readyState !== 4) { + return; + } - resolvingCount++; + // The request errored out and we didn't get a response, this will be + // handled by onerror instead + // With one exception: request that using file: protocol, most browsers + // will return status as 0 even though it's a successful request + if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { + return; + } - (async () => { - try { - const element = await nextItem.value; - ret[i] = await mapper(element, i); - resolvingCount--; - next(); - } catch (error) { - if (stopOnError) { - isRejected = true; - reject(error); - } else { - errors.push(error); - resolvingCount--; - next(); - } - } - })(); - }; + // Prepare the response + var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; + var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; + var response = { + data: responseData, + status: request.status, + statusText: request.statusText, + headers: responseHeaders, + config: config, + request: request + }; - for (let i = 0; i < concurrency; i++) { - next(); + settle(resolve, reject, response); - if (isIterableDone) { - break; - } - } - }); -}; + // Clean up request + request = null; + }; + // Handle browser request cancellation (as opposed to a manual cancellation) + request.onabort = function handleAbort() { + if (!request) { + return; + } -/***/ }), -/* 524 */ -/***/ (function(module, exports, __webpack_require__) { + reject(createError('Request aborted', config, 'ECONNABORTED', request)); -"use strict"; + // Clean up request + request = null; + }; -const indentString = __webpack_require__(525); -const cleanStack = __webpack_require__(244); + // Handle low level network errors + request.onerror = function handleError() { + // Real errors are hidden from us by the browser + // onerror should only fire if it's a network error + reject(createError('Network Error', config, null, request)); -const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); + // Clean up request + request = null; + }; -class AggregateError extends Error { - constructor(errors) { - if (!Array.isArray(errors)) { - throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); - } + // Handle timeout + request.ontimeout = function handleTimeout() { + var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded'; + if (config.timeoutErrorMessage) { + timeoutErrorMessage = config.timeoutErrorMessage; + } + reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', + request)); - errors = [...errors].map(error => { - if (error instanceof Error) { - return error; - } + // Clean up request + request = null; + }; - if (error !== null && typeof error === 'object') { - // Handle plain error objects with message property and/or possibly other metadata - return Object.assign(new Error(error.message), error); - } + // Add xsrf header + // This is only done if running in a standard browser environment. + // Specifically not if we're in a web worker, or react-native. + if (utils.isStandardBrowserEnv()) { + // Add xsrf header + var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? + cookies.read(config.xsrfCookieName) : + undefined; - return new Error(error); - }); + if (xsrfValue) { + requestHeaders[config.xsrfHeaderName] = xsrfValue; + } + } - let message = errors - .map(error => { - // The `stack` property is not standardized, so we can't assume it exists - return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); - }) - .join('\n'); - message = '\n' + indentString(message, 4); - super(message); + // Add headers to the request + if ('setRequestHeader' in request) { + utils.forEach(requestHeaders, function setRequestHeader(val, key) { + if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { + // Remove Content-Type if data is undefined + delete requestHeaders[key]; + } else { + // Otherwise add header to the request + request.setRequestHeader(key, val); + } + }); + } - this.name = 'AggregateError'; + // Add withCredentials to request if needed + if (!utils.isUndefined(config.withCredentials)) { + request.withCredentials = !!config.withCredentials; + } - Object.defineProperty(this, '_errors', {value: errors}); - } + // Add responseType to request if needed + if (config.responseType) { + try { + request.responseType = config.responseType; + } catch (e) { + // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. + // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. + if (config.responseType !== 'json') { + throw e; + } + } + } - * [Symbol.iterator]() { - for (const error of this._errors) { - yield error; - } - } -} + // Handle progress if needed + if (typeof config.onDownloadProgress === 'function') { + request.addEventListener('progress', config.onDownloadProgress); + } -module.exports = AggregateError; + // Not all browsers support upload events + if (typeof config.onUploadProgress === 'function' && request.upload) { + request.upload.addEventListener('progress', config.onUploadProgress); + } + + if (config.cancelToken) { + // Handle cancellation + config.cancelToken.promise.then(function onCanceled(cancel) { + if (!request) { + return; + } + + request.abort(); + reject(cancel); + // Clean up request + request = null; + }); + } + + if (!requestData) { + requestData = null; + } + + // Send the request + request.send(requestData); + }); +}; /***/ }), -/* 525 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = (string, count = 1, options) => { - options = { - indent: ' ', - includeEmptyLines: false, - ...options - }; +var createError = __webpack_require__(530); - if (typeof string !== 'string') { - throw new TypeError( - `Expected \`input\` to be a \`string\`, got \`${typeof string}\`` - ); - } +/** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + */ +module.exports = function settle(resolve, reject, response) { + var validateStatus = response.config.validateStatus; + if (!response.status || !validateStatus || validateStatus(response.status)) { + resolve(response); + } else { + reject(createError( + 'Request failed with status code ' + response.status, + response.config, + null, + response.request, + response + )); + } +}; - if (typeof count !== 'number') { - throw new TypeError( - `Expected \`count\` to be a \`number\`, got \`${typeof count}\`` - ); - } - if (typeof options.indent !== 'string') { - throw new TypeError( - `Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\`` - ); - } +/***/ }), +/* 530 */ +/***/ (function(module, exports, __webpack_require__) { - if (count === 0) { - return string; - } +"use strict"; - const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; - return string.replace(regex, options.indent.repeat(count)); +var enhanceError = __webpack_require__(531); + +/** + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The created error. + */ +module.exports = function createError(message, config, code, request, response) { + var error = new Error(message); + return enhanceError(error, config, code, request, response); }; /***/ }), -/* 526 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(527); -const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(529); -const dirGlob = __webpack_require__(714); -const gitignore = __webpack_require__(717); -const DEFAULT_FILTER = () => false; +/** + * Update an Error with the specified config, error code, and response. + * + * @param {Error} error The error to update. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The error. + */ +module.exports = function enhanceError(error, config, code, request, response) { + error.config = config; + if (code) { + error.code = code; + } -const isNegative = pattern => pattern[0] === '!'; + error.request = request; + error.response = response; + error.isAxiosError = true; -const assertPatternsInput = patterns => { - if (!patterns.every(x => typeof x === 'string')) { - throw new TypeError('Patterns must be a string or an array of strings'); - } + error.toJSON = function toJSON() { + return { + // Standard + message: this.message, + name: this.name, + // Microsoft + description: this.description, + number: this.number, + // Mozilla + fileName: this.fileName, + lineNumber: this.lineNumber, + columnNumber: this.columnNumber, + stack: this.stack, + // Axios + config: this.config, + code: this.code + }; + }; + return error; }; -const checkCwdOption = options => { - if (options && options.cwd && !fs.statSync(options.cwd).isDirectory()) { - throw new Error('The `cwd` option must be a path to a directory'); - } -}; -const generateGlobTasks = (patterns, taskOptions) => { - patterns = arrayUnion([].concat(patterns)); - assertPatternsInput(patterns); - checkCwdOption(taskOptions); +/***/ }), +/* 532 */ +/***/ (function(module, exports, __webpack_require__) { - const globTasks = []; +"use strict"; - taskOptions = Object.assign({ - ignore: [], - expandDirectories: true - }, taskOptions); - patterns.forEach((pattern, i) => { - if (isNegative(pattern)) { - return; - } +var utils = __webpack_require__(518); - const ignore = patterns - .slice(i) - .filter(isNegative) - .map(pattern => pattern.slice(1)); +module.exports = ( + utils.isStandardBrowserEnv() ? - const options = Object.assign({}, taskOptions, { - ignore: taskOptions.ignore.concat(ignore) - }); + // Standard browser envs support document.cookie + (function standardBrowserEnv() { + return { + write: function write(name, value, expires, path, domain, secure) { + var cookie = []; + cookie.push(name + '=' + encodeURIComponent(value)); - globTasks.push({pattern, options}); - }); + if (utils.isNumber(expires)) { + cookie.push('expires=' + new Date(expires).toGMTString()); + } - return globTasks; -}; + if (utils.isString(path)) { + cookie.push('path=' + path); + } -const globDirs = (task, fn) => { - let options = {}; - if (task.options.cwd) { - options.cwd = task.options.cwd; - } + if (utils.isString(domain)) { + cookie.push('domain=' + domain); + } - if (Array.isArray(task.options.expandDirectories)) { - options = Object.assign(options, {files: task.options.expandDirectories}); - } else if (typeof task.options.expandDirectories === 'object') { - options = Object.assign(options, task.options.expandDirectories); - } + if (secure === true) { + cookie.push('secure'); + } - return fn(task.pattern, options); -}; + document.cookie = cookie.join('; '); + }, -const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; + read: function read(name) { + var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); + return (match ? decodeURIComponent(match[3]) : null); + }, + + remove: function remove(name) { + this.write(name, '', Date.now() - 86400000); + } + }; + })() : + + // Non standard browser env (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return { + write: function write() {}, + read: function read() { return null; }, + remove: function remove() {} + }; + })() +); + + +/***/ }), +/* 533 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var isAbsoluteURL = __webpack_require__(534); +var combineURLs = __webpack_require__(535); + +/** + * Creates a new URL by combining the baseURL with the requestedURL, + * only when the requestedURL is not already an absolute URL. + * If the requestURL is absolute, this function returns the requestedURL untouched. + * + * @param {string} baseURL The base URL + * @param {string} requestedURL Absolute or relative URL to combine + * @returns {string} The combined full path + */ +module.exports = function buildFullPath(baseURL, requestedURL) { + if (baseURL && !isAbsoluteURL(requestedURL)) { + return combineURLs(baseURL, requestedURL); + } + return requestedURL; +}; + + +/***/ }), +/* 534 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Determines whether the specified URL is absolute + * + * @param {string} url The URL to test + * @returns {boolean} True if the specified URL is absolute, otherwise false + */ +module.exports = function isAbsoluteURL(url) { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); +}; + + +/***/ }), +/* 535 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Creates a new URL by combining the specified URLs + * + * @param {string} baseURL The base URL + * @param {string} relativeURL The relative URL + * @returns {string} The combined URL + */ +module.exports = function combineURLs(baseURL, relativeURL) { + return relativeURL + ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') + : baseURL; +}; + + +/***/ }), +/* 536 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(518); + +// Headers whose duplicates are ignored by node +// c.f. https://nodejs.org/api/http.html#http_message_headers +var ignoreDuplicateOf = [ + 'age', 'authorization', 'content-length', 'content-type', 'etag', + 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', + 'last-modified', 'location', 'max-forwards', 'proxy-authorization', + 'referer', 'retry-after', 'user-agent' +]; + +/** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} headers Headers needing to be parsed + * @returns {Object} Headers parsed into an object + */ +module.exports = function parseHeaders(headers) { + var parsed = {}; + var key; + var val; + var i; + + if (!headers) { return parsed; } + + utils.forEach(headers.split('\n'), function parser(line) { + i = line.indexOf(':'); + key = utils.trim(line.substr(0, i)).toLowerCase(); + val = utils.trim(line.substr(i + 1)); + + if (key) { + if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { + return; + } + if (key === 'set-cookie') { + parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); + } else { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + } + }); + + return parsed; +}; + + +/***/ }), +/* 537 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(518); + +module.exports = ( + utils.isStandardBrowserEnv() ? + + // Standard browser envs have full support of the APIs needed to test + // whether the request URL is of the same origin as current location. + (function standardBrowserEnv() { + var msie = /(msie|trident)/i.test(navigator.userAgent); + var urlParsingNode = document.createElement('a'); + var originURL; + + /** + * Parse a URL to discover it's components + * + * @param {String} url The URL to be parsed + * @returns {Object} + */ + function resolveURL(url) { + var href = url; + + if (msie) { + // IE needs attribute set twice to normalize properties + urlParsingNode.setAttribute('href', href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') ? + urlParsingNode.pathname : + '/' + urlParsingNode.pathname + }; + } + + originURL = resolveURL(window.location.href); + + /** + * Determine if a URL shares the same origin as the current location + * + * @param {String} requestURL The URL to test + * @returns {boolean} True if URL shares the same origin, otherwise false + */ + return function isURLSameOrigin(requestURL) { + var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; + return (parsed.protocol === originURL.protocol && + parsed.host === originURL.host); + }; + })() : + + // Non standard browser envs (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return function isURLSameOrigin() { + return true; + }; + })() +); + + +/***/ }), +/* 538 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(518); +var settle = __webpack_require__(529); +var buildFullPath = __webpack_require__(533); +var buildURL = __webpack_require__(521); +var http = __webpack_require__(539); +var https = __webpack_require__(540); +var httpFollow = __webpack_require__(541).http; +var httpsFollow = __webpack_require__(541).https; +var url = __webpack_require__(283); +var zlib = __webpack_require__(549); +var pkg = __webpack_require__(550); +var createError = __webpack_require__(530); +var enhanceError = __webpack_require__(531); + +var isHttps = /https:?/; + +/** + * + * @param {http.ClientRequestArgs} options + * @param {AxiosProxyConfig} proxy + * @param {string} location + */ +function setProxy(options, proxy, location) { + options.hostname = proxy.host; + options.host = proxy.host; + options.port = proxy.port; + options.path = location; + + // Basic proxy authorization + if (proxy.auth) { + var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64'); + options.headers['Proxy-Authorization'] = 'Basic ' + base64; + } + + // If a proxy is used, any redirects must also pass through the proxy + options.beforeRedirect = function beforeRedirect(redirection) { + redirection.headers.host = redirection.host; + setProxy(redirection, proxy, redirection.href); + }; +} + +/*eslint consistent-return:0*/ +module.exports = function httpAdapter(config) { + return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) { + var resolve = function resolve(value) { + resolvePromise(value); + }; + var reject = function reject(value) { + rejectPromise(value); + }; + var data = config.data; + var headers = config.headers; + + // Set User-Agent (required by some servers) + // Only set header if it hasn't been set in config + // See https://github.com/axios/axios/issues/69 + if (!headers['User-Agent'] && !headers['user-agent']) { + headers['User-Agent'] = 'axios/' + pkg.version; + } + + if (data && !utils.isStream(data)) { + if (Buffer.isBuffer(data)) { + // Nothing to do... + } else if (utils.isArrayBuffer(data)) { + data = Buffer.from(new Uint8Array(data)); + } else if (utils.isString(data)) { + data = Buffer.from(data, 'utf-8'); + } else { + return reject(createError( + 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream', + config + )); + } + + // Add Content-Length header if data exists + headers['Content-Length'] = data.length; + } + + // HTTP basic authentication + var auth = undefined; + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password || ''; + auth = username + ':' + password; + } + + // Parse url + var fullPath = buildFullPath(config.baseURL, config.url); + var parsed = url.parse(fullPath); + var protocol = parsed.protocol || 'http:'; + + if (!auth && parsed.auth) { + var urlAuth = parsed.auth.split(':'); + var urlUsername = urlAuth[0] || ''; + var urlPassword = urlAuth[1] || ''; + auth = urlUsername + ':' + urlPassword; + } + + if (auth) { + delete headers.Authorization; + } + + var isHttpsRequest = isHttps.test(protocol); + var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent; + + var options = { + path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''), + method: config.method.toUpperCase(), + headers: headers, + agent: agent, + agents: { http: config.httpAgent, https: config.httpsAgent }, + auth: auth + }; + + if (config.socketPath) { + options.socketPath = config.socketPath; + } else { + options.hostname = parsed.hostname; + options.port = parsed.port; + } + + var proxy = config.proxy; + if (!proxy && proxy !== false) { + var proxyEnv = protocol.slice(0, -1) + '_proxy'; + var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()]; + if (proxyUrl) { + var parsedProxyUrl = url.parse(proxyUrl); + var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY; + var shouldProxy = true; + + if (noProxyEnv) { + var noProxy = noProxyEnv.split(',').map(function trim(s) { + return s.trim(); + }); + + shouldProxy = !noProxy.some(function proxyMatch(proxyElement) { + if (!proxyElement) { + return false; + } + if (proxyElement === '*') { + return true; + } + if (proxyElement[0] === '.' && + parsed.hostname.substr(parsed.hostname.length - proxyElement.length) === proxyElement) { + return true; + } + + return parsed.hostname === proxyElement; + }); + } + + if (shouldProxy) { + proxy = { + host: parsedProxyUrl.hostname, + port: parsedProxyUrl.port, + protocol: parsedProxyUrl.protocol + }; + + if (parsedProxyUrl.auth) { + var proxyUrlAuth = parsedProxyUrl.auth.split(':'); + proxy.auth = { + username: proxyUrlAuth[0], + password: proxyUrlAuth[1] + }; + } + } + } + } + + if (proxy) { + options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : ''); + setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path); + } + + var transport; + var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true); + if (config.transport) { + transport = config.transport; + } else if (config.maxRedirects === 0) { + transport = isHttpsProxy ? https : http; + } else { + if (config.maxRedirects) { + options.maxRedirects = config.maxRedirects; + } + transport = isHttpsProxy ? httpsFollow : httpFollow; + } + + if (config.maxBodyLength > -1) { + options.maxBodyLength = config.maxBodyLength; + } + + // Create the request + var req = transport.request(options, function handleResponse(res) { + if (req.aborted) return; + + // uncompress the response body transparently if required + var stream = res; + + // return the last request in case of redirects + var lastRequest = res.req || req; + + + // if no content, is HEAD request or decompress disabled we should not decompress + if (res.statusCode !== 204 && lastRequest.method !== 'HEAD' && config.decompress !== false) { + switch (res.headers['content-encoding']) { + /*eslint default-case:0*/ + case 'gzip': + case 'compress': + case 'deflate': + // add the unzipper to the body stream processing pipeline + stream = stream.pipe(zlib.createUnzip()); + + // remove the content-encoding in order to not confuse downstream operations + delete res.headers['content-encoding']; + break; + } + } + + var response = { + status: res.statusCode, + statusText: res.statusMessage, + headers: res.headers, + config: config, + request: lastRequest + }; + + if (config.responseType === 'stream') { + response.data = stream; + settle(resolve, reject, response); + } else { + var responseBuffer = []; + stream.on('data', function handleStreamData(chunk) { + responseBuffer.push(chunk); + + // make sure the content length is not over the maxContentLength if specified + if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > config.maxContentLength) { + stream.destroy(); + reject(createError('maxContentLength size of ' + config.maxContentLength + ' exceeded', + config, null, lastRequest)); + } + }); + + stream.on('error', function handleStreamError(err) { + if (req.aborted) return; + reject(enhanceError(err, config, null, lastRequest)); + }); + + stream.on('end', function handleStreamEnd() { + var responseData = Buffer.concat(responseBuffer); + if (config.responseType !== 'arraybuffer') { + responseData = responseData.toString(config.responseEncoding); + if (!config.responseEncoding || config.responseEncoding === 'utf8') { + responseData = utils.stripBOM(responseData); + } + } + + response.data = responseData; + settle(resolve, reject, response); + }); + } + }); + + // Handle errors + req.on('error', function handleRequestError(err) { + if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return; + reject(enhanceError(err, config, null, req)); + }); + + // Handle request timeout + if (config.timeout) { + // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system. + // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET. + // At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up. + // And then these socket which be hang up will devoring CPU little by little. + // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect. + req.setTimeout(config.timeout, function handleRequestTimeout() { + req.abort(); + reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req)); + }); + } + + if (config.cancelToken) { + // Handle cancellation + config.cancelToken.promise.then(function onCanceled(cancel) { + if (req.aborted) return; + + req.abort(); + reject(cancel); + }); + } + + // Send the request + if (utils.isStream(data)) { + data.on('error', function handleStreamError(err) { + reject(enhanceError(err, config, null, req)); + }).pipe(req); + } else { + req.end(data); + } + }); +}; + + +/***/ }), +/* 539 */ +/***/ (function(module, exports) { + +module.exports = require("http"); + +/***/ }), +/* 540 */ +/***/ (function(module, exports) { + +module.exports = require("https"); + +/***/ }), +/* 541 */ +/***/ (function(module, exports, __webpack_require__) { + +var url = __webpack_require__(283); +var URL = url.URL; +var http = __webpack_require__(539); +var https = __webpack_require__(540); +var Writable = __webpack_require__(138).Writable; +var assert = __webpack_require__(140); +var debug = __webpack_require__(542); + +// Create handlers that pass events from native requests +var eventHandlers = Object.create(null); +["abort", "aborted", "connect", "error", "socket", "timeout"].forEach(function (event) { + eventHandlers[event] = function (arg1, arg2, arg3) { + this._redirectable.emit(event, arg1, arg2, arg3); + }; +}); + +// Error types with codes +var RedirectionError = createErrorType( + "ERR_FR_REDIRECTION_FAILURE", + "" +); +var TooManyRedirectsError = createErrorType( + "ERR_FR_TOO_MANY_REDIRECTS", + "Maximum number of redirects exceeded" +); +var MaxBodyLengthExceededError = createErrorType( + "ERR_FR_MAX_BODY_LENGTH_EXCEEDED", + "Request body larger than maxBodyLength limit" +); +var WriteAfterEndError = createErrorType( + "ERR_STREAM_WRITE_AFTER_END", + "write after end" +); + +// An HTTP(S) request that can be redirected +function RedirectableRequest(options, responseCallback) { + // Initialize the request + Writable.call(this); + this._sanitizeOptions(options); + this._options = options; + this._ended = false; + this._ending = false; + this._redirectCount = 0; + this._redirects = []; + this._requestBodyLength = 0; + this._requestBodyBuffers = []; + + // Attach a callback if passed + if (responseCallback) { + this.on("response", responseCallback); + } + + // React to responses of native requests + var self = this; + this._onNativeResponse = function (response) { + self._processResponse(response); + }; + + // Perform the first request + this._performRequest(); +} +RedirectableRequest.prototype = Object.create(Writable.prototype); + +// Writes buffered data to the current native request +RedirectableRequest.prototype.write = function (data, encoding, callback) { + // Writing is not allowed if end has been called + if (this._ending) { + throw new WriteAfterEndError(); + } + + // Validate input and shift parameters if necessary + if (!(typeof data === "string" || typeof data === "object" && ("length" in data))) { + throw new TypeError("data should be a string, Buffer or Uint8Array"); + } + if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + // Ignore empty buffers, since writing them doesn't invoke the callback + // https://github.com/nodejs/node/issues/22066 + if (data.length === 0) { + if (callback) { + callback(); + } + return; + } + // Only write when we don't exceed the maximum body length + if (this._requestBodyLength + data.length <= this._options.maxBodyLength) { + this._requestBodyLength += data.length; + this._requestBodyBuffers.push({ data: data, encoding: encoding }); + this._currentRequest.write(data, encoding, callback); + } + // Error when we exceed the maximum body length + else { + this.emit("error", new MaxBodyLengthExceededError()); + this.abort(); + } +}; + +// Ends the current native request +RedirectableRequest.prototype.end = function (data, encoding, callback) { + // Shift parameters if necessary + if (typeof data === "function") { + callback = data; + data = encoding = null; + } + else if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + // Write data if needed and end + if (!data) { + this._ended = this._ending = true; + this._currentRequest.end(null, null, callback); + } + else { + var self = this; + var currentRequest = this._currentRequest; + this.write(data, encoding, function () { + self._ended = true; + currentRequest.end(null, null, callback); + }); + this._ending = true; + } +}; + +// Sets a header value on the current native request +RedirectableRequest.prototype.setHeader = function (name, value) { + this._options.headers[name] = value; + this._currentRequest.setHeader(name, value); +}; + +// Clears a header value on the current native request +RedirectableRequest.prototype.removeHeader = function (name) { + delete this._options.headers[name]; + this._currentRequest.removeHeader(name); +}; + +// Global timeout for all underlying requests +RedirectableRequest.prototype.setTimeout = function (msecs, callback) { + if (callback) { + this.once("timeout", callback); + } + + if (this.socket) { + startTimer(this, msecs); + } + else { + var self = this; + this._currentRequest.once("socket", function () { + startTimer(self, msecs); + }); + } + + this.once("response", clearTimer); + this.once("error", clearTimer); + + return this; +}; + +function startTimer(request, msecs) { + clearTimeout(request._timeout); + request._timeout = setTimeout(function () { + request.emit("timeout"); + }, msecs); +} + +function clearTimer() { + clearTimeout(this._timeout); +} + +// Proxy all other public ClientRequest methods +[ + "abort", "flushHeaders", "getHeader", + "setNoDelay", "setSocketKeepAlive", +].forEach(function (method) { + RedirectableRequest.prototype[method] = function (a, b) { + return this._currentRequest[method](a, b); + }; +}); + +// Proxy all public ClientRequest properties +["aborted", "connection", "socket"].forEach(function (property) { + Object.defineProperty(RedirectableRequest.prototype, property, { + get: function () { return this._currentRequest[property]; }, + }); +}); + +RedirectableRequest.prototype._sanitizeOptions = function (options) { + // Ensure headers are always present + if (!options.headers) { + options.headers = {}; + } + + // Since http.request treats host as an alias of hostname, + // but the url module interprets host as hostname plus port, + // eliminate the host property to avoid confusion. + if (options.host) { + // Use hostname if set, because it has precedence + if (!options.hostname) { + options.hostname = options.host; + } + delete options.host; + } + + // Complete the URL object when necessary + if (!options.pathname && options.path) { + var searchPos = options.path.indexOf("?"); + if (searchPos < 0) { + options.pathname = options.path; + } + else { + options.pathname = options.path.substring(0, searchPos); + options.search = options.path.substring(searchPos); + } + } +}; + + +// Executes the next native request (initial or redirect) +RedirectableRequest.prototype._performRequest = function () { + // Load the native protocol + var protocol = this._options.protocol; + var nativeProtocol = this._options.nativeProtocols[protocol]; + if (!nativeProtocol) { + this.emit("error", new TypeError("Unsupported protocol " + protocol)); + return; + } + + // If specified, use the agent corresponding to the protocol + // (HTTP and HTTPS use different types of agents) + if (this._options.agents) { + var scheme = protocol.substr(0, protocol.length - 1); + this._options.agent = this._options.agents[scheme]; + } + + // Create the native request + var request = this._currentRequest = + nativeProtocol.request(this._options, this._onNativeResponse); + this._currentUrl = url.format(this._options); + + // Set up event handlers + request._redirectable = this; + for (var event in eventHandlers) { + /* istanbul ignore else */ + if (event) { + request.on(event, eventHandlers[event]); + } + } + + // End a redirected request + // (The first request must be ended explicitly with RedirectableRequest#end) + if (this._isRedirect) { + // Write the request entity and end. + var i = 0; + var self = this; + var buffers = this._requestBodyBuffers; + (function writeNext(error) { + // Only write if this request has not been redirected yet + /* istanbul ignore else */ + if (request === self._currentRequest) { + // Report any write errors + /* istanbul ignore if */ + if (error) { + self.emit("error", error); + } + // Write the next buffer if there are still left + else if (i < buffers.length) { + var buffer = buffers[i++]; + /* istanbul ignore else */ + if (!request.finished) { + request.write(buffer.data, buffer.encoding, writeNext); + } + } + // End the request if `end` has been called on us + else if (self._ended) { + request.end(); + } + } + }()); + } +}; + +// Processes a response from the current native request +RedirectableRequest.prototype._processResponse = function (response) { + // Store the redirected response + var statusCode = response.statusCode; + if (this._options.trackRedirects) { + this._redirects.push({ + url: this._currentUrl, + headers: response.headers, + statusCode: statusCode, + }); + } + + // RFC7231§6.4: The 3xx (Redirection) class of status code indicates + // that further action needs to be taken by the user agent in order to + // fulfill the request. If a Location header field is provided, + // the user agent MAY automatically redirect its request to the URI + // referenced by the Location field value, + // even if the specific status code is not understood. + var location = response.headers.location; + if (location && this._options.followRedirects !== false && + statusCode >= 300 && statusCode < 400) { + // Abort the current request + this._currentRequest.removeAllListeners(); + this._currentRequest.on("error", noop); + this._currentRequest.abort(); + // Discard the remainder of the response to avoid waiting for data + response.destroy(); + + // RFC7231§6.4: A client SHOULD detect and intervene + // in cyclical redirections (i.e., "infinite" redirection loops). + if (++this._redirectCount > this._options.maxRedirects) { + this.emit("error", new TooManyRedirectsError()); + return; + } + + // RFC7231§6.4: Automatic redirection needs to done with + // care for methods not known to be safe, […] + // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change + // the request method from POST to GET for the subsequent request. + if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || + // RFC7231§6.4.4: The 303 (See Other) status code indicates that + // the server is redirecting the user agent to a different resource […] + // A user agent can perform a retrieval request targeting that URI + // (a GET or HEAD request if using HTTP) […] + (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) { + this._options.method = "GET"; + // Drop a possible entity and headers related to it + this._requestBodyBuffers = []; + removeMatchingHeaders(/^content-/i, this._options.headers); + } + + // Drop the Host header, as the redirect might lead to a different host + var previousHostName = removeMatchingHeaders(/^host$/i, this._options.headers) || + url.parse(this._currentUrl).hostname; + + // Create the redirected request + var redirectUrl = url.resolve(this._currentUrl, location); + debug("redirecting to", redirectUrl); + this._isRedirect = true; + var redirectUrlParts = url.parse(redirectUrl); + Object.assign(this._options, redirectUrlParts); + + // Drop the Authorization header if redirecting to another host + if (redirectUrlParts.hostname !== previousHostName) { + removeMatchingHeaders(/^authorization$/i, this._options.headers); + } + + // Evaluate the beforeRedirect callback + if (typeof this._options.beforeRedirect === "function") { + var responseDetails = { headers: response.headers }; + try { + this._options.beforeRedirect.call(null, this._options, responseDetails); + } + catch (err) { + this.emit("error", err); + return; + } + this._sanitizeOptions(this._options); + } + + // Perform the redirected request + try { + this._performRequest(); + } + catch (cause) { + var error = new RedirectionError("Redirected request failed: " + cause.message); + error.cause = cause; + this.emit("error", error); + } + } + else { + // The response is not a redirect; return it as-is + response.responseUrl = this._currentUrl; + response.redirects = this._redirects; + this.emit("response", response); + + // Clean up + this._requestBodyBuffers = []; + } +}; + +// Wraps the key/value object of protocols with redirect functionality +function wrap(protocols) { + // Default settings + var exports = { + maxRedirects: 21, + maxBodyLength: 10 * 1024 * 1024, + }; + + // Wrap each protocol + var nativeProtocols = {}; + Object.keys(protocols).forEach(function (scheme) { + var protocol = scheme + ":"; + var nativeProtocol = nativeProtocols[protocol] = protocols[scheme]; + var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol); + + // Executes a request, following redirects + wrappedProtocol.request = function (input, options, callback) { + // Parse parameters + if (typeof input === "string") { + var urlStr = input; + try { + input = urlToOptions(new URL(urlStr)); + } + catch (err) { + /* istanbul ignore next */ + input = url.parse(urlStr); + } + } + else if (URL && (input instanceof URL)) { + input = urlToOptions(input); + } + else { + callback = options; + options = input; + input = { protocol: protocol }; + } + if (typeof options === "function") { + callback = options; + options = null; + } + + // Set defaults + options = Object.assign({ + maxRedirects: exports.maxRedirects, + maxBodyLength: exports.maxBodyLength, + }, input, options); + options.nativeProtocols = nativeProtocols; + + assert.equal(options.protocol, protocol, "protocol mismatch"); + debug("options", options); + return new RedirectableRequest(options, callback); + }; + + // Executes a GET request, following redirects + wrappedProtocol.get = function (input, options, callback) { + var request = wrappedProtocol.request(input, options, callback); + request.end(); + return request; + }; + }); + return exports; +} + +/* istanbul ignore next */ +function noop() { /* empty */ } + +// from https://github.com/nodejs/node/blob/master/lib/internal/url.js +function urlToOptions(urlObject) { + var options = { + protocol: urlObject.protocol, + hostname: urlObject.hostname.startsWith("[") ? + /* istanbul ignore next */ + urlObject.hostname.slice(1, -1) : + urlObject.hostname, + hash: urlObject.hash, + search: urlObject.search, + pathname: urlObject.pathname, + path: urlObject.pathname + urlObject.search, + href: urlObject.href, + }; + if (urlObject.port !== "") { + options.port = Number(urlObject.port); + } + return options; +} + +function removeMatchingHeaders(regex, headers) { + var lastValue; + for (var header in headers) { + if (regex.test(header)) { + lastValue = headers[header]; + delete headers[header]; + } + } + return lastValue; +} + +function createErrorType(code, defaultMessage) { + function CustomError(message) { + Error.captureStackTrace(this, this.constructor); + this.message = message || defaultMessage; + } + CustomError.prototype = new Error(); + CustomError.prototype.constructor = CustomError; + CustomError.prototype.name = "Error [" + code + "]"; + CustomError.prototype.code = code; + return CustomError; +} + +// Exports +module.exports = wrap({ http: http, https: https }); +module.exports.wrap = wrap; + + +/***/ }), +/* 542 */ +/***/ (function(module, exports, __webpack_require__) { + +var debug; +try { + /* eslint global-require: off */ + debug = __webpack_require__(543)("follow-redirects"); +} +catch (error) { + debug = function () { /* */ }; +} +module.exports = debug; + + +/***/ }), +/* 543 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ + +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(544); +} else { + module.exports = __webpack_require__(547); +} + + +/***/ }), +/* 544 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(545); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } + + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return; + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + + +/***/ }), +/* 545 */ +/***/ (function(module, exports, __webpack_require__) { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(546); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + +exports.formatters = {}; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ + +function selectColor(namespace) { + var hash = 0, i; + + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return exports.colors[Math.abs(hash) % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function createDebug(namespace) { + + function debug() { + // disabled? + if (!debug.enabled) return; + + var self = debug; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); + + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + return debug; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + exports.names = []; + exports.skips = []; + + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + + +/***/ }), +/* 546 */ +/***/ (function(module, exports) { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + + +/***/ }), +/* 547 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var tty = __webpack_require__(122); +var util = __webpack_require__(112); + +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(545); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(function (key) { + return /^debug_/i.test(key); +}).reduce(function (obj, key) { + // camel-case + var prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + + // coerce string value into JS value + var val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) val = true; + else if (/^(no|off|false|disabled)$/i.test(val)) val = false; + else if (val === 'null') val = null; + else val = Number(val); + + obj[prop] = val; + return obj; +}, {}); + +/** + * The file descriptor to write the `debug()` calls to. + * Set the `DEBUG_FD` env variable to override with another value. i.e.: + * + * $ DEBUG_FD=3 node script.js 3>debug.log + */ + +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + +if (1 !== fd && 2 !== fd) { + util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() +} + +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +exports.formatters.o = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n').map(function(str) { + return str.trim() + }).join(' '); +}; + +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ + +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; + + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = new Date().toUTCString() + + ' ' + name + ' ' + args[0]; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ + +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + if (null == namespaces) { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } else { + process.env.DEBUG = namespaces; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Copied from `node/src/node.js`. + * + * XXX: It's lame that node doesn't expose this API out-of-the-box. It also + * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. + */ + +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); + + // Note stream._type is used for test-module-load-list.js + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + + // Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + case 'FILE': + var fs = __webpack_require__(134); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + var net = __webpack_require__(548); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); + + // FIXME Should probably have an option in net.Socket to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stream.readable = false; + stream.read = null; + stream._type = 'pipe'; + + // FIXME Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init (debug) { + debug.inspectOpts = {}; + + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ + +exports.enable(load()); + + +/***/ }), +/* 548 */ +/***/ (function(module, exports) { + +module.exports = require("net"); + +/***/ }), +/* 549 */ +/***/ (function(module, exports) { + +module.exports = require("zlib"); + +/***/ }), +/* 550 */ +/***/ (function(module) { + +module.exports = JSON.parse("{\"name\":\"axios\",\"version\":\"0.21.1\",\"description\":\"Promise based HTTP client for the browser and node.js\",\"main\":\"index.js\",\"scripts\":{\"test\":\"grunt test && bundlesize\",\"start\":\"node ./sandbox/server.js\",\"build\":\"NODE_ENV=production grunt build\",\"preversion\":\"npm test\",\"version\":\"npm run build && grunt version && git add -A dist && git add CHANGELOG.md bower.json package.json\",\"postversion\":\"git push && git push --tags\",\"examples\":\"node ./examples/server.js\",\"coveralls\":\"cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js\",\"fix\":\"eslint --fix lib/**/*.js\"},\"repository\":{\"type\":\"git\",\"url\":\"https://github.com/axios/axios.git\"},\"keywords\":[\"xhr\",\"http\",\"ajax\",\"promise\",\"node\"],\"author\":\"Matt Zabriskie\",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/axios/axios/issues\"},\"homepage\":\"https://github.com/axios/axios\",\"devDependencies\":{\"bundlesize\":\"^0.17.0\",\"coveralls\":\"^3.0.0\",\"es6-promise\":\"^4.2.4\",\"grunt\":\"^1.0.2\",\"grunt-banner\":\"^0.6.0\",\"grunt-cli\":\"^1.2.0\",\"grunt-contrib-clean\":\"^1.1.0\",\"grunt-contrib-watch\":\"^1.0.0\",\"grunt-eslint\":\"^20.1.0\",\"grunt-karma\":\"^2.0.0\",\"grunt-mocha-test\":\"^0.13.3\",\"grunt-ts\":\"^6.0.0-beta.19\",\"grunt-webpack\":\"^1.0.18\",\"istanbul-instrumenter-loader\":\"^1.0.0\",\"jasmine-core\":\"^2.4.1\",\"karma\":\"^1.3.0\",\"karma-chrome-launcher\":\"^2.2.0\",\"karma-coverage\":\"^1.1.1\",\"karma-firefox-launcher\":\"^1.1.0\",\"karma-jasmine\":\"^1.1.1\",\"karma-jasmine-ajax\":\"^0.1.13\",\"karma-opera-launcher\":\"^1.0.0\",\"karma-safari-launcher\":\"^1.0.0\",\"karma-sauce-launcher\":\"^1.2.0\",\"karma-sinon\":\"^1.0.5\",\"karma-sourcemap-loader\":\"^0.3.7\",\"karma-webpack\":\"^1.7.0\",\"load-grunt-tasks\":\"^3.5.2\",\"minimist\":\"^1.2.0\",\"mocha\":\"^5.2.0\",\"sinon\":\"^4.5.0\",\"typescript\":\"^2.8.1\",\"url-search-params\":\"^0.10.0\",\"webpack\":\"^1.13.1\",\"webpack-dev-server\":\"^1.14.1\"},\"browser\":{\"./lib/adapters/http.js\":\"./lib/adapters/xhr.js\"},\"jsdelivr\":\"dist/axios.min.js\",\"unpkg\":\"dist/axios.min.js\",\"typings\":\"./index.d.ts\",\"dependencies\":{\"follow-redirects\":\"^1.10.0\"},\"bundlesize\":[{\"path\":\"./dist/axios.min.js\",\"threshold\":\"5kB\"}]}"); + +/***/ }), +/* 551 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(518); + +/** + * Config-specific merge-function which creates a new config-object + * by merging two configuration objects together. + * + * @param {Object} config1 + * @param {Object} config2 + * @returns {Object} New object resulting from merging config2 to config1 + */ +module.exports = function mergeConfig(config1, config2) { + // eslint-disable-next-line no-param-reassign + config2 = config2 || {}; + var config = {}; + + var valueFromConfig2Keys = ['url', 'method', 'data']; + var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params']; + var defaultToConfig2Keys = [ + 'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer', + 'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', + 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress', + 'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent', + 'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding' + ]; + var directMergeKeys = ['validateStatus']; + + function getMergedValue(target, source) { + if (utils.isPlainObject(target) && utils.isPlainObject(source)) { + return utils.merge(target, source); + } else if (utils.isPlainObject(source)) { + return utils.merge({}, source); + } else if (utils.isArray(source)) { + return source.slice(); + } + return source; + } + + function mergeDeepProperties(prop) { + if (!utils.isUndefined(config2[prop])) { + config[prop] = getMergedValue(config1[prop], config2[prop]); + } else if (!utils.isUndefined(config1[prop])) { + config[prop] = getMergedValue(undefined, config1[prop]); + } + } + + utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) { + if (!utils.isUndefined(config2[prop])) { + config[prop] = getMergedValue(undefined, config2[prop]); + } + }); + + utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties); + + utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) { + if (!utils.isUndefined(config2[prop])) { + config[prop] = getMergedValue(undefined, config2[prop]); + } else if (!utils.isUndefined(config1[prop])) { + config[prop] = getMergedValue(undefined, config1[prop]); + } + }); + + utils.forEach(directMergeKeys, function merge(prop) { + if (prop in config2) { + config[prop] = getMergedValue(config1[prop], config2[prop]); + } else if (prop in config1) { + config[prop] = getMergedValue(undefined, config1[prop]); + } + }); + + var axiosKeys = valueFromConfig2Keys + .concat(mergeDeepPropertiesKeys) + .concat(defaultToConfig2Keys) + .concat(directMergeKeys); + + var otherKeys = Object + .keys(config1) + .concat(Object.keys(config2)) + .filter(function filterAxiosKeys(key) { + return axiosKeys.indexOf(key) === -1; + }); + + utils.forEach(otherKeys, mergeDeepProperties); + + return config; +}; + + +/***/ }), +/* 552 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * A `Cancel` is an object that is thrown when an operation is canceled. + * + * @class + * @param {string=} message The message. + */ +function Cancel(message) { + this.message = message; +} + +Cancel.prototype.toString = function toString() { + return 'Cancel' + (this.message ? ': ' + this.message : ''); +}; + +Cancel.prototype.__CANCEL__ = true; + +module.exports = Cancel; + + +/***/ }), +/* 553 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var Cancel = __webpack_require__(552); + +/** + * A `CancelToken` is an object that can be used to request cancellation of an operation. + * + * @class + * @param {Function} executor The executor function. + */ +function CancelToken(executor) { + if (typeof executor !== 'function') { + throw new TypeError('executor must be a function.'); + } + + var resolvePromise; + this.promise = new Promise(function promiseExecutor(resolve) { + resolvePromise = resolve; + }); + + var token = this; + executor(function cancel(message) { + if (token.reason) { + // Cancellation has already been requested + return; + } + + token.reason = new Cancel(message); + resolvePromise(token.reason); + }); +} + +/** + * Throws a `Cancel` if cancellation has been requested. + */ +CancelToken.prototype.throwIfRequested = function throwIfRequested() { + if (this.reason) { + throw this.reason; + } +}; + +/** + * Returns an object that contains a new `CancelToken` and a function that, when called, + * cancels the `CancelToken`. + */ +CancelToken.source = function source() { + var cancel; + var token = new CancelToken(function executor(c) { + cancel = c; + }); + return { + token: token, + cancel: cancel + }; +}; + +module.exports = CancelToken; + + +/***/ }), +/* 554 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Syntactic sugar for invoking a function and expanding an array for arguments. + * + * Common use case would be to use `Function.prototype.apply`. + * + * ```js + * function f(x, y, z) {} + * var args = [1, 2, 3]; + * f.apply(null, args); + * ``` + * + * With `spread` this example can be re-written. + * + * ```js + * spread(function(x, y, z) {})([1, 2, 3]); + * ``` + * + * @param {Function} callback + * @returns {Function} + */ +module.exports = function spread(callback) { + return function wrap(arr) { + return callback.apply(null, arr); + }; +}; + + +/***/ }), +/* 555 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Determines whether the payload is an error thrown by Axios + * + * @param {*} payload The value to test + * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false + */ +module.exports = function isAxiosError(payload) { + return (typeof payload === 'object') && (payload.isAxiosError === true); +}; + + +/***/ }), +/* 556 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseConfig = void 0; +function validateConfig(log, config) { + const validApiToken = typeof config.apiToken === 'string' && config.apiToken.length !== 0; + if (!validApiToken) { + log.warning('KIBANA_CI_STATS_CONFIG is missing a valid api token, stats will not be reported'); + return; + } + const validId = typeof config.buildId === 'string' && config.buildId.length !== 0; + if (!validId) { + log.warning('KIBANA_CI_STATS_CONFIG is missing a valid build id, stats will not be reported'); + return; + } + return config; +} +function parseConfig(log) { + const configJson = process.env.KIBANA_CI_STATS_CONFIG; + if (!configJson) { + log.debug('KIBANA_CI_STATS_CONFIG environment variable not found, disabling CiStatsReporter'); + return; + } + let config; + try { + config = JSON.parse(configJson); + } + catch (_) { + // handled below + } + if (typeof config === 'object' && config !== null) { + return validateConfig(log, config); + } + log.warning('KIBANA_CI_STATS_CONFIG is invalid, stats will not be reported'); + return; +} +exports.parseConfig = parseConfig; + + +/***/ }), +/* 557 */ +/***/ (function(module, exports) { + +function webpackEmptyContext(req) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; +} +webpackEmptyContext.keys = function() { return []; }; +webpackEmptyContext.resolve = webpackEmptyContext; +module.exports = webpackEmptyContext; +webpackEmptyContext.id = 557; + +/***/ }), +/* 558 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(134); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(559); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(239); +/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(366); +/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(562); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + + + + + + + +/** + * Helper class for dealing with a set of projects as children of + * the Kibana project. The kbn/pm is currently implemented to be + * more generic, where everything is an operation of generic projects, + * but that leads to exceptions where we need the kibana project and + * do things like `project.get('kibana')!`. + * + * Using this helper we can restructre the generic list of projects + * as a Kibana object which encapulates all the projects in the + * workspace and knows about the root Kibana project. + */ + +class Kibana { + static async loadFrom(rootPath) { + return new Kibana(await Object(_projects__WEBPACK_IMPORTED_MODULE_5__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_6__["getProjectPaths"])({ + rootPath + }))); + } + + constructor(allWorkspaceProjects) { + this.allWorkspaceProjects = allWorkspaceProjects; + + _defineProperty(this, "kibanaProject", void 0); + + const kibanaProject = allWorkspaceProjects.get('kibana'); + + if (!kibanaProject) { + throw new TypeError('Unable to create Kibana object without all projects, including the Kibana project.'); + } + + this.kibanaProject = kibanaProject; + } + /** make an absolute path by resolving subPath relative to the kibana repo */ + + + getAbsolute(...subPath) { + return path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(this.kibanaProject.path, ...subPath); + } + /** convert an absolute path to a relative path, relative to the kibana repo */ + + + getRelative(absolute) { + return path__WEBPACK_IMPORTED_MODULE_0___default.a.relative(this.kibanaProject.path, absolute); + } + /** get a copy of the map of all projects in the kibana workspace */ + + + getAllProjects() { + return new Map(this.allWorkspaceProjects); + } + /** determine if a project with the given name exists */ + + + hasProject(name) { + return this.allWorkspaceProjects.has(name); + } + /** get a specific project, throws if the name is not known (use hasProject() first) */ + + + getProject(name) { + const project = this.allWorkspaceProjects.get(name); + + if (!project) { + throw new Error(`No package with name "${name}" in the workspace`); + } + + return project; + } + /** get a project and all of the projects it depends on in a ProjectMap */ + + + getProjectAndDeps(name) { + const project = this.getProject(name); + return Object(_projects__WEBPACK_IMPORTED_MODULE_5__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); + } + /** filter the projects to just those matching certain paths/include/exclude tags */ + + + getFilteredProjects(options) { + const allProjects = this.getAllProjects(); + const filteredProjects = new Map(); + const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); + const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_6__["getProjectPaths"])(_objectSpread(_objectSpread({}, options), {}, { + rootPath: this.kibanaProject.path + })).map(g => path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(g, 'package.json')); + const matchingPkgJsonPaths = multimatch__WEBPACK_IMPORTED_MODULE_2___default()(pkgJsonPaths, filteredPkgJsonGlobs); + + for (const project of allProjects.values()) { + const pathMatches = matchingPkgJsonPaths.includes(project.packageJsonLocation); + const notExcluded = !options.exclude.includes(project.name); + const isIncluded = !options.include.length || options.include.includes(project.name); + + if (pathMatches && notExcluded && isIncluded) { + filteredProjects.set(project.name, project); + } + } + + return filteredProjects; + } + + isPartOfRepo(project) { + return project.path === this.kibanaProject.path || is_path_inside__WEBPACK_IMPORTED_MODULE_3___default()(project.path, this.kibanaProject.path); + } + + isOutsideRepo(project) { + return !this.isPartOfRepo(project); + } + + resolveAllProductionDependencies(yarnLock, log) { + const kibanaDeps = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_4__["resolveDepsForProject"])({ + project: this.kibanaProject, + yarnLock, + kbn: this, + includeDependentProject: true, + productionDepsOnly: true, + log + }); + const xpackDeps = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_4__["resolveDepsForProject"])({ + project: this.getProject('x-pack'), + yarnLock, + kbn: this, + includeDependentProject: true, + productionDepsOnly: true, + log + }); + return new Map([...kibanaDeps.entries(), ...xpackDeps.entries()]); + } + + getUuid() { + try { + return fs__WEBPACK_IMPORTED_MODULE_1___default.a.readFileSync(this.getAbsolute('data/uuid'), 'utf-8').trim(); + } catch (error) { + if (error.code === 'ENOENT') { + return undefined; + } + + throw error; + } + } + +} + +/***/ }), +/* 559 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const minimatch = __webpack_require__(150); +const arrayUnion = __webpack_require__(145); +const arrayDiffer = __webpack_require__(560); +const arrify = __webpack_require__(561); + +module.exports = (list, patterns, options = {}) => { + list = arrify(list); + patterns = arrify(patterns); + + if (list.length === 0 || patterns.length === 0) { + return []; + } + + return patterns.reduce((result, pattern) => { + let process = arrayUnion; + + if (pattern[0] === '!') { + pattern = pattern.slice(1); + process = arrayDiffer; + } + + return process(result, minimatch.match(list, pattern, options)); + }, []); +}; + + +/***/ }), +/* 560 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const arrayDiffer = (array, ...values) => { + const rest = new Set([].concat(...values)); + return array.filter(element => !rest.has(element)); +}; + +module.exports = arrayDiffer; + + +/***/ }), +/* 561 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const arrify = value => { + if (value === null || value === undefined) { + return []; + } + + if (Array.isArray(value)) { + return value; + } + + if (typeof value === 'string') { + return [value]; + } + + if (typeof value[Symbol.iterator] === 'function') { + return [...value]; + } + + return [value]; +}; + +module.exports = arrify; + + +/***/ }), +/* 562 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + + +/** + * Returns all the paths where plugins are located + */ +function getProjectPaths({ + rootPath, + ossOnly, + skipKibanaPlugins +}) { + const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared + // plugin functional used in the selenium functional tests. + // As we are now using the webpack dll for the client vendors dependencies + // when we run the plugin functional tests against the distributable + // dependencies used by such plugins like @eui, react and react-dom can't + // be loaded from the dll as the context is different from the one declared + // into the webpack dll reference plugin. + // In anyway, have a plugin declaring their own dependencies is the + // correct and the expect behavior. + + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*')); + + if (!ossOnly) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/test/functional_with_es_ssl/fixtures/plugins/*')); + } + + if (!skipKibanaPlugins) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); + } + + return projectPaths; +} + +/***/ }), +/* 563 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(564); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildBazelProductionProjects"]; }); + +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(783); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + + + +/***/ }), +/* 564 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return buildBazelProductionProjects; }); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(774); +/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(globby__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(783); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(372); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(131); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); +/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(251); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(248); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + + + + + + + + + +async function buildBazelProductionProjects({ + kibanaRoot, + buildRoot, + onlyOSS +}) { + const projects = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_8__["getBazelProjectsOnly"])(await Object(_build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__["getProductionProjects"])(kibanaRoot, onlyOSS)); + const projectNames = [...projects.values()].map(project => project.name); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`Preparing Bazel projects production build for [${projectNames.join(', ')}]`); + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['build', '//packages:build']); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`All Bazel projects production builds for [${projectNames.join(', ')}] are complete}]`); + + for (const project of projects.values()) { + await copyToBuild(project, kibanaRoot, buildRoot); + await applyCorrectPermissions(project, kibanaRoot, buildRoot); + } +} +/** + * Copy all the project's files from its Bazel dist directory into the + * project build folder. + * + * When copying all the files into the build, we exclude `node_modules` because + * we want the Kibana build to be responsible for actually installing all + * dependencies. The primary reason for allowing the Kibana build process to + * manage dependencies is that it will "dedupe" them, so we don't include + * unnecessary copies of dependencies. We also exclude every related Bazel build + * files in order to get the most cleaner package module we can in the final distributable. + */ + +async function copyToBuild(project, kibanaRoot, buildRoot) { + // We want the package to have the same relative location within the build + const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); + const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); + await cpy__WEBPACK_IMPORTED_MODULE_0___default()(['**/*'], buildProjectPath, { + cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel', 'bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), + dot: true, + onlyFiles: true, + parents: true + }); // If a project is using an intermediate build directory, we special-case our + // handling of `package.json`, as the project build process might have copied + // (a potentially modified) `package.json` into the intermediate build + // directory already. If so, we want to use that `package.json` as the basis + // for creating the production-ready `package.json`. If it's not present in + // the intermediate build, we fall back to using the project's already defined + // `package.json`. + + const packageJson = (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isFile"])(Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(buildProjectPath, 'package.json'))) ? await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_7__["readPackageJson"])(buildProjectPath) : project.json; + const preparedPackageJson = Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_7__["createProductionPackageJson"])(packageJson); + await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_7__["writePackageJson"])(buildProjectPath, preparedPackageJson); +} + +async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { + const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); + const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); + const allPluginPaths = await globby__WEBPACK_IMPORTED_MODULE_1___default()([`**/*`], { + onlyFiles: false, + cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel', 'bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), + dot: true + }); + + for (const pluginPath of allPluginPaths) { + const resolvedPluginPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, pluginPath); + + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isFile"])(resolvedPluginPath)) { + await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["chmod"])(resolvedPluginPath, 0o644); + } + + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isDirectory"])(resolvedPluginPath)) { + await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["chmod"])(resolvedPluginPath, 0o755); + } + } +} + +/***/ }), +/* 565 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const EventEmitter = __webpack_require__(156); +const path = __webpack_require__(4); +const os = __webpack_require__(121); +const pMap = __webpack_require__(566); +const arrify = __webpack_require__(561); +const globby = __webpack_require__(569); +const hasGlob = __webpack_require__(758); +const cpFile = __webpack_require__(760); +const junk = __webpack_require__(770); +const pFilter = __webpack_require__(771); +const CpyError = __webpack_require__(773); + +const defaultOptions = { + ignoreJunk: true +}; + +class SourceFile { + constructor(relativePath, path) { + this.path = path; + this.relativePath = relativePath; + Object.freeze(this); + } + + get name() { + return path.basename(this.relativePath); + } + + get nameWithoutExtension() { + return path.basename(this.relativePath, path.extname(this.relativePath)); + } + + get extension() { + return path.extname(this.relativePath).slice(1); + } +} + +const preprocessSourcePath = (source, options) => path.resolve(options.cwd ? options.cwd : process.cwd(), source); + +const preprocessDestinationPath = (source, destination, options) => { + let basename = path.basename(source); + + if (typeof options.rename === 'string') { + basename = options.rename; + } else if (typeof options.rename === 'function') { + basename = options.rename(basename); + } + + if (options.cwd) { + destination = path.resolve(options.cwd, destination); + } + + if (options.parents) { + const dirname = path.dirname(source); + const parsedDirectory = path.parse(dirname); + return path.join(destination, dirname.replace(parsedDirectory.root, path.sep), basename); + } + + return path.join(destination, basename); +}; + +module.exports = (source, destination, { + concurrency = (os.cpus().length || 1) * 2, + ...options +} = {}) => { + const progressEmitter = new EventEmitter(); + + options = { + ...defaultOptions, + ...options + }; + + const promise = (async () => { + source = arrify(source); + + if (source.length === 0 || !destination) { + throw new CpyError('`source` and `destination` required'); + } + + const copyStatus = new Map(); + let completedFiles = 0; + let completedSize = 0; + + let files; + try { + files = await globby(source, options); + + if (options.ignoreJunk) { + files = files.filter(file => junk.not(path.basename(file))); + } + } catch (error) { + throw new CpyError(`Cannot glob \`${source}\`: ${error.message}`, error); + } + + if (files.length === 0 && !hasGlob(source)) { + throw new CpyError(`Cannot copy \`${source}\`: the file doesn't exist`); + } + + let sources = files.map(sourcePath => new SourceFile(sourcePath, preprocessSourcePath(sourcePath, options))); + + if (options.filter !== undefined) { + const filteredSources = await pFilter(sources, options.filter, {concurrency: 1024}); + sources = filteredSources; + } + + if (sources.length === 0) { + progressEmitter.emit('progress', { + totalFiles: 0, + percent: 1, + completedFiles: 0, + completedSize: 0 + }); + } + + const fileProgressHandler = event => { + const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; + + if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { + completedSize -= fileStatus.written; + completedSize += event.written; + + if (event.percent === 1 && fileStatus.percent !== 1) { + completedFiles++; + } + + copyStatus.set(event.src, { + written: event.written, + percent: event.percent + }); + + progressEmitter.emit('progress', { + totalFiles: files.length, + percent: completedFiles / files.length, + completedFiles, + completedSize + }); + } + }; + + return pMap(sources, async source => { + const to = preprocessDestinationPath(source.relativePath, destination, options); + + try { + await cpFile(source.path, to, options).on('progress', fileProgressHandler); + } catch (error) { + throw new CpyError(`Cannot copy from \`${source.relativePath}\` to \`${to}\`: ${error.message}`, error); + } + + return to; + }, {concurrency}); + })(); + + promise.on = (...arguments_) => { + progressEmitter.on(...arguments_); + return promise; + }; + + return promise; +}; + + +/***/ }), +/* 566 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const AggregateError = __webpack_require__(567); + +module.exports = async ( + iterable, + mapper, + { + concurrency = Infinity, + stopOnError = true + } = {} +) => { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } + + if (!(typeof concurrency === 'number' && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); + } + + const ret = []; + const errors = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; + + const next = () => { + if (isRejected) { + return; + } + + const nextItem = iterator.next(); + const i = currentIndex; + currentIndex++; + + if (nextItem.done) { + isIterableDone = true; + + if (resolvingCount === 0) { + if (!stopOnError && errors.length !== 0) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } + } + + return; + } + + resolvingCount++; + + (async () => { + try { + const element = await nextItem.value; + ret[i] = await mapper(element, i); + resolvingCount--; + next(); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } + } + })(); + }; + + for (let i = 0; i < concurrency; i++) { + next(); + + if (isIterableDone) { + break; + } + } + }); +}; + + +/***/ }), +/* 567 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const indentString = __webpack_require__(568); +const cleanStack = __webpack_require__(244); + +const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); + +class AggregateError extends Error { + constructor(errors) { + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } + + errors = [...errors].map(error => { + if (error instanceof Error) { + return error; + } + + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } + + return new Error(error); + }); + + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); + + this.name = 'AggregateError'; + + Object.defineProperty(this, '_errors', {value: errors}); + } + + * [Symbol.iterator]() { + for (const error of this._errors) { + yield error; + } + } +} + +module.exports = AggregateError; + + +/***/ }), +/* 568 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = (string, count = 1, options) => { + options = { + indent: ' ', + includeEmptyLines: false, + ...options + }; + + if (typeof string !== 'string') { + throw new TypeError( + `Expected \`input\` to be a \`string\`, got \`${typeof string}\`` + ); + } + + if (typeof count !== 'number') { + throw new TypeError( + `Expected \`count\` to be a \`number\`, got \`${typeof count}\`` + ); + } + + if (typeof options.indent !== 'string') { + throw new TypeError( + `Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\`` + ); + } + + if (count === 0) { + return string; + } + + const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + + return string.replace(regex, options.indent.repeat(count)); +}; + + +/***/ }), +/* 569 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const fs = __webpack_require__(134); +const arrayUnion = __webpack_require__(570); +const glob = __webpack_require__(147); +const fastGlob = __webpack_require__(572); +const dirGlob = __webpack_require__(751); +const gitignore = __webpack_require__(754); + +const DEFAULT_FILTER = () => false; + +const isNegative = pattern => pattern[0] === '!'; + +const assertPatternsInput = patterns => { + if (!patterns.every(x => typeof x === 'string')) { + throw new TypeError('Patterns must be a string or an array of strings'); + } +}; + +const checkCwdOption = options => { + if (options && options.cwd && !fs.statSync(options.cwd).isDirectory()) { + throw new Error('The `cwd` option must be a path to a directory'); + } +}; + +const generateGlobTasks = (patterns, taskOptions) => { + patterns = arrayUnion([].concat(patterns)); + assertPatternsInput(patterns); + checkCwdOption(taskOptions); + + const globTasks = []; + + taskOptions = Object.assign({ + ignore: [], + expandDirectories: true + }, taskOptions); + + patterns.forEach((pattern, i) => { + if (isNegative(pattern)) { + return; + } + + const ignore = patterns + .slice(i) + .filter(isNegative) + .map(pattern => pattern.slice(1)); + + const options = Object.assign({}, taskOptions, { + ignore: taskOptions.ignore.concat(ignore) + }); + + globTasks.push({pattern, options}); + }); + + return globTasks; +}; + +const globDirs = (task, fn) => { + let options = {}; + if (task.options.cwd) { + options.cwd = task.options.cwd; + } + + if (Array.isArray(task.options.expandDirectories)) { + options = Object.assign(options, {files: task.options.expandDirectories}); + } else if (typeof task.options.expandDirectories === 'object') { + options = Object.assign(options, task.options.expandDirectories); + } + + return fn(task.pattern, options); +}; + +const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; const globToTask = task => glob => { const {options} = task; @@ -60499,12 +64217,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 527 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(528); +var arrayUniq = __webpack_require__(571); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -60512,7 +64230,7 @@ module.exports = function () { /***/ }), -/* 528 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60581,10 +64299,10 @@ if ('Set' in global) { /***/ }), -/* 529 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(530); +const pkg = __webpack_require__(573); module.exports = pkg.async; module.exports.default = pkg.async; @@ -60597,19 +64315,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 530 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(531); -var taskManager = __webpack_require__(532); -var reader_async_1 = __webpack_require__(685); -var reader_stream_1 = __webpack_require__(709); -var reader_sync_1 = __webpack_require__(710); -var arrayUtils = __webpack_require__(712); -var streamUtils = __webpack_require__(713); +var optionsManager = __webpack_require__(574); +var taskManager = __webpack_require__(575); +var reader_async_1 = __webpack_require__(722); +var reader_stream_1 = __webpack_require__(746); +var reader_sync_1 = __webpack_require__(747); +var arrayUtils = __webpack_require__(749); +var streamUtils = __webpack_require__(750); /** * Synchronous API. */ @@ -60675,7 +64393,7 @@ function isString(source) { /***/ }), -/* 531 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60713,13 +64431,13 @@ exports.prepare = prepare; /***/ }), -/* 532 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(533); +var patternUtils = __webpack_require__(576); /** * Generate tasks based on parent directory of each pattern. */ @@ -60810,16 +64528,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 533 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(534); +var globParent = __webpack_require__(577); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(537); +var micromatch = __webpack_require__(580); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -60965,15 +64683,15 @@ exports.matchAny = matchAny; /***/ }), -/* 534 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(535); -var pathDirname = __webpack_require__(536); +var isglob = __webpack_require__(578); +var pathDirname = __webpack_require__(579); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -60996,7 +64714,7 @@ module.exports = function globParent(str) { /***/ }), -/* 535 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -61027,7 +64745,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 536 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61177,7 +64895,7 @@ module.exports.win32 = win32; /***/ }), -/* 537 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61188,18 +64906,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(538); -var toRegex = __webpack_require__(539); -var extend = __webpack_require__(653); +var braces = __webpack_require__(581); +var toRegex = __webpack_require__(582); +var extend = __webpack_require__(690); /** * Local dependencies */ -var compilers = __webpack_require__(655); -var parsers = __webpack_require__(681); -var cache = __webpack_require__(682); -var utils = __webpack_require__(683); +var compilers = __webpack_require__(692); +var parsers = __webpack_require__(718); +var cache = __webpack_require__(719); +var utils = __webpack_require__(720); var MAX_LENGTH = 1024 * 64; /** @@ -62061,7 +65779,7 @@ module.exports = micromatch; /***/ }), -/* 538 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62071,18 +65789,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(539); -var unique = __webpack_require__(559); -var extend = __webpack_require__(560); +var toRegex = __webpack_require__(582); +var unique = __webpack_require__(602); +var extend = __webpack_require__(603); /** * Local dependencies */ -var compilers = __webpack_require__(562); -var parsers = __webpack_require__(575); -var Braces = __webpack_require__(580); -var utils = __webpack_require__(563); +var compilers = __webpack_require__(605); +var parsers = __webpack_require__(618); +var Braces = __webpack_require__(623); +var utils = __webpack_require__(606); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -62386,16 +66104,16 @@ module.exports = braces; /***/ }), -/* 539 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(540); -var define = __webpack_require__(546); -var extend = __webpack_require__(552); -var not = __webpack_require__(556); +var safe = __webpack_require__(583); +var define = __webpack_require__(589); +var extend = __webpack_require__(595); +var not = __webpack_require__(599); var MAX_LENGTH = 1024 * 64; /** @@ -62548,10 +66266,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 540 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(541); +var parse = __webpack_require__(584); var types = parse.types; module.exports = function (re, opts) { @@ -62597,13 +66315,13 @@ function isRegExp (x) { /***/ }), -/* 541 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(542); -var types = __webpack_require__(543); -var sets = __webpack_require__(544); -var positions = __webpack_require__(545); +var util = __webpack_require__(585); +var types = __webpack_require__(586); +var sets = __webpack_require__(587); +var positions = __webpack_require__(588); module.exports = function(regexpStr) { @@ -62885,11 +66603,11 @@ module.exports.types = types; /***/ }), -/* 542 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(543); -var sets = __webpack_require__(544); +var types = __webpack_require__(586); +var sets = __webpack_require__(587); // All of these are private and only used by randexp. @@ -63002,7 +66720,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 543 */ +/* 586 */ /***/ (function(module, exports) { module.exports = { @@ -63018,10 +66736,10 @@ module.exports = { /***/ }), -/* 544 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(543); +var types = __webpack_require__(586); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -63106,10 +66824,10 @@ exports.anyChar = function() { /***/ }), -/* 545 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(543); +var types = __webpack_require__(586); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -63129,7 +66847,7 @@ exports.end = function() { /***/ }), -/* 546 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63142,8 +66860,8 @@ exports.end = function() { -var isobject = __webpack_require__(547); -var isDescriptor = __webpack_require__(548); +var isobject = __webpack_require__(590); +var isDescriptor = __webpack_require__(591); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -63174,7 +66892,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 547 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63193,7 +66911,7 @@ module.exports = function isObject(val) { /***/ }), -/* 548 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63206,9 +66924,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(549); -var isAccessor = __webpack_require__(550); -var isData = __webpack_require__(551); +var typeOf = __webpack_require__(592); +var isAccessor = __webpack_require__(593); +var isData = __webpack_require__(594); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -63222,7 +66940,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 549 */ +/* 592 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -63357,7 +67075,7 @@ function isBuffer(val) { /***/ }), -/* 550 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63370,7 +67088,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(549); +var typeOf = __webpack_require__(592); // accessor descriptor properties var accessor = { @@ -63433,7 +67151,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 551 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63446,7 +67164,7 @@ module.exports = isAccessorDescriptor; -var typeOf = __webpack_require__(549); +var typeOf = __webpack_require__(592); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -63489,14 +67207,14 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 552 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(553); -var assignSymbols = __webpack_require__(555); +var isExtendable = __webpack_require__(596); +var assignSymbols = __webpack_require__(598); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63556,7 +67274,7 @@ function isEnum(obj, key) { /***/ }), -/* 553 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63569,7 +67287,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(554); +var isPlainObject = __webpack_require__(597); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63577,7 +67295,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 554 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63590,7 +67308,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(547); +var isObject = __webpack_require__(590); function isObjectObject(o) { return isObject(o) === true @@ -63621,7 +67339,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 555 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63668,14 +67386,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 556 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(557); -var safe = __webpack_require__(540); +var extend = __webpack_require__(600); +var safe = __webpack_require__(583); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -63747,14 +67465,14 @@ module.exports = toRegex; /***/ }), -/* 557 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(558); -var assignSymbols = __webpack_require__(555); +var isExtendable = __webpack_require__(601); +var assignSymbols = __webpack_require__(598); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63814,7 +67532,7 @@ function isEnum(obj, key) { /***/ }), -/* 558 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63827,7 +67545,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(554); +var isPlainObject = __webpack_require__(597); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63835,7 +67553,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 559 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63885,13 +67603,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 560 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(561); +var isObject = __webpack_require__(604); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -63925,7 +67643,7 @@ function hasOwn(obj, key) { /***/ }), -/* 561 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63945,13 +67663,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 562 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(563); +var utils = __webpack_require__(606); module.exports = function(braces, options) { braces.compiler @@ -64234,25 +67952,25 @@ function hasQueue(node) { /***/ }), -/* 563 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(564); +var splitString = __webpack_require__(607); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(560); -utils.flatten = __webpack_require__(567); -utils.isObject = __webpack_require__(547); -utils.fillRange = __webpack_require__(568); -utils.repeat = __webpack_require__(574); -utils.unique = __webpack_require__(559); +utils.extend = __webpack_require__(603); +utils.flatten = __webpack_require__(610); +utils.isObject = __webpack_require__(590); +utils.fillRange = __webpack_require__(611); +utils.repeat = __webpack_require__(617); +utils.unique = __webpack_require__(602); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -64584,7 +68302,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 564 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64597,7 +68315,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(565); +var extend = __webpack_require__(608); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -64762,14 +68480,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 565 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(566); -var assignSymbols = __webpack_require__(555); +var isExtendable = __webpack_require__(609); +var assignSymbols = __webpack_require__(598); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -64829,7 +68547,7 @@ function isEnum(obj, key) { /***/ }), -/* 566 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64842,7 +68560,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(554); +var isPlainObject = __webpack_require__(597); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -64850,7 +68568,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 567 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64879,7 +68597,7 @@ function flat(arr, res) { /***/ }), -/* 568 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64893,10 +68611,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(569); -var extend = __webpack_require__(560); -var repeat = __webpack_require__(572); -var toRegex = __webpack_require__(573); +var isNumber = __webpack_require__(612); +var extend = __webpack_require__(603); +var repeat = __webpack_require__(615); +var toRegex = __webpack_require__(616); /** * Return a range of numbers or letters. @@ -65094,7 +68812,7 @@ module.exports = fillRange; /***/ }), -/* 569 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65107,7 +68825,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(570); +var typeOf = __webpack_require__(613); module.exports = function isNumber(num) { var type = typeOf(num); @@ -65123,10 +68841,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 570 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(571); +var isBuffer = __webpack_require__(614); var toString = Object.prototype.toString; /** @@ -65245,7 +68963,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 571 */ +/* 614 */ /***/ (function(module, exports) { /*! @@ -65272,7 +68990,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 572 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65349,7 +69067,7 @@ function repeat(str, num) { /***/ }), -/* 573 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65362,8 +69080,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(572); -var isNumber = __webpack_require__(569); +var repeat = __webpack_require__(615); +var isNumber = __webpack_require__(612); var cache = {}; function toRegexRange(min, max, options) { @@ -65650,7 +69368,7 @@ module.exports = toRegexRange; /***/ }), -/* 574 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65675,14 +69393,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 575 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(576); -var utils = __webpack_require__(563); +var Node = __webpack_require__(619); +var utils = __webpack_require__(606); /** * Braces parsers @@ -66042,15 +69760,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 576 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(547); -var define = __webpack_require__(577); -var utils = __webpack_require__(578); +var isObject = __webpack_require__(590); +var define = __webpack_require__(620); +var utils = __webpack_require__(621); var ownNames; /** @@ -66541,7 +70259,7 @@ exports = module.exports = Node; /***/ }), -/* 577 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66554,7 +70272,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(548); +var isDescriptor = __webpack_require__(591); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66579,13 +70297,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 578 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(579); +var typeOf = __webpack_require__(622); var utils = module.exports; /** @@ -67605,10 +71323,10 @@ function assert(val, message) { /***/ }), -/* 579 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(571); +var isBuffer = __webpack_require__(614); var toString = Object.prototype.toString; /** @@ -67727,17 +71445,17 @@ module.exports = function kindOf(val) { /***/ }), -/* 580 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(560); -var Snapdragon = __webpack_require__(581); -var compilers = __webpack_require__(562); -var parsers = __webpack_require__(575); -var utils = __webpack_require__(563); +var extend = __webpack_require__(603); +var Snapdragon = __webpack_require__(624); +var compilers = __webpack_require__(605); +var parsers = __webpack_require__(618); +var utils = __webpack_require__(606); /** * Customize Snapdragon parser and renderer @@ -67838,17 +71556,17 @@ module.exports = Braces; /***/ }), -/* 581 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(582); -var define = __webpack_require__(610); -var Compiler = __webpack_require__(621); -var Parser = __webpack_require__(650); -var utils = __webpack_require__(630); +var Base = __webpack_require__(625); +var define = __webpack_require__(653); +var Compiler = __webpack_require__(664); +var Parser = __webpack_require__(687); +var utils = __webpack_require__(667); var regexCache = {}; var cache = {}; @@ -68019,20 +71737,20 @@ module.exports.Parser = Parser; /***/ }), -/* 582 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(583); -var CacheBase = __webpack_require__(584); -var Emitter = __webpack_require__(585); -var isObject = __webpack_require__(547); -var merge = __webpack_require__(604); -var pascal = __webpack_require__(607); -var cu = __webpack_require__(608); +var define = __webpack_require__(626); +var CacheBase = __webpack_require__(627); +var Emitter = __webpack_require__(628); +var isObject = __webpack_require__(590); +var merge = __webpack_require__(647); +var pascal = __webpack_require__(650); +var cu = __webpack_require__(651); /** * Optionally define a custom `cache` namespace to use. @@ -68461,7 +72179,7 @@ module.exports.namespace = namespace; /***/ }), -/* 583 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68474,7 +72192,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(548); +var isDescriptor = __webpack_require__(591); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68499,21 +72217,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 584 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(547); -var Emitter = __webpack_require__(585); -var visit = __webpack_require__(586); -var toPath = __webpack_require__(589); -var union = __webpack_require__(591); -var del = __webpack_require__(595); -var get = __webpack_require__(593); -var has = __webpack_require__(600); -var set = __webpack_require__(603); +var isObject = __webpack_require__(590); +var Emitter = __webpack_require__(628); +var visit = __webpack_require__(629); +var toPath = __webpack_require__(632); +var union = __webpack_require__(634); +var del = __webpack_require__(638); +var get = __webpack_require__(636); +var has = __webpack_require__(643); +var set = __webpack_require__(646); /** * Create a `Cache` constructor that when instantiated will @@ -68767,7 +72485,7 @@ module.exports.namespace = namespace; /***/ }), -/* 585 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { @@ -68936,7 +72654,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 586 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68949,8 +72667,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(587); -var mapVisit = __webpack_require__(588); +var visit = __webpack_require__(630); +var mapVisit = __webpack_require__(631); module.exports = function(collection, method, val) { var result; @@ -68973,7 +72691,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 587 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68986,7 +72704,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(547); +var isObject = __webpack_require__(590); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -69013,14 +72731,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 588 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(587); +var visit = __webpack_require__(630); /** * Map `visit` over an array of objects. @@ -69057,7 +72775,7 @@ function isObject(val) { /***/ }), -/* 589 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69070,7 +72788,7 @@ function isObject(val) { -var typeOf = __webpack_require__(590); +var typeOf = __webpack_require__(633); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -69097,10 +72815,10 @@ function filter(arr) { /***/ }), -/* 590 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(571); +var isBuffer = __webpack_require__(614); var toString = Object.prototype.toString; /** @@ -69219,16 +72937,16 @@ module.exports = function kindOf(val) { /***/ }), -/* 591 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(561); -var union = __webpack_require__(592); -var get = __webpack_require__(593); -var set = __webpack_require__(594); +var isObject = __webpack_require__(604); +var union = __webpack_require__(635); +var get = __webpack_require__(636); +var set = __webpack_require__(637); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -69256,7 +72974,7 @@ function arrayify(val) { /***/ }), -/* 592 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69292,7 +73010,7 @@ module.exports = function union(init) { /***/ }), -/* 593 */ +/* 636 */ /***/ (function(module, exports) { /*! @@ -69348,7 +73066,7 @@ function toString(val) { /***/ }), -/* 594 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69361,10 +73079,10 @@ function toString(val) { -var split = __webpack_require__(564); -var extend = __webpack_require__(560); -var isPlainObject = __webpack_require__(554); -var isObject = __webpack_require__(561); +var split = __webpack_require__(607); +var extend = __webpack_require__(603); +var isPlainObject = __webpack_require__(597); +var isObject = __webpack_require__(604); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -69410,7 +73128,7 @@ function isValidKey(key) { /***/ }), -/* 595 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69423,8 +73141,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(547); -var has = __webpack_require__(596); +var isObject = __webpack_require__(590); +var has = __webpack_require__(639); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -69449,7 +73167,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 596 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69462,9 +73180,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(597); -var hasValues = __webpack_require__(599); -var get = __webpack_require__(593); +var isObject = __webpack_require__(640); +var hasValues = __webpack_require__(642); +var get = __webpack_require__(636); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -69475,7 +73193,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 597 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69488,7 +73206,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(598); +var isArray = __webpack_require__(641); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -69496,7 +73214,7 @@ module.exports = function isObject(val) { /***/ }), -/* 598 */ +/* 641 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -69507,7 +73225,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 599 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69550,7 +73268,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 600 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69563,9 +73281,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(547); -var hasValues = __webpack_require__(601); -var get = __webpack_require__(593); +var isObject = __webpack_require__(590); +var hasValues = __webpack_require__(644); +var get = __webpack_require__(636); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -69573,7 +73291,7 @@ module.exports = function(val, prop) { /***/ }), -/* 601 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69586,8 +73304,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(602); -var isNumber = __webpack_require__(569); +var typeOf = __webpack_require__(645); +var isNumber = __webpack_require__(612); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -69640,10 +73358,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 602 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(571); +var isBuffer = __webpack_require__(614); var toString = Object.prototype.toString; /** @@ -69765,7 +73483,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 603 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69778,10 +73496,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(564); -var extend = __webpack_require__(560); -var isPlainObject = __webpack_require__(554); -var isObject = __webpack_require__(561); +var split = __webpack_require__(607); +var extend = __webpack_require__(603); +var isPlainObject = __webpack_require__(597); +var isObject = __webpack_require__(604); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -69827,14 +73545,14 @@ function isValidKey(key) { /***/ }), -/* 604 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(605); -var forIn = __webpack_require__(606); +var isExtendable = __webpack_require__(648); +var forIn = __webpack_require__(649); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -69898,7 +73616,7 @@ module.exports = mixinDeep; /***/ }), -/* 605 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69911,7 +73629,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(554); +var isPlainObject = __webpack_require__(597); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -69919,7 +73637,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 606 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69942,7 +73660,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 607 */ +/* 650 */ /***/ (function(module, exports) { /*! @@ -69969,14 +73687,14 @@ module.exports = pascalcase; /***/ }), -/* 608 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(609); +var utils = __webpack_require__(652); /** * Expose class utils @@ -70341,7 +74059,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 609 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70351,508 +74069,94 @@ var utils = {}; -/** - * Lazily required module dependencies - */ - -utils.union = __webpack_require__(592); -utils.define = __webpack_require__(610); -utils.isObj = __webpack_require__(547); -utils.staticExtend = __webpack_require__(617); - - -/** - * Expose `utils` - */ - -module.exports = utils; - - -/***/ }), -/* 610 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var isDescriptor = __webpack_require__(611); - -module.exports = function defineProperty(obj, prop, val) { - if (typeof obj !== 'object' && typeof obj !== 'function') { - throw new TypeError('expected an object or function.'); - } - - if (typeof prop !== 'string') { - throw new TypeError('expected `prop` to be a string.'); - } - - if (isDescriptor(val) && ('set' in val || 'get' in val)) { - return Object.defineProperty(obj, prop, val); - } - - return Object.defineProperty(obj, prop, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); -}; - - -/***/ }), -/* 611 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-descriptor - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var typeOf = __webpack_require__(612); -var isAccessor = __webpack_require__(613); -var isData = __webpack_require__(615); - -module.exports = function isDescriptor(obj, key) { - if (typeOf(obj) !== 'object') { - return false; - } - if ('get' in obj) { - return isAccessor(obj, key); - } - return isData(obj, key); -}; - - -/***/ }), -/* 612 */ -/***/ (function(module, exports) { - -var toString = Object.prototype.toString; - -/** - * Get the native `typeof` a value. - * - * @param {*} `val` - * @return {*} Native javascript type - */ - -module.exports = function kindOf(val) { - var type = typeof val; - - // primitivies - if (type === 'undefined') { - return 'undefined'; - } - if (val === null) { - return 'null'; - } - if (val === true || val === false || val instanceof Boolean) { - return 'boolean'; - } - if (type === 'string' || val instanceof String) { - return 'string'; - } - if (type === 'number' || val instanceof Number) { - return 'number'; - } - - // functions - if (type === 'function' || val instanceof Function) { - if (typeof val.constructor.name !== 'undefined' && val.constructor.name.slice(0, 9) === 'Generator') { - return 'generatorfunction'; - } - return 'function'; - } - - // array - if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { - return 'array'; - } - - // check for instances of RegExp and Date before calling `toString` - if (val instanceof RegExp) { - return 'regexp'; - } - if (val instanceof Date) { - return 'date'; - } - - // other objects - type = toString.call(val); - - if (type === '[object RegExp]') { - return 'regexp'; - } - if (type === '[object Date]') { - return 'date'; - } - if (type === '[object Arguments]') { - return 'arguments'; - } - if (type === '[object Error]') { - return 'error'; - } - if (type === '[object Promise]') { - return 'promise'; - } - - // buffer - if (isBuffer(val)) { - return 'buffer'; - } - - // es6: Map, WeakMap, Set, WeakSet - if (type === '[object Set]') { - return 'set'; - } - if (type === '[object WeakSet]') { - return 'weakset'; - } - if (type === '[object Map]') { - return 'map'; - } - if (type === '[object WeakMap]') { - return 'weakmap'; - } - if (type === '[object Symbol]') { - return 'symbol'; - } - - if (type === '[object Map Iterator]') { - return 'mapiterator'; - } - if (type === '[object Set Iterator]') { - return 'setiterator'; - } - if (type === '[object String Iterator]') { - return 'stringiterator'; - } - if (type === '[object Array Iterator]') { - return 'arrayiterator'; - } - - // typed arrays - if (type === '[object Int8Array]') { - return 'int8array'; - } - if (type === '[object Uint8Array]') { - return 'uint8array'; - } - if (type === '[object Uint8ClampedArray]') { - return 'uint8clampedarray'; - } - if (type === '[object Int16Array]') { - return 'int16array'; - } - if (type === '[object Uint16Array]') { - return 'uint16array'; - } - if (type === '[object Int32Array]') { - return 'int32array'; - } - if (type === '[object Uint32Array]') { - return 'uint32array'; - } - if (type === '[object Float32Array]') { - return 'float32array'; - } - if (type === '[object Float64Array]') { - return 'float64array'; - } - - // must be a plain object - return 'object'; -}; - -/** - * If you need to support Safari 5-7 (8-10 yr-old browser), - * take a look at https://github.com/feross/is-buffer - */ - -function isBuffer(val) { - return val.constructor - && typeof val.constructor.isBuffer === 'function' - && val.constructor.isBuffer(val); -} - - -/***/ }), -/* 613 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-accessor-descriptor - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var typeOf = __webpack_require__(614); - -// accessor descriptor properties -var accessor = { - get: 'function', - set: 'function', - configurable: 'boolean', - enumerable: 'boolean' -}; - -function isAccessorDescriptor(obj, prop) { - if (typeof prop === 'string') { - var val = Object.getOwnPropertyDescriptor(obj, prop); - return typeof val !== 'undefined'; - } - - if (typeOf(obj) !== 'object') { - return false; - } - - if (has(obj, 'value') || has(obj, 'writable')) { - return false; - } - - if (!has(obj, 'get') || typeof obj.get !== 'function') { - return false; - } - - // tldr: it's valid to have "set" be undefined - // "set" might be undefined if `Object.getOwnPropertyDescriptor` - // was used to get the value, and only `get` was defined by the user - if (has(obj, 'set') && typeof obj[key] !== 'function' && typeof obj[key] !== 'undefined') { - return false; - } - - for (var key in obj) { - if (!accessor.hasOwnProperty(key)) { - continue; - } - - if (typeOf(obj[key]) === accessor[key]) { - continue; - } - - if (typeof obj[key] !== 'undefined') { - return false; - } - } - return true; -} +/** + * Lazily required module dependencies + */ + +utils.union = __webpack_require__(635); +utils.define = __webpack_require__(653); +utils.isObj = __webpack_require__(590); +utils.staticExtend = __webpack_require__(660); -function has(obj, key) { - return {}.hasOwnProperty.call(obj, key); -} /** - * Expose `isAccessorDescriptor` + * Expose `utils` */ -module.exports = isAccessorDescriptor; +module.exports = utils; /***/ }), -/* 614 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(571); -var toString = Object.prototype.toString; - -/** - * Get the native `typeof` a value. +"use strict"; +/*! + * define-property * - * @param {*} `val` - * @return {*} Native javascript type + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. */ -module.exports = function kindOf(val) { - // primitivies - if (typeof val === 'undefined') { - return 'undefined'; - } - if (val === null) { - return 'null'; - } - if (val === true || val === false || val instanceof Boolean) { - return 'boolean'; - } - if (typeof val === 'string' || val instanceof String) { - return 'string'; - } - if (typeof val === 'number' || val instanceof Number) { - return 'number'; - } - - // functions - if (typeof val === 'function' || val instanceof Function) { - return 'function'; - } - - // array - if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { - return 'array'; - } - - // check for instances of RegExp and Date before calling `toString` - if (val instanceof RegExp) { - return 'regexp'; - } - if (val instanceof Date) { - return 'date'; - } - // other objects - var type = toString.call(val); - if (type === '[object RegExp]') { - return 'regexp'; - } - if (type === '[object Date]') { - return 'date'; - } - if (type === '[object Arguments]') { - return 'arguments'; - } - if (type === '[object Error]') { - return 'error'; - } +var isDescriptor = __webpack_require__(654); - // buffer - if (isBuffer(val)) { - return 'buffer'; +module.exports = function defineProperty(obj, prop, val) { + if (typeof obj !== 'object' && typeof obj !== 'function') { + throw new TypeError('expected an object or function.'); } - // es6: Map, WeakMap, Set, WeakSet - if (type === '[object Set]') { - return 'set'; - } - if (type === '[object WeakSet]') { - return 'weakset'; - } - if (type === '[object Map]') { - return 'map'; - } - if (type === '[object WeakMap]') { - return 'weakmap'; - } - if (type === '[object Symbol]') { - return 'symbol'; + if (typeof prop !== 'string') { + throw new TypeError('expected `prop` to be a string.'); } - // typed arrays - if (type === '[object Int8Array]') { - return 'int8array'; - } - if (type === '[object Uint8Array]') { - return 'uint8array'; - } - if (type === '[object Uint8ClampedArray]') { - return 'uint8clampedarray'; - } - if (type === '[object Int16Array]') { - return 'int16array'; - } - if (type === '[object Uint16Array]') { - return 'uint16array'; - } - if (type === '[object Int32Array]') { - return 'int32array'; - } - if (type === '[object Uint32Array]') { - return 'uint32array'; - } - if (type === '[object Float32Array]') { - return 'float32array'; - } - if (type === '[object Float64Array]') { - return 'float64array'; + if (isDescriptor(val) && ('set' in val || 'get' in val)) { + return Object.defineProperty(obj, prop, val); } - // must be a plain object - return 'object'; + return Object.defineProperty(obj, prop, { + configurable: true, + enumerable: false, + writable: true, + value: val + }); }; /***/ }), -/* 615 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /*! - * is-data-descriptor + * is-descriptor * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. + * Copyright (c) 2015-2017, Jon Schlinkert. + * Released under the MIT License. */ -var typeOf = __webpack_require__(616); - -// data descriptor properties -var data = { - configurable: 'boolean', - enumerable: 'boolean', - writable: 'boolean' -}; +var typeOf = __webpack_require__(655); +var isAccessor = __webpack_require__(656); +var isData = __webpack_require__(658); -function isDataDescriptor(obj, prop) { +module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { return false; } - - if (typeof prop === 'string') { - var val = Object.getOwnPropertyDescriptor(obj, prop); - return typeof val !== 'undefined'; - } - - if (!('value' in obj) && !('writable' in obj)) { - return false; - } - - for (var key in obj) { - if (key === 'value') continue; - - if (!data.hasOwnProperty(key)) { - continue; - } - - if (typeOf(obj[key]) === data[key]) { - continue; - } - - if (typeof obj[key] !== 'undefined') { - return false; - } + if ('get' in obj) { + return isAccessor(obj, key); } - return true; -} - -/** - * Expose `isDataDescriptor` - */ - -module.exports = isDataDescriptor; + return isData(obj, key); +}; /***/ }), -/* 616 */ -/***/ (function(module, exports, __webpack_require__) { +/* 655 */ +/***/ (function(module, exports) { -var isBuffer = __webpack_require__(571); var toString = Object.prototype.toString; /** @@ -70863,8 +74167,10 @@ var toString = Object.prototype.toString; */ module.exports = function kindOf(val) { + var type = typeof val; + // primitivies - if (typeof val === 'undefined') { + if (type === 'undefined') { return 'undefined'; } if (val === null) { @@ -70873,15 +74179,18 @@ module.exports = function kindOf(val) { if (val === true || val === false || val instanceof Boolean) { return 'boolean'; } - if (typeof val === 'string' || val instanceof String) { + if (type === 'string' || val instanceof String) { return 'string'; } - if (typeof val === 'number' || val instanceof Number) { + if (type === 'number' || val instanceof Number) { return 'number'; } // functions - if (typeof val === 'function' || val instanceof Function) { + if (type === 'function' || val instanceof Function) { + if (typeof val.constructor.name !== 'undefined' && val.constructor.name.slice(0, 9) === 'Generator') { + return 'generatorfunction'; + } return 'function'; } @@ -70899,7 +74208,7 @@ module.exports = function kindOf(val) { } // other objects - var type = toString.call(val); + type = toString.call(val); if (type === '[object RegExp]') { return 'regexp'; @@ -70913,6 +74222,9 @@ module.exports = function kindOf(val) { if (type === '[object Error]') { return 'error'; } + if (type === '[object Promise]') { + return 'promise'; + } // buffer if (isBuffer(val)) { @@ -70935,7 +74247,20 @@ module.exports = function kindOf(val) { if (type === '[object Symbol]') { return 'symbol'; } - + + if (type === '[object Map Iterator]') { + return 'mapiterator'; + } + if (type === '[object Set Iterator]') { + return 'setiterator'; + } + if (type === '[object String Iterator]') { + return 'stringiterator'; + } + if (type === '[object Array Iterator]') { + return 'arrayiterator'; + } + // typed arrays if (type === '[object Int8Array]') { return 'int8array'; @@ -70969,290 +74294,99 @@ module.exports = function kindOf(val) { return 'object'; }; - -/***/ }), -/* 617 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * static-extend - * - * Copyright (c) 2016, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var copy = __webpack_require__(618); -var define = __webpack_require__(610); -var util = __webpack_require__(112); - -/** - * Returns a function for extending the static properties, - * prototype properties, and descriptors from the `Parent` - * constructor onto `Child` constructors. - * - * ```js - * var extend = require('static-extend'); - * Parent.extend = extend(Parent); - * - * // optionally pass a custom merge function as the second arg - * Parent.extend = extend(Parent, function(Child) { - * Child.prototype.mixin = function(key, val) { - * Child.prototype[key] = val; - * }; - * }); - * - * // extend "child" constructors - * Parent.extend(Child); - * - * // optionally define prototype methods as the second arg - * Parent.extend(Child, { - * foo: function() {}, - * bar: function() {} - * }); - * ``` - * @param {Function} `Parent` Parent ctor - * @param {Function} `extendFn` Optional extend function for handling any necessary custom merging. Useful when updating methods that require a specific prototype. - * @param {Function} `Child` Child ctor - * @param {Object} `proto` Optionally pass additional prototype properties to inherit. - * @return {Object} - * @api public - */ - -function extend(Parent, extendFn) { - if (typeof Parent !== 'function') { - throw new TypeError('expected Parent to be a function.'); - } - - return function(Ctor, proto) { - if (typeof Ctor !== 'function') { - throw new TypeError('expected Ctor to be a function.'); - } - - util.inherits(Ctor, Parent); - copy(Ctor, Parent); - - // proto can be null or a plain object - if (typeof proto === 'object') { - var obj = Object.create(proto); - - for (var k in obj) { - Ctor.prototype[k] = obj[k]; - } - } - - // keep a reference to the parent prototype - define(Ctor.prototype, '_parent_', { - configurable: true, - set: function() {}, - get: function() { - return Parent.prototype; - } - }); - - if (typeof extendFn === 'function') { - extendFn(Ctor, Parent); - } - - Ctor.extend = extend(Ctor, extendFn); - }; -}; - /** - * Expose `extend` + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer */ -module.exports = extend; +function isBuffer(val) { + return val.constructor + && typeof val.constructor.isBuffer === 'function' + && val.constructor.isBuffer(val); +} /***/ }), -/* 618 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -var typeOf = __webpack_require__(619); -var copyDescriptor = __webpack_require__(620); -var define = __webpack_require__(610); - -/** - * Copy static properties, prototype properties, and descriptors from one object to another. - * - * ```js - * function App() {} - * var proto = App.prototype; - * App.prototype.set = function() {}; - * App.prototype.get = function() {}; +/*! + * is-accessor-descriptor * - * var obj = {}; - * copy(obj, proto); - * ``` - * @param {Object} `receiver` - * @param {Object} `provider` - * @param {String|Array} `omit` One or more properties to omit - * @return {Object} - * @api public + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. */ -function copy(receiver, provider, omit) { - if (!isObject(receiver)) { - throw new TypeError('expected receiving object to be an object.'); - } - if (!isObject(provider)) { - throw new TypeError('expected providing object to be an object.'); - } - var props = nativeKeys(provider); - var keys = Object.keys(provider); - var len = props.length; - omit = arrayify(omit); - while (len--) { - var key = props[len]; +var typeOf = __webpack_require__(657); - if (has(keys, key)) { - define(receiver, key, provider[key]); - } else if (!(key in receiver) && !has(omit, key)) { - copyDescriptor(receiver, provider, key); - } - } +// accessor descriptor properties +var accessor = { + get: 'function', + set: 'function', + configurable: 'boolean', + enumerable: 'boolean' }; -/** - * Return true if the given value is an object or function - */ - -function isObject(val) { - return typeOf(val) === 'object' || typeof val === 'function'; -} - -/** - * Returns true if an array has any of the given elements, or an - * object has any of the give keys. - * - * ```js - * has(['a', 'b', 'c'], 'c'); - * //=> true - * - * has(['a', 'b', 'c'], ['c', 'z']); - * //=> true - * - * has({a: 'b', c: 'd'}, ['c', 'z']); - * //=> true - * ``` - * @param {Object} `obj` - * @param {String|Array} `val` - * @return {Boolean} - */ - -function has(obj, val) { - val = arrayify(val); - var len = val.length; - - if (isObject(obj)) { - for (var key in obj) { - if (val.indexOf(key) > -1) { - return true; - } - } +function isAccessorDescriptor(obj, prop) { + if (typeof prop === 'string') { + var val = Object.getOwnPropertyDescriptor(obj, prop); + return typeof val !== 'undefined'; + } - var keys = nativeKeys(obj); - return has(keys, val); + if (typeOf(obj) !== 'object') { + return false; } - if (Array.isArray(obj)) { - var arr = obj; - while (len--) { - if (arr.indexOf(val[len]) > -1) { - return true; - } - } + if (has(obj, 'value') || has(obj, 'writable')) { return false; } - throw new TypeError('expected an array or object.'); -} + if (!has(obj, 'get') || typeof obj.get !== 'function') { + return false; + } -/** - * Cast the given value to an array. - * - * ```js - * arrayify('foo'); - * //=> ['foo'] - * - * arrayify(['foo']); - * //=> ['foo'] - * ``` - * - * @param {String|Array} `val` - * @return {Array} - */ + // tldr: it's valid to have "set" be undefined + // "set" might be undefined if `Object.getOwnPropertyDescriptor` + // was used to get the value, and only `get` was defined by the user + if (has(obj, 'set') && typeof obj[key] !== 'function' && typeof obj[key] !== 'undefined') { + return false; + } -function arrayify(val) { - return val ? (Array.isArray(val) ? val : [val]) : []; -} + for (var key in obj) { + if (!accessor.hasOwnProperty(key)) { + continue; + } -/** - * Returns true if a value has a `contructor` - * - * ```js - * hasConstructor({}); - * //=> true - * - * hasConstructor(Object.create(null)); - * //=> false - * ``` - * @param {Object} `value` - * @return {Boolean} - */ + if (typeOf(obj[key]) === accessor[key]) { + continue; + } -function hasConstructor(val) { - return isObject(val) && typeof val.constructor !== 'undefined'; + if (typeof obj[key] !== 'undefined') { + return false; + } + } + return true; } -/** - * Get the native `ownPropertyNames` from the constructor of the - * given `object`. An empty array is returned if the object does - * not have a constructor. - * - * ```js - * nativeKeys({a: 'b', b: 'c', c: 'd'}) - * //=> ['a', 'b', 'c'] - * - * nativeKeys(function(){}) - * //=> ['length', 'caller'] - * ``` - * - * @param {Object} `obj` Object that has a `constructor`. - * @return {Array} Array of keys. - */ - -function nativeKeys(val) { - if (!hasConstructor(val)) return []; - return Object.getOwnPropertyNames(val); +function has(obj, key) { + return {}.hasOwnProperty.call(obj, key); } /** - * Expose `copy` - */ - -module.exports = copy; - -/** - * Expose `copy.has` for tests + * Expose `isAccessorDescriptor` */ -module.exports.has = has; +module.exports = isAccessorDescriptor; /***/ }), -/* 619 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(571); +var isBuffer = __webpack_require__(614); var toString = Object.prototype.toString; /** @@ -71370,1271 +74504,1022 @@ module.exports = function kindOf(val) { }; -/***/ }), -/* 620 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * copy-descriptor - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -/** - * Copy a descriptor from one object to another. - * - * ```js - * function App() { - * this.cache = {}; - * } - * App.prototype.set = function(key, val) { - * this.cache[key] = val; - * return this; - * }; - * Object.defineProperty(App.prototype, 'count', { - * get: function() { - * return Object.keys(this.cache).length; - * } - * }); - * - * copy(App.prototype, 'count', 'len'); - * - * // create an instance - * var app = new App(); - * - * app.set('a', true); - * app.set('b', true); - * app.set('c', true); - * - * console.log(app.count); - * //=> 3 - * console.log(app.len); - * //=> 3 - * ``` - * @name copy - * @param {Object} `receiver` The target object - * @param {Object} `provider` The provider object - * @param {String} `from` The key to copy on provider. - * @param {String} `to` Optionally specify a new key name to use. - * @return {Object} - * @api public - */ - -module.exports = function copyDescriptor(receiver, provider, from, to) { - if (!isObject(provider) && typeof provider !== 'function') { - to = from; - from = provider; - provider = receiver; - } - if (!isObject(receiver) && typeof receiver !== 'function') { - throw new TypeError('expected the first argument to be an object'); - } - if (!isObject(provider) && typeof provider !== 'function') { - throw new TypeError('expected provider to be an object'); - } - - if (typeof to !== 'string') { - to = from; - } - if (typeof from !== 'string') { - throw new TypeError('expected key to be a string'); - } - - if (!(from in provider)) { - throw new Error('property "' + from + '" does not exist'); - } - - var val = Object.getOwnPropertyDescriptor(provider, from); - if (val) Object.defineProperty(receiver, to, val); -}; - -function isObject(val) { - return {}.toString.call(val) === '[object Object]'; -} - - - -/***/ }), -/* 621 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var use = __webpack_require__(622); -var define = __webpack_require__(610); -var debug = __webpack_require__(624)('snapdragon:compiler'); -var utils = __webpack_require__(630); - -/** - * Create a new `Compiler` with the given `options`. - * @param {Object} `options` - */ - -function Compiler(options, state) { - debug('initializing', __filename); - this.options = utils.extend({source: 'string'}, options); - this.state = state || {}; - this.compilers = {}; - this.output = ''; - this.set('eos', function(node) { - return this.emit(node.val, node); - }); - this.set('noop', function(node) { - return this.emit(node.val, node); - }); - this.set('bos', function(node) { - return this.emit(node.val, node); - }); - use(this); -} - -/** - * Prototype methods - */ - -Compiler.prototype = { - - /** - * Throw an error message with details including the cursor position. - * @param {String} `msg` Message to use in the Error. - */ - - error: function(msg, node) { - var pos = node.position || {start: {column: 0}}; - var message = this.options.source + ' column:' + pos.start.column + ': ' + msg; - - var err = new Error(message); - err.reason = msg; - err.column = pos.start.column; - err.source = this.pattern; - - if (this.options.silent) { - this.errors.push(err); - } else { - throw err; - } - }, - - /** - * Define a non-enumberable property on the `Compiler` instance. - * - * ```js - * compiler.define('foo', 'bar'); - * ``` - * @name .define - * @param {String} `key` propery name - * @param {any} `val` property value - * @return {Object} Returns the Compiler instance for chaining. - * @api public - */ - - define: function(key, val) { - define(this, key, val); - return this; - }, - - /** - * Emit `node.val` - */ - - emit: function(str, node) { - this.output += str; - return str; - }, - - /** - * Add a compiler `fn` with the given `name` - */ - - set: function(name, fn) { - this.compilers[name] = fn; - return this; - }, - - /** - * Get compiler `name`. - */ - - get: function(name) { - return this.compilers[name]; - }, - - /** - * Get the previous AST node. - */ - - prev: function(n) { - return this.ast.nodes[this.idx - (n || 1)] || { type: 'bos', val: '' }; - }, - - /** - * Get the next AST node. - */ - - next: function(n) { - return this.ast.nodes[this.idx + (n || 1)] || { type: 'eos', val: '' }; - }, - - /** - * Visit `node`. - */ - - visit: function(node, nodes, i) { - var fn = this.compilers[node.type]; - this.idx = i; - - if (typeof fn !== 'function') { - throw this.error('compiler "' + node.type + '" is not registered', node); - } - return fn.call(this, node, nodes, i); - }, - - /** - * Map visit over array of `nodes`. - */ - - mapVisit: function(nodes) { - if (!Array.isArray(nodes)) { - throw new TypeError('expected an array'); - } - var len = nodes.length; - var idx = -1; - while (++idx < len) { - this.visit(nodes[idx], nodes, idx); - } - return this; - }, - - /** - * Compile `ast`. - */ - - compile: function(ast, options) { - var opts = utils.extend({}, this.options, options); - this.ast = ast; - this.parsingErrors = this.ast.errors; - this.output = ''; - - // source map support - if (opts.sourcemap) { - var sourcemaps = __webpack_require__(649); - sourcemaps(this); - this.mapVisit(this.ast.nodes); - this.applySourceMaps(); - this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON(); - return this; - } - - this.mapVisit(this.ast.nodes); - return this; - } -}; - -/** - * Expose `Compiler` - */ - -module.exports = Compiler; - - -/***/ }), -/* 622 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * use - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var utils = __webpack_require__(623); - -module.exports = function base(app, opts) { - if (!utils.isObject(app) && typeof app !== 'function') { - throw new TypeError('use: expect `app` be an object or function'); - } - - if (!utils.isObject(opts)) { - opts = {}; - } - - var prop = utils.isString(opts.prop) ? opts.prop : 'fns'; - if (!Array.isArray(app[prop])) { - utils.define(app, prop, []); - } - - /** - * Define a plugin function to be passed to use. The only - * parameter exposed to the plugin is `app`, the object or function. - * passed to `use(app)`. `app` is also exposed as `this` in plugins. - * - * Additionally, **if a plugin returns a function, the function will - * be pushed onto the `fns` array**, allowing the plugin to be - * called at a later point by the `run` method. - * - * ```js - * var use = require('use'); - * - * // define a plugin - * function foo(app) { - * // do stuff - * } - * - * var app = function(){}; - * use(app); - * - * // register plugins - * app.use(foo); - * app.use(bar); - * app.use(baz); - * ``` - * @name .use - * @param {Function} `fn` plugin function to call - * @api public - */ - - utils.define(app, 'use', use); - - /** - * Run all plugins on `fns`. Any plugin that returns a function - * when called by `use` is pushed onto the `fns` array. - * - * ```js - * var config = {}; - * app.run(config); - * ``` - * @name .run - * @param {Object} `value` Object to be modified by plugins. - * @return {Object} Returns the object passed to `run` - * @api public - */ - - utils.define(app, 'run', function(val) { - if (!utils.isObject(val)) return; - decorate(val); - - var self = this || app; - var fns = self[prop]; - var len = fns.length; - var idx = -1; - - while (++idx < len) { - val.use(fns[idx]); - } - return val; - }); - - /** - * Call plugin `fn`. If a function is returned push it into the - * `fns` array to be called by the `run` method. - */ - - function use(fn, options) { - if (typeof fn !== 'function') { - throw new TypeError('.use expects `fn` be a function'); - } - - var self = this || app; - if (typeof opts.fn === 'function') { - opts.fn.call(self, self, options); - } - - var plugin = fn.call(self, self); - if (typeof plugin === 'function') { - var fns = self[prop]; - fns.push(plugin); - } - return self; - } - - /** - * Ensure the `.use` method exists on `val` - */ - - function decorate(val) { - if (!val.use || !val.run) { - base(val); - } - } - - return app; -}; - - -/***/ }), -/* 623 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = {}; - - - -/** - * Lazily required module dependencies - */ - -utils.define = __webpack_require__(610); -utils.isObject = __webpack_require__(547); - - -utils.isString = function(val) { - return val && typeof val === 'string'; -}; - -/** - * Expose `utils` modules - */ - -module.exports = utils; - - -/***/ }), -/* 624 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Detect Electron renderer process, which is node, but we should - * treat as a browser. - */ - -if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(625); -} else { - module.exports = __webpack_require__(628); -} - - -/***/ }), -/* 625 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(626); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } - - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; - } -}; - - -/** - * Colorize log arguments if enabled. +/***/ }), +/* 658 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-data-descriptor * - * @api public + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. */ -function formatArgs(args) { - var useColors = this.useColors; - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - if (!useColors) return; +var typeOf = __webpack_require__(659); - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') +// data descriptor properties +var data = { + configurable: 'boolean', + enumerable: 'boolean', + writable: 'boolean' +}; - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; +function isDataDescriptor(obj, prop) { + if (typeOf(obj) !== 'object') { + return false; + } + + if (typeof prop === 'string') { + var val = Object.getOwnPropertyDescriptor(obj, prop); + return typeof val !== 'undefined'; + } + + if (!('value' in obj) && !('writable' in obj)) { + return false; + } + + for (var key in obj) { + if (key === 'value') continue; + + if (!data.hasOwnProperty(key)) { + continue; } - }); - args.splice(lastC, 0, c); + if (typeOf(obj[key]) === data[key]) { + continue; + } + + if (typeof obj[key] !== 'undefined') { + return false; + } + } + return true; } /** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public + * Expose `isDataDescriptor` */ -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); -} +module.exports = isDataDescriptor; -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; - } - } catch(e) {} -} +/***/ }), +/* 659 */ +/***/ (function(module, exports, __webpack_require__) { + +var isBuffer = __webpack_require__(614); +var toString = Object.prototype.toString; /** - * Load `namespaces`. + * Get the native `typeof` a value. * - * @return {String} returns the previously persisted debug modes - * @api private + * @param {*} `val` + * @return {*} Native javascript type */ -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} +module.exports = function kindOf(val) { + // primitivies + if (typeof val === 'undefined') { + return 'undefined'; + } + if (val === null) { + return 'null'; + } + if (val === true || val === false || val instanceof Boolean) { + return 'boolean'; + } + if (typeof val === 'string' || val instanceof String) { + return 'string'; + } + if (typeof val === 'number' || val instanceof Number) { + return 'number'; + } - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; + // functions + if (typeof val === 'function' || val instanceof Function) { + return 'function'; } - return r; -} + // array + if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { + return 'array'; + } -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ + // check for instances of RegExp and Date before calling `toString` + if (val instanceof RegExp) { + return 'regexp'; + } + if (val instanceof Date) { + return 'date'; + } -exports.enable(load()); + // other objects + var type = toString.call(val); -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ + if (type === '[object RegExp]') { + return 'regexp'; + } + if (type === '[object Date]') { + return 'date'; + } + if (type === '[object Arguments]') { + return 'arguments'; + } + if (type === '[object Error]') { + return 'error'; + } -function localstorage() { - try { - return window.localStorage; - } catch (e) {} -} + // buffer + if (isBuffer(val)) { + return 'buffer'; + } + + // es6: Map, WeakMap, Set, WeakSet + if (type === '[object Set]') { + return 'set'; + } + if (type === '[object WeakSet]') { + return 'weakset'; + } + if (type === '[object Map]') { + return 'map'; + } + if (type === '[object WeakMap]') { + return 'weakmap'; + } + if (type === '[object Symbol]') { + return 'symbol'; + } + + // typed arrays + if (type === '[object Int8Array]') { + return 'int8array'; + } + if (type === '[object Uint8Array]') { + return 'uint8array'; + } + if (type === '[object Uint8ClampedArray]') { + return 'uint8clampedarray'; + } + if (type === '[object Int16Array]') { + return 'int16array'; + } + if (type === '[object Uint16Array]') { + return 'uint16array'; + } + if (type === '[object Int32Array]') { + return 'int32array'; + } + if (type === '[object Uint32Array]') { + return 'uint32array'; + } + if (type === '[object Float32Array]') { + return 'float32array'; + } + if (type === '[object Float64Array]') { + return 'float64array'; + } + + // must be a plain object + return 'object'; +}; /***/ }), -/* 626 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. +"use strict"; +/*! + * static-extend * - * Expose `debug()` as the module. + * Copyright (c) 2016, Jon Schlinkert. + * Licensed under the MIT License. */ -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = __webpack_require__(627); -/** - * The currently active debug mode names, and names to skip. - */ -exports.names = []; -exports.skips = []; +var copy = __webpack_require__(661); +var define = __webpack_require__(653); +var util = __webpack_require__(112); /** - * Map of special "%n" handling functions, for the debug "format" argument. + * Returns a function for extending the static properties, + * prototype properties, and descriptors from the `Parent` + * constructor onto `Child` constructors. * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + * ```js + * var extend = require('static-extend'); + * Parent.extend = extend(Parent); + * + * // optionally pass a custom merge function as the second arg + * Parent.extend = extend(Parent, function(Child) { + * Child.prototype.mixin = function(key, val) { + * Child.prototype[key] = val; + * }; + * }); + * + * // extend "child" constructors + * Parent.extend(Child); + * + * // optionally define prototype methods as the second arg + * Parent.extend(Child, { + * foo: function() {}, + * bar: function() {} + * }); + * ``` + * @param {Function} `Parent` Parent ctor + * @param {Function} `extendFn` Optional extend function for handling any necessary custom merging. Useful when updating methods that require a specific prototype. + * @param {Function} `Child` Child ctor + * @param {Object} `proto` Optionally pass additional prototype properties to inherit. + * @return {Object} + * @api public */ -exports.formatters = {}; +function extend(Parent, extendFn) { + if (typeof Parent !== 'function') { + throw new TypeError('expected Parent to be a function.'); + } -/** - * Previous log timestamp. - */ + return function(Ctor, proto) { + if (typeof Ctor !== 'function') { + throw new TypeError('expected Ctor to be a function.'); + } -var prevTime; + util.inherits(Ctor, Parent); + copy(Ctor, Parent); -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ + // proto can be null or a plain object + if (typeof proto === 'object') { + var obj = Object.create(proto); -function selectColor(namespace) { - var hash = 0, i; + for (var k in obj) { + Ctor.prototype[k] = obj[k]; + } + } - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } + // keep a reference to the parent prototype + define(Ctor.prototype, '_parent_', { + configurable: true, + set: function() {}, + get: function() { + return Parent.prototype; + } + }); - return exports.colors[Math.abs(hash) % exports.colors.length]; -} + if (typeof extendFn === 'function') { + extendFn(Ctor, Parent); + } + + Ctor.extend = extend(Ctor, extendFn); + }; +}; /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public + * Expose `extend` */ -function createDebug(namespace) { +module.exports = extend; - function debug() { - // disabled? - if (!debug.enabled) return; - var self = debug; +/***/ }), +/* 661 */ +/***/ (function(module, exports, __webpack_require__) { - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; +"use strict"; - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - args[0] = exports.coerce(args[0]); +var typeOf = __webpack_require__(662); +var copyDescriptor = __webpack_require__(663); +var define = __webpack_require__(653); - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } +/** + * Copy static properties, prototype properties, and descriptors from one object to another. + * + * ```js + * function App() {} + * var proto = App.prototype; + * App.prototype.set = function() {}; + * App.prototype.get = function() {}; + * + * var obj = {}; + * copy(obj, proto); + * ``` + * @param {Object} `receiver` + * @param {Object} `provider` + * @param {String|Array} `omit` One or more properties to omit + * @return {Object} + * @api public + */ - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); +function copy(receiver, provider, omit) { + if (!isObject(receiver)) { + throw new TypeError('expected receiving object to be an object.'); + } + if (!isObject(provider)) { + throw new TypeError('expected providing object to be an object.'); + } - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); + var props = nativeKeys(provider); + var keys = Object.keys(provider); + var len = props.length; + omit = arrayify(omit); - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); + while (len--) { + var key = props[len]; - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); + if (has(keys, key)) { + define(receiver, key, provider[key]); + } else if (!(key in receiver) && !has(omit, key)) { + copyDescriptor(receiver, provider, key); + } } +}; - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } +/** + * Return true if the given value is an object or function + */ - return debug; +function isObject(val) { + return typeOf(val) === 'object' || typeof val === 'function'; } /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. + * Returns true if an array has any of the given elements, or an + * object has any of the give keys. * - * @param {String} namespaces - * @api public + * ```js + * has(['a', 'b', 'c'], 'c'); + * //=> true + * + * has(['a', 'b', 'c'], ['c', 'z']); + * //=> true + * + * has({a: 'b', c: 'd'}, ['c', 'z']); + * //=> true + * ``` + * @param {Object} `obj` + * @param {String|Array} `val` + * @return {Boolean} */ -function enable(namespaces) { - exports.save(namespaces); +function has(obj, val) { + val = arrayify(val); + var len = val.length; - exports.names = []; - exports.skips = []; + if (isObject(obj)) { + for (var key in obj) { + if (val.indexOf(key) > -1) { + return true; + } + } - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; + var keys = nativeKeys(obj); + return has(keys, val); + } - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); + if (Array.isArray(obj)) { + var arr = obj; + while (len--) { + if (arr.indexOf(val[len]) > -1) { + return true; + } } + return false; } + + throw new TypeError('expected an array or object.'); } /** - * Disable debug output. + * Cast the given value to an array. * - * @api public + * ```js + * arrayify('foo'); + * //=> ['foo'] + * + * arrayify(['foo']); + * //=> ['foo'] + * ``` + * + * @param {String|Array} `val` + * @return {Array} */ -function disable() { - exports.enable(''); +function arrayify(val) { + return val ? (Array.isArray(val) ? val : [val]) : []; } /** - * Returns true if the given mode name is enabled, false otherwise. + * Returns true if a value has a `contructor` * - * @param {String} name + * ```js + * hasConstructor({}); + * //=> true + * + * hasConstructor(Object.create(null)); + * //=> false + * ``` + * @param {Object} `value` * @return {Boolean} - * @api public */ -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; +function hasConstructor(val) { + return isObject(val) && typeof val.constructor !== 'undefined'; } /** - * Coerce `val`. + * Get the native `ownPropertyNames` from the constructor of the + * given `object`. An empty array is returned if the object does + * not have a constructor. * - * @param {Mixed} val - * @return {Mixed} - * @api private + * ```js + * nativeKeys({a: 'b', b: 'c', c: 'd'}) + * //=> ['a', 'b', 'c'] + * + * nativeKeys(function(){}) + * //=> ['length', 'caller'] + * ``` + * + * @param {Object} `obj` Object that has a `constructor`. + * @return {Array} Array of keys. */ -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; +function nativeKeys(val) { + if (!hasConstructor(val)) return []; + return Object.getOwnPropertyNames(val); } +/** + * Expose `copy` + */ -/***/ }), -/* 627 */ -/***/ (function(module, exports) { +module.exports = copy; /** - * Helpers. + * Expose `copy.has` for tests */ -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; +module.exports.has = has; + + +/***/ }), +/* 662 */ +/***/ (function(module, exports, __webpack_require__) { + +var isBuffer = __webpack_require__(614); +var toString = Object.prototype.toString; /** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] + * Get the native `typeof` a value. * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public + * @param {*} `val` + * @return {*} Native javascript type */ -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); +module.exports = function kindOf(val) { + // primitivies + if (typeof val === 'undefined') { + return 'undefined'; + } + if (val === null) { + return 'null'; + } + if (val === true || val === false || val instanceof Boolean) { + return 'boolean'; + } + if (typeof val === 'string' || val instanceof String) { + return 'string'; + } + if (typeof val === 'number' || val instanceof Number) { + return 'number'; } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ + // functions + if (typeof val === 'function' || val instanceof Function) { + return 'function'; + } -function parse(str) { - str = String(str); - if (str.length > 100) { - return; + // array + if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { + return 'array'; } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; + + // check for instances of RegExp and Date before calling `toString` + if (val instanceof RegExp) { + return 'regexp'; } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; + if (val instanceof Date) { + return 'date'; } -} -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ + // other objects + var type = toString.call(val); -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; + if (type === '[object RegExp]') { + return 'regexp'; } - if (ms >= h) { - return Math.round(ms / h) + 'h'; + if (type === '[object Date]') { + return 'date'; } - if (ms >= m) { - return Math.round(ms / m) + 'm'; + if (type === '[object Arguments]') { + return 'arguments'; } - if (ms >= s) { - return Math.round(ms / s) + 's'; + if (type === '[object Error]') { + return 'error'; } - return ms + 'ms'; -} -/** - * Long format for `ms`. + // buffer + if (isBuffer(val)) { + return 'buffer'; + } + + // es6: Map, WeakMap, Set, WeakSet + if (type === '[object Set]') { + return 'set'; + } + if (type === '[object WeakSet]') { + return 'weakset'; + } + if (type === '[object Map]') { + return 'map'; + } + if (type === '[object WeakMap]') { + return 'weakmap'; + } + if (type === '[object Symbol]') { + return 'symbol'; + } + + // typed arrays + if (type === '[object Int8Array]') { + return 'int8array'; + } + if (type === '[object Uint8Array]') { + return 'uint8array'; + } + if (type === '[object Uint8ClampedArray]') { + return 'uint8clampedarray'; + } + if (type === '[object Int16Array]') { + return 'int16array'; + } + if (type === '[object Uint16Array]') { + return 'uint16array'; + } + if (type === '[object Int32Array]') { + return 'int32array'; + } + if (type === '[object Uint32Array]') { + return 'uint32array'; + } + if (type === '[object Float32Array]') { + return 'float32array'; + } + if (type === '[object Float64Array]') { + return 'float64array'; + } + + // must be a plain object + return 'object'; +}; + + +/***/ }), +/* 663 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * copy-descriptor * - * @param {Number} ms - * @return {String} - * @api private + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. */ -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; -} + /** - * Pluralization helper. + * Copy a descriptor from one object to another. + * + * ```js + * function App() { + * this.cache = {}; + * } + * App.prototype.set = function(key, val) { + * this.cache[key] = val; + * return this; + * }; + * Object.defineProperty(App.prototype, 'count', { + * get: function() { + * return Object.keys(this.cache).length; + * } + * }); + * + * copy(App.prototype, 'count', 'len'); + * + * // create an instance + * var app = new App(); + * + * app.set('a', true); + * app.set('b', true); + * app.set('c', true); + * + * console.log(app.count); + * //=> 3 + * console.log(app.len); + * //=> 3 + * ``` + * @name copy + * @param {Object} `receiver` The target object + * @param {Object} `provider` The provider object + * @param {String} `from` The key to copy on provider. + * @param {String} `to` Optionally specify a new key name to use. + * @return {Object} + * @api public */ -function plural(ms, n, name) { - if (ms < n) { - return; +module.exports = function copyDescriptor(receiver, provider, from, to) { + if (!isObject(provider) && typeof provider !== 'function') { + to = from; + from = provider; + provider = receiver; } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; + if (!isObject(receiver) && typeof receiver !== 'function') { + throw new TypeError('expected the first argument to be an object'); } - return Math.ceil(ms / n) + ' ' + name + 's'; + if (!isObject(provider) && typeof provider !== 'function') { + throw new TypeError('expected provider to be an object'); + } + + if (typeof to !== 'string') { + to = from; + } + if (typeof from !== 'string') { + throw new TypeError('expected key to be a string'); + } + + if (!(from in provider)) { + throw new Error('property "' + from + '" does not exist'); + } + + var val = Object.getOwnPropertyDescriptor(provider, from); + if (val) Object.defineProperty(receiver, to, val); +}; + +function isObject(val) { + return {}.toString.call(val) === '[object Object]'; } + /***/ }), -/* 628 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { -/** - * Module dependencies. - */ +"use strict"; -var tty = __webpack_require__(122); -var util = __webpack_require__(112); + +var use = __webpack_require__(665); +var define = __webpack_require__(653); +var debug = __webpack_require__(543)('snapdragon:compiler'); +var utils = __webpack_require__(667); /** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. + * Create a new `Compiler` with the given `options`. + * @param {Object} `options` */ -exports = module.exports = __webpack_require__(626); -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; +function Compiler(options, state) { + debug('initializing', __filename); + this.options = utils.extend({source: 'string'}, options); + this.state = state || {}; + this.compilers = {}; + this.output = ''; + this.set('eos', function(node) { + return this.emit(node.val, node); + }); + this.set('noop', function(node) { + return this.emit(node.val, node); + }); + this.set('bos', function(node) { + return this.emit(node.val, node); + }); + use(this); +} /** - * Colors. + * Prototype methods */ -exports.colors = [6, 2, 3, 4, 5, 1]; +Compiler.prototype = { -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ + /** + * Throw an error message with details including the cursor position. + * @param {String} `msg` Message to use in the Error. + */ -exports.inspectOpts = Object.keys(process.env).filter(function (key) { - return /^debug_/i.test(key); -}).reduce(function (obj, key) { - // camel-case - var prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + error: function(msg, node) { + var pos = node.position || {start: {column: 0}}; + var message = this.options.source + ' column:' + pos.start.column + ': ' + msg; - // coerce string value into JS value - var val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) val = true; - else if (/^(no|off|false|disabled)$/i.test(val)) val = false; - else if (val === 'null') val = null; - else val = Number(val); + var err = new Error(message); + err.reason = msg; + err.column = pos.start.column; + err.source = this.pattern; - obj[prop] = val; - return obj; -}, {}); + if (this.options.silent) { + this.errors.push(err); + } else { + throw err; + } + }, -/** - * The file descriptor to write the `debug()` calls to. - * Set the `DEBUG_FD` env variable to override with another value. i.e.: - * - * $ DEBUG_FD=3 node script.js 3>debug.log - */ + /** + * Define a non-enumberable property on the `Compiler` instance. + * + * ```js + * compiler.define('foo', 'bar'); + * ``` + * @name .define + * @param {String} `key` propery name + * @param {any} `val` property value + * @return {Object} Returns the Compiler instance for chaining. + * @api public + */ + + define: function(key, val) { + define(this, key, val); + return this; + }, + + /** + * Emit `node.val` + */ + + emit: function(str, node) { + this.output += str; + return str; + }, + + /** + * Add a compiler `fn` with the given `name` + */ + + set: function(name, fn) { + this.compilers[name] = fn; + return this; + }, + + /** + * Get compiler `name`. + */ + + get: function(name) { + return this.compilers[name]; + }, + + /** + * Get the previous AST node. + */ + + prev: function(n) { + return this.ast.nodes[this.idx - (n || 1)] || { type: 'bos', val: '' }; + }, + + /** + * Get the next AST node. + */ + + next: function(n) { + return this.ast.nodes[this.idx + (n || 1)] || { type: 'eos', val: '' }; + }, -var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + /** + * Visit `node`. + */ -if (1 !== fd && 2 !== fd) { - util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() -} + visit: function(node, nodes, i) { + var fn = this.compilers[node.type]; + this.idx = i; -var stream = 1 === fd ? process.stdout : - 2 === fd ? process.stderr : - createWritableStdioStream(fd); + if (typeof fn !== 'function') { + throw this.error('compiler "' + node.type + '" is not registered', node); + } + return fn.call(this, node, nodes, i); + }, -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ + /** + * Map visit over array of `nodes`. + */ -function useColors() { - return 'colors' in exports.inspectOpts - ? Boolean(exports.inspectOpts.colors) - : tty.isatty(fd); -} + mapVisit: function(nodes) { + if (!Array.isArray(nodes)) { + throw new TypeError('expected an array'); + } + var len = nodes.length; + var idx = -1; + while (++idx < len) { + this.visit(nodes[idx], nodes, idx); + } + return this; + }, -/** - * Map %o to `util.inspect()`, all on a single line. - */ + /** + * Compile `ast`. + */ -exports.formatters.o = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n').map(function(str) { - return str.trim() - }).join(' '); -}; + compile: function(ast, options) { + var opts = utils.extend({}, this.options, options); + this.ast = ast; + this.parsingErrors = this.ast.errors; + this.output = ''; -/** - * Map %o to `util.inspect()`, allowing multiple lines if needed. - */ + // source map support + if (opts.sourcemap) { + var sourcemaps = __webpack_require__(686); + sourcemaps(this); + this.mapVisit(this.ast.nodes); + this.applySourceMaps(); + this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON(); + return this; + } -exports.formatters.O = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); + this.mapVisit(this.ast.nodes); + return this; + } }; /** - * Adds ANSI color escape codes if enabled. - * - * @api public + * Expose `Compiler` */ -function formatArgs(args) { - var name = this.namespace; - var useColors = this.useColors; +module.exports = Compiler; - if (useColors) { - var c = this.color; - var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } -} +/***/ }), +/* 665 */ +/***/ (function(module, exports, __webpack_require__) { -/** - * Invokes `util.format()` with the specified arguments and writes to `stream`. +"use strict"; +/*! + * use + * + * Copyright (c) 2015, 2017, Jon Schlinkert. + * Released under the MIT License. */ -function log() { - return stream.write(util.format.apply(util, arguments) + '\n'); -} -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; +var utils = __webpack_require__(666); + +module.exports = function base(app, opts) { + if (!utils.isObject(app) && typeof app !== 'function') { + throw new TypeError('use: expect `app` be an object or function'); } -} -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ + if (!utils.isObject(opts)) { + opts = {}; + } -function load() { - return process.env.DEBUG; -} + var prop = utils.isString(opts.prop) ? opts.prop : 'fns'; + if (!Array.isArray(app[prop])) { + utils.define(app, prop, []); + } -/** - * Copied from `node/src/node.js`. - * - * XXX: It's lame that node doesn't expose this API out-of-the-box. It also - * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. - */ + /** + * Define a plugin function to be passed to use. The only + * parameter exposed to the plugin is `app`, the object or function. + * passed to `use(app)`. `app` is also exposed as `this` in plugins. + * + * Additionally, **if a plugin returns a function, the function will + * be pushed onto the `fns` array**, allowing the plugin to be + * called at a later point by the `run` method. + * + * ```js + * var use = require('use'); + * + * // define a plugin + * function foo(app) { + * // do stuff + * } + * + * var app = function(){}; + * use(app); + * + * // register plugins + * app.use(foo); + * app.use(bar); + * app.use(baz); + * ``` + * @name .use + * @param {Function} `fn` plugin function to call + * @api public + */ -function createWritableStdioStream (fd) { - var stream; - var tty_wrap = process.binding('tty_wrap'); + utils.define(app, 'use', use); - // Note stream._type is used for test-module-load-list.js + /** + * Run all plugins on `fns`. Any plugin that returns a function + * when called by `use` is pushed onto the `fns` array. + * + * ```js + * var config = {}; + * app.run(config); + * ``` + * @name .run + * @param {Object} `value` Object to be modified by plugins. + * @return {Object} Returns the object passed to `run` + * @api public + */ - switch (tty_wrap.guessHandleType(fd)) { - case 'TTY': - stream = new tty.WriteStream(fd); - stream._type = 'tty'; + utils.define(app, 'run', function(val) { + if (!utils.isObject(val)) return; + decorate(val); - // Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; + var self = this || app; + var fns = self[prop]; + var len = fns.length; + var idx = -1; - case 'FILE': - var fs = __webpack_require__(134); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; + while (++idx < len) { + val.use(fns[idx]); + } + return val; + }); - case 'PIPE': - case 'TCP': - var net = __webpack_require__(629); - stream = new net.Socket({ - fd: fd, - readable: false, - writable: true - }); + /** + * Call plugin `fn`. If a function is returned push it into the + * `fns` array to be called by the `run` method. + */ - // FIXME Should probably have an option in net.Socket to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stream.readable = false; - stream.read = null; - stream._type = 'pipe'; + function use(fn, options) { + if (typeof fn !== 'function') { + throw new TypeError('.use expects `fn` be a function'); + } - // FIXME Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; + var self = this || app; + if (typeof opts.fn === 'function') { + opts.fn.call(self, self, options); + } - default: - // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); + var plugin = fn.call(self, self); + if (typeof plugin === 'function') { + var fns = self[prop]; + fns.push(plugin); + } + return self; } - // For supporting legacy API we put the FD here. - stream.fd = fd; + /** + * Ensure the `.use` method exists on `val` + */ - stream._isStdio = true; + function decorate(val) { + if (!val.use || !val.run) { + base(val); + } + } - return stream; -} + return app; +}; -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ -function init (debug) { - debug.inspectOpts = {}; +/***/ }), +/* 666 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = {}; + - var keys = Object.keys(exports.inspectOpts); - for (var i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} /** - * Enable namespaces listed in `process.env.DEBUG` initially. + * Lazily required module dependencies */ -exports.enable(load()); +utils.define = __webpack_require__(653); +utils.isObject = __webpack_require__(590); -/***/ }), -/* 629 */ -/***/ (function(module, exports) { +utils.isString = function(val) { + return val && typeof val === 'string'; +}; + +/** + * Expose `utils` modules + */ + +module.exports = utils; -module.exports = require("net"); /***/ }), -/* 630 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72644,9 +75529,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(560); -exports.SourceMap = __webpack_require__(631); -exports.sourceMapResolve = __webpack_require__(642); +exports.extend = __webpack_require__(603); +exports.SourceMap = __webpack_require__(668); +exports.sourceMapResolve = __webpack_require__(679); /** * Convert backslash in the given string to forward slashes @@ -72689,7 +75574,7 @@ exports.last = function(arr, n) { /***/ }), -/* 631 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -72697,13 +75582,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(632).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(638).SourceMapConsumer; -exports.SourceNode = __webpack_require__(641).SourceNode; +exports.SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(675).SourceMapConsumer; +exports.SourceNode = __webpack_require__(678).SourceNode; /***/ }), -/* 632 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72713,10 +75598,10 @@ exports.SourceNode = __webpack_require__(641).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(633); -var util = __webpack_require__(635); -var ArraySet = __webpack_require__(636).ArraySet; -var MappingList = __webpack_require__(637).MappingList; +var base64VLQ = __webpack_require__(670); +var util = __webpack_require__(672); +var ArraySet = __webpack_require__(673).ArraySet; +var MappingList = __webpack_require__(674).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -73125,7 +76010,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 633 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73165,7 +76050,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(634); +var base64 = __webpack_require__(671); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -73271,7 +76156,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 634 */ +/* 671 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73344,7 +76229,7 @@ exports.decode = function (charCode) { /***/ }), -/* 635 */ +/* 672 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73767,7 +76652,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 636 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73777,7 +76662,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(635); +var util = __webpack_require__(672); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -73894,7 +76779,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 637 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73904,7 +76789,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(635); +var util = __webpack_require__(672); /** * Determine whether mappingB is after mappingA with respect to generated @@ -73979,7 +76864,7 @@ exports.MappingList = MappingList; /***/ }), -/* 638 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73989,11 +76874,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(635); -var binarySearch = __webpack_require__(639); -var ArraySet = __webpack_require__(636).ArraySet; -var base64VLQ = __webpack_require__(633); -var quickSort = __webpack_require__(640).quickSort; +var util = __webpack_require__(672); +var binarySearch = __webpack_require__(676); +var ArraySet = __webpack_require__(673).ArraySet; +var base64VLQ = __webpack_require__(670); +var quickSort = __webpack_require__(677).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -75067,7 +77952,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 639 */ +/* 676 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75184,7 +78069,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 640 */ +/* 677 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75304,7 +78189,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 641 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75314,8 +78199,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(632).SourceMapGenerator; -var util = __webpack_require__(635); +var SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; +var util = __webpack_require__(672); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -75723,17 +78608,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 642 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(643) -var resolveUrl = __webpack_require__(644) -var decodeUriComponent = __webpack_require__(645) -var urix = __webpack_require__(647) -var atob = __webpack_require__(648) +var sourceMappingURL = __webpack_require__(680) +var resolveUrl = __webpack_require__(681) +var decodeUriComponent = __webpack_require__(682) +var urix = __webpack_require__(684) +var atob = __webpack_require__(685) @@ -76031,7 +78916,7 @@ module.exports = { /***/ }), -/* 643 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -76094,7 +78979,7 @@ void (function(root, factory) { /***/ }), -/* 644 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -76112,13 +78997,13 @@ module.exports = resolveUrl /***/ }), -/* 645 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(646) +var decodeUriComponent = __webpack_require__(683) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -76129,7 +79014,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 646 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76230,7 +79115,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 647 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -76253,7 +79138,7 @@ module.exports = urix /***/ }), -/* 648 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76267,7 +79152,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 649 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76275,8 +79160,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(610); -var utils = __webpack_require__(630); +var define = __webpack_require__(653); +var utils = __webpack_require__(667); /** * Expose `mixin()`. @@ -76419,19 +79304,19 @@ exports.comment = function(node) { /***/ }), -/* 650 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(622); +var use = __webpack_require__(665); var util = __webpack_require__(112); -var Cache = __webpack_require__(651); -var define = __webpack_require__(610); -var debug = __webpack_require__(624)('snapdragon:parser'); -var Position = __webpack_require__(652); -var utils = __webpack_require__(630); +var Cache = __webpack_require__(688); +var define = __webpack_require__(653); +var debug = __webpack_require__(543)('snapdragon:parser'); +var Position = __webpack_require__(689); +var utils = __webpack_require__(667); /** * Create a new `Parser` with the given `input` and `options`. @@ -76959,7 +79844,7 @@ module.exports = Parser; /***/ }), -/* 651 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77066,13 +79951,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 652 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(610); +var define = __webpack_require__(653); /** * Store position for a node @@ -77087,14 +79972,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 653 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(654); -var assignSymbols = __webpack_require__(555); +var isExtendable = __webpack_require__(691); +var assignSymbols = __webpack_require__(598); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -77154,7 +80039,7 @@ function isEnum(obj, key) { /***/ }), -/* 654 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77167,7 +80052,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(554); +var isPlainObject = __webpack_require__(597); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -77175,14 +80060,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 655 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(656); -var extglob = __webpack_require__(670); +var nanomatch = __webpack_require__(693); +var extglob = __webpack_require__(707); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -77259,7 +80144,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 656 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77270,17 +80155,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(539); -var extend = __webpack_require__(657); +var toRegex = __webpack_require__(582); +var extend = __webpack_require__(694); /** * Local dependencies */ -var compilers = __webpack_require__(659); -var parsers = __webpack_require__(660); -var cache = __webpack_require__(663); -var utils = __webpack_require__(665); +var compilers = __webpack_require__(696); +var parsers = __webpack_require__(697); +var cache = __webpack_require__(700); +var utils = __webpack_require__(702); var MAX_LENGTH = 1024 * 64; /** @@ -78104,14 +80989,14 @@ module.exports = nanomatch; /***/ }), -/* 657 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(658); -var assignSymbols = __webpack_require__(555); +var isExtendable = __webpack_require__(695); +var assignSymbols = __webpack_require__(598); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -78171,7 +81056,7 @@ function isEnum(obj, key) { /***/ }), -/* 658 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78184,7 +81069,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(554); +var isPlainObject = __webpack_require__(597); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -78192,7 +81077,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 659 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78538,15 +81423,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 660 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(556); -var toRegex = __webpack_require__(539); -var isOdd = __webpack_require__(661); +var regexNot = __webpack_require__(599); +var toRegex = __webpack_require__(582); +var isOdd = __webpack_require__(698); /** * Characters to use in negation regex (we want to "not" match @@ -78932,7 +81817,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 661 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78945,7 +81830,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(662); +var isNumber = __webpack_require__(699); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -78959,7 +81844,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 662 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78987,14 +81872,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 663 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(664))(); +module.exports = new (__webpack_require__(701))(); /***/ }), -/* 664 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79007,7 +81892,7 @@ module.exports = new (__webpack_require__(664))(); -var MapCache = __webpack_require__(651); +var MapCache = __webpack_require__(688); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -79129,7 +82014,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 665 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79142,14 +82027,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(666)(); -var Snapdragon = __webpack_require__(581); -utils.define = __webpack_require__(667); -utils.diff = __webpack_require__(668); -utils.extend = __webpack_require__(657); -utils.pick = __webpack_require__(669); -utils.typeOf = __webpack_require__(549); -utils.unique = __webpack_require__(559); +var isWindows = __webpack_require__(703)(); +var Snapdragon = __webpack_require__(624); +utils.define = __webpack_require__(704); +utils.diff = __webpack_require__(705); +utils.extend = __webpack_require__(694); +utils.pick = __webpack_require__(706); +utils.typeOf = __webpack_require__(592); +utils.unique = __webpack_require__(602); /** * Returns true if the given value is effectively an empty string @@ -79515,7 +82400,7 @@ utils.unixify = function(options) { /***/ }), -/* 666 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -79543,7 +82428,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 667 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79556,8 +82441,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(547); -var isDescriptor = __webpack_require__(548); +var isobject = __webpack_require__(590); +var isDescriptor = __webpack_require__(591); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -79588,7 +82473,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 668 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79642,7 +82527,7 @@ function diffArray(one, two) { /***/ }), -/* 669 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79655,7 +82540,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(547); +var isObject = __webpack_require__(590); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -79684,7 +82569,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 670 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79694,18 +82579,18 @@ module.exports = function pick(obj, keys) { * Module dependencies */ -var extend = __webpack_require__(560); -var unique = __webpack_require__(559); -var toRegex = __webpack_require__(539); +var extend = __webpack_require__(603); +var unique = __webpack_require__(602); +var toRegex = __webpack_require__(582); /** * Local dependencies */ -var compilers = __webpack_require__(671); -var parsers = __webpack_require__(677); -var Extglob = __webpack_require__(680); -var utils = __webpack_require__(679); +var compilers = __webpack_require__(708); +var parsers = __webpack_require__(714); +var Extglob = __webpack_require__(717); +var utils = __webpack_require__(716); var MAX_LENGTH = 1024 * 64; /** @@ -80022,13 +82907,13 @@ module.exports = extglob; /***/ }), -/* 671 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(672); +var brackets = __webpack_require__(709); /** * Extglob compilers @@ -80198,7 +83083,7 @@ module.exports = function(extglob) { /***/ }), -/* 672 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80208,17 +83093,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(673); -var parsers = __webpack_require__(675); +var compilers = __webpack_require__(710); +var parsers = __webpack_require__(712); /** * Module dependencies */ -var debug = __webpack_require__(624)('expand-brackets'); -var extend = __webpack_require__(560); -var Snapdragon = __webpack_require__(581); -var toRegex = __webpack_require__(539); +var debug = __webpack_require__(543)('expand-brackets'); +var extend = __webpack_require__(603); +var Snapdragon = __webpack_require__(624); +var toRegex = __webpack_require__(582); /** * Parses the given POSIX character class `pattern` and returns a @@ -80416,13 +83301,13 @@ module.exports = brackets; /***/ }), -/* 673 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(674); +var posix = __webpack_require__(711); module.exports = function(brackets) { brackets.compiler @@ -80510,7 +83395,7 @@ module.exports = function(brackets) { /***/ }), -/* 674 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80539,14 +83424,14 @@ module.exports = { /***/ }), -/* 675 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(676); -var define = __webpack_require__(610); +var utils = __webpack_require__(713); +var define = __webpack_require__(653); /** * Text regex @@ -80765,14 +83650,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 676 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(539); -var regexNot = __webpack_require__(556); +var toRegex = __webpack_require__(582); +var regexNot = __webpack_require__(599); var cached; /** @@ -80806,15 +83691,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 677 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(672); -var define = __webpack_require__(678); -var utils = __webpack_require__(679); +var brackets = __webpack_require__(709); +var define = __webpack_require__(715); +var utils = __webpack_require__(716); /** * Characters to use in text regex (we want to "not" match @@ -80969,7 +83854,7 @@ module.exports = parsers; /***/ }), -/* 678 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80982,7 +83867,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(548); +var isDescriptor = __webpack_require__(591); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -81007,14 +83892,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 679 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(556); -var Cache = __webpack_require__(664); +var regex = __webpack_require__(599); +var Cache = __webpack_require__(701); /** * Utils @@ -81083,7 +83968,7 @@ utils.createRegex = function(str) { /***/ }), -/* 680 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81093,16 +83978,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(581); -var define = __webpack_require__(678); -var extend = __webpack_require__(560); +var Snapdragon = __webpack_require__(624); +var define = __webpack_require__(715); +var extend = __webpack_require__(603); /** * Local dependencies */ -var compilers = __webpack_require__(671); -var parsers = __webpack_require__(677); +var compilers = __webpack_require__(708); +var parsers = __webpack_require__(714); /** * Customize Snapdragon parser and renderer @@ -81168,16 +84053,16 @@ module.exports = Extglob; /***/ }), -/* 681 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(670); -var nanomatch = __webpack_require__(656); -var regexNot = __webpack_require__(556); -var toRegex = __webpack_require__(539); +var extglob = __webpack_require__(707); +var nanomatch = __webpack_require__(693); +var regexNot = __webpack_require__(599); +var toRegex = __webpack_require__(582); var not; /** @@ -81258,14 +84143,14 @@ function textRegex(pattern) { /***/ }), -/* 682 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(664))(); +module.exports = new (__webpack_require__(701))(); /***/ }), -/* 683 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81278,13 +84163,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(581); -utils.define = __webpack_require__(684); -utils.diff = __webpack_require__(668); -utils.extend = __webpack_require__(653); -utils.pick = __webpack_require__(669); -utils.typeOf = __webpack_require__(549); -utils.unique = __webpack_require__(559); +var Snapdragon = __webpack_require__(624); +utils.define = __webpack_require__(721); +utils.diff = __webpack_require__(705); +utils.extend = __webpack_require__(690); +utils.pick = __webpack_require__(706); +utils.typeOf = __webpack_require__(592); +utils.unique = __webpack_require__(602); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -81581,7 +84466,7 @@ utils.unixify = function(options) { /***/ }), -/* 684 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81594,8 +84479,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(547); -var isDescriptor = __webpack_require__(548); +var isobject = __webpack_require__(590); +var isDescriptor = __webpack_require__(591); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -81626,7 +84511,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 685 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81645,9 +84530,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(686); -var reader_1 = __webpack_require__(699); -var fs_stream_1 = __webpack_require__(703); +var readdir = __webpack_require__(723); +var reader_1 = __webpack_require__(736); +var fs_stream_1 = __webpack_require__(740); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -81708,15 +84593,15 @@ exports.default = ReaderAsync; /***/ }), -/* 686 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(687); -const readdirAsync = __webpack_require__(695); -const readdirStream = __webpack_require__(698); +const readdirSync = __webpack_require__(724); +const readdirAsync = __webpack_require__(732); +const readdirStream = __webpack_require__(735); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -81800,7 +84685,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 687 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81808,11 +84693,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(688); +const DirectoryReader = __webpack_require__(725); let syncFacade = { - fs: __webpack_require__(693), - forEach: __webpack_require__(694), + fs: __webpack_require__(730), + forEach: __webpack_require__(731), sync: true }; @@ -81841,7 +84726,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 688 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81850,9 +84735,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(689); -const stat = __webpack_require__(691); -const call = __webpack_require__(692); +const normalizeOptions = __webpack_require__(726); +const stat = __webpack_require__(728); +const call = __webpack_require__(729); /** * Asynchronously reads the contents of a directory and streams the results @@ -82228,14 +85113,14 @@ module.exports = DirectoryReader; /***/ }), -/* 689 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(690); +const globToRegExp = __webpack_require__(727); module.exports = normalizeOptions; @@ -82412,7 +85297,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 690 */ +/* 727 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -82549,13 +85434,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 691 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(692); +const call = __webpack_require__(729); module.exports = stat; @@ -82630,7 +85515,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 692 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82691,14 +85576,14 @@ function callOnce (fn) { /***/ }), -/* 693 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(692); +const call = __webpack_require__(729); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -82762,7 +85647,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 694 */ +/* 731 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82791,7 +85676,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 695 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82799,12 +85684,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(696); -const DirectoryReader = __webpack_require__(688); +const maybe = __webpack_require__(733); +const DirectoryReader = __webpack_require__(725); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(697), + forEach: __webpack_require__(734), async: true }; @@ -82846,7 +85731,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 696 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82873,7 +85758,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 697 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82909,7 +85794,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 698 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82917,11 +85802,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(688); +const DirectoryReader = __webpack_require__(725); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(697), + forEach: __webpack_require__(734), async: true }; @@ -82941,16 +85826,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 699 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(700); -var entry_1 = __webpack_require__(702); -var pathUtil = __webpack_require__(701); +var deep_1 = __webpack_require__(737); +var entry_1 = __webpack_require__(739); +var pathUtil = __webpack_require__(738); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -83016,14 +85901,14 @@ exports.default = Reader; /***/ }), -/* 700 */ +/* 737 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(701); -var patternUtils = __webpack_require__(533); +var pathUtils = __webpack_require__(738); +var patternUtils = __webpack_require__(576); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -83106,7 +85991,7 @@ exports.default = DeepFilter; /***/ }), -/* 701 */ +/* 738 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83137,14 +86022,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 702 */ +/* 739 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(701); -var patternUtils = __webpack_require__(533); +var pathUtils = __webpack_require__(738); +var patternUtils = __webpack_require__(576); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -83229,7 +86114,7 @@ exports.default = EntryFilter; /***/ }), -/* 703 */ +/* 740 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83249,8 +86134,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(704); -var fs_1 = __webpack_require__(708); +var fsStat = __webpack_require__(741); +var fs_1 = __webpack_require__(745); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -83300,14 +86185,14 @@ exports.default = FileSystemStream; /***/ }), -/* 704 */ +/* 741 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(705); -const statProvider = __webpack_require__(707); +const optionsManager = __webpack_require__(742); +const statProvider = __webpack_require__(744); /** * Asynchronous API. */ @@ -83338,13 +86223,13 @@ exports.statSync = statSync; /***/ }), -/* 705 */ +/* 742 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(706); +const fsAdapter = __webpack_require__(743); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -83357,7 +86242,7 @@ exports.prepare = prepare; /***/ }), -/* 706 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83380,7 +86265,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 707 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83432,7 +86317,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 708 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83463,7 +86348,7 @@ exports.default = FileSystem; /***/ }), -/* 709 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83483,9 +86368,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(686); -var reader_1 = __webpack_require__(699); -var fs_stream_1 = __webpack_require__(703); +var readdir = __webpack_require__(723); +var reader_1 = __webpack_require__(736); +var fs_stream_1 = __webpack_require__(740); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -83553,7 +86438,7 @@ exports.default = ReaderStream; /***/ }), -/* 710 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83572,9 +86457,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(686); -var reader_1 = __webpack_require__(699); -var fs_sync_1 = __webpack_require__(711); +var readdir = __webpack_require__(723); +var reader_1 = __webpack_require__(736); +var fs_sync_1 = __webpack_require__(748); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -83634,7 +86519,7 @@ exports.default = ReaderSync; /***/ }), -/* 711 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83653,8 +86538,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(704); -var fs_1 = __webpack_require__(708); +var fsStat = __webpack_require__(741); +var fs_1 = __webpack_require__(745); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -83700,7 +86585,7 @@ exports.default = FileSystemSync; /***/ }), -/* 712 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83716,7 +86601,7 @@ exports.flatten = flatten; /***/ }), -/* 713 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83737,13 +86622,13 @@ exports.merge = merge; /***/ }), -/* 714 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(715); +const pathType = __webpack_require__(752); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -83809,13 +86694,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 715 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(716); +const pify = __webpack_require__(753); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -83858,7 +86743,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 716 */ +/* 753 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83949,17 +86834,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 717 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(529); -const gitIgnore = __webpack_require__(718); -const pify = __webpack_require__(719); -const slash = __webpack_require__(720); +const fastGlob = __webpack_require__(572); +const gitIgnore = __webpack_require__(755); +const pify = __webpack_require__(756); +const slash = __webpack_require__(757); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -84057,7 +86942,7 @@ module.exports.sync = options => { /***/ }), -/* 718 */ +/* 755 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -84526,7 +87411,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 719 */ +/* 756 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84601,7 +87486,7 @@ module.exports = (input, options) => { /***/ }), -/* 720 */ +/* 757 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84619,7 +87504,7 @@ module.exports = input => { /***/ }), -/* 721 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84632,7 +87517,7 @@ module.exports = input => { -var isGlob = __webpack_require__(722); +var isGlob = __webpack_require__(759); module.exports = function hasGlob(val) { if (val == null) return false; @@ -84652,7 +87537,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 722 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -84683,17 +87568,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 723 */ +/* 760 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(724); -const CpFileError = __webpack_require__(727); -const fs = __webpack_require__(729); -const ProgressEmitter = __webpack_require__(732); +const pEvent = __webpack_require__(761); +const CpFileError = __webpack_require__(764); +const fs = __webpack_require__(766); +const ProgressEmitter = __webpack_require__(769); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -84807,12 +87692,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 724 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(725); +const pTimeout = __webpack_require__(762); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -85103,12 +87988,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 725 */ +/* 762 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(726); +const pFinally = __webpack_require__(763); class TimeoutError extends Error { constructor(message) { @@ -85154,7 +88039,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 726 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85176,12 +88061,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 727 */ +/* 764 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(728); +const NestedError = __webpack_require__(765); class CpFileError extends NestedError { constructor(message, nested) { @@ -85195,7 +88080,7 @@ module.exports = CpFileError; /***/ }), -/* 728 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -85251,16 +88136,16 @@ module.exports = NestedError; /***/ }), -/* 729 */ +/* 766 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(730); -const pEvent = __webpack_require__(724); -const CpFileError = __webpack_require__(727); +const makeDir = __webpack_require__(767); +const pEvent = __webpack_require__(761); +const CpFileError = __webpack_require__(764); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -85357,7 +88242,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 730 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85365,7 +88250,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(731); +const semver = __webpack_require__(768); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -85520,7 +88405,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 731 */ +/* 768 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -87122,7 +90007,7 @@ function coerce (version, options) { /***/ }), -/* 732 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87163,7 +90048,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 733 */ +/* 770 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87209,12 +90094,12 @@ exports.default = module.exports; /***/ }), -/* 734 */ +/* 771 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(735); +const pMap = __webpack_require__(772); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -87231,7 +90116,7 @@ module.exports.default = pFilter; /***/ }), -/* 735 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87310,12 +90195,12 @@ module.exports.default = pMap; /***/ }), -/* 736 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(728); +const NestedError = __webpack_require__(765); class CpyError extends NestedError { constructor(message, nested) { @@ -87329,16 +90214,16 @@ module.exports = CpyError; /***/ }), -/* 737 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const arrayUnion = __webpack_require__(738); +const arrayUnion = __webpack_require__(775); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(529); -const dirGlob = __webpack_require__(739); -const gitignore = __webpack_require__(743); +const fastGlob = __webpack_require__(572); +const dirGlob = __webpack_require__(776); +const gitignore = __webpack_require__(780); const DEFAULT_FILTER = () => false; @@ -87464,12 +90349,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 738 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(528); +var arrayUniq = __webpack_require__(571); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -87477,14 +90362,14 @@ module.exports = function () { /***/ }), -/* 739 */ +/* 776 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const arrify = __webpack_require__(740); -const pathType = __webpack_require__(741); +const arrify = __webpack_require__(777); +const pathType = __webpack_require__(778); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; const getPath = filepath => filepath[0] === '!' ? filepath.slice(1) : filepath; @@ -87532,7 +90417,7 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 740 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87547,13 +90432,13 @@ module.exports = function (val) { /***/ }), -/* 741 */ +/* 778 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(742); +const pify = __webpack_require__(779); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -87596,7 +90481,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 742 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87687,17 +90572,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 743 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(529); -const gitIgnore = __webpack_require__(744); -const pify = __webpack_require__(742); -const slash = __webpack_require__(745); +const fastGlob = __webpack_require__(572); +const gitIgnore = __webpack_require__(781); +const pify = __webpack_require__(779); +const slash = __webpack_require__(782); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -87789,7 +90674,7 @@ module.exports.sync = o => { /***/ }), -/* 744 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88221,7 +91106,7 @@ typeof process !== 'undefined' && (process.env && process.env.IGNORE_TEST_WIN32 /***/ }), -/* 745 */ +/* 782 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88239,7 +91124,7 @@ module.exports = function (str) { /***/ }), -/* 746 */ +/* 783 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -88247,13 +91132,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return buildNonBazelProductionProjects; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProductionProjects", function() { return getProductionProjects; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProject", function() { return buildProject; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(522); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(519); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(562); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 544bfd5587e00..4a6a43ff2d91f 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -23,6 +23,11 @@ export const BootstrapCommand: ICommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', + reportTiming: { + group: 'bootstrap', + id: 'overall time', + }, + async run(projects, projectGraph, { options, kbn, rootPath }) { const nonBazelProjectsOnly = await getNonBazelProjectsOnly(projects); const batchedNonBazelProjects = topologicallyBatchProjects(nonBazelProjectsOnly, projectGraph); diff --git a/packages/kbn-pm/src/commands/index.ts b/packages/kbn-pm/src/commands/index.ts index 9f02a6bf7038a..0ab6bc9c7808a 100644 --- a/packages/kbn-pm/src/commands/index.ts +++ b/packages/kbn-pm/src/commands/index.ts @@ -18,6 +18,10 @@ export interface ICommandConfig { export interface ICommand { name: string; description: string; + reportTiming?: { + group: string; + id: string; + }; run: (projects: ProjectMap, projectGraph: ProjectGraph, config: ICommandConfig) => Promise; } diff --git a/packages/kbn-pm/src/run.ts b/packages/kbn-pm/src/run.ts index 5ffe2beeeb77b..e5d74cee65ba7 100644 --- a/packages/kbn-pm/src/run.ts +++ b/packages/kbn-pm/src/run.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { CiStatsReporter } from '@kbn/dev-utils/ci_stats_reporter'; + import { ICommand, ICommandConfig } from './commands'; import { CliError } from './utils/errors'; import { log } from './utils/log'; @@ -14,10 +16,13 @@ import { renderProjectsTree } from './utils/projects_tree'; import { Kibana } from './utils/kibana'; export async function runCommand(command: ICommand, config: Omit) { + const runStartTime = Date.now(); + let kbn; + try { log.debug(`Running [${command.name}] command from [${config.rootPath}]`); - const kbn = await Kibana.loadFrom(config.rootPath); + kbn = await Kibana.loadFrom(config.rootPath); const projects = kbn.getFilteredProjects({ skipKibanaPlugins: Boolean(config.options['skip-kibana-plugins']), ossOnly: Boolean(config.options.oss), @@ -41,7 +46,46 @@ export async function runCommand(command: ICommand, config: Omit { * A handler that will be executed before leaving the application, either when * going to another application or when closing the browser tab or manually changing * the url. - * Should return `confirm` to to prompt a message to the user before leaving the page, or `default` + * Should return `confirm` to prompt a message to the user before leaving the page, or `default` * to keep the default behavior (doing nothing). * * See {@link AppMountParameters} for detailed usage examples. diff --git a/src/core/public/core_app/styles/_globals_v7dark.scss b/src/core/public/core_app/styles/_globals_v7dark.scss index 9a4a965d63a38..9341601089737 100644 --- a/src/core/public/core_app/styles/_globals_v7dark.scss +++ b/src/core/public/core_app/styles/_globals_v7dark.scss @@ -6,3 +6,5 @@ @import '@elastic/eui/src/themes/eui/eui_globals'; @import './mixins'; + +$kbnThemeVersion: 'v7dark'; diff --git a/src/core/public/core_app/styles/_globals_v7light.scss b/src/core/public/core_app/styles/_globals_v7light.scss index ddb4b5b31fa1f..e1ff6ec70aab5 100644 --- a/src/core/public/core_app/styles/_globals_v7light.scss +++ b/src/core/public/core_app/styles/_globals_v7light.scss @@ -6,3 +6,5 @@ @import '@elastic/eui/src/themes/eui/eui_globals'; @import './mixins'; + +$kbnThemeVersion: 'v7light'; diff --git a/src/core/public/core_app/styles/_globals_v8dark.scss b/src/core/public/core_app/styles/_globals_v8dark.scss index 9ad9108f350ff..ce0eeea02eb3b 100644 --- a/src/core/public/core_app/styles/_globals_v8dark.scss +++ b/src/core/public/core_app/styles/_globals_v8dark.scss @@ -6,3 +6,5 @@ @import '@elastic/eui/src/themes/eui-amsterdam/eui_amsterdam_globals'; @import './mixins'; + +$kbnThemeVersion: 'v8dark'; diff --git a/src/core/public/core_app/styles/_globals_v8light.scss b/src/core/public/core_app/styles/_globals_v8light.scss index a6b2cb84c2062..1ec76902f6589 100644 --- a/src/core/public/core_app/styles/_globals_v8light.scss +++ b/src/core/public/core_app/styles/_globals_v8light.scss @@ -6,3 +6,5 @@ @import '@elastic/eui/src/themes/eui-amsterdam/eui_amsterdam_globals'; @import './mixins'; + +$kbnThemeVersion: 'v8light'; diff --git a/src/core/public/core_app/styles/_mixins.scss b/src/core/public/core_app/styles/_mixins.scss index d088a47144f33..b2adbdb691bb6 100644 --- a/src/core/public/core_app/styles/_mixins.scss +++ b/src/core/public/core_app/styles/_mixins.scss @@ -120,3 +120,31 @@ } } } + +@mixin kbnThemeStyle($theme, $mode: 'both') { + $themes: 'v7', 'v8'; + @if (index($themes, $theme)) { + @if ($mode == 'both') { + $themeLight: $theme + 'light'; + $themeDark: $theme + 'dark'; + // $kbnThemeVersion comes from the active theme's globals file (e.g. _globals_v8light.scss) + @if ($kbnThemeVersion == $themeLight or $kbnThemeVersion == $themeDark) { + @content; + } + } @else if ($mode == 'light') { + $themeLight: $theme + 'light'; + @if ($kbnThemeVersion == $themeLight) { + @content; + } + } @else if ($mode == 'dark') { + $themeDark: $theme + 'dark'; + @if ($kbnThemeVersion == $themeDark) { + @content; + } + } @else { + @warn 'The second parameter must be a valid mode (light, dark, or both) -- got #{$mode}'; + } + } @else { + @warn 'Invalid $theme. Valid options are: #{$themes}. Got #{$theme} instead'; + } +} diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index df09e6f52cd86..e08a8e0be0a41 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -57,4 +57,21 @@ describe('CoreApp', () => { ); }); }); + + describe('`/app/{id}/{any*}` route', () => { + it('is registered with the correct parameters', () => { + coreApp.setup(internalCoreSetup); + + expect(httpResourcesRegistrar.register).toHaveBeenCalledWith( + { + path: '/app/{id}/{any*}', + validate: false, + options: { + authRequired: true, + }, + }, + expect.any(Function) + ); + }); + }); }); diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index 437e7ecd0b34f..24ddc305d8232 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -16,9 +16,11 @@ import { Logger } from '../logging'; /** @internal */ export class CoreApp { private readonly logger: Logger; + constructor(core: CoreContext) { this.logger = core.logger.get('core-app'); } + setup(coreSetup: InternalCoreSetup) { this.logger.debug('Setting up core app.'); this.registerDefaultRoutes(coreSetup); @@ -27,7 +29,9 @@ export class CoreApp { private registerDefaultRoutes(coreSetup: InternalCoreSetup) { const httpSetup = coreSetup.http; - const router = httpSetup.createRouter('/'); + const router = httpSetup.createRouter(''); + const resources = coreSetup.httpResources.createRegistrar(router); + router.get({ path: '/', validate: false }, async (context, req, res) => { const defaultRoute = await context.core.uiSettings.client.get('defaultRoute'); const basePath = httpSetup.basePath.get(req); @@ -39,12 +43,26 @@ export class CoreApp { }, }); }); + router.get({ path: '/core', validate: false }, async (context, req, res) => res.ok({ body: { version: '0.0.1' } }) ); + resources.register( + { + path: '/app/{id}/{any*}', + validate: false, + options: { + authRequired: true, + }, + }, + async (context, request, response) => { + return response.renderCoreApp(); + } + ); + const anonymousStatusPage = coreSetup.status.isStatusPageAnonymous(); - coreSetup.httpResources.createRegistrar(router).register( + resources.register( { path: '/status', validate: false, @@ -61,6 +79,7 @@ export class CoreApp { } ); } + private registerStaticDirs(coreSetup: InternalCoreSetup) { coreSetup.http.registerStaticDir('/ui/{path*}', Path.resolve(__dirname, './assets')); diff --git a/src/core/server/environment/environment_service.test.ts b/src/core/server/environment/environment_service.test.ts index efcf349075940..fb3ddaa77b416 100644 --- a/src/core/server/environment/environment_service.test.ts +++ b/src/core/server/environment/environment_service.test.ts @@ -62,18 +62,24 @@ describe('UuidService', () => { let logger: ReturnType; let configService: ReturnType; let coreContext: CoreContext; + let service: EnvironmentService; - beforeEach(() => { - jest.clearAllMocks(); + beforeEach(async () => { logger = loggingSystemMock.create(); configService = getConfigService(); coreContext = mockCoreContext.create({ logger, configService }); + + service = new EnvironmentService(coreContext); + }); + + afterEach(() => { + jest.clearAllMocks(); }); describe('#setup()', () => { it('calls resolveInstanceUuid with correct parameters', async () => { - const service = new EnvironmentService(coreContext); await service.setup(); + expect(resolveInstanceUuid).toHaveBeenCalledTimes(1); expect(resolveInstanceUuid).toHaveBeenCalledWith({ pathConfig, @@ -83,8 +89,8 @@ describe('UuidService', () => { }); it('calls createDataFolder with correct parameters', async () => { - const service = new EnvironmentService(coreContext); await service.setup(); + expect(createDataFolder).toHaveBeenCalledTimes(1); expect(createDataFolder).toHaveBeenCalledWith({ pathConfig, @@ -93,8 +99,8 @@ describe('UuidService', () => { }); it('calls writePidFile with correct parameters', async () => { - const service = new EnvironmentService(coreContext); await service.setup(); + expect(writePidFile).toHaveBeenCalledTimes(1); expect(writePidFile).toHaveBeenCalledWith({ pidConfig, @@ -103,9 +109,31 @@ describe('UuidService', () => { }); it('returns the uuid resolved from resolveInstanceUuid', async () => { - const service = new EnvironmentService(coreContext); const setup = await service.setup(); + expect(setup.instanceUuid).toEqual('SOME_UUID'); }); + + describe('process warnings', () => { + it('logs warnings coming from the process', async () => { + await service.setup(); + + const warning = new Error('something went wrong'); + process.emit('warning', warning); + + expect(logger.get('process').warn).toHaveBeenCalledTimes(1); + expect(logger.get('process').warn).toHaveBeenCalledWith(warning); + }); + + it('does not log deprecation warnings', async () => { + await service.setup(); + + const warning = new Error('something went wrong'); + warning.name = 'DeprecationWarning'; + process.emit('warning', warning); + + expect(logger.get('process').warn).not.toHaveBeenCalled(); + }); + }); }); }); diff --git a/src/core/server/environment/environment_service.ts b/src/core/server/environment/environment_service.ts index a6bcdf4c35661..e652622049cfa 100644 --- a/src/core/server/environment/environment_service.ts +++ b/src/core/server/environment/environment_service.ts @@ -30,11 +30,13 @@ export interface InternalEnvironmentServiceSetup { /** @internal */ export class EnvironmentService { private readonly log: Logger; + private readonly processLogger: Logger; private readonly configService: IConfigService; private uuid: string = ''; constructor(core: CoreContext) { this.log = core.logger.get('environment'); + this.processLogger = core.logger.get('process'); this.configService = core.configService; } @@ -50,6 +52,14 @@ export class EnvironmentService { this.log.warn(`Detected an unhandled Promise rejection.\n${reason}`); }); + process.on('warning', (warning) => { + // deprecation warnings do no reflect a current problem for the user and should be filtered out. + if (warning.name === 'DeprecationWarning') { + return; + } + this.processLogger.warn(warning); + }); + await createDataFolder({ pathConfig, logger: this.log }); await writePidFile({ pidConfig, logger: this.log }); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 8435050a238c6..b0510bc414bf8 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -30,11 +30,11 @@ import { SessionStorageCookieOptions, createCookieSessionStorageFactory, } from './cookie_session_storage'; -import { IsAuthenticated, AuthStateStorage, GetAuthState } from './auth_state_storage'; +import { AuthStateStorage } from './auth_state_storage'; import { AuthHeadersStorage, GetAuthHeaders } from './auth_headers_storage'; import { BasePath } from './base_path_service'; import { getEcsResponseLog } from './logging'; -import { HttpServiceSetup, HttpServerInfo } from './types'; +import { HttpServiceSetup, HttpServerInfo, HttpAuth } from './types'; /** @internal */ export interface HttpServerSetup { @@ -54,10 +54,7 @@ export interface HttpServerSetup { registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; registerOnPreResponse: HttpServiceSetup['registerOnPreResponse']; getAuthHeaders: GetAuthHeaders; - auth: { - get: GetAuthState; - isAuthenticated: IsAuthenticated; - }; + auth: HttpAuth; getServerInfo: () => HttpServerInfo; } @@ -228,14 +225,16 @@ export class HttpServer { private getAuthOption( authRequired: RouteConfigOptions['authRequired'] = true - ): undefined | false | { mode: 'required' | 'optional' } { + ): undefined | false | { mode: 'required' | 'try' } { if (this.authRegistered === false) return undefined; if (authRequired === true) { return { mode: 'required' }; } if (authRequired === 'optional') { - return { mode: 'optional' }; + // we want to use HAPI `try` mode and not `optional` to not throw unauthorized errors when the user + // has invalid or expired credentials + return { mode: 'try' }; } if (authRequired === false) { return false; diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index c05937ef618c9..6c11534df0d11 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -65,7 +65,7 @@ describe('http service', () => { const { http } = await root.setup(); const { registerAuth, createRouter, auth } = http; - await registerAuth((req, res, toolkit) => toolkit.authenticated()); + registerAuth((req, res, toolkit) => toolkit.authenticated()); const router = createRouter(''); router.get({ path: '/is-auth', validate: false }, (context, req, res) => @@ -179,7 +179,7 @@ describe('http service', () => { const { http } = await root.setup(); const { createRouter, registerAuth, auth } = http; - await registerAuth(authenticate); + registerAuth(authenticate); const router = createRouter(''); router.get( { path: '/get-auth', validate: false, options: { authRequired: false } }, diff --git a/src/core/server/http/integration_tests/http_auth.test.ts b/src/core/server/http/integration_tests/http_auth.test.ts new file mode 100644 index 0000000000000..0696deb9c07ae --- /dev/null +++ b/src/core/server/http/integration_tests/http_auth.test.ts @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as kbnTestServer from '../../../test_helpers/kbn_server'; +import { IRouter, RouteConfigOptions } from '../router'; +import { HttpAuth } from '../types'; + +describe('http auth', () => { + let root: ReturnType; + + beforeEach(async () => { + root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + }, 30000); + + afterEach(async () => { + await root.shutdown(); + }); + + const registerRoute = ( + router: IRouter, + auth: HttpAuth, + authRequired: RouteConfigOptions<'get'>['authRequired'] + ) => { + router.get( + { + path: '/route', + validate: false, + options: { + authRequired, + }, + }, + (context, req, res) => res.ok({ body: { authenticated: auth.isAuthenticated(req) } }) + ); + }; + + describe('when auth is registered', () => { + describe('when authRequired is `true`', () => { + it('allows authenticated access when auth returns `authenticated`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => toolkit.authenticated()); + + const router = createRouter(''); + registerRoute(router, auth, true); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: true }); + }); + + it('blocks access when auth returns `notHandled`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => toolkit.notHandled()); + + const router = createRouter(''); + registerRoute(router, auth, true); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(401); + }); + + it('blocks access when auth returns `unauthorized`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => res.unauthorized()); + + const router = createRouter(''); + registerRoute(router, auth, true); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(401); + }); + }); + describe('when authRequired is `false`', () => { + it('allows anonymous access when auth returns `authenticated`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => toolkit.authenticated()); + + const router = createRouter(''); + registerRoute(router, auth, false); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + + it('allows anonymous access when auth returns `notHandled`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => toolkit.notHandled()); + + const router = createRouter(''); + registerRoute(router, auth, false); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + + it('allows anonymous access when auth returns `unauthorized`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => res.unauthorized()); + + const router = createRouter(''); + registerRoute(router, auth, false); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + }); + describe('when authRequired is `optional`', () => { + it('allows authenticated access when auth returns `authenticated`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => toolkit.authenticated()); + + const router = createRouter(''); + registerRoute(router, auth, 'optional'); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: true }); + }); + + it('allows anonymous access when auth returns `notHandled`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => toolkit.notHandled()); + + const router = createRouter(''); + registerRoute(router, auth, 'optional'); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + + it('allows anonymous access when auth returns `unauthorized`', async () => { + const { http } = await root.setup(); + const { registerAuth, createRouter, auth } = http; + + registerAuth((req, res, toolkit) => res.unauthorized()); + + const router = createRouter(''); + registerRoute(router, auth, 'optional'); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + }); + }); + + describe('when auth is not registered', () => { + it('allow anonymous access to resources when `authRequired` is `true`', async () => { + const { http } = await root.setup(); + const { createRouter, auth } = http; + + const router = createRouter(''); + registerRoute(router, auth, true); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + + it('allow anonymous access to resources when `authRequired` is `false`', async () => { + const { http } = await root.setup(); + const { createRouter, auth } = http; + + const router = createRouter(''); + registerRoute(router, auth, false); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + + it('allow anonymous access to resources when `authRequired` is `optional`', async () => { + const { http } = await root.setup(); + const { createRouter, auth } = http; + + const router = createRouter(''); + registerRoute(router, auth, 'optional'); + + await root.start(); + await kbnTestServer.request.get(root, '/route').expect(200, { authenticated: false }); + }); + }); +}); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 6c54067435405..03324dc6c722f 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -114,19 +114,30 @@ describe('Options', () => { }); }); - it('User with invalid credentials cannot access a route', async () => { - const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); + it('User with invalid credentials can access a route', async () => { + const { server: innerServer, createRouter, registerAuth, auth } = await server.setup( + setupDeps + ); const router = createRouter('/'); registerAuth((req, res, toolkit) => res.unauthorized()); router.get( { path: '/', validate: false, options: { authRequired: 'optional' } }, - (context, req, res) => res.ok({ body: 'ok' }) + (context, req, res) => + res.ok({ + body: { + httpAuthIsAuthenticated: auth.isAuthenticated(req), + requestIsAuthenticated: req.auth.isAuthenticated, + }, + }) ); await server.start(); - await supertest(innerServer.listener).get('/').expect(401); + await supertest(innerServer.listener).get('/').expect(200, { + httpAuthIsAuthenticated: false, + requestIsAuthenticated: false, + }); }); it('does not redirect user and allows access to a resource', async () => { @@ -900,7 +911,7 @@ describe('Response factory', () => { return res.ok({ body: 'value', headers: { - etag: '1234', + age: '42', }, }); }); @@ -910,7 +921,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toEqual('value'); - expect(result.header.etag).toBe('1234'); + expect(result.header.age).toBe('42'); }); it('supports configuring non-standard headers', async () => { @@ -921,7 +932,7 @@ describe('Response factory', () => { return res.ok({ body: 'value', headers: { - etag: '1234', + age: '42', 'x-kibana': 'key', }, }); @@ -932,7 +943,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(200); expect(result.text).toEqual('value'); - expect(result.header.etag).toBe('1234'); + expect(result.header.age).toBe('42'); expect(result.header['x-kibana']).toBe('key'); }); @@ -944,7 +955,7 @@ describe('Response factory', () => { return res.ok({ body: 'value', headers: { - ETag: '1234', + AgE: '42', }, }); }); @@ -953,7 +964,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(200); - expect(result.header.etag).toBe('1234'); + expect(result.header.age).toBe('42'); }); it('accept array of headers', async () => { @@ -1776,3 +1787,55 @@ describe('Response factory', () => { }); }); }); + +describe('ETag', () => { + it('returns the `etag` header', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + + const router = createRouter(''); + router.get( + { + path: '/route', + validate: false, + }, + (context, req, res) => + res.ok({ + body: { foo: 'bar' }, + headers: { + etag: 'etag-1', + }, + }) + ); + + await server.start(); + const response = await supertest(innerServer.listener) + .get('/route') + .expect(200, { foo: 'bar' }); + expect(response.get('etag')).toEqual('"etag-1"'); + }); + + it('returns a 304 when the etag value matches', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + + const router = createRouter(''); + router.get( + { + path: '/route', + validate: false, + }, + (context, req, res) => + res.ok({ + body: { foo: 'bar' }, + headers: { + etag: 'etag-1', + }, + }) + ); + + await server.start(); + await supertest(innerServer.listener) + .get('/route') + .set('If-None-Match', '"etag-1"') + .expect(304, ''); + }); +}); diff --git a/src/core/server/http/lifecycle/auth.ts b/src/core/server/http/lifecycle/auth.ts index c4c872bb6f75f..167cf0747b4c1 100644 --- a/src/core/server/http/lifecycle/auth.ts +++ b/src/core/server/http/lifecycle/auth.ts @@ -123,7 +123,7 @@ export interface AuthToolkit { authenticated: (data?: AuthResultParams) => AuthResult; /** * User has no credentials. - * Allows user to access a resource when authRequired: 'optional' + * Allows user to access a resource when authRequired is 'optional' * Rejects a request when authRequired: true * */ notHandled: () => AuthResult; diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index 94352b205d129..15c29e261c30b 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -28,6 +28,7 @@ function setHeaders(response: HapiResponseObject, headers: Record) { + const etagHeader = Object.keys(headers).find((header) => header.toLowerCase() === 'etag'); + if (etagHeader) { + response.etag(headers[etagHeader] as string); + } +} diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index bcac1bee67e58..77b40ca5995bb 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -108,8 +108,8 @@ export interface RouteConfigOptions { * Defines authentication mode for a route: * - true. A user has to have valid credentials to access a resource * - false. A user can access a resource without any credentials. - * - 'optional'. A user can access a resource if has valid credentials or no credentials at all. - * Can be useful when we grant access to a resource but want to identify a user if possible. + * - 'optional'. A user can access a resource, and will be authenticated if provided credentials are valid. + * Can be useful when we grant access to a resource but want to identify a user if possible. * * Defaults to `true` if an auth mechanism is registered. */ diff --git a/src/legacy/server/config/complete.js b/src/core/server/http_resources/get_apm_config.test.mocks.ts similarity index 62% rename from src/legacy/server/config/complete.js rename to src/core/server/http_resources/get_apm_config.test.mocks.ts index 5d3b2e55288bb..635e93913c222 100644 --- a/src/legacy/server/config/complete.js +++ b/src/core/server/http_resources/get_apm_config.test.mocks.ts @@ -6,8 +6,10 @@ * Side Public License, v 1. */ -export default function (kbnServer, server) { - server.decorate('server', 'config', function () { - return kbnServer.config; - }); -} +export const getConfigMock = jest.fn(); +jest.doMock('../../../apm', () => ({ + getConfig: getConfigMock, +})); + +export const agentMock = {} as Record; +jest.doMock('elastic-apm-node', () => agentMock); diff --git a/src/core/server/http_resources/get_apm_config.test.ts b/src/core/server/http_resources/get_apm_config.test.ts new file mode 100644 index 0000000000000..6ef68494ced07 --- /dev/null +++ b/src/core/server/http_resources/get_apm_config.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getConfigMock, agentMock } from './get_apm_config.test.mocks'; +import { getApmConfig } from './get_apm_config'; + +const defaultApmConfig = { + active: true, + someConfigProp: 'value', +}; + +describe('getApmConfig', () => { + beforeEach(() => { + getConfigMock.mockReturnValue(defaultApmConfig); + }); + + afterEach(() => { + getConfigMock.mockReset(); + agentMock.currentTransaction = null; + }); + + it('returns null if apm is disabled', () => { + getConfigMock.mockReturnValue({ + active: false, + }); + expect(getApmConfig('/path')).toBeNull(); + + getConfigMock.mockReturnValue(undefined); + expect(getApmConfig('/path')).toBeNull(); + }); + + it('calls `getConfig` with the correct parameters', () => { + getApmConfig('/path'); + + expect(getConfigMock).toHaveBeenCalledWith('kibana-frontend'); + }); + + it('returns the configuration from the `getConfig` call', () => { + const config = getApmConfig('/path'); + + expect(config).toEqual(expect.objectContaining(defaultApmConfig)); + }); + + it('returns the requestPath as `pageLoadTransactionName`', () => { + const config = getApmConfig('/some-other-path'); + + expect(config).toEqual( + expect.objectContaining({ + pageLoadTransactionName: '/some-other-path', + }) + ); + }); + + it('enhance the configuration with values from the current server-side transaction', () => { + agentMock.currentTransaction = { + sampled: 'sampled', + traceId: 'traceId', + ensureParentId: () => 'parentId', + } as any; + + const config = getApmConfig('/some-other-path'); + + expect(config).toEqual( + expect.objectContaining({ + pageLoadTraceId: 'traceId', + pageLoadSampled: 'sampled', + pageLoadSpanId: 'parentId', + }) + ); + }); +}); diff --git a/src/legacy/ui/apm/index.js b/src/core/server/http_resources/get_apm_config.ts similarity index 53% rename from src/legacy/ui/apm/index.js rename to src/core/server/http_resources/get_apm_config.ts index fc1f402128f56..0b0eb7426f863 100644 --- a/src/legacy/ui/apm/index.js +++ b/src/core/server/http_resources/get_apm_config.ts @@ -6,36 +6,34 @@ * Side Public License, v 1. */ -import { getConfig } from '../../../apm'; import agent from 'elastic-apm-node'; +// @ts-expect-error apm module is a js file outside of core (need to split APM/rum configuration) +import { getConfig } from '../../../apm'; -const apmEnabled = getConfig()?.active; - -export function getApmConfig(requestPath) { - if (!apmEnabled) { +export const getApmConfig = (requestPath: string) => { + const baseConfig = getConfig('kibana-frontend'); + if (!baseConfig?.active) { return null; } - const config = { - ...getConfig('kibana-frontend'), + + const config: Record = { + ...baseConfig, pageLoadTransactionName: requestPath, }; - /** - * Get current active backend transaction to make distrubuted tracing - * work for rendering the app - */ + // Get current active backend transaction to make distributed tracing + // work for rendering the app const backendTransaction = agent.currentTransaction; if (backendTransaction) { - const { sampled, traceId } = backendTransaction; + const { sampled, traceId } = backendTransaction as any; return { ...config, - ...{ - pageLoadTraceId: traceId, - pageLoadSampled: sampled, - pageLoadSpanId: backendTransaction.ensureParentId(), - }, + pageLoadTraceId: traceId, + pageLoadSampled: sampled, + pageLoadSpanId: backendTransaction.ensureParentId(), }; } + return config; -} +}; diff --git a/src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js b/src/core/server/http_resources/http_resources_service.test.mocks.ts similarity index 74% rename from src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js rename to src/core/server/http_resources/http_resources_service.test.mocks.ts index 1a953be530eb7..0d0c637a768df 100644 --- a/src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js +++ b/src/core/server/http_resources/http_resources_service.test.mocks.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ -import React from 'react'; +export const getApmConfigMock = jest.fn(); -export const VisDataContext = React.createContext({}); +jest.doMock('./get_apm_config', () => ({ + getApmConfig: getApmConfigMock, +})); diff --git a/src/core/server/http_resources/http_resources_service.test.ts b/src/core/server/http_resources/http_resources_service.test.ts index 82c0763084d40..8b24e05fc5bf4 100644 --- a/src/core/server/http_resources/http_resources_service.test.ts +++ b/src/core/server/http_resources/http_resources_service.test.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { getApmConfigMock } from './http_resources_service.test.mocks'; + import { IRouter, RouteConfig } from '../http'; import { coreMock } from '../mocks'; @@ -24,6 +26,12 @@ describe('HttpResources service', () => { let router: jest.Mocked; const kibanaRequest = httpServerMock.createKibanaRequest(); const context = { core: coreMock.createRequestHandlerContext() }; + const apmConfig = { mockApmConfig: true }; + + beforeEach(() => { + getApmConfigMock.mockReturnValue(apmConfig); + }); + describe('#createRegistrar', () => { beforeEach(() => { setupDeps = { @@ -52,6 +60,9 @@ describe('HttpResources service', () => { context.core.uiSettings.client, { includeUserSettings: true, + vars: { + apmConfig, + }, } ); }); @@ -101,6 +112,9 @@ describe('HttpResources service', () => { context.core.uiSettings.client, { includeUserSettings: false, + vars: { + apmConfig, + }, } ); }); diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts index a1cb9f5f6c07a..44caa456e9955 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/src/core/server/http_resources/http_resources_service.ts @@ -29,6 +29,7 @@ import { HttpResourcesRequestHandler, HttpResourcesServiceToolkit, } from './types'; +import { getApmConfig } from './get_apm_config'; export interface SetupDeps { http: InternalHttpServiceSetup; @@ -37,6 +38,7 @@ export interface SetupDeps { export class HttpResourcesService implements CoreService { private readonly logger: Logger; + constructor(core: CoreContext) { this.logger = core.logger.get('http-resources'); } @@ -49,6 +51,7 @@ export class HttpResourcesService implements CoreService( registerOnPostAuth: deps.http.registerOnPostAuth, registerOnPreResponse: deps.http.registerOnPreResponse, basePath: deps.http.basePath, - auth: { get: deps.http.auth.get, isAuthenticated: deps.http.auth.isAuthenticated }, + auth: { + get: deps.http.auth.get, + isAuthenticated: deps.http.auth.isAuthenticated, + }, csp: deps.http.csp, getServerInfo: deps.http.getServerInfo, }, diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap similarity index 65% rename from src/legacy/ui/ui_render/bootstrap/template.js.hbs rename to src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap index 89c7125b39e36..fde12088b7735 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/core/server/rendering/bootstrap/__snapshots__/render_template.test.ts.snap @@ -1,15 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renderTemplate interpolates templateData into string template 1`] = ` +" +function kbnBundlesLoader() { + var modules = {}; + + function has(prop) { + return Object.prototype.hasOwnProperty.call(modules, prop); + } + + function define(key, bundleRequire, bundleModuleKey) { + if (has(key)) { + throw new Error('__kbnBundles__ already has a module defined for \\"' + key + '\\"'); + } + + modules[key] = { + bundleRequire, + bundleModuleKey, + }; + } + + function get(key) { + if (!has(key)) { + throw new Error('__kbnBundles__ does not have a module defined for \\"' + key + '\\"'); + } + + return modules[key].bundleRequire(modules[key].bundleModuleKey); + } + + return { has: has, define: define, get: get }; +} + var kbnCsp = JSON.parse(document.querySelector('kbn-csp').getAttribute('data')); window.__kbnStrictCsp__ = kbnCsp.strictCsp; -window.__kbnThemeTag__ = "{{themeTag}}"; -window.__kbnPublicPath__ = {{publicPathMap}}; -window.__kbnBundles__ = {{kbnBundlesLoaderSource}} +window.__kbnThemeTag__ = \\"v7\\"; +window.__kbnPublicPath__ = {\\"foo\\": \\"bar\\"}; +window.__kbnBundles__ = kbnBundlesLoader(); if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); legacyBrowserError.style.display = 'flex'; } else { if (!window.__kbnCspNotEnforced__ && window.console) { - window.console.log("^ A single error about an inline script not firing due to content security policy is expected!"); + window.console.log(\\"^ A single error about an inline script not firing due to content security policy is expected!\\"); } var loadingMessage = document.getElementById('kbn_loading_message'); loadingMessage.style.display = 'flex'; @@ -30,7 +63,7 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { document.body.innerHTML = err.outerHTML; } - var stylesheetTarget = document.querySelector('head meta[name="add-styles-here"]') + var stylesheetTarget = document.querySelector('head meta[name=\\"add-styles-here\\"]') function loadStyleSheet(url, cb) { var dom = document.createElement('link'); dom.rel = 'stylesheet'; @@ -41,10 +74,9 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { document.head.insertBefore(dom, stylesheetTarget); } - var scriptsTarget = document.querySelector('head meta[name="add-scripts-here"]') + var scriptsTarget = document.querySelector('head meta[name=\\"add-scripts-here\\"]') function loadScript(url, cb) { var dom = document.createElement('script'); - {{!-- NOTE: async = false is used to trigger async-download/ordered-execution as outlined here: https://www.html5rocks.com/en/tutorials/speed/script-loading/ --}} dom.async = false; dom.src = url; dom.addEventListener('error', failure); @@ -73,17 +105,11 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { } load([ - {{#each jsDependencyPaths}} - '{{this}}', - {{/each}} + '/js-1','/js-2' ], function () { __kbnBundles__.get('entry/core/public').__kbnBootstrap__(); - - load([ - {{#each styleSheetPaths}} - '{{this}}', - {{/each}} - ]); }); } } + " +`; diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.test.mocks.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.mocks.ts new file mode 100644 index 0000000000000..2b3a6788a465d --- /dev/null +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.mocks.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const renderTemplateMock = jest.fn(); +jest.doMock('./render_template', () => ({ + renderTemplate: renderTemplateMock, +})); + +export const getThemeTagMock = jest.fn(); +jest.doMock('./get_theme_tag', () => ({ + getThemeTag: getThemeTagMock, +})); + +export const getPluginsBundlePathsMock = jest.fn(); +jest.doMock('./get_plugin_bundle_paths', () => ({ + getPluginsBundlePaths: getPluginsBundlePathsMock, +})); + +export const getJsDependencyPathsMock = jest.fn(); +jest.doMock('./get_js_dependency_paths', () => ({ + getJsDependencyPaths: getJsDependencyPathsMock, +})); diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts new file mode 100644 index 0000000000000..3803d38a968c1 --- /dev/null +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts @@ -0,0 +1,241 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + renderTemplateMock, + getPluginsBundlePathsMock, + getThemeTagMock, + getJsDependencyPathsMock, +} from './bootstrap_renderer.test.mocks'; + +import { PackageInfo } from '@kbn/config'; +import { UiPlugins } from '../../plugins'; +import { httpServiceMock } from '../../http/http_service.mock'; +import { httpServerMock } from '../../http/http_server.mocks'; +import { AuthStatus } from '../../http'; +import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; +import { bootstrapRendererFactory, BootstrapRenderer } from './bootstrap_renderer'; + +const createPackageInfo = (parts: Partial = {}): PackageInfo => ({ + branch: 'master', + buildNum: 42, + buildSha: 'buildSha', + dist: false, + version: '8.0.0', + ...parts, +}); + +const createUiPlugins = (): UiPlugins => ({ + public: new Map(), + internal: new Map(), + browserConfigs: new Map(), +}); + +describe('bootstrapRenderer', () => { + let auth: ReturnType; + let uiSettingsClient: ReturnType; + let renderer: BootstrapRenderer; + let uiPlugins: UiPlugins; + let packageInfo: PackageInfo; + + beforeEach(() => { + auth = httpServiceMock.createAuth(); + uiSettingsClient = uiSettingsServiceMock.createClient(); + uiPlugins = createUiPlugins(); + packageInfo = createPackageInfo(); + + getThemeTagMock.mockReturnValue('v8light'); + getPluginsBundlePathsMock.mockReturnValue(new Map()); + renderTemplateMock.mockReturnValue('__rendered__'); + getJsDependencyPathsMock.mockReturnValue([]); + + renderer = bootstrapRendererFactory({ + auth, + packageInfo, + uiPlugins, + serverBasePath: '/base-path', + }); + }); + + afterEach(() => { + getThemeTagMock.mockReset(); + getPluginsBundlePathsMock.mockReset(); + renderTemplateMock.mockReset(); + getJsDependencyPathsMock.mockReset(); + }); + + describe('when the auth status is `authenticated`', () => { + beforeEach(() => { + auth.get.mockReturnValue({ + status: 'authenticated' as AuthStatus, + state: {}, + }); + }); + + it('calls uiSettingsClient.get with the correct parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(uiSettingsClient.get).toHaveBeenCalledTimes(2); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:darkMode'); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:version'); + }); + + it('calls getThemeTag with the correct parameters', async () => { + uiSettingsClient.get.mockImplementation((settingName) => { + return Promise.resolve(settingName === 'theme:darkMode' ? true : 'v8'); + }); + + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getThemeTagMock).toHaveBeenCalledTimes(1); + expect(getThemeTagMock).toHaveBeenCalledWith({ + themeVersion: 'v8', + darkMode: true, + }); + }); + }); + + describe('when the auth status is `unknown`', () => { + beforeEach(() => { + auth.get.mockReturnValue({ + status: 'unknown' as AuthStatus, + state: {}, + }); + }); + + it('calls uiSettingsClient.get with the correct parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(uiSettingsClient.get).toHaveBeenCalledTimes(2); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:darkMode'); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:version'); + }); + + it('calls getThemeTag with the correct parameters', async () => { + uiSettingsClient.get.mockImplementation((settingName) => { + return Promise.resolve(settingName === 'theme:darkMode' ? true : 'v8'); + }); + + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getThemeTagMock).toHaveBeenCalledTimes(1); + expect(getThemeTagMock).toHaveBeenCalledWith({ + themeVersion: 'v8', + darkMode: true, + }); + }); + }); + + describe('when the auth status is `unauthenticated`', () => { + beforeEach(() => { + auth.get.mockReturnValue({ + status: 'unauthenticated' as AuthStatus, + state: {}, + }); + }); + + it('does not call uiSettingsClient.get', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(uiSettingsClient.get).not.toHaveBeenCalled(); + }); + + it('calls getThemeTag with the default parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getThemeTagMock).toHaveBeenCalledTimes(1); + expect(getThemeTagMock).toHaveBeenCalledWith({ + themeVersion: 'v7', + darkMode: false, + }); + }); + }); + + it('calls getPluginsBundlePaths with the correct parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getPluginsBundlePathsMock).toHaveBeenCalledTimes(1); + expect(getPluginsBundlePathsMock).toHaveBeenCalledWith({ + uiPlugins, + regularBundlePath: '/base-path/42/bundles', + }); + }); + + // here + it('calls getJsDependencyPaths with the correct parameters', async () => { + const pluginsBundlePaths = new Map(); + + getPluginsBundlePathsMock.mockReturnValue(pluginsBundlePaths); + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getJsDependencyPathsMock).toHaveBeenCalledTimes(1); + expect(getJsDependencyPathsMock).toHaveBeenCalledWith( + '/base-path/42/bundles', + pluginsBundlePaths + ); + }); + + it('calls renderTemplate with the correct parameters', async () => { + getThemeTagMock.mockReturnValue('customThemeTag'); + getJsDependencyPathsMock.mockReturnValue(['path-1', 'path-2']); + + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(renderTemplateMock).toHaveBeenCalledTimes(1); + expect(renderTemplateMock).toHaveBeenCalledWith({ + themeTag: 'customThemeTag', + jsDependencyPaths: ['path-1', 'path-2'], + publicPathMap: expect.any(String), + }); + }); +}); diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.ts new file mode 100644 index 0000000000000..cff593e5c5aa9 --- /dev/null +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createHash } from 'crypto'; +import { PackageInfo } from '@kbn/config'; +import { UiPlugins } from '../../plugins'; +import { IUiSettingsClient } from '../../ui_settings'; +import { HttpAuth, KibanaRequest } from '../../http'; +import { getPluginsBundlePaths } from './get_plugin_bundle_paths'; +import { getJsDependencyPaths } from './get_js_dependency_paths'; +import { getThemeTag } from './get_theme_tag'; +import { renderTemplate } from './render_template'; + +export type BootstrapRendererFactory = (factoryOptions: FactoryOptions) => BootstrapRenderer; +export type BootstrapRenderer = (options: RenderedOptions) => Promise; + +interface FactoryOptions { + serverBasePath: string; + packageInfo: PackageInfo; + uiPlugins: UiPlugins; + auth: HttpAuth; +} + +interface RenderedOptions { + request: KibanaRequest; + uiSettingsClient: IUiSettingsClient; +} + +interface RendererResult { + body: string; + etag: string; +} + +export const bootstrapRendererFactory: BootstrapRendererFactory = ({ + packageInfo, + serverBasePath, + uiPlugins, + auth, +}) => { + const isAuthenticated = (request: KibanaRequest) => { + const { status: authStatus } = auth.get(request); + // status is 'unknown' when auth is disabled. we just need to not be `unauthenticated` here. + return authStatus !== 'unauthenticated'; + }; + + return async function bootstrapRenderer({ uiSettingsClient, request }) { + let darkMode = false; + let themeVersion = 'v7'; + + try { + const authenticated = isAuthenticated(request); + darkMode = authenticated ? await uiSettingsClient.get('theme:darkMode') : false; + themeVersion = authenticated ? await uiSettingsClient.get('theme:version') : 'v7'; + } catch (e) { + // just use the default values in case of connectivity issues with ES + } + + const themeTag = getThemeTag({ + themeVersion, + darkMode, + }); + const buildHash = packageInfo.buildNum; + const regularBundlePath = `${serverBasePath}/${buildHash}/bundles`; + + const bundlePaths = getPluginsBundlePaths({ + uiPlugins, + regularBundlePath, + }); + + const jsDependencyPaths = getJsDependencyPaths(regularBundlePath, bundlePaths); + + // These paths should align with the bundle routes configured in + // src/optimize/bundles_route/bundles_route.ts + const publicPathMap = JSON.stringify({ + core: `${regularBundlePath}/core/`, + 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`, + ...Object.fromEntries( + [...bundlePaths.entries()].map(([pluginId, plugin]) => [pluginId, plugin.publicPath]) + ), + }); + + const body = renderTemplate({ + themeTag, + jsDependencyPaths, + publicPathMap, + }); + + const hash = createHash('sha1'); + hash.update(body); + const etag = hash.digest('hex'); + + return { + body, + etag, + }; + }; +}; diff --git a/src/core/server/rendering/bootstrap/get_js_dependency_paths.test.ts b/src/core/server/rendering/bootstrap/get_js_dependency_paths.test.ts new file mode 100644 index 0000000000000..964e64186459f --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_js_dependency_paths.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getJsDependencyPaths } from './get_js_dependency_paths'; +import type { PluginInfo } from './get_plugin_bundle_paths'; + +describe('getJsDependencyPaths', () => { + it('returns the correct list of paths', () => { + const bundlePaths = new Map(); + bundlePaths.set('plugin1', { + bundlePath: 'plugin1/bundle-path.js', + publicPath: 'plugin1/public-path', + }); + bundlePaths.set('plugin2', { + bundlePath: 'plugin2/bundle-path.js', + publicPath: 'plugin2/public-path', + }); + + expect(getJsDependencyPaths('/regular-bundle-path', bundlePaths)).toEqual([ + '/regular-bundle-path/kbn-ui-shared-deps/kbn-ui-shared-deps.@elastic.js', + '/regular-bundle-path/kbn-ui-shared-deps/kbn-ui-shared-deps.js', + '/regular-bundle-path/core/core.entry.js', + 'plugin1/bundle-path.js', + 'plugin2/bundle-path.js', + ]); + }); +}); diff --git a/src/core/server/rendering/bootstrap/get_js_dependency_paths.ts b/src/core/server/rendering/bootstrap/get_js_dependency_paths.ts new file mode 100644 index 0000000000000..72c7b84fbee2e --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_js_dependency_paths.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import type { PluginInfo } from './get_plugin_bundle_paths'; + +export const getJsDependencyPaths = ( + regularBundlePath: string, + bundlePaths: Map +) => { + return [ + ...UiSharedDeps.jsDepFilenames.map( + (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` + ), + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, + `${regularBundlePath}/core/core.entry.js`, + ...[...bundlePaths.values()].map((plugin) => plugin.bundlePath), + ]; +}; diff --git a/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.ts b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.ts new file mode 100644 index 0000000000000..ea3843884df31 --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UiPlugins } from '../../plugins'; +import { getPluginsBundlePaths } from './get_plugin_bundle_paths'; + +const createUiPlugins = (pluginDeps: Record) => { + const uiPlugins: UiPlugins = { + internal: new Map(), + public: new Map(), + browserConfigs: new Map(), + }; + + Object.entries(pluginDeps).forEach(([pluginId, deps]) => { + uiPlugins.internal.set(pluginId, { + requiredBundles: deps, + publicTargetDir: '', + publicAssetsDir: '', + } as any); + uiPlugins.public.set(pluginId, { + id: pluginId, + configPath: 'config-path', + optionalPlugins: [], + requiredPlugins: [], + requiredBundles: deps, + }); + }); + + return uiPlugins; +}; + +describe('getPluginsBundlePaths', () => { + it('returns an entry for each plugin and their bundle dependencies', () => { + const pluginBundlePaths = getPluginsBundlePaths({ + regularBundlePath: '/regular-bundle-path', + uiPlugins: createUiPlugins({ + a: ['b', 'c'], + b: ['d'], + }), + }); + + expect([...pluginBundlePaths.keys()].sort()).toEqual(['a', 'b', 'c', 'd']); + }); + + it('returns correct paths for each bundle', () => { + const pluginBundlePaths = getPluginsBundlePaths({ + regularBundlePath: '/regular-bundle-path', + uiPlugins: createUiPlugins({ + a: ['b'], + }), + }); + + expect(pluginBundlePaths.get('a')).toEqual({ + bundlePath: '/regular-bundle-path/plugin/a/a.plugin.js', + publicPath: '/regular-bundle-path/plugin/a/', + }); + + expect(pluginBundlePaths.get('b')).toEqual({ + bundlePath: '/regular-bundle-path/plugin/b/b.plugin.js', + publicPath: '/regular-bundle-path/plugin/b/', + }); + }); +}); diff --git a/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts new file mode 100644 index 0000000000000..c8291b2720a92 --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UiPlugins } from '../../plugins'; + +export interface PluginInfo { + publicPath: string; + bundlePath: string; +} + +export const getPluginsBundlePaths = ({ + uiPlugins, + regularBundlePath, +}: { + uiPlugins: UiPlugins; + regularBundlePath: string; +}) => { + const pluginBundlePaths = new Map(); + const pluginsToProcess = [...uiPlugins.public.keys()]; + + while (pluginsToProcess.length > 0) { + const pluginId = pluginsToProcess.pop() as string; + pluginBundlePaths.set(pluginId, { + publicPath: `${regularBundlePath}/plugin/${pluginId}/`, + bundlePath: `${regularBundlePath}/plugin/${pluginId}/${pluginId}.plugin.js`, + }); + + const pluginBundleIds = uiPlugins.internal.get(pluginId)?.requiredBundles ?? []; + pluginBundleIds.forEach((bundleId) => { + if (!pluginBundlePaths.has(bundleId)) { + pluginsToProcess.push(bundleId); + } + }); + } + + return pluginBundlePaths; +}; diff --git a/src/core/server/rendering/bootstrap/get_theme_tag.test.ts b/src/core/server/rendering/bootstrap/get_theme_tag.test.ts new file mode 100644 index 0000000000000..6fe56d425c13d --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_theme_tag.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getThemeTag } from './get_theme_tag'; + +describe('getThemeTag', () => { + it('returns the correct value for version:v7 and darkMode:false', () => { + expect( + getThemeTag({ + themeVersion: 'v7', + darkMode: false, + }) + ).toEqual('v7light'); + }); + it('returns the correct value for version:v7 and darkMode:true', () => { + expect( + getThemeTag({ + themeVersion: 'v7', + darkMode: true, + }) + ).toEqual('v7dark'); + }); + it('returns the correct value for version:v8 and darkMode:false', () => { + expect( + getThemeTag({ + themeVersion: 'v8', + darkMode: false, + }) + ).toEqual('v8light'); + }); + it('returns the correct value for version:v8 and darkMode:true', () => { + expect( + getThemeTag({ + themeVersion: 'v8', + darkMode: true, + }) + ).toEqual('v8dark'); + }); +}); diff --git a/src/legacy/server/warnings/index.js b/src/core/server/rendering/bootstrap/get_theme_tag.ts similarity index 51% rename from src/legacy/server/warnings/index.js rename to src/core/server/rendering/bootstrap/get_theme_tag.ts index d08b9c4219744..29701d9e9c78e 100644 --- a/src/legacy/server/warnings/index.js +++ b/src/core/server/rendering/bootstrap/get_theme_tag.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -export default function (kbnServer, server) { - process.on('warning', (warning) => { - // deprecation warnings do no reflect a current problem for - // the user and therefor should be filtered out. - if (warning.name === 'DeprecationWarning') { - return; - } - - server.log(['warning', 'process'], warning); - }); -} +/** + * Computes the themeTag that will be used on the client-side as `__kbnThemeTag__` + * @see `packages/kbn-ui-shared-deps/theme.ts` + */ +export const getThemeTag = ({ + themeVersion, + darkMode, +}: { + themeVersion: string; + darkMode: boolean; +}) => { + return `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`; +}; diff --git a/src/legacy/ui/ui_mixin.js b/src/core/server/rendering/bootstrap/index.ts similarity index 72% rename from src/legacy/ui/ui_mixin.js rename to src/core/server/rendering/bootstrap/index.ts index 0dcc86adc5886..42f2c0508ac31 100644 --- a/src/legacy/ui/ui_mixin.js +++ b/src/core/server/rendering/bootstrap/index.ts @@ -6,8 +6,5 @@ * Side Public License, v 1. */ -import { uiRenderMixin } from './ui_render'; - -export async function uiMixin(kbnServer) { - await kbnServer.mixin(uiRenderMixin); -} +export { registerBootstrapRoute } from './register_bootstrap_route'; +export { bootstrapRendererFactory } from './bootstrap_renderer'; diff --git a/src/core/server/rendering/bootstrap/register_bootstrap_route.ts b/src/core/server/rendering/bootstrap/register_bootstrap_route.ts new file mode 100644 index 0000000000000..5644b44f3508b --- /dev/null +++ b/src/core/server/rendering/bootstrap/register_bootstrap_route.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IRouter } from '../../http'; +import type { BootstrapRenderer } from './bootstrap_renderer'; + +export const registerBootstrapRoute = ({ + router, + renderer, +}: { + router: IRouter; + renderer: BootstrapRenderer; +}) => { + router.get( + { + path: '/bootstrap.js', + options: { + authRequired: 'optional', + tags: ['api'], + }, + validate: false, + }, + async (ctx, req, res) => { + const uiSettingsClient = ctx.core.uiSettings.client; + const { body, etag } = await renderer({ uiSettingsClient, request: req }); + + return res.ok({ + body, + headers: { + etag, + 'content-type': 'application/javascript', + 'cache-control': 'must-revalidate', + }, + }); + } + ); +}; diff --git a/src/core/server/rendering/bootstrap/render_template.test.ts b/src/core/server/rendering/bootstrap/render_template.test.ts new file mode 100644 index 0000000000000..b061e8c31065c --- /dev/null +++ b/src/core/server/rendering/bootstrap/render_template.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { renderTemplate } from './render_template'; + +function mockParams() { + return { + themeTag: 'v7', + jsDependencyPaths: ['/js-1', '/js-2'], + styleSheetPaths: ['/style-1', '/style-2'], + publicPathMap: '{"foo": "bar"}', + }; +} + +describe('renderTemplate', () => { + test('resolves to a string', async () => { + const content = await renderTemplate(mockParams()); + expect(typeof content).toEqual('string'); + }); + + test('interpolates templateData into string template', async () => { + const content = await renderTemplate(mockParams()); + expect(content).toMatchSnapshot(); + }); +}); diff --git a/src/core/server/rendering/bootstrap/render_template.ts b/src/core/server/rendering/bootstrap/render_template.ts new file mode 100644 index 0000000000000..14127017b1c0f --- /dev/null +++ b/src/core/server/rendering/bootstrap/render_template.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface BootstrapTemplateData { + themeTag: string; + jsDependencyPaths: string[]; + publicPathMap: string; +} + +export const renderTemplate = ({ + themeTag, + jsDependencyPaths, + publicPathMap, +}: BootstrapTemplateData) => { + return ` +function kbnBundlesLoader() { + var modules = {}; + + function has(prop) { + return Object.prototype.hasOwnProperty.call(modules, prop); + } + + function define(key, bundleRequire, bundleModuleKey) { + if (has(key)) { + throw new Error('__kbnBundles__ already has a module defined for "' + key + '"'); + } + + modules[key] = { + bundleRequire, + bundleModuleKey, + }; + } + + function get(key) { + if (!has(key)) { + throw new Error('__kbnBundles__ does not have a module defined for "' + key + '"'); + } + + return modules[key].bundleRequire(modules[key].bundleModuleKey); + } + + return { has: has, define: define, get: get }; +} + +var kbnCsp = JSON.parse(document.querySelector('kbn-csp').getAttribute('data')); +window.__kbnStrictCsp__ = kbnCsp.strictCsp; +window.__kbnThemeTag__ = "${themeTag}"; +window.__kbnPublicPath__ = ${publicPathMap}; +window.__kbnBundles__ = kbnBundlesLoader(); + +if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { + var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); + legacyBrowserError.style.display = 'flex'; +} else { + if (!window.__kbnCspNotEnforced__ && window.console) { + window.console.log("^ A single error about an inline script not firing due to content security policy is expected!"); + } + var loadingMessage = document.getElementById('kbn_loading_message'); + loadingMessage.style.display = 'flex'; + + window.onload = function () { + function failure() { + // make subsequent calls to failure() noop + failure = function () {}; + + var err = document.createElement('h1'); + err.style['color'] = 'white'; + err.style['font-family'] = 'monospace'; + err.style['text-align'] = 'center'; + err.style['background'] = '#F44336'; + err.style['padding'] = '25px'; + err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage; + + document.body.innerHTML = err.outerHTML; + } + + var stylesheetTarget = document.querySelector('head meta[name="add-styles-here"]') + function loadStyleSheet(url, cb) { + var dom = document.createElement('link'); + dom.rel = 'stylesheet'; + dom.type = 'text/css'; + dom.href = url; + dom.addEventListener('error', failure); + dom.addEventListener('load', cb); + document.head.insertBefore(dom, stylesheetTarget); + } + + var scriptsTarget = document.querySelector('head meta[name="add-scripts-here"]') + function loadScript(url, cb) { + var dom = document.createElement('script'); + dom.async = false; + dom.src = url; + dom.addEventListener('error', failure); + dom.addEventListener('load', cb); + document.head.insertBefore(dom, scriptsTarget); + } + + function load(urls, cb) { + var pending = urls.length; + urls.forEach(function (url) { + var innerCb = function () { + pending = pending - 1; + if (pending === 0 && typeof cb === 'function') { + cb(); + } + } + + if (typeof url !== 'string') { + load(url, innerCb); + } else if (url.slice(-4) === '.css') { + loadStyleSheet(url, innerCb); + } else { + loadScript(url, innerCb); + } + }); + } + + load([ + ${jsDependencyPaths.map((path) => `'${path}'`).join(',')} + ], function () { + __kbnBundles__.get('entry/core/public').__kbnBootstrap__(); + }); + } +} + `; +}; diff --git a/src/core/server/rendering/render_utils.test.ts b/src/core/server/rendering/render_utils.test.ts new file mode 100644 index 0000000000000..8794bbdf2f930 --- /dev/null +++ b/src/core/server/rendering/render_utils.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getStylesheetPaths } from './render_utils'; + +describe('getStylesheetPaths', () => { + describe('when darkMode is `true`', () => { + describe('when themeVersion is `v7`', () => { + it('returns the correct list', () => { + expect( + getStylesheetPaths({ + darkMode: true, + themeVersion: 'v7', + basePath: '/base-path', + buildNum: 9000, + }) + ).toMatchInlineSnapshot(` + Array [ + "/base-path/9000/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.css", + "/base-path/9000/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.v7.dark.css", + "/base-path/node_modules/@kbn/ui-framework/dist/kui_dark.css", + "/base-path/ui/legacy_dark_theme.css", + ] + `); + }); + }); + describe('when themeVersion is `v8`', () => { + it('returns the correct list', () => { + expect( + getStylesheetPaths({ + darkMode: true, + themeVersion: 'v8', + basePath: '/base-path', + buildNum: 17, + }) + ).toMatchInlineSnapshot(` + Array [ + "/base-path/17/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.css", + "/base-path/17/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.v8.dark.css", + "/base-path/node_modules/@kbn/ui-framework/dist/kui_dark.css", + "/base-path/ui/legacy_dark_theme.css", + ] + `); + }); + }); + }); + describe('when darkMode is `false`', () => { + describe('when themeVersion is `v7`', () => { + it('returns the correct list', () => { + expect( + getStylesheetPaths({ + darkMode: false, + themeVersion: 'v7', + basePath: '/base-path', + buildNum: 42, + }) + ).toMatchInlineSnapshot(` + Array [ + "/base-path/42/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.css", + "/base-path/42/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.v7.light.css", + "/base-path/node_modules/@kbn/ui-framework/dist/kui_light.css", + "/base-path/ui/legacy_light_theme.css", + ] + `); + }); + }); + describe('when themeVersion is `v8`', () => { + it('returns the correct list', () => { + expect( + getStylesheetPaths({ + darkMode: false, + themeVersion: 'v8', + basePath: '/base-path', + buildNum: 69, + }) + ).toMatchInlineSnapshot(` + Array [ + "/base-path/69/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.css", + "/base-path/69/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.v8.light.css", + "/base-path/node_modules/@kbn/ui-framework/dist/kui_light.css", + "/base-path/ui/legacy_light_theme.css", + ] + `); + }); + }); + }); +}); diff --git a/src/core/server/rendering/render_utils.ts b/src/core/server/rendering/render_utils.ts new file mode 100644 index 0000000000000..469b46c0e2bf7 --- /dev/null +++ b/src/core/server/rendering/render_utils.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import { PublicUiSettingsParams, UserProvidedValues } from '../ui_settings'; + +export const getSettingValue = ( + settingName: string, + settings: { + user?: Record>; + defaults: Readonly>; + }, + convert: (raw: unknown) => T +): T => { + const value = settings.user?.[settingName]?.userValue ?? settings.defaults[settingName].value; + return convert(value); +}; + +export const getStylesheetPaths = ({ + themeVersion, + darkMode, + basePath, + buildNum, +}: { + themeVersion: string; + darkMode: boolean; + buildNum: number; + basePath: string; +}) => { + const regularBundlePath = `${basePath}/${buildNum}/bundles`; + return [ + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, + ...(darkMode + ? [ + themeVersion === 'v7' + ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}` + : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`, + `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, + `${basePath}/ui/legacy_dark_theme.css`, + ] + : [ + themeVersion === 'v7' + ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}` + : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`, + `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, + `${basePath}/ui/legacy_light_theme.css`, + ]), + ]; +}; diff --git a/src/core/server/rendering/rendering_service.test.mocks.ts b/src/core/server/rendering/rendering_service.test.mocks.ts new file mode 100644 index 0000000000000..580b9ca90dfa1 --- /dev/null +++ b/src/core/server/rendering/rendering_service.test.mocks.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const bootstrapRendererMock = jest.fn(); +export const registerBootstrapRouteMock = jest.fn(); +export const bootstrapRendererFactoryMock = jest.fn(() => bootstrapRendererMock); + +jest.doMock('./bootstrap', () => ({ + registerBootstrapRoute: registerBootstrapRouteMock, + bootstrapRendererFactory: bootstrapRendererFactoryMock, +})); + +export const getSettingValueMock = jest.fn(); +export const getStylesheetPathsMock = jest.fn(); + +jest.doMock('./render_utils', () => ({ + getSettingValue: getSettingValueMock, + getStylesheetPaths: getStylesheetPathsMock, +})); diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index acc55631f5d6b..65df5cd6aa312 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -6,6 +6,13 @@ * Side Public License, v 1. */ +import { + registerBootstrapRouteMock, + bootstrapRendererMock, + getSettingValueMock, + getStylesheetPathsMock, +} from './rendering_service.test.mocks'; + import { load } from 'cheerio'; import { httpServerMock } from '../http/http_server.mocks'; @@ -42,9 +49,22 @@ describe('RenderingService', () => { beforeEach(() => { jest.clearAllMocks(); service = new RenderingService(mockRenderingServiceParams); + + getSettingValueMock.mockImplementation((settingName: string) => settingName); + getStylesheetPathsMock.mockReturnValue(['/style-1.css', '/style-2.css']); }); describe('setup()', () => { + it('calls `registerBootstrapRoute` with the correct parameters', async () => { + await service.setup(mockRenderingSetupDeps); + + expect(registerBootstrapRouteMock).toHaveBeenCalledTimes(1); + expect(registerBootstrapRouteMock).toHaveBeenCalledWith({ + router: expect.any(Object), + renderer: bootstrapRendererMock, + }); + }); + describe('render()', () => { let uiSettings: ReturnType; let render: InternalRenderingServiceSetup['render']; @@ -101,6 +121,28 @@ describe('RenderingService', () => { expect(data).toMatchSnapshot(INJECTED_METADATA); }); + + it('calls `getStylesheetPaths` with the correct parameters', async () => { + getSettingValueMock.mockImplementation((settingName: string) => { + if (settingName === 'theme:darkMode') { + return true; + } + if (settingName === 'theme:version') { + return 'v8'; + } + return settingName; + }); + + await render(createKibanaRequest(), uiSettings); + + expect(getStylesheetPathsMock).toHaveBeenCalledTimes(1); + expect(getStylesheetPathsMock).toHaveBeenCalledWith({ + darkMode: true, + themeVersion: 'v8', + basePath: '/mock-server-basepath', + buildNum: expect.any(Number), + }); + }); }); }); }); diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index c7e9a703d9ad4..0d41105f53ca7 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -20,6 +20,8 @@ import { InternalRenderingServiceSetup, RenderingMetadata, } from './types'; +import { registerBootstrapRoute, bootstrapRendererFactory } from './bootstrap'; +import { getSettingValue, getStylesheetPaths } from './render_utils'; /** @internal */ export class RenderingService { @@ -30,6 +32,16 @@ export class RenderingService { status, uiPlugins, }: RenderingSetupDeps): Promise { + const router = http.createRouter(''); + + const bootstrapRenderer = bootstrapRendererFactory({ + uiPlugins, + serverBasePath: http.basePath.serverBasePath, + packageInfo: this.coreContext.env.packageInfo, + auth: http.auth, + }); + registerBootstrapRoute({ router, renderer: bootstrapRenderer }); + return { render: async ( request, @@ -40,21 +52,32 @@ export class RenderingService { mode: this.coreContext.env.mode, packageInfo: this.coreContext.env.packageInfo, }; + const buildNum = env.packageInfo.buildNum; const basePath = http.basePath.get(request); const { serverBasePath, publicBaseUrl } = http.basePath; const settings = { defaults: uiSettings.getRegistered(), user: includeUserSettings ? await uiSettings.getUserProvided() : {}, }; + + const darkMode = getSettingValue('theme:darkMode', settings, Boolean); + const themeVersion = getSettingValue('theme:version', settings, String); + + const stylesheetPaths = getStylesheetPaths({ + darkMode, + themeVersion, + basePath: serverBasePath, + buildNum, + }); + const metadata: RenderingMetadata = { strictCsp: http.csp.strict, uiPublicUrl: `${basePath}/ui`, bootstrapScriptUrl: `${basePath}/bootstrap.js`, i18n: i18n.translate, locale: i18n.getLocale(), - darkMode: settings.user?.['theme:darkMode']?.userValue - ? Boolean(settings.user['theme:darkMode'].userValue) - : false, + darkMode, + stylesheetPaths, injectedMetadata: { version: env.packageInfo.version, buildNumber: env.packageInfo.buildNum, @@ -74,7 +97,7 @@ export class RenderingService { [...uiPlugins.public].map(async ([id, plugin]) => ({ id, plugin, - config: await this.getUiConfig(uiPlugins, id), + config: await getUiConfig(uiPlugins, id), })) ), legacyMetadata: { @@ -89,10 +112,9 @@ export class RenderingService { } public async stop() {} - - private async getUiConfig(uiPlugins: UiPlugins, pluginId: string) { - const browserConfig = uiPlugins.browserConfigs.get(pluginId); - - return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record; - } } + +const getUiConfig = async (uiPlugins: UiPlugins, pluginId: string) => { + const browserConfig = uiPlugins.browserConfigs.get(pluginId); + return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record; +}; diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 6654b6b6cfbbd..fb92567db73f9 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -24,6 +24,7 @@ export interface RenderingMetadata { i18n: typeof i18n.translate; locale: string; darkMode: boolean; + stylesheetPaths: string[]; injectedMetadata: { version: string; buildNumber: number; diff --git a/src/core/server/rendering/views/logo.tsx b/src/core/server/rendering/views/logo.tsx new file mode 100644 index 0000000000000..34a892c850e2e --- /dev/null +++ b/src/core/server/rendering/views/logo.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; + +export const Logo: FC = () => ( + + + + + + + + + + +); diff --git a/src/core/server/rendering/views/styles.tsx b/src/core/server/rendering/views/styles.tsx index 7acc9437b2caa..018ee2d48d8c7 100644 --- a/src/core/server/rendering/views/styles.tsx +++ b/src/core/server/rendering/views/styles.tsx @@ -8,15 +8,25 @@ /* eslint-disable react/no-danger */ -import React, { FunctionComponent } from 'react'; - -import { RenderingMetadata } from '../types'; +import React, { FC } from 'react'; interface Props { - darkMode: RenderingMetadata['darkMode']; + darkMode: boolean; + stylesheetPaths: string[]; } -export const Styles: FunctionComponent = ({ darkMode }) => { +export const Styles: FC = ({ darkMode, stylesheetPaths }) => { + return ( + <> + + {stylesheetPaths.map((path) => ( + + ))} + + ); +}; + +const InlineStyles: FC<{ darkMode: boolean }> = ({ darkMode }) => { return ( + + + + diff --git a/x-pack/plugins/apm/public/components/shared/AgentIcon/index.tsx b/x-pack/plugins/apm/public/components/shared/AgentIcon/index.tsx index 25abaac82b0a0..f91eb49717782 100644 --- a/x-pack/plugins/apm/public/components/shared/AgentIcon/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/AgentIcon/index.tsx @@ -6,19 +6,19 @@ */ import React from 'react'; +import { EuiIcon } from '@elastic/eui'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; -import { useTheme } from '../../../hooks/use_theme'; import { getAgentIcon } from './get_agent_icon'; +import { useTheme } from '../../../hooks/use_theme'; interface Props { agentName: AgentName; } export function AgentIcon(props: Props) { - const theme = useTheme(); const { agentName } = props; - const size = theme.eui.euiIconSizes.large; - const icon = getAgentIcon(agentName); + const theme = useTheme(); + const icon = getAgentIcon(agentName, theme.darkMode); - return {agentName}; + return ; } diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx index f81157c5cffd5..f94bba84526a7 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx @@ -8,6 +8,9 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { ReactNode } from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; +import { HeaderMenuPortal } from '../../../../../observability/public'; +import { ActionMenu } from '../../../application/action_menu'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { EnvironmentFilter } from '../EnvironmentFilter'; const HeaderFlexGroup = euiStyled(EuiFlexGroup)` @@ -17,8 +20,13 @@ const HeaderFlexGroup = euiStyled(EuiFlexGroup)` `; export function ApmHeader({ children }: { children: ReactNode }) { + const { setHeaderActionMenu } = useApmPluginContext().appMountParameters; + return ( + + + {children} diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx index 3cccf543c3e60..3f61273729e64 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx @@ -13,6 +13,7 @@ import { LatencyAggregationType } from '../../../../../common/latency_aggregatio import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { useLicenseContext } from '../../../../context/license/use_license_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useTheme } from '../../../../hooks/use_theme'; import { useTransactionLatencyChartsFetcher } from '../../../../hooks/use_transaction_latency_chart_fetcher'; import { TimeseriesChart } from '../../../shared/charts/timeseries_chart'; import { @@ -21,6 +22,7 @@ import { } from '../../../shared/charts/transaction_charts/helper'; import { MLHeader } from '../../../shared/charts/transaction_charts/ml_header'; import * as urlHelpers from '../../../shared/Links/url_helpers'; +import { getComparisonChartTheme } from '../../time_comparison/get_time_range_comparison'; interface Props { height?: number; @@ -32,10 +34,16 @@ const options: Array<{ value: LatencyAggregationType; text: string }> = [ { value: LatencyAggregationType.p99, text: '99th percentile' }, ]; +function filterNil(value: T | null | undefined): value is T { + return value != null; +} + export function LatencyChart({ height }: Props) { const history = useHistory(); + const theme = useTheme(); + const comparisonChartTheme = getComparisonChartTheme(theme); const { urlParams } = useUrlParams(); - const { latencyAggregationType } = urlParams; + const { latencyAggregationType, comparisonEnabled } = urlParams; const license = useLicenseContext(); const { @@ -43,9 +51,19 @@ export function LatencyChart({ height }: Props) { latencyChartsStatus, } = useTransactionLatencyChartsFetcher(); - const { latencyTimeseries, anomalyTimeseries, mlJobId } = latencyChartsData; + const { + currentPeriod, + previousPeriod, + anomalyTimeseries, + mlJobId, + } = latencyChartsData; + + const timeseries = [ + currentPeriod, + comparisonEnabled ? previousPeriod : undefined, + ].filter(filterNil); - const latencyMaxY = getMaxY(latencyTimeseries); + const latencyMaxY = getMaxY(timeseries); const latencyFormatter = getDurationFormatter(latencyMaxY); return ( @@ -99,7 +117,8 @@ export function LatencyChart({ height }: Props) { height={height} fetchStatus={latencyChartsStatus} id="latencyChart" - timeseries={latencyTimeseries} + customTheme={comparisonChartTheme} + timeseries={timeseries} yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)} anomalyTimeseries={anomalyTimeseries} /> diff --git a/x-pack/plugins/apm/public/components/shared/span_icon/index.tsx b/x-pack/plugins/apm/public/components/shared/span_icon/index.tsx index db21d781e9eba..05e4067f522a1 100644 --- a/x-pack/plugins/apm/public/components/shared/span_icon/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/span_icon/index.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useTheme } from '../../../hooks/use_theme'; +import { EuiIcon } from '@elastic/eui'; import { getSpanIcon } from './get_span_icon'; interface Props { @@ -15,9 +15,7 @@ interface Props { } export function SpanIcon({ type, subType }: Props) { - const theme = useTheme(); - const size = theme.eui.euiIconSizes.large; const icon = getSpanIcon(type, subType); - return {type; + return ; } diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx index 0b6c1a2c52a98..1769119593c0e 100644 --- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx @@ -84,12 +84,14 @@ function getSelectOptions({ }), }; - const dateDiff = getDateDifference({ - start: momentStart, - end: momentEnd, - unitOfTime: 'days', - precise: true, - }); + const dateDiff = Number( + getDateDifference({ + start: momentStart, + end: momentEnd, + unitOfTime: 'days', + precise: true, + }).toFixed(2) + ); const isRangeToNow = rangeTo === 'now'; diff --git a/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx new file mode 100644 index 0000000000000..b8d6feda826a5 --- /dev/null +++ b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, ReactChild, useState } from 'react'; +import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; +import { APIReturnType } from '../../services/rest/createCallApmApi'; + +export interface AnomalyDetectionJobsContextValue { + anomalyDetectionJobsData?: APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; + anomalyDetectionJobsStatus: FETCH_STATUS; + anomalyDetectionJobsRefetch: () => void; +} + +export const AnomalyDetectionJobsContext = createContext( + {} as AnomalyDetectionJobsContextValue +); + +export function AnomalyDetectionJobsContextProvider({ + children, +}: { + children: ReactChild; +}) { + const [fetchId, setFetchId] = useState(0); + const refetch = () => setFetchId((id) => id + 1); + + const { data, status } = useFetcher( + (callApmApi) => + callApmApi({ + endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, + }), + [fetchId], // eslint-disable-line react-hooks/exhaustive-deps + { showToastOnError: false } + ); + + return ( + + {children} + + ); +} diff --git a/x-pack/plugins/apm/public/context/anomaly_detection_jobs/use_anomaly_detection_jobs_context.ts b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/use_anomaly_detection_jobs_context.ts new file mode 100644 index 0000000000000..4a81bd9ec9da7 --- /dev/null +++ b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/use_anomaly_detection_jobs_context.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useContext } from 'react'; +import { AnomalyDetectionJobsContext } from './anomaly_detection_jobs_context'; + +export function useAnomalyDetectionJobsContext() { + return useContext(AnomalyDetectionJobsContext); +} diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index b92b812bdd430..16a82b1d4972b 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -12,6 +12,7 @@ import { useUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; import { getLatencyChartSelector } from '../selectors/latency_chart_selectors'; import { useTheme } from './use_theme'; +import { getTimeRangeComparison } from '../components/shared/time_comparison/get_time_range_comparison'; export function useTransactionLatencyChartsFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); @@ -25,9 +26,16 @@ export function useTransactionLatencyChartsFetcher() { end, transactionName, latencyAggregationType, + comparisonType, }, } = useUrlParams(); + const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ + start, + end, + comparisonType, + }); + const { data, error, status } = useFetcher( (callApmApi) => { if ( @@ -50,6 +58,8 @@ export function useTransactionLatencyChartsFetcher() { transactionType, transactionName, latencyAggregationType, + comparisonStart, + comparisonEnd, }, }, }); @@ -64,6 +74,8 @@ export function useTransactionLatencyChartsFetcher() { transactionName, transactionType, latencyAggregationType, + comparisonStart, + comparisonEnd, ] ); diff --git a/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts b/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts index d4c9ba0878f28..252ced2be5e0e 100644 --- a/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts +++ b/x-pack/plugins/apm/public/selectors/latency_chart_selector.test.ts @@ -18,12 +18,19 @@ const theme = { euiColorVis5: 'red', euiColorVis7: 'black', euiColorVis9: 'yellow', + euiColorLightestShade: 'green', }, } as EuiTheme; const latencyChartData = { - overallAvgDuration: 1, - latencyTimeseries: [{ x: 1, y: 10 }], + currentPeriod: { + overallAvgDuration: 1, + latencyTimeseries: [{ x: 1, y: 10 }], + }, + previousPeriod: { + overallAvgDuration: 1, + latencyTimeseries: [{ x: 1, y: 10 }], + }, anomalyTimeseries: { jobId: '1', anomalyBoundaries: [{ x: 1, y: 2, y0: 1 }], @@ -36,69 +43,84 @@ describe('getLatencyChartSelector', () => { it('returns default values when data is undefined', () => { const latencyChart = getLatencyChartSelector({ theme }); expect(latencyChart).toEqual({ - latencyTimeseries: [], + currentPeriod: undefined, + previousPeriod: undefined, mlJobId: undefined, anomalyTimeseries: undefined, }); }); it('returns average timeseries', () => { - const { anomalyTimeseries, ...latencyWithouAnomaly } = latencyChartData; + const { anomalyTimeseries, ...latencyWithoutAnomaly } = latencyChartData; const latencyTimeseries = getLatencyChartSelector({ - latencyChart: latencyWithouAnomaly as LatencyChartsResponse, + latencyChart: latencyWithoutAnomaly as LatencyChartsResponse, theme, latencyAggregationType: LatencyAggregationType.avg, }); expect(latencyTimeseries).toEqual({ - latencyTimeseries: [ - { - title: 'Average', - data: [{ x: 1, y: 10 }], - legendValue: '1 μs', - type: 'linemark', - color: 'blue', - }, - ], + currentPeriod: { + title: 'Average', + data: [{ x: 1, y: 10 }], + legendValue: '1 μs', + type: 'linemark', + color: 'blue', + }, + + previousPeriod: { + color: 'green', + data: [{ x: 1, y: 10 }], + type: 'area', + title: 'Previous period', + }, }); }); it('returns 95th percentile timeseries', () => { - const { anomalyTimeseries, ...latencyWithouAnomaly } = latencyChartData; + const { anomalyTimeseries, ...latencyWithoutAnomaly } = latencyChartData; const latencyTimeseries = getLatencyChartSelector({ - latencyChart: latencyWithouAnomaly as LatencyChartsResponse, + latencyChart: latencyWithoutAnomaly as LatencyChartsResponse, theme, latencyAggregationType: LatencyAggregationType.p95, }); expect(latencyTimeseries).toEqual({ - latencyTimeseries: [ - { - title: '95th percentile', - data: [{ x: 1, y: 10 }], - titleShort: '95th', - type: 'linemark', - color: 'red', - }, - ], + currentPeriod: { + title: '95th percentile', + titleShort: '95th', + data: [{ x: 1, y: 10 }], + type: 'linemark', + color: 'red', + }, + previousPeriod: { + data: [{ x: 1, y: 10 }], + type: 'area', + color: 'green', + title: 'Previous period', + }, }); }); it('returns 99th percentile timeseries', () => { - const { anomalyTimeseries, ...latencyWithouAnomaly } = latencyChartData; + const { anomalyTimeseries, ...latencyWithoutAnomaly } = latencyChartData; const latencyTimeseries = getLatencyChartSelector({ - latencyChart: latencyWithouAnomaly as LatencyChartsResponse, + latencyChart: latencyWithoutAnomaly as LatencyChartsResponse, theme, latencyAggregationType: LatencyAggregationType.p99, }); + expect(latencyTimeseries).toEqual({ - latencyTimeseries: [ - { - title: '99th percentile', - data: [{ x: 1, y: 10 }], - titleShort: '99th', - type: 'linemark', - color: 'black', - }, - ], + currentPeriod: { + title: '99th percentile', + titleShort: '99th', + data: [{ x: 1, y: 10 }], + type: 'linemark', + color: 'black', + }, + previousPeriod: { + data: [{ x: 1, y: 10 }], + type: 'area', + color: 'green', + title: 'Previous period', + }, }); }); }); @@ -111,76 +133,52 @@ describe('getLatencyChartSelector', () => { latencyAggregationType: LatencyAggregationType.p99, }); expect(latencyTimeseries).toEqual({ + currentPeriod: { + title: '99th percentile', + titleShort: '99th', + data: [{ x: 1, y: 10 }], + type: 'linemark', + color: 'black', + }, + previousPeriod: { + data: [{ x: 1, y: 10 }], + type: 'area', + color: 'green', + title: 'Previous period', + }, + mlJobId: '1', anomalyTimeseries: { boundaries: [ { - color: 'rgba(0,0,0,0)', - areaSeriesStyle: { - point: { - opacity: 0, - }, - }, - data: [ - { - x: 1, - y: 1, - }, - ], + type: 'area', fit: 'lookahead', hideLegend: true, hideTooltipValue: true, stackAccessors: ['y'], + areaSeriesStyle: { point: { opacity: 0 } }, title: 'anomalyBoundariesLower', - type: 'area', + data: [{ x: 1, y: 1 }], + color: 'rgba(0,0,0,0)', }, { - color: 'rgba(0,0,255,0.5)', - areaSeriesStyle: { - point: { - opacity: 0, - }, - }, - data: [ - { - x: 1, - y: 1, - }, - ], + type: 'area', fit: 'lookahead', hideLegend: true, hideTooltipValue: true, stackAccessors: ['y'], + areaSeriesStyle: { point: { opacity: 0 } }, title: 'anomalyBoundariesUpper', - type: 'area', + data: [{ x: 1, y: 1 }], + color: 'rgba(0,0,255,0.5)', }, ], scores: { - color: 'yellow', - data: [ - { - x: 1, - x0: 2, - }, - ], title: 'anomalyScores', type: 'rectAnnotation', + data: [{ x: 1, x0: 2 }], + color: 'yellow', }, }, - latencyTimeseries: [ - { - color: 'black', - data: [ - { - x: 1, - y: 10, - }, - ], - title: '99th percentile', - titleShort: '99th', - type: 'linemark', - }, - ], - mlJobId: '1', }); }); }); diff --git a/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts b/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts index 858d44de8bb7a..2ee4a717106eb 100644 --- a/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts +++ b/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts @@ -16,7 +16,8 @@ import { APIReturnType } from '../services/rest/createCallApmApi'; export type LatencyChartsResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; export interface LatencyChartData { - latencyTimeseries: Array>; + currentPeriod?: APMChartSpec; + previousPeriod?: APMChartSpec; mlJobId?: string; anomalyTimeseries?: { boundaries: APMChartSpec[]; scores: APMChartSpec }; } @@ -29,20 +30,23 @@ export function getLatencyChartSelector({ latencyChart?: LatencyChartsResponse; theme: EuiTheme; latencyAggregationType?: string; -}): LatencyChartData { - if (!latencyChart?.latencyTimeseries || !latencyAggregationType) { - return { - latencyTimeseries: [], - mlJobId: undefined, - anomalyTimeseries: undefined, - }; +}): Partial { + if ( + !latencyChart?.currentPeriod.latencyTimeseries || + !latencyAggregationType + ) { + return {}; } return { - latencyTimeseries: getLatencyTimeseries({ - latencyChart, + currentPeriod: getLatencyTimeseries({ + latencyChart: latencyChart.currentPeriod, theme, latencyAggregationType, }), + previousPeriod: getPreviousPeriodTimeseries({ + previousPeriod: latencyChart.previousPeriod, + theme, + }), mlJobId: latencyChart.anomalyTimeseries?.jobId, anomalyTimeseries: getAnomalyTimeseries({ anomalyTimeseries: latencyChart.anomalyTimeseries, @@ -51,12 +55,30 @@ export function getLatencyChartSelector({ }; } +function getPreviousPeriodTimeseries({ + previousPeriod, + theme, +}: { + previousPeriod: LatencyChartsResponse['previousPeriod']; + theme: EuiTheme; +}) { + return { + data: previousPeriod.latencyTimeseries ?? [], + type: 'area', + color: theme.eui.euiColorLightestShade, + title: i18n.translate( + 'xpack.apm.serviceOverview.latencyChartTitle.previousPeriodLabel', + { defaultMessage: 'Previous period' } + ), + }; +} + function getLatencyTimeseries({ latencyChart, theme, latencyAggregationType, }: { - latencyChart: LatencyChartsResponse; + latencyChart: LatencyChartsResponse['currentPeriod']; theme: EuiTheme; latencyAggregationType: string; }) { @@ -65,49 +87,42 @@ function getLatencyTimeseries({ switch (latencyAggregationType) { case 'avg': { - return [ - { - title: i18n.translate( - 'xpack.apm.transactions.latency.chart.averageLabel', - { defaultMessage: 'Average' } - ), - data: latencyTimeseries, - legendValue: asDuration(overallAvgDuration), - type: 'linemark', - color: theme.eui.euiColorVis1, - }, - ]; + return { + title: i18n.translate( + 'xpack.apm.transactions.latency.chart.averageLabel', + { defaultMessage: 'Average' } + ), + data: latencyTimeseries, + legendValue: asDuration(overallAvgDuration), + type: 'linemark', + color: theme.eui.euiColorVis1, + }; } case 'p95': { - return [ - { - title: i18n.translate( - 'xpack.apm.transactions.latency.chart.95thPercentileLabel', - { defaultMessage: '95th percentile' } - ), - titleShort: '95th', - data: latencyTimeseries, - type: 'linemark', - color: theme.eui.euiColorVis5, - }, - ]; + return { + title: i18n.translate( + 'xpack.apm.transactions.latency.chart.95thPercentileLabel', + { defaultMessage: '95th percentile' } + ), + titleShort: '95th', + data: latencyTimeseries, + type: 'linemark', + color: theme.eui.euiColorVis5, + }; } case 'p99': { - return [ - { - title: i18n.translate( - 'xpack.apm.transactions.latency.chart.99thPercentileLabel', - { defaultMessage: '99th percentile' } - ), - titleShort: '99th', - data: latencyTimeseries, - type: 'linemark', - color: theme.eui.euiColorVis7, - }, - ]; + return { + title: i18n.translate( + 'xpack.apm.transactions.latency.chart.99thPercentileLabel', + { defaultMessage: '99th percentile' } + ), + titleShort: '99th', + data: latencyTimeseries, + type: 'linemark', + color: theme.eui.euiColorVis7, + }; } } - return []; } function getAnomalyTimeseries({ diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts index ebdb5fb5bbdc2..54e882d1dd6da 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts @@ -47,7 +47,6 @@ export async function getServiceTransactionGroupComparisonStatistics({ latencyAggregationType, start, end, - getOffsetXCoordinate, }: { environment?: string; kuery?: string; @@ -60,7 +59,6 @@ export async function getServiceTransactionGroupComparisonStatistics({ latencyAggregationType: LatencyAggregationType; start: number; end: number; - getOffsetXCoordinate?: (timeseries: Coordinate[]) => Coordinate[]; }): Promise< Array<{ transactionName: string; @@ -175,15 +173,9 @@ export async function getServiceTransactionGroupComparisonStatistics({ bucket.transaction_group_total_duration.value || 0; return { transactionName, - latency: getOffsetXCoordinate - ? getOffsetXCoordinate(latency) - : latency, - throughput: getOffsetXCoordinate - ? getOffsetXCoordinate(throughput) - : throughput, - errorRate: getOffsetXCoordinate - ? getOffsetXCoordinate(errorRate) - : errorRate, + latency, + throughput, + errorRate, impact: totalDuration ? (transactionGroupTotalDuration * 100) / totalDuration : 0, @@ -244,12 +236,6 @@ export async function getServiceTransactionGroupComparisonStatisticsPeriods({ ...commonProps, start: comparisonStart, end: comparisonEnd, - getOffsetXCoordinate: (timeseries: Coordinate[]) => - offsetPreviousPeriodCoordinates({ - currentPeriodStart: start, - previousPeriodStart: comparisonStart, - previousPeriodTimeseries: timeseries, - }), }) : []; @@ -258,8 +244,29 @@ export async function getServiceTransactionGroupComparisonStatisticsPeriods({ previousPeriodPromise, ]); + const firtCurrentPeriod = currentPeriod.length ? currentPeriod[0] : undefined; + return { currentPeriod: keyBy(currentPeriod, 'transactionName'), - previousPeriod: keyBy(previousPeriod, 'transactionName'), + previousPeriod: keyBy( + previousPeriod.map((data) => { + return { + ...data, + errorRate: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firtCurrentPeriod?.errorRate, + previousPeriodTimeseries: data.errorRate, + }), + throughput: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firtCurrentPeriod?.throughput, + previousPeriodTimeseries: data.throughput, + }), + latency: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: firtCurrentPeriod?.latency, + previousPeriodTimeseries: data.latency, + }), + }; + }), + 'transactionName' + ), }; } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index 0be72c95b0a60..31b5c6ff64dfd 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -25,6 +25,7 @@ import { } from '../../../lib/helpers/aggregated_transactions'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; +import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getLatencyAggregation, @@ -43,17 +44,21 @@ function searchLatency({ setup, searchAggregatedTransactions, latencyAggregationType, + start, + end, }: { environment?: string; kuery?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup & SetupTimeRange; + setup: Setup; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; + start: number; + end: number; }) { - const { start, end, apmEventClient } = setup; + const { apmEventClient } = setup; const { intervalString } = getBucketSize({ start, end }); const filter: ESFilter[] = [ @@ -119,15 +124,19 @@ export function getLatencyTimeseries({ setup, searchAggregatedTransactions, latencyAggregationType, + start, + end, }: { environment?: string; kuery?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup & SetupTimeRange; + setup: Setup; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; + start: number; + end: number; }) { return withApmSpan('get_latency_charts', async () => { const response = await searchLatency({ @@ -139,6 +148,8 @@ export function getLatencyTimeseries({ setup, searchAggregatedTransactions, latencyAggregationType, + start, + end, }); if (!response.aggregations) { @@ -162,3 +173,65 @@ export function getLatencyTimeseries({ }; }); } + +export async function getLatencyPeriods({ + serviceName, + transactionType, + transactionName, + setup, + searchAggregatedTransactions, + latencyAggregationType, + comparisonStart, + comparisonEnd, +}: { + serviceName: string; + transactionType: string | undefined; + transactionName: string | undefined; + setup: Setup & SetupTimeRange; + searchAggregatedTransactions: boolean; + latencyAggregationType: LatencyAggregationType; + comparisonStart?: number; + comparisonEnd?: number; +}) { + const { start, end } = setup; + const options = { + serviceName, + transactionType, + transactionName, + setup, + searchAggregatedTransactions, + }; + + const currentPeriodPromise = getLatencyTimeseries({ + ...options, + start, + end, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }); + + const previousPeriodPromise = + comparisonStart && comparisonEnd + ? getLatencyTimeseries({ + ...options, + start: comparisonStart, + end: comparisonEnd, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }) + : { latencyTimeseries: [], overallAvgDuration: null }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + + return { + currentPeriod, + previousPeriod: { + ...previousPeriod, + latencyTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.latencyTimeseries, + previousPeriodTimeseries: previousPeriod.latencyTimeseries, + }), + }, + }; +} diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 58e6f6ccadc0a..a84c8dc274248 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -408,19 +408,16 @@ export const serviceThroughputRoute = createRoute({ ...commonProps, start: comparisonStart, end: comparisonEnd, - }).then((coordinates) => - offsetPreviousPeriodCoordinates({ - currentPeriodStart: start, - previousPeriodStart: comparisonStart, - previousPeriodTimeseries: coordinates, - }) - ) + }) : [], ]); return { currentPeriod, - previousPeriod, + previousPeriod: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod, + previousPeriodTimeseries: previousPeriod, + }), }; }, }); diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index f7ff93d104647..1571efb373cc9 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -19,14 +19,14 @@ import { getServiceTransactionGroupComparisonStatisticsPeriods } from '../lib/se import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionDistribution } from '../lib/transactions/distribution'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; -import { getLatencyTimeseries } from '../lib/transactions/get_latency_charts'; +import { getLatencyPeriods } from '../lib/transactions/get_latency_charts'; import { getThroughputCharts } from '../lib/transactions/get_throughput_charts'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; import { createRoute } from './create_route'; import { - environmentRt, comparisonRangeRt, + environmentRt, rangeRt, kueryRt, } from './default_api_types'; @@ -179,16 +179,12 @@ export const transactionLatencyChartsRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ - t.partial({ - transactionName: t.string, - }), t.type({ transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, }), - environmentRt, - kueryRt, - rangeRt, + t.partial({ transactionName: t.string }), + t.intersection([environmentRt, kueryRt, rangeRt, comparisonRangeRt]), ]), }), options: { tags: ['access:apm'] }, @@ -202,6 +198,8 @@ export const transactionLatencyChartsRoute = createRoute({ transactionType, transactionName, latencyAggregationType, + comparisonStart, + comparisonEnd, } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( @@ -219,10 +217,15 @@ export const transactionLatencyChartsRoute = createRoute({ logger, }; - const [latencyData, anomalyTimeseries] = await Promise.all([ - getLatencyTimeseries({ + const [ + { currentPeriod, previousPeriod }, + anomalyTimeseries, + ] = await Promise.all([ + getLatencyPeriods({ ...options, latencyAggregationType: latencyAggregationType as LatencyAggregationType, + comparisonStart, + comparisonEnd, }), getAnomalySeries(options).catch((error) => { logger.warn(`Unable to retrieve anomalies for latency charts.`); @@ -231,11 +234,9 @@ export const transactionLatencyChartsRoute = createRoute({ }), ]); - const { latencyTimeseries, overallAvgDuration } = latencyData; - return { - latencyTimeseries, - overallAvgDuration, + currentPeriod, + previousPeriod, anomalyTimeseries, }; }, diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts index 6436c7c5193ec..f965751333838 100644 --- a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts +++ b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts @@ -7,16 +7,16 @@ import { Coordinate } from '../../typings/timeseries'; import { offsetPreviousPeriodCoordinates } from './offset_previous_period_coordinate'; -const previousPeriodStart = new Date('2021-01-27T14:45:00.000Z').valueOf(); -const currentPeriodStart = new Date('2021-01-28T14:45:00.000Z').valueOf(); +const currentPeriodTimeseries: Coordinate[] = [ + { x: new Date('2021-01-28T14:45:00.000Z').valueOf(), y: 0 }, +]; describe('mergePeriodsTimeseries', () => { describe('returns empty array', () => { it('when previous timeseries is not defined', () => { expect( offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries: undefined, }) ).toEqual([]); @@ -25,8 +25,7 @@ describe('mergePeriodsTimeseries', () => { it('when previous timeseries is empty', () => { expect( offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries: [], }) ).toEqual([]); @@ -43,8 +42,7 @@ describe('mergePeriodsTimeseries', () => { expect( offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries, }) ).toEqual([ diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts index 7ca153ec6181f..4d095a79394a1 100644 --- a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts +++ b/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts @@ -9,19 +9,20 @@ import moment from 'moment'; import { Coordinate } from '../../typings/timeseries'; export function offsetPreviousPeriodCoordinates({ - currentPeriodStart, - previousPeriodStart, + currentPeriodTimeseries, previousPeriodTimeseries, }: { - currentPeriodStart: number; - previousPeriodStart: number; + currentPeriodTimeseries?: Coordinate[]; previousPeriodTimeseries?: Coordinate[]; }) { - if (!previousPeriodTimeseries) { + if (!previousPeriodTimeseries?.length) { return []; } + const currentPeriodStart = currentPeriodTimeseries?.length + ? currentPeriodTimeseries[0].x + : 0; - const dateDiff = currentPeriodStart - previousPeriodStart; + const dateDiff = currentPeriodStart - previousPeriodTimeseries[0].x; return previousPeriodTimeseries.map(({ x, y }) => { const offsetX = moment(x).add(dateDiff).valueOf(); diff --git a/x-pack/plugins/case/server/client/index.ts b/x-pack/plugins/case/server/client/index.ts deleted file mode 100644 index 900b5a92ebf92..0000000000000 --- a/x-pack/plugins/case/server/client/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { CaseClientFactoryArguments, CaseClient } from './types'; -import { CaseClientHandler } from './client'; - -export { CaseClientHandler } from './client'; -export { CaseClient } from './types'; - -/** - * Create a CaseClientHandler to external services (other plugins). - */ -export const createExternalCaseClient = (clientArgs: CaseClientFactoryArguments): CaseClient => { - const client = new CaseClientHandler(clientArgs); - return client; -}; diff --git a/x-pack/plugins/case/README.md b/x-pack/plugins/cases/README.md similarity index 100% rename from x-pack/plugins/case/README.md rename to x-pack/plugins/cases/README.md diff --git a/x-pack/plugins/case/common/api/cases/case.ts b/x-pack/plugins/cases/common/api/cases/case.ts similarity index 96% rename from x-pack/plugins/case/common/api/cases/case.ts rename to x-pack/plugins/cases/common/api/cases/case.ts index 33a93952b0e2d..a2bba7dba4b39 100644 --- a/x-pack/plugins/case/common/api/cases/case.ts +++ b/x-pack/plugins/cases/common/api/cases/case.ts @@ -83,7 +83,7 @@ const CasePostRequestNoTypeRt = rt.type({ /** * This type is used for validating a create case request. It requires that the type field be defined. */ -export const CaseClientPostRequestRt = rt.type({ +export const CasesClientPostRequestRt = rt.type({ ...CasePostRequestNoTypeRt.props, [caseTypeField]: CaseTypeRt, }); @@ -92,7 +92,7 @@ export const CaseClientPostRequestRt = rt.type({ * This type is not used for validation when decoding a request because intersection does not have props defined which * required for the excess function. Instead we use this as the type used by the UI. This allows the type field to be * optional and the server will handle setting it to a default value before validating that the request - * has all the necessary fields. CaseClientPostRequestRt is used for validation. + * has all the necessary fields. CasesClientPostRequestRt is used for validation. */ export const CasePostRequestRt = rt.intersection([ rt.partial({ type: CaseTypeRt }), @@ -178,7 +178,7 @@ export type CaseAttributes = rt.TypeOf; * that the type field be defined. The CasePostRequest should be used in most places (the UI etc). This type is really * only necessary for validation. */ -export type CaseClientPostRequest = rt.TypeOf; +export type CasesClientPostRequest = rt.TypeOf; export type CasePostRequest = rt.TypeOf; export type CaseResponse = rt.TypeOf; export type CasesResponse = rt.TypeOf; diff --git a/x-pack/plugins/case/common/api/cases/comment.ts b/x-pack/plugins/cases/common/api/cases/comment.ts similarity index 100% rename from x-pack/plugins/case/common/api/cases/comment.ts rename to x-pack/plugins/cases/common/api/cases/comment.ts diff --git a/x-pack/plugins/case/common/api/cases/configure.ts b/x-pack/plugins/cases/common/api/cases/configure.ts similarity index 100% rename from x-pack/plugins/case/common/api/cases/configure.ts rename to x-pack/plugins/cases/common/api/cases/configure.ts diff --git a/x-pack/plugins/case/common/api/cases/index.ts b/x-pack/plugins/cases/common/api/cases/index.ts similarity index 100% rename from x-pack/plugins/case/common/api/cases/index.ts rename to x-pack/plugins/cases/common/api/cases/index.ts diff --git a/x-pack/plugins/case/common/api/cases/status.ts b/x-pack/plugins/cases/common/api/cases/status.ts similarity index 100% rename from x-pack/plugins/case/common/api/cases/status.ts rename to x-pack/plugins/cases/common/api/cases/status.ts diff --git a/x-pack/plugins/case/common/api/cases/sub_case.ts b/x-pack/plugins/cases/common/api/cases/sub_case.ts similarity index 100% rename from x-pack/plugins/case/common/api/cases/sub_case.ts rename to x-pack/plugins/cases/common/api/cases/sub_case.ts diff --git a/x-pack/plugins/case/common/api/cases/user_actions.ts b/x-pack/plugins/cases/common/api/cases/user_actions.ts similarity index 95% rename from x-pack/plugins/case/common/api/cases/user_actions.ts rename to x-pack/plugins/cases/common/api/cases/user_actions.ts index 6c8e0de80903d..7188ee44efa93 100644 --- a/x-pack/plugins/case/common/api/cases/user_actions.ts +++ b/x-pack/plugins/cases/common/api/cases/user_actions.ts @@ -10,7 +10,7 @@ import * as rt from 'io-ts'; import { UserRT } from '../user'; /* To the next developer, if you add/removed fields here - * make sure to check this file (x-pack/plugins/case/server/services/user_actions/helpers.ts) too + * make sure to check this file (x-pack/plugins/cases/server/services/user_actions/helpers.ts) too */ const UserActionFieldTypeRt = rt.union([ rt.literal('comment'), diff --git a/x-pack/plugins/case/common/api/connectors/index.ts b/x-pack/plugins/cases/common/api/connectors/index.ts similarity index 100% rename from x-pack/plugins/case/common/api/connectors/index.ts rename to x-pack/plugins/cases/common/api/connectors/index.ts diff --git a/x-pack/plugins/case/common/api/connectors/jira.ts b/x-pack/plugins/cases/common/api/connectors/jira.ts similarity index 84% rename from x-pack/plugins/case/common/api/connectors/jira.ts rename to x-pack/plugins/cases/common/api/connectors/jira.ts index d61f4ba91575e..b8a8d0147420e 100644 --- a/x-pack/plugins/case/common/api/connectors/jira.ts +++ b/x-pack/plugins/cases/common/api/connectors/jira.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; -// New fields should also be added at: x-pack/plugins/case/server/connectors/case/schema.ts +// New fields should also be added at: x-pack/plugins/cases/server/connectors/case/schema.ts export const JiraFieldsRT = rt.type({ issueType: rt.union([rt.string, rt.null]), priority: rt.union([rt.string, rt.null]), diff --git a/x-pack/plugins/case/common/api/connectors/mappings.ts b/x-pack/plugins/cases/common/api/connectors/mappings.ts similarity index 100% rename from x-pack/plugins/case/common/api/connectors/mappings.ts rename to x-pack/plugins/cases/common/api/connectors/mappings.ts diff --git a/x-pack/plugins/case/common/api/connectors/resilient.ts b/x-pack/plugins/cases/common/api/connectors/resilient.ts similarity index 84% rename from x-pack/plugins/case/common/api/connectors/resilient.ts rename to x-pack/plugins/cases/common/api/connectors/resilient.ts index dc59588d1e6ed..d28c96968d6e5 100644 --- a/x-pack/plugins/case/common/api/connectors/resilient.ts +++ b/x-pack/plugins/cases/common/api/connectors/resilient.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; -// New fields should also be added at: x-pack/plugins/case/server/connectors/case/schema.ts +// New fields should also be added at: x-pack/plugins/cases/server/connectors/case/schema.ts export const ResilientFieldsRT = rt.type({ incidentTypes: rt.union([rt.array(rt.string), rt.null]), severityCode: rt.union([rt.string, rt.null]), diff --git a/x-pack/plugins/case/common/api/connectors/servicenow_itsm.ts b/x-pack/plugins/cases/common/api/connectors/servicenow_itsm.ts similarity index 87% rename from x-pack/plugins/case/common/api/connectors/servicenow_itsm.ts rename to x-pack/plugins/cases/common/api/connectors/servicenow_itsm.ts index 9eedbcb44907a..6a5453561c73d 100644 --- a/x-pack/plugins/case/common/api/connectors/servicenow_itsm.ts +++ b/x-pack/plugins/cases/common/api/connectors/servicenow_itsm.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; -// New fields should also be added at: x-pack/plugins/case/server/connectors/case/schema.ts +// New fields should also be added at: x-pack/plugins/cases/server/connectors/case/schema.ts export const ServiceNowITSMFieldsRT = rt.type({ impact: rt.union([rt.string, rt.null]), severity: rt.union([rt.string, rt.null]), diff --git a/x-pack/plugins/case/common/api/connectors/servicenow_sir.ts b/x-pack/plugins/cases/common/api/connectors/servicenow_sir.ts similarity index 88% rename from x-pack/plugins/case/common/api/connectors/servicenow_sir.ts rename to x-pack/plugins/cases/common/api/connectors/servicenow_sir.ts index b8d33f259ade7..3da8e694b473d 100644 --- a/x-pack/plugins/case/common/api/connectors/servicenow_sir.ts +++ b/x-pack/plugins/cases/common/api/connectors/servicenow_sir.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; -// New fields should also be added at: x-pack/plugins/case/server/connectors/case/schema.ts +// New fields should also be added at: x-pack/plugins/cases/server/connectors/case/schema.ts export const ServiceNowSIRFieldsRT = rt.type({ category: rt.union([rt.string, rt.null]), destIp: rt.union([rt.boolean, rt.null]), diff --git a/x-pack/plugins/case/common/api/helpers.ts b/x-pack/plugins/cases/common/api/helpers.ts similarity index 100% rename from x-pack/plugins/case/common/api/helpers.ts rename to x-pack/plugins/cases/common/api/helpers.ts diff --git a/x-pack/plugins/case/common/api/index.ts b/x-pack/plugins/cases/common/api/index.ts similarity index 100% rename from x-pack/plugins/case/common/api/index.ts rename to x-pack/plugins/cases/common/api/index.ts diff --git a/x-pack/plugins/case/common/api/runtime_types.ts b/x-pack/plugins/cases/common/api/runtime_types.ts similarity index 100% rename from x-pack/plugins/case/common/api/runtime_types.ts rename to x-pack/plugins/cases/common/api/runtime_types.ts diff --git a/x-pack/plugins/case/common/api/saved_object.ts b/x-pack/plugins/cases/common/api/saved_object.ts similarity index 100% rename from x-pack/plugins/case/common/api/saved_object.ts rename to x-pack/plugins/cases/common/api/saved_object.ts diff --git a/x-pack/plugins/case/common/api/user.ts b/x-pack/plugins/cases/common/api/user.ts similarity index 100% rename from x-pack/plugins/case/common/api/user.ts rename to x-pack/plugins/cases/common/api/user.ts diff --git a/x-pack/plugins/case/common/constants.ts b/x-pack/plugins/cases/common/constants.ts similarity index 98% rename from x-pack/plugins/case/common/constants.ts rename to x-pack/plugins/cases/common/constants.ts index cc69c7ecc2909..1e7cff99a00bd 100644 --- a/x-pack/plugins/case/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -7,7 +7,7 @@ import { DEFAULT_MAX_SIGNALS } from '../../security_solution/common/constants'; -export const APP_ID = 'case'; +export const APP_ID = 'cases'; /** * Case routes diff --git a/x-pack/plugins/case/jest.config.js b/x-pack/plugins/cases/jest.config.js similarity index 87% rename from x-pack/plugins/case/jest.config.js rename to x-pack/plugins/cases/jest.config.js index 49eaf9f7145a7..6368eb8895ad1 100644 --- a/x-pack/plugins/case/jest.config.js +++ b/x-pack/plugins/cases/jest.config.js @@ -8,5 +8,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/x-pack/plugins/case'], + roots: ['/x-pack/plugins/cases'], }; diff --git a/x-pack/plugins/case/kibana.json b/x-pack/plugins/cases/kibana.json similarity index 78% rename from x-pack/plugins/case/kibana.json rename to x-pack/plugins/cases/kibana.json index 2048ae41fa8ab..1aaf84decbe36 100644 --- a/x-pack/plugins/case/kibana.json +++ b/x-pack/plugins/cases/kibana.json @@ -1,6 +1,6 @@ { - "configPath": ["xpack", "case"], - "id": "case", + "configPath": ["xpack", "cases"], + "id": "cases", "kibanaVersion": "kibana", "requiredPlugins": ["actions", "securitySolution"], "optionalPlugins": [ diff --git a/x-pack/plugins/case/package.json b/x-pack/plugins/cases/package.json similarity index 90% rename from x-pack/plugins/case/package.json rename to x-pack/plugins/cases/package.json index 5a25414296946..bd9a547324ca6 100644 --- a/x-pack/plugins/case/package.json +++ b/x-pack/plugins/cases/package.json @@ -1,6 +1,6 @@ { "author": "Elastic", - "name": "case", + "name": "cases", "version": "8.0.0", "private": true, "license": "Elastic-License", diff --git a/x-pack/plugins/case/server/client/alerts/get.ts b/x-pack/plugins/cases/server/client/alerts/get.ts similarity index 89% rename from x-pack/plugins/case/server/client/alerts/get.ts rename to x-pack/plugins/cases/server/client/alerts/get.ts index 6a6e961e952c0..88298450e499a 100644 --- a/x-pack/plugins/case/server/client/alerts/get.ts +++ b/x-pack/plugins/cases/server/client/alerts/get.ts @@ -8,7 +8,7 @@ import { ElasticsearchClient, Logger } from 'kibana/server'; import { AlertInfo } from '../../common'; import { AlertServiceContract } from '../../services'; -import { CaseClientGetAlertsResponse } from './types'; +import { CasesClientGetAlertsResponse } from './types'; interface GetParams { alertsService: AlertServiceContract; @@ -22,7 +22,7 @@ export const get = async ({ alertsInfo, scopedClusterClient, logger, -}: GetParams): Promise => { +}: GetParams): Promise => { if (alertsInfo.length === 0) { return []; } diff --git a/x-pack/plugins/case/server/client/alerts/types.ts b/x-pack/plugins/cases/server/client/alerts/types.ts similarity index 87% rename from x-pack/plugins/case/server/client/alerts/types.ts rename to x-pack/plugins/cases/server/client/alerts/types.ts index 7b9d4a8856f48..26a582d92e54b 100644 --- a/x-pack/plugins/case/server/client/alerts/types.ts +++ b/x-pack/plugins/cases/server/client/alerts/types.ts @@ -16,4 +16,4 @@ interface Alert { }; } -export type CaseClientGetAlertsResponse = Alert[]; +export type CasesClientGetAlertsResponse = Alert[]; diff --git a/x-pack/plugins/case/server/client/alerts/update_status.test.ts b/x-pack/plugins/cases/server/client/alerts/update_status.test.ts similarity index 73% rename from x-pack/plugins/case/server/client/alerts/update_status.test.ts rename to x-pack/plugins/cases/server/client/alerts/update_status.test.ts index 4662ce1976620..5dfe6060da1db 100644 --- a/x-pack/plugins/case/server/client/alerts/update_status.test.ts +++ b/x-pack/plugins/cases/server/client/alerts/update_status.test.ts @@ -7,18 +7,18 @@ import { CaseStatuses } from '../../../common/api'; import { createMockSavedObjectsRepository } from '../../routes/api/__fixtures__'; -import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; describe('updateAlertsStatus', () => { it('updates the status of the alert correctly', async () => { const savedObjectsClient = createMockSavedObjectsRepository(); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - await caseClient.client.updateAlertsStatus({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + await casesClient.client.updateAlertsStatus({ alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }], }); - expect(caseClient.services.alertsService.updateAlertsStatus).toHaveBeenCalledWith({ + expect(casesClient.services.alertsService.updateAlertsStatus).toHaveBeenCalledWith({ logger: expect.anything(), scopedClusterClient: expect.anything(), alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }], diff --git a/x-pack/plugins/case/server/client/alerts/update_status.ts b/x-pack/plugins/cases/server/client/alerts/update_status.ts similarity index 100% rename from x-pack/plugins/case/server/client/alerts/update_status.ts rename to x-pack/plugins/cases/server/client/alerts/update_status.ts diff --git a/x-pack/plugins/case/server/client/cases/create.test.ts b/x-pack/plugins/cases/server/client/cases/create.test.ts similarity index 87% rename from x-pack/plugins/case/server/client/cases/create.test.ts rename to x-pack/plugins/cases/server/client/cases/create.test.ts index e8cc1a8898f04..fe301dcca37ac 100644 --- a/x-pack/plugins/case/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { ConnectorTypes, CaseStatuses, CaseType, CaseClientPostRequest } from '../../../common/api'; +import { + ConnectorTypes, + CaseStatuses, + CaseType, + CasesClientPostRequest, +} from '../../../common/api'; import { isCaseError } from '../../common/error'; import { @@ -13,7 +18,7 @@ import { mockCaseConfigure, mockCases, } from '../../routes/api/__fixtures__'; -import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; describe('create', () => { beforeEach(async () => { @@ -26,7 +31,7 @@ describe('create', () => { describe('happy path', () => { test('it creates the case correctly', async () => { - const postCase: CaseClientPostRequest = { + const postCase: CasesClientPostRequest = { description: 'This is a brand new case of a bad meanie defacing data', title: 'Super Bad Security Issue', tags: ['defacement'], @@ -46,8 +51,8 @@ describe('create', () => { caseSavedObject: mockCases, caseConfigureSavedObject: mockCaseConfigure, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.create(postCase); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.create(postCase); expect(res).toMatchInlineSnapshot(` Object { @@ -93,7 +98,7 @@ describe('create', () => { `); expect( - caseClient.services.userActionService.postUserActions.mock.calls[0][0].actions + casesClient.services.userActionService.postUserActions.mock.calls[0][0].actions // using a snapshot here so we don't have to update the text field manually each time it changes ).toMatchInlineSnapshot(` Array [ @@ -130,7 +135,7 @@ describe('create', () => { }); test('it creates the case without connector in the configuration', async () => { - const postCase: CaseClientPostRequest = { + const postCase: CasesClientPostRequest = { description: 'This is a brand new case of a bad meanie defacing data', title: 'Super Bad Security Issue', tags: ['defacement'], @@ -149,8 +154,8 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.create(postCase); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.create(postCase); expect(res).toMatchInlineSnapshot(` Object { @@ -193,7 +198,7 @@ describe('create', () => { }); test('Allow user to create case without authentication', async () => { - const postCase: CaseClientPostRequest = { + const postCase: CasesClientPostRequest = { description: 'This is a brand new case of a bad meanie defacing data', title: 'Super Bad Security Issue', tags: ['defacement'], @@ -212,11 +217,11 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient, badAuth: true, }); - const res = await caseClient.client.create(postCase); + const res = await casesClient.client.create(postCase); expect(res).toMatchInlineSnapshot(` Object { @@ -276,9 +281,9 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .create({ theCase: postCase }) .catch((e) => { @@ -305,9 +310,9 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .create({ theCase: postCase }) .catch((e) => { @@ -334,9 +339,9 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .create({ theCase: postCase }) .catch((e) => { @@ -358,9 +363,9 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .create({ theCase: postCase }) .catch((e) => { @@ -388,9 +393,9 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .create({ theCase: postCase }) .catch((e) => { @@ -423,8 +428,8 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client.create(postCase).catch((e) => { + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client.create(postCase).catch((e) => { expect(e).not.toBeNull(); expect(e.isBoom).toBe(true); expect(e.output.statusCode).toBe(400); @@ -432,7 +437,7 @@ describe('create', () => { }); it(`Returns an error if postNewCase throws`, async () => { - const postCase: CaseClientPostRequest = { + const postCase: CasesClientPostRequest = { description: 'Throw an error', title: 'Super Bad Security Issue', tags: ['error'], @@ -450,9 +455,9 @@ describe('create', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client.create(postCase).catch((e) => { + return casesClient.client.create(postCase).catch((e) => { expect(e).not.toBeNull(); expect(isCaseError(e)).toBeTruthy(); const boomErr = e.boomify(); diff --git a/x-pack/plugins/case/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts similarity index 96% rename from x-pack/plugins/case/server/client/cases/create.ts rename to x-pack/plugins/cases/server/client/cases/create.ts index f88924483e0b8..59f9688836341 100644 --- a/x-pack/plugins/case/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -18,7 +18,7 @@ import { excess, CaseResponseRt, CaseResponse, - CaseClientPostRequestRt, + CasesClientPostRequestRt, CasePostRequest, CaseType, User, @@ -62,7 +62,7 @@ export const create = async ({ const { type = CaseType.individual, ...nonTypeCaseFields } = theCase; const query = pipe( // decode with the defaulted type field - excess(CaseClientPostRequestRt).decode({ type, ...nonTypeCaseFields }), + excess(CasesClientPostRequestRt).decode({ type, ...nonTypeCaseFields }), fold(throwErrors(Boom.badRequest), identity) ); diff --git a/x-pack/plugins/case/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts similarity index 100% rename from x-pack/plugins/case/server/client/cases/get.ts rename to x-pack/plugins/cases/server/client/cases/get.ts diff --git a/x-pack/plugins/case/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts similarity index 100% rename from x-pack/plugins/case/server/client/cases/mock.ts rename to x-pack/plugins/cases/server/client/cases/mock.ts diff --git a/x-pack/plugins/case/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts similarity index 96% rename from x-pack/plugins/case/server/client/cases/push.ts rename to x-pack/plugins/cases/server/client/cases/push.ts index 8aab11be21b01..3217178768f89 100644 --- a/x-pack/plugins/case/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -38,7 +38,7 @@ import { CaseServiceSetup, CaseUserActionServiceSetup, } from '../../services'; -import { CaseClientHandler } from '../client'; +import { CasesClientHandler } from '../client'; import { createCaseError } from '../../common/error'; /** @@ -65,7 +65,7 @@ interface PushParams { user: User; caseId: string; connectorId: string; - caseClient: CaseClientHandler; + casesClient: CasesClientHandler; actionsClient: ActionsClient; logger: Logger; } @@ -75,7 +75,7 @@ export const push = async ({ caseService, caseConfigureService, userActionService, - caseClient, + casesClient, actionsClient, connectorId, caseId, @@ -92,9 +92,9 @@ export const push = async ({ try { [theCase, connector, userActions] = await Promise.all([ - caseClient.get({ id: caseId, includeComments: true, includeSubCaseComments: true }), + casesClient.get({ id: caseId, includeComments: true, includeSubCaseComments: true }), actionsClient.get({ id: connectorId }), - caseClient.getUserActions({ caseId }), + casesClient.getUserActions({ caseId }), ]); } catch (e) { const message = `Error getting case and/or connector and/or user actions: ${e.message}`; @@ -111,7 +111,7 @@ export const push = async ({ const alertsInfo = getAlertInfoFromComments(theCase?.comments); try { - alerts = await caseClient.getAlerts({ + alerts = await casesClient.getAlerts({ alertsInfo, }); } catch (e) { @@ -123,7 +123,7 @@ export const push = async ({ } try { - connectorMappings = await caseClient.getMappings({ + connectorMappings = await casesClient.getMappings({ actionsClient, connectorId: connector.id, connectorType: connector.actionTypeId, diff --git a/x-pack/plugins/case/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts similarity index 100% rename from x-pack/plugins/case/server/client/cases/types.ts rename to x-pack/plugins/cases/server/client/cases/types.ts diff --git a/x-pack/plugins/case/server/client/cases/update.test.ts b/x-pack/plugins/cases/server/client/cases/update.test.ts similarity index 86% rename from x-pack/plugins/case/server/client/cases/update.test.ts rename to x-pack/plugins/cases/server/client/cases/update.test.ts index be68aa1266023..79c3b2838c3b2 100644 --- a/x-pack/plugins/case/server/client/cases/update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/update.test.ts @@ -13,7 +13,7 @@ import { mockCases, mockCaseComments, } from '../../routes/api/__fixtures__'; -import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; describe('update', () => { beforeEach(async () => { @@ -40,8 +40,8 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.update(patchCases); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.update(patchCases); expect(res).toMatchInlineSnapshot(` Array [ @@ -93,7 +93,7 @@ describe('update', () => { `); expect( - caseClient.services.userActionService.postUserActions.mock.calls[0][0].actions + casesClient.services.userActionService.postUserActions.mock.calls[0][0].actions ).toEqual([ { attributes: { @@ -140,8 +140,8 @@ describe('update', () => { ], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.update(patchCases); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.update(patchCases); expect(res).toMatchInlineSnapshot(` Array [ @@ -204,8 +204,8 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.update(patchCases); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.update(patchCases); expect(res).toMatchInlineSnapshot(` Array [ @@ -272,8 +272,8 @@ describe('update', () => { caseSavedObject: [mockCaseNoConnectorId], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.update(patchCases); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.update(patchCases); expect(res).toMatchInlineSnapshot(` Array [ @@ -344,8 +344,8 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.update(patchCases); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.update(patchCases); expect(res).toMatchInlineSnapshot(` Array [ @@ -424,12 +424,12 @@ describe('update', () => { ], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - caseClient.client.updateAlertsStatus = jest.fn(); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + casesClient.client.updateAlertsStatus = jest.fn(); - await caseClient.client.update(patchCases); + await casesClient.client.update(patchCases); - expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({ + expect(casesClient.client.updateAlertsStatus).toHaveBeenCalledWith({ alerts: [ { id: 'test-id', @@ -461,11 +461,11 @@ describe('update', () => { caseCommentSavedObject: [{ ...mockCaseComments[3] }], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); - await caseClient.client.update(patchCases); + await casesClient.client.update(patchCases); - expect(caseClient.esClient.bulk).not.toHaveBeenCalled(); + expect(casesClient.esClient.bulk).not.toHaveBeenCalled(); }); test('it updates alert status when syncAlerts is turned on', async () => { @@ -489,12 +489,12 @@ describe('update', () => { caseCommentSavedObject: [{ ...mockCaseComments[3] }], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - caseClient.client.updateAlertsStatus = jest.fn(); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + casesClient.client.updateAlertsStatus = jest.fn(); - await caseClient.client.update(patchCases); + await casesClient.client.update(patchCases); - expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({ + expect(casesClient.client.updateAlertsStatus).toHaveBeenCalledWith({ alerts: [{ id: 'test-id', index: 'test-index', status: 'open' }], }); }); @@ -515,11 +515,11 @@ describe('update', () => { caseCommentSavedObject: [{ ...mockCaseComments[3] }], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); - await caseClient.client.update(patchCases); + await casesClient.client.update(patchCases); - expect(caseClient.esClient.bulk).not.toHaveBeenCalled(); + expect(casesClient.esClient.bulk).not.toHaveBeenCalled(); }); test('it updates alert status for multiple cases', async () => { @@ -572,12 +572,12 @@ describe('update', () => { ], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - caseClient.client.updateAlertsStatus = jest.fn(); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + casesClient.client.updateAlertsStatus = jest.fn(); - await caseClient.client.update(patchCases); + await casesClient.client.update(patchCases); - expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({ + expect(casesClient.client.updateAlertsStatus).toHaveBeenCalledWith({ alerts: [ { id: 'test-id', index: 'test-index', status: 'open' }, { id: 'test-id-2', index: 'test-index-2', status: 'closed' }, @@ -600,11 +600,11 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); - await caseClient.client.update(patchCases); + await casesClient.client.update(patchCases); - expect(caseClient.esClient.bulk).not.toHaveBeenCalled(); + expect(casesClient.esClient.bulk).not.toHaveBeenCalled(); }); }); @@ -629,9 +629,9 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .update({ cases: patchCases }) .catch((e) => { @@ -662,9 +662,9 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); return ( - caseClient.client + casesClient.client // @ts-expect-error .update({ cases: patchCases }) .catch((e) => { @@ -691,8 +691,8 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client.update(patchCases).catch((e) => { + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client.update(patchCases).catch((e) => { expect(e).not.toBeNull(); expect(isCaseError(e)).toBeTruthy(); const boomErr = e.boomify(); @@ -723,8 +723,8 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client.update(patchCases).catch((e) => { + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client.update(patchCases).catch((e) => { expect(e).not.toBeNull(); expect(isCaseError(e)).toBeTruthy(); const boomErr = e.boomify(); @@ -752,8 +752,8 @@ describe('update', () => { caseSavedObject: mockCases, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client.update(patchCases).catch((e) => { + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client.update(patchCases).catch((e) => { expect(e).not.toBeNull(); expect(isCaseError(e)).toBeTruthy(); const boomErr = e.boomify(); diff --git a/x-pack/plugins/case/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts similarity index 98% rename from x-pack/plugins/case/server/client/cases/update.ts rename to x-pack/plugins/cases/server/client/cases/update.ts index 8c788d6f3bcd9..ff3c0a62407a1 100644 --- a/x-pack/plugins/case/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -51,7 +51,7 @@ import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT, } from '../../saved_object_types'; -import { CaseClientHandler } from '..'; +import { CasesClientHandler } from '..'; import { createAlertUpdateRequest } from '../../common'; import { UpdateAlertRequest } from '../types'; import { createCaseError } from '../../common/error'; @@ -254,14 +254,14 @@ async function updateAlerts({ casesMap, caseService, client, - caseClient, + casesClient, }: { casesWithSyncSettingChangedToOn: ESCasePatchRequest[]; casesWithStatusChangedAndSynced: ESCasePatchRequest[]; casesMap: Map>; caseService: CaseServiceSetup; client: SavedObjectsClientContract; - caseClient: CaseClientHandler; + casesClient: CasesClientHandler; }) { /** * It's possible that a case ID can appear multiple times in each array. I'm intentionally placing the status changes @@ -309,7 +309,7 @@ async function updateAlerts({ [] ); - await caseClient.updateAlertsStatus({ alerts: alertsToUpdate }); + await casesClient.updateAlertsStatus({ alerts: alertsToUpdate }); } interface UpdateArgs { @@ -317,7 +317,7 @@ interface UpdateArgs { caseService: CaseServiceSetup; userActionService: CaseUserActionServiceSetup; user: User; - caseClient: CaseClientHandler; + casesClient: CasesClientHandler; cases: CasesPatchRequest; logger: Logger; } @@ -327,7 +327,7 @@ export const update = async ({ caseService, userActionService, user, - caseClient, + casesClient, cases, logger, }: UpdateArgs): Promise => { @@ -470,7 +470,7 @@ export const update = async ({ casesWithSyncSettingChangedToOn, caseService, client: savedObjectsClient, - caseClient, + casesClient, casesMap, }); diff --git a/x-pack/plugins/case/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts similarity index 100% rename from x-pack/plugins/case/server/client/cases/utils.test.ts rename to x-pack/plugins/cases/server/client/cases/utils.test.ts diff --git a/x-pack/plugins/case/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts similarity index 95% rename from x-pack/plugins/case/server/client/cases/utils.ts rename to x-pack/plugins/cases/server/client/cases/utils.ts index 67d5ef55f83c3..7e77bf4ac84cc 100644 --- a/x-pack/plugins/case/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -23,7 +23,7 @@ import { } from '../../../common/api'; import { ActionsClient } from '../../../../actions/server'; import { externalServiceFormatters, FormatterConnectorTypes } from '../../connectors'; -import { CaseClientGetAlertsResponse } from '../../client/alerts/types'; +import { CasesClientGetAlertsResponse } from '../../client/alerts/types'; import { BasicParams, EntityInformation, @@ -46,7 +46,7 @@ interface CreateIncidentArgs { userActions: CaseUserActionsResponse; connector: ActionConnector; mappings: ConnectorMappingsAttributes[]; - alerts: CaseClientGetAlertsResponse; + alerts: CasesClientGetAlertsResponse; } export const getLatestPushInfo = ( @@ -210,22 +210,22 @@ export const FIELD_INFORMATION = ( ) => { switch (mode) { case 'create': - return i18n.translate('xpack.case.connectors.case.externalIncidentCreated', { + return i18n.translate('xpack.cases.connectors.cases.externalIncidentCreated', { values: { date, user }, defaultMessage: '(created at {date} by {user})', }); case 'update': - return i18n.translate('xpack.case.connectors.case.externalIncidentUpdated', { + return i18n.translate('xpack.cases.connectors.cases.externalIncidentUpdated', { values: { date, user }, defaultMessage: '(updated at {date} by {user})', }); case 'add': - return i18n.translate('xpack.case.connectors.case.externalIncidentAdded', { + return i18n.translate('xpack.cases.connectors.cases.externalIncidentAdded', { values: { date, user }, defaultMessage: '(added at {date} by {user})', }); default: - return i18n.translate('xpack.case.connectors.case.externalIncidentDefault', { + return i18n.translate('xpack.cases.connectors.cases.externalIncidentDefault', { values: { date, user }, defaultMessage: '(created at {date} by {user})', }); diff --git a/x-pack/plugins/case/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts similarity index 89% rename from x-pack/plugins/case/server/client/client.ts rename to x-pack/plugins/cases/server/client/client.ts index 9f4bf60677649..8f9058654d6fd 100644 --- a/x-pack/plugins/case/server/client/client.ts +++ b/x-pack/plugins/cases/server/client/client.ts @@ -7,16 +7,16 @@ import { ElasticsearchClient, SavedObjectsClientContract, Logger } from 'src/core/server'; import { - CaseClientFactoryArguments, - CaseClient, + CasesClientFactoryArguments, + CasesClient, ConfigureFields, MappingsClient, - CaseClientUpdateAlertsStatus, - CaseClientAddComment, - CaseClientGet, - CaseClientGetUserActions, - CaseClientGetAlerts, - CaseClientPush, + CasesClientUpdateAlertsStatus, + CasesClientAddComment, + CasesClientGet, + CasesClientGetUserActions, + CasesClientGetAlerts, + CasesClientPush, } from './types'; import { create } from './cases/create'; import { update } from './cases/update'; @@ -41,7 +41,7 @@ import { createCaseError } from '../common/error'; /** * This class is a pass through for common case functionality (like creating, get a case). */ -export class CaseClientHandler implements CaseClient { +export class CasesClientHandler implements CasesClient { private readonly _scopedClusterClient: ElasticsearchClient; private readonly _caseConfigureService: CaseConfigureServiceSetup; private readonly _caseService: CaseServiceSetup; @@ -52,7 +52,7 @@ export class CaseClientHandler implements CaseClient { private readonly _alertsService: AlertServiceContract; private readonly logger: Logger; - constructor(clientArgs: CaseClientFactoryArguments) { + constructor(clientArgs: CasesClientFactoryArguments) { this._scopedClusterClient = clientArgs.scopedClusterClient; this._caseConfigureService = clientArgs.caseConfigureService; this._caseService = clientArgs.caseService; @@ -92,7 +92,7 @@ export class CaseClientHandler implements CaseClient { userActionService: this._userActionService, user: this.user, cases, - caseClient: this, + casesClient: this, logger: this.logger, }); } catch (error) { @@ -108,13 +108,13 @@ export class CaseClientHandler implements CaseClient { } } - public async addComment({ caseId, comment }: CaseClientAddComment) { + public async addComment({ caseId, comment }: CasesClientAddComment) { try { return addComment({ savedObjectsClient: this._savedObjectsClient, caseService: this._caseService, userActionService: this._userActionService, - caseClient: this, + casesClient: this, caseId, comment, user: this.user, @@ -147,7 +147,7 @@ export class CaseClientHandler implements CaseClient { ...args, savedObjectsClient: this._savedObjectsClient, connectorMappingsService: this._connectorMappingsService, - caseClient: this, + casesClient: this, logger: this.logger, }); } catch (error) { @@ -159,7 +159,7 @@ export class CaseClientHandler implements CaseClient { } } - public async updateAlertsStatus(args: CaseClientUpdateAlertsStatus) { + public async updateAlertsStatus(args: CasesClientUpdateAlertsStatus) { try { return updateAlertsStatus({ ...args, @@ -178,7 +178,7 @@ export class CaseClientHandler implements CaseClient { } } - public async get(args: CaseClientGet) { + public async get(args: CasesClientGet) { try { return get({ ...args, @@ -192,7 +192,7 @@ export class CaseClientHandler implements CaseClient { } } - public async getUserActions(args: CaseClientGetUserActions) { + public async getUserActions(args: CasesClientGetUserActions) { try { return getUserActions({ ...args, @@ -208,7 +208,7 @@ export class CaseClientHandler implements CaseClient { } } - public async getAlerts(args: CaseClientGetAlerts) { + public async getAlerts(args: CasesClientGetAlerts) { try { return getAlerts({ ...args, @@ -227,7 +227,7 @@ export class CaseClientHandler implements CaseClient { } } - public async push(args: CaseClientPush) { + public async push(args: CasesClientPush) { try { return push({ ...args, @@ -235,7 +235,7 @@ export class CaseClientHandler implements CaseClient { caseService: this._caseService, userActionService: this._userActionService, user: this.user, - caseClient: this, + casesClient: this, caseConfigureService: this._caseConfigureService, logger: this.logger, }); diff --git a/x-pack/plugins/case/server/client/comments/add.test.ts b/x-pack/plugins/cases/server/client/comments/add.test.ts similarity index 85% rename from x-pack/plugins/case/server/client/comments/add.test.ts rename to x-pack/plugins/cases/server/client/comments/add.test.ts index 460a03643b63d..23b7bc37dc814 100644 --- a/x-pack/plugins/case/server/client/comments/add.test.ts +++ b/x-pack/plugins/cases/server/client/comments/add.test.ts @@ -13,7 +13,7 @@ import { mockCaseComments, mockCases, } from '../../routes/api/__fixtures__'; -import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; type AlertComment = CommentType.alert | CommentType.generatedAlert; @@ -33,8 +33,8 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.addComment({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -71,8 +71,8 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.addComment({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { type: CommentType.alert, @@ -119,8 +119,8 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.addComment({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -142,8 +142,8 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - await caseClient.client.addComment({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -152,7 +152,7 @@ describe('addComment', () => { }); expect( - caseClient.services.userActionService.postUserActions.mock.calls[0][0].actions + casesClient.services.userActionService.postUserActions.mock.calls[0][0].actions ).toEqual([ { attributes: { @@ -189,11 +189,11 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient, badAuth: true, }); - const res = await caseClient.client.addComment({ + const res = await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -229,14 +229,14 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient, badAuth: true, }); - caseClient.client.updateAlertsStatus = jest.fn(); + casesClient.client.updateAlertsStatus = jest.fn(); - await caseClient.client.addComment({ + await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { type: CommentType.alert, @@ -249,7 +249,7 @@ describe('addComment', () => { }, }); - expect(caseClient.client.updateAlertsStatus).toHaveBeenCalledWith({ + expect(casesClient.client.updateAlertsStatus).toHaveBeenCalledWith({ alerts: [{ id: 'test-alert', index: 'test-index', status: 'open' }], }); }); @@ -265,14 +265,14 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient, badAuth: true, }); - caseClient.client.updateAlertsStatus = jest.fn(); + casesClient.client.updateAlertsStatus = jest.fn(); - await caseClient.client.addComment({ + await casesClient.client.addComment({ caseId: 'mock-id-1', comment: { type: CommentType.alert, @@ -285,7 +285,7 @@ describe('addComment', () => { }, }); - expect(caseClient.client.updateAlertsStatus).not.toHaveBeenCalled(); + expect(casesClient.client.updateAlertsStatus).not.toHaveBeenCalled(); }); }); @@ -297,8 +297,8 @@ describe('addComment', () => { caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client .addComment({ caseId: 'mock-id-1', // @ts-expect-error @@ -319,7 +319,7 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); const allRequestAttributes = { type: CommentType.user, comment: 'a comment', @@ -327,7 +327,7 @@ describe('addComment', () => { ['comment'].forEach((attribute) => { const requestAttributes = omit(attribute, allRequestAttributes); - return caseClient.client + return casesClient.client .addComment({ caseId: 'mock-id-1', // @ts-expect-error @@ -351,10 +351,10 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); ['alertId', 'index'].forEach((attribute) => { - return caseClient.client + return casesClient.client .addComment({ caseId: 'mock-id-1', comment: { @@ -379,7 +379,7 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); const allRequestAttributes = { type: CommentType.alert, index: 'test-index', @@ -388,7 +388,7 @@ describe('addComment', () => { ['alertId', 'index'].forEach((attribute) => { const requestAttributes = omit(attribute, allRequestAttributes); - return caseClient.client + return casesClient.client .addComment({ caseId: 'mock-id-1', // @ts-expect-error @@ -412,10 +412,10 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); ['comment'].forEach((attribute) => { - return caseClient.client + return casesClient.client .addComment({ caseId: 'mock-id-1', comment: { @@ -444,8 +444,8 @@ describe('addComment', () => { caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client .addComment({ caseId: 'not-exists', comment: { @@ -469,8 +469,8 @@ describe('addComment', () => { caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client .addComment({ caseId: 'mock-id-1', comment: { @@ -495,8 +495,8 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - return caseClient.client + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + return casesClient.client .addComment({ caseId: 'mock-id-4', comment: { @@ -534,11 +534,11 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient, }); await expect( - caseClient.client.addComment({ + casesClient.client.addComment({ caseId: 'mock-id-4', comment: { // casting because type must be either alert or generatedAlert but type is CommentType @@ -568,11 +568,11 @@ describe('addComment', () => { caseCommentSavedObject: mockCaseComments, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient, }); await expect( - caseClient.client.addComment({ + casesClient.client.addComment({ caseId: 'mock-id-1', comment: { // casting because type must be either alert or generatedAlert but type is CommentType diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/cases/server/client/comments/add.ts similarity index 97% rename from x-pack/plugins/case/server/client/comments/add.ts rename to x-pack/plugins/cases/server/client/comments/add.ts index 22a59e4d0539b..45746613dc1d4 100644 --- a/x-pack/plugins/case/server/client/comments/add.ts +++ b/x-pack/plugins/cases/server/client/comments/add.ts @@ -33,7 +33,7 @@ import { import { CaseServiceSetup, CaseUserActionServiceSetup } from '../../services'; import { CommentableCase, createAlertUpdateRequest } from '../../common'; -import { CaseClientHandler } from '..'; +import { CasesClientHandler } from '..'; import { createCaseError } from '../../common/error'; import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types'; import { MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants'; @@ -95,7 +95,7 @@ async function getSubCase({ } interface AddCommentFromRuleArgs { - caseClient: CaseClientHandler; + casesClient: CasesClientHandler; caseId: string; comment: CommentRequestAlertType; savedObjectsClient: SavedObjectsClientContract; @@ -108,7 +108,7 @@ const addGeneratedAlerts = async ({ savedObjectsClient, caseService, userActionService, - caseClient, + casesClient, caseId, comment, logger, @@ -177,7 +177,7 @@ const addGeneratedAlerts = async ({ comment: query, status: subCase.attributes.status, }); - await caseClient.updateAlertsStatus({ + await casesClient.updateAlertsStatus({ alerts: alertsToUpdate, }); } @@ -261,7 +261,7 @@ async function getCombinedCase({ } interface AddCommentArgs { - caseClient: CaseClientHandler; + casesClient: CasesClientHandler; caseId: string; comment: CommentRequest; savedObjectsClient: SavedObjectsClientContract; @@ -275,7 +275,7 @@ export const addComment = async ({ savedObjectsClient, caseService, userActionService, - caseClient, + casesClient, caseId, comment, user, @@ -290,7 +290,7 @@ export const addComment = async ({ return addGeneratedAlerts({ caseId, comment, - caseClient, + casesClient, savedObjectsClient, userActionService, caseService, @@ -329,7 +329,7 @@ export const addComment = async ({ status: updatedCase.status, }); - await caseClient.updateAlertsStatus({ + await casesClient.updateAlertsStatus({ alerts: alertsToUpdate, }); } diff --git a/x-pack/plugins/case/server/client/configure/get_fields.test.ts b/x-pack/plugins/cases/server/client/configure/get_fields.test.ts similarity index 84% rename from x-pack/plugins/case/server/client/configure/get_fields.test.ts rename to x-pack/plugins/cases/server/client/configure/get_fields.test.ts index 55355b33baead..2e2973516d0fd 100644 --- a/x-pack/plugins/case/server/client/configure/get_fields.test.ts +++ b/x-pack/plugins/cases/server/client/configure/get_fields.test.ts @@ -8,7 +8,7 @@ import { ConnectorTypes } from '../../../common/api'; import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; -import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; import { actionsErrResponse, mappings, mockGetFieldsResponse } from './mock'; describe('get_fields', () => { @@ -23,8 +23,8 @@ describe('get_fields', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseMappingsSavedObject: mockCaseMappings, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.getFields({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.getFields({ actionsClient: actionsMock, connectorType: ConnectorTypes.jira, connectorId: '123', @@ -44,8 +44,8 @@ describe('get_fields', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseMappingsSavedObject: mockCaseMappings, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - await caseClient.client + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + await casesClient.client .getFields({ actionsClient: { ...actionsMock, execute: jest.fn().mockReturnValue(actionsErrResponse) }, connectorType: ConnectorTypes.jira, diff --git a/x-pack/plugins/case/server/client/configure/get_fields.ts b/x-pack/plugins/cases/server/client/configure/get_fields.ts similarity index 100% rename from x-pack/plugins/case/server/client/configure/get_fields.ts rename to x-pack/plugins/cases/server/client/configure/get_fields.ts diff --git a/x-pack/plugins/case/server/client/configure/get_mappings.test.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.test.ts similarity index 81% rename from x-pack/plugins/case/server/client/configure/get_mappings.test.ts rename to x-pack/plugins/cases/server/client/configure/get_mappings.test.ts index d4dad182d815e..0ec2fc8b4621d 100644 --- a/x-pack/plugins/case/server/client/configure/get_mappings.test.ts +++ b/x-pack/plugins/cases/server/client/configure/get_mappings.test.ts @@ -8,7 +8,7 @@ import { ConnectorTypes } from '../../../common/api'; import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; -import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; import { mappings, mockGetFieldsResponse } from './mock'; @@ -28,8 +28,8 @@ describe('get_mappings', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseMappingsSavedObject: mockCaseMappings, }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.getMappings({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.getMappings({ actionsClient: actionsMock, connectorType: ConnectorTypes.jira, connectorId: '123', @@ -41,8 +41,8 @@ describe('get_mappings', () => { const savedObjectsClient = createMockSavedObjectsRepository({ caseMappingsSavedObject: [], }); - const caseClient = await createCaseClientWithMockSavedObjectsClient({ savedObjectsClient }); - const res = await caseClient.client.getMappings({ + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.getMappings({ actionsClient: actionsMock, connectorType: ConnectorTypes.jira, connectorId: '123', diff --git a/x-pack/plugins/case/server/client/configure/get_mappings.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.ts similarity index 94% rename from x-pack/plugins/case/server/client/configure/get_mappings.ts rename to x-pack/plugins/cases/server/client/configure/get_mappings.ts index 5553580a41560..558c961f89e5b 100644 --- a/x-pack/plugins/case/server/client/configure/get_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/get_mappings.ts @@ -11,14 +11,14 @@ import { ConnectorMappingsAttributes, ConnectorTypes } from '../../../common/api // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server/saved_objects'; import { ConnectorMappingsServiceSetup } from '../../services'; -import { CaseClientHandler } from '..'; +import { CasesClientHandler } from '..'; import { createCaseError } from '../../common/error'; interface GetMappingsArgs { savedObjectsClient: SavedObjectsClientContract; connectorMappingsService: ConnectorMappingsServiceSetup; actionsClient: ActionsClient; - caseClient: CaseClientHandler; + casesClient: CasesClientHandler; connectorType: string; connectorId: string; logger: Logger; @@ -28,7 +28,7 @@ export const getMappings = async ({ savedObjectsClient, connectorMappingsService, actionsClient, - caseClient, + casesClient, connectorType, connectorId, logger, @@ -49,7 +49,7 @@ export const getMappings = async ({ let theMapping; // Create connector mappings if there are none if (myConnectorMappings.total === 0) { - const res = await caseClient.getFields({ + const res = await casesClient.getFields({ actionsClient, connectorId, connectorType, diff --git a/x-pack/plugins/case/server/client/configure/mock.ts b/x-pack/plugins/cases/server/client/configure/mock.ts similarity index 100% rename from x-pack/plugins/case/server/client/configure/mock.ts rename to x-pack/plugins/cases/server/client/configure/mock.ts diff --git a/x-pack/plugins/case/server/client/configure/utils.test.ts b/x-pack/plugins/cases/server/client/configure/utils.test.ts similarity index 100% rename from x-pack/plugins/case/server/client/configure/utils.test.ts rename to x-pack/plugins/cases/server/client/configure/utils.test.ts diff --git a/x-pack/plugins/case/server/client/configure/utils.ts b/x-pack/plugins/cases/server/client/configure/utils.ts similarity index 100% rename from x-pack/plugins/case/server/client/configure/utils.ts rename to x-pack/plugins/cases/server/client/configure/utils.ts diff --git a/x-pack/plugins/case/server/client/index.test.ts b/x-pack/plugins/cases/server/client/index.test.ts similarity index 85% rename from x-pack/plugins/case/server/client/index.test.ts rename to x-pack/plugins/cases/server/client/index.test.ts index 6f4b4b136f68f..cfb30d6d5bcb6 100644 --- a/x-pack/plugins/case/server/client/index.test.ts +++ b/x-pack/plugins/cases/server/client/index.test.ts @@ -20,8 +20,8 @@ import { } from '../services/mocks'; jest.mock('./client'); -import { CaseClientHandler } from './client'; -import { createExternalCaseClient } from './index'; +import { CasesClientHandler } from './client'; +import { createExternalCasesClient } from './index'; const logger = loggingSystemMock.create().get('case'); const esClient = elasticsearchServiceMock.createElasticsearchClient(); @@ -32,9 +32,9 @@ const connectorMappingsService = connectorMappingsServiceMock(); const savedObjectsClient = savedObjectsClientMock.create(); const userActionService = createUserActionServiceMock(); -describe('createExternalCaseClient()', () => { +describe('createExternalCasesClient()', () => { test('it creates the client correctly', async () => { - createExternalCaseClient({ + createExternalCasesClient({ scopedClusterClient: esClient, alertsService, caseConfigureService, @@ -45,6 +45,6 @@ describe('createExternalCaseClient()', () => { userActionService, logger, }); - expect(CaseClientHandler).toHaveBeenCalledTimes(1); + expect(CasesClientHandler).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/plugins/cases/server/client/index.ts b/x-pack/plugins/cases/server/client/index.ts new file mode 100644 index 0000000000000..fd7cae0edd2ea --- /dev/null +++ b/x-pack/plugins/cases/server/client/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CasesClientFactoryArguments, CasesClient } from './types'; +import { CasesClientHandler } from './client'; + +export { CasesClientHandler } from './client'; +export { CasesClient } from './types'; + +/** + * Create a CasesClientHandler to external services (other plugins). + */ +export const createExternalCasesClient = (clientArgs: CasesClientFactoryArguments): CasesClient => { + const client = new CasesClientHandler(clientArgs); + return client; +}; diff --git a/x-pack/plugins/case/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts similarity index 85% rename from x-pack/plugins/case/server/client/mocks.ts rename to x-pack/plugins/cases/server/client/mocks.ts index 5cbd31c79885e..51119070a798d 100644 --- a/x-pack/plugins/case/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -15,12 +15,12 @@ import { CaseUserActionServiceSetup, ConnectorMappingsService, } from '../services'; -import { CaseClient } from './types'; +import { CasesClient } from './types'; import { authenticationMock } from '../routes/api/__fixtures__'; -import { createExternalCaseClient } from '.'; +import { createExternalCasesClient } from '.'; -export type CaseClientPluginContractMock = jest.Mocked; -export const createExternalCaseClientMock = (): CaseClientPluginContractMock => ({ +export type CasesClientPluginContractMock = jest.Mocked; +export const createExternalCasesClientMock = (): CasesClientPluginContractMock => ({ addComment: jest.fn(), create: jest.fn(), get: jest.fn(), @@ -33,7 +33,7 @@ export const createExternalCaseClientMock = (): CaseClientPluginContractMock => updateAlertsStatus: jest.fn(), }); -export const createCaseClientWithMockSavedObjectsClient = async ({ +export const createCasesClientWithMockSavedObjectsClient = async ({ savedObjectsClient, badAuth = false, omitFromContext = [], @@ -42,7 +42,7 @@ export const createCaseClientWithMockSavedObjectsClient = async ({ badAuth?: boolean; omitFromContext?: string[]; }): Promise<{ - client: CaseClient; + client: CasesClient; services: { userActionService: jest.Mocked; alertsService: jest.Mocked; @@ -71,7 +71,7 @@ export const createCaseClientWithMockSavedObjectsClient = async ({ getAlerts: jest.fn(), }; - const caseClient = createExternalCaseClient({ + const casesClient = createExternalCasesClient({ savedObjectsClient, user: auth.getCurrentUser(), caseService, @@ -83,7 +83,7 @@ export const createCaseClientWithMockSavedObjectsClient = async ({ logger: log, }); return { - client: caseClient, + client: casesClient, services: { userActionService, alertsService }, esClient, }; diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts similarity index 75% rename from x-pack/plugins/case/server/client/types.ts rename to x-pack/plugins/cases/server/client/types.ts index 3f4ef77d7f348..c62b3913da763 100644 --- a/x-pack/plugins/case/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -27,34 +27,34 @@ import { AlertServiceContract, } from '../services'; import { ConnectorMappingsServiceSetup } from '../services/connector_mappings'; -import { CaseClientGetAlertsResponse } from './alerts/types'; +import { CasesClientGetAlertsResponse } from './alerts/types'; -export interface CaseClientGet { +export interface CasesClientGet { id: string; includeComments?: boolean; includeSubCaseComments?: boolean; } -export interface CaseClientPush { +export interface CasesClientPush { actionsClient: ActionsClient; caseId: string; connectorId: string; } -export interface CaseClientAddComment { +export interface CasesClientAddComment { caseId: string; comment: CommentRequest; } -export interface CaseClientUpdateAlertsStatus { +export interface CasesClientUpdateAlertsStatus { alerts: UpdateAlertRequest[]; } -export interface CaseClientGetAlerts { +export interface CasesClientGetAlerts { alertsInfo: AlertInfo[]; } -export interface CaseClientGetUserActions { +export interface CasesClientGetUserActions { caseId: string; subCaseId?: string; } @@ -65,7 +65,7 @@ export interface MappingsClient { connectorType: string; } -export interface CaseClientFactoryArguments { +export interface CasesClientFactoryArguments { scopedClusterClient: ElasticsearchClient; caseConfigureService: CaseConfigureServiceSetup; caseService: CaseServiceSetup; @@ -95,17 +95,17 @@ export interface UpdateAlertRequest { /** * This represents the interface that other plugins can access. */ -export interface CaseClient { - addComment(args: CaseClientAddComment): Promise; +export interface CasesClient { + addComment(args: CasesClientAddComment): Promise; create(theCase: CasePostRequest): Promise; - get(args: CaseClientGet): Promise; - getAlerts(args: CaseClientGetAlerts): Promise; + get(args: CasesClientGet): Promise; + getAlerts(args: CasesClientGetAlerts): Promise; getFields(args: ConfigureFields): Promise; getMappings(args: MappingsClient): Promise; - getUserActions(args: CaseClientGetUserActions): Promise; - push(args: CaseClientPush): Promise; + getUserActions(args: CasesClientGetUserActions): Promise; + push(args: CasesClientPush): Promise; update(args: CasesPatchRequest): Promise; - updateAlertsStatus(args: CaseClientUpdateAlertsStatus): Promise; + updateAlertsStatus(args: CasesClientUpdateAlertsStatus): Promise; } export interface MappingsClient { diff --git a/x-pack/plugins/case/server/client/user_actions/get.ts b/x-pack/plugins/cases/server/client/user_actions/get.ts similarity index 100% rename from x-pack/plugins/case/server/client/user_actions/get.ts rename to x-pack/plugins/cases/server/client/user_actions/get.ts diff --git a/x-pack/plugins/case/server/common/error.ts b/x-pack/plugins/cases/server/common/error.ts similarity index 100% rename from x-pack/plugins/case/server/common/error.ts rename to x-pack/plugins/cases/server/common/error.ts diff --git a/x-pack/plugins/case/server/common/index.ts b/x-pack/plugins/cases/server/common/index.ts similarity index 100% rename from x-pack/plugins/case/server/common/index.ts rename to x-pack/plugins/cases/server/common/index.ts diff --git a/x-pack/plugins/case/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts similarity index 100% rename from x-pack/plugins/case/server/common/models/commentable_case.ts rename to x-pack/plugins/cases/server/common/models/commentable_case.ts diff --git a/x-pack/plugins/case/server/common/models/index.ts b/x-pack/plugins/cases/server/common/models/index.ts similarity index 100% rename from x-pack/plugins/case/server/common/models/index.ts rename to x-pack/plugins/cases/server/common/models/index.ts diff --git a/x-pack/plugins/case/server/common/types.ts b/x-pack/plugins/cases/server/common/types.ts similarity index 100% rename from x-pack/plugins/case/server/common/types.ts rename to x-pack/plugins/cases/server/common/types.ts diff --git a/x-pack/plugins/case/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts similarity index 100% rename from x-pack/plugins/case/server/common/utils.test.ts rename to x-pack/plugins/cases/server/common/utils.test.ts diff --git a/x-pack/plugins/case/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts similarity index 100% rename from x-pack/plugins/case/server/common/utils.ts rename to x-pack/plugins/cases/server/common/utils.ts diff --git a/x-pack/plugins/case/server/config.ts b/x-pack/plugins/cases/server/config.ts similarity index 100% rename from x-pack/plugins/case/server/config.ts rename to x-pack/plugins/cases/server/config.ts diff --git a/x-pack/plugins/case/server/connectors/case/index.test.ts b/x-pack/plugins/cases/server/connectors/case/index.test.ts similarity index 98% rename from x-pack/plugins/case/server/connectors/case/index.test.ts rename to x-pack/plugins/cases/server/connectors/case/index.test.ts index e4c29bb099f0e..122f6bd77c693 100644 --- a/x-pack/plugins/case/server/connectors/case/index.test.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.test.ts @@ -28,12 +28,12 @@ import { } from '../../services/mocks'; import { CaseActionType, CaseActionTypeExecutorOptions, CaseExecutorParams } from './types'; import { getActionType } from '.'; -import { createExternalCaseClientMock } from '../../client/mocks'; +import { createExternalCasesClientMock } from '../../client/mocks'; -const mockCaseClient = createExternalCaseClientMock(); +const mockCasesClient = createExternalCasesClientMock(); jest.mock('../../client', () => ({ - createExternalCaseClient: () => mockCaseClient, + createExternalCasesClient: () => mockCasesClient, })); const services = actionsMock.createServices(); @@ -877,7 +877,7 @@ describe('case connector', () => { }, }; - mockCaseClient.create.mockReturnValue(Promise.resolve(createReturn)); + mockCasesClient.create.mockReturnValue(Promise.resolve(createReturn)); const actionId = 'some-id'; const params: CaseExecutorParams = { @@ -913,7 +913,7 @@ describe('case connector', () => { const result = await caseActionType.executor(executorOptions); expect(result).toEqual({ actionId, status: 'ok', data: createReturn }); - expect(mockCaseClient.create).toHaveBeenCalledWith({ + expect(mockCasesClient.create).toHaveBeenCalledWith({ ...params.subActionParams, connector: { id: 'jira', @@ -974,7 +974,7 @@ describe('case connector', () => { }, ]; - mockCaseClient.update.mockReturnValue(Promise.resolve(updateReturn)); + mockCasesClient.update.mockReturnValue(Promise.resolve(updateReturn)); const actionId = 'some-id'; const params: CaseExecutorParams = { @@ -1002,7 +1002,7 @@ describe('case connector', () => { const result = await caseActionType.executor(executorOptions); expect(result).toEqual({ actionId, status: 'ok', data: updateReturn }); - expect(mockCaseClient.update).toHaveBeenCalledWith({ + expect(mockCasesClient.update).toHaveBeenCalledWith({ // Null values have been striped out. cases: [ { @@ -1064,7 +1064,7 @@ describe('case connector', () => { }, }; - mockCaseClient.addComment.mockReturnValue(Promise.resolve(commentReturn)); + mockCasesClient.addComment.mockReturnValue(Promise.resolve(commentReturn)); const actionId = 'some-id'; const params: CaseExecutorParams = { @@ -1089,7 +1089,7 @@ describe('case connector', () => { const result = await caseActionType.executor(executorOptions); expect(result).toEqual({ actionId, status: 'ok', data: commentReturn }); - expect(mockCaseClient.addComment).toHaveBeenCalledWith({ + expect(mockCasesClient.addComment).toHaveBeenCalledWith({ caseId: 'case-id', comment: { comment: 'a comment', diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/cases/server/connectors/case/index.ts similarity index 95% rename from x-pack/plugins/case/server/connectors/case/index.ts rename to x-pack/plugins/cases/server/connectors/case/index.ts index 4a1d0569bde6d..da993faf0ef5c 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.ts @@ -14,7 +14,7 @@ import { CommentRequest, CommentType, } from '../../../common/api'; -import { createExternalCaseClient } from '../../client'; +import { createExternalCasesClient } from '../../client'; import { CaseExecutorParamsSchema, CaseConfigurationSchema, CommentSchemaType } from './schema'; import { CaseExecutorResponse, @@ -75,7 +75,7 @@ async function executor( let data: CaseExecutorResponse | null = null; const { savedObjectsClient, scopedClusterClient } = services; - const caseClient = createExternalCaseClient({ + const casesClient = createExternalCasesClient({ savedObjectsClient, scopedClusterClient, // we might want the user information to be passed as part of the action request @@ -96,7 +96,7 @@ async function executor( if (subAction === 'create') { try { - data = await caseClient.create({ + data = await casesClient.create({ ...(subActionParams as CasePostRequest), }); } catch (error) { @@ -118,7 +118,7 @@ async function executor( ); try { - data = await caseClient.update({ cases: [updateParamsWithoutNullValues] }); + data = await casesClient.update({ cases: [updateParamsWithoutNullValues] }); } catch (error) { throw createCaseError({ message: `Failed to update case using connector id: ${updateParamsWithoutNullValues?.id} version: ${updateParamsWithoutNullValues?.version}: ${error}`, @@ -132,7 +132,7 @@ async function executor( const { caseId, comment } = subActionParams as ExecutorSubActionAddCommentParams; try { const formattedComment = transformConnectorComment(comment, logger); - data = await caseClient.addComment({ caseId, comment: formattedComment }); + data = await casesClient.addComment({ caseId, comment: formattedComment }); } catch (error) { throw createCaseError({ message: `Failed to create comment using connector case id: ${caseId}: ${error}`, diff --git a/x-pack/plugins/case/server/connectors/case/schema.ts b/x-pack/plugins/cases/server/connectors/case/schema.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/case/schema.ts rename to x-pack/plugins/cases/server/connectors/case/schema.ts diff --git a/x-pack/plugins/case/server/connectors/case/translations.ts b/x-pack/plugins/cases/server/connectors/case/translations.ts similarity index 80% rename from x-pack/plugins/case/server/connectors/case/translations.ts rename to x-pack/plugins/cases/server/connectors/case/translations.ts index e6434b454e846..aa14ee5aa7a6e 100644 --- a/x-pack/plugins/case/server/connectors/case/translations.ts +++ b/x-pack/plugins/cases/server/connectors/case/translations.ts @@ -7,6 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const NAME = i18n.translate('xpack.case.connectors.case.title', { +export const NAME = i18n.translate('xpack.cases.connectors.cases.title', { defaultMessage: 'Case', }); diff --git a/x-pack/plugins/case/server/connectors/case/types.ts b/x-pack/plugins/cases/server/connectors/case/types.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/case/types.ts rename to x-pack/plugins/cases/server/connectors/case/types.ts diff --git a/x-pack/plugins/case/server/connectors/case/validators.ts b/x-pack/plugins/cases/server/connectors/case/validators.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/case/validators.ts rename to x-pack/plugins/cases/server/connectors/case/validators.ts diff --git a/x-pack/plugins/case/server/connectors/index.ts b/x-pack/plugins/cases/server/connectors/index.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/index.ts rename to x-pack/plugins/cases/server/connectors/index.ts diff --git a/x-pack/plugins/case/server/connectors/jira/external_service_formatter.test.ts b/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/jira/external_service_formatter.test.ts rename to x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts diff --git a/x-pack/plugins/case/server/connectors/jira/external_service_formatter.ts b/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/jira/external_service_formatter.ts rename to x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts diff --git a/x-pack/plugins/case/server/connectors/resilient/external_service_formatter.test.ts b/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/resilient/external_service_formatter.test.ts rename to x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts diff --git a/x-pack/plugins/case/server/connectors/resilient/external_service_formatter.ts b/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/resilient/external_service_formatter.ts rename to x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts diff --git a/x-pack/plugins/case/server/connectors/servicenow/itsm_formatter.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/servicenow/itsm_formatter.ts rename to x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts diff --git a/x-pack/plugins/case/server/connectors/servicenow/itsm_formmater.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/servicenow/itsm_formmater.test.ts rename to x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts diff --git a/x-pack/plugins/case/server/connectors/servicenow/sir_formatter.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/servicenow/sir_formatter.test.ts rename to x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts diff --git a/x-pack/plugins/case/server/connectors/servicenow/sir_formatter.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts similarity index 100% rename from x-pack/plugins/case/server/connectors/servicenow/sir_formatter.ts rename to x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts diff --git a/x-pack/plugins/case/server/connectors/types.ts b/x-pack/plugins/cases/server/connectors/types.ts similarity index 91% rename from x-pack/plugins/case/server/connectors/types.ts rename to x-pack/plugins/cases/server/connectors/types.ts index ffda6f96ae3ba..f6c284b74667b 100644 --- a/x-pack/plugins/case/server/connectors/types.ts +++ b/x-pack/plugins/cases/server/connectors/types.ts @@ -14,7 +14,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../actions/server/types'; import { CaseResponse, ConnectorTypes } from '../../common/api'; -import { CaseClientGetAlertsResponse } from '../client/alerts/types'; +import { CasesClientGetAlertsResponse } from '../client/alerts/types'; import { CaseServiceSetup, CaseConfigureServiceSetup, @@ -52,7 +52,7 @@ export interface RegisterConnectorsArgs extends GetActionTypeParams { export type FormatterConnectorTypes = Exclude; export interface ExternalServiceFormatter { - format: (theCase: CaseResponse, alerts: CaseClientGetAlertsResponse) => TExternalServiceParams; + format: (theCase: CaseResponse, alerts: CasesClientGetAlertsResponse) => TExternalServiceParams; } export type ExternalServiceFormatterMapper = { diff --git a/x-pack/plugins/case/server/index.ts b/x-pack/plugins/cases/server/index.ts similarity index 100% rename from x-pack/plugins/case/server/index.ts rename to x-pack/plugins/cases/server/index.ts diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts similarity index 94% rename from x-pack/plugins/case/server/plugin.ts rename to x-pack/plugins/cases/server/plugin.ts index 43daa51958429..0c661cc18c21b 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -34,7 +34,7 @@ import { AlertService, AlertServiceContract, } from './services'; -import { CaseClientHandler, createExternalCaseClient } from './client'; +import { CasesClientHandler, createExternalCasesClient } from './client'; import { registerConnectors } from './connectors'; import type { CasesRequestHandlerContext } from './types'; @@ -88,7 +88,7 @@ export class CasePlugin { this.userActionService = await new CaseUserActionService(this.log).setup(); this.alertsService = new AlertService(); - core.http.registerRouteHandlerContext( + core.http.registerRouteHandlerContext( APP_ID, this.createRouteHandlerContext({ core, @@ -125,12 +125,12 @@ export class CasePlugin { public start(core: CoreStart) { this.log.debug(`Starting Case Workflow`); - const getCaseClientWithRequestAndContext = async ( + const getCasesClientWithRequestAndContext = async ( context: CasesRequestHandlerContext, request: KibanaRequest ) => { const user = await this.caseService!.getUser({ request }); - return createExternalCaseClient({ + return createExternalCasesClient({ scopedClusterClient: context.core.elasticsearch.client.asCurrentUser, savedObjectsClient: core.savedObjects.getScopedClient(request), user, @@ -144,7 +144,7 @@ export class CasePlugin { }; return { - getCaseClientWithRequestAndContext, + getCasesClientWithRequestAndContext, }; } @@ -168,13 +168,13 @@ export class CasePlugin { userActionService: CaseUserActionServiceSetup; alertsService: AlertServiceContract; logger: Logger; - }): IContextProvider => { + }): IContextProvider => { return async (context, request, response) => { const [{ savedObjects }] = await core.getStartServices(); const user = await caseService.getUser({ request }); return { - getCaseClient: () => { - return new CaseClientHandler({ + getCasesClient: () => { + return new CasesClientHandler({ scopedClusterClient: context.core.elasticsearch.client.asCurrentUser, savedObjectsClient: savedObjects.getScopedClient(request), caseService, diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/authc_mock.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/authc_mock.ts diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/create_mock_so_repository.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/create_mock_so_repository.ts diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/index.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/__fixtures__/index.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/index.ts diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_actions_client.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_actions_client.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/__fixtures__/mock_actions_client.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/mock_actions_client.ts diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_router.ts similarity index 96% rename from x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/mock_router.ts index 6b0c4adf9a680..18cce1b087e5d 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts +++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_router.ts @@ -18,7 +18,7 @@ export const createRoute = async ( const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); - const log = loggingSystemMock.create().get('case'); + const log = loggingSystemMock.create().get('cases'); const auth = badAuth ? authenticationMock.createInvalid() : authenticationMock.create(); const caseService = new CaseService(log, auth); const caseConfigureServicePlugin = new CaseConfigureService(log); diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/route_contexts.ts similarity index 92% rename from x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts rename to x-pack/plugins/cases/server/routes/api/__fixtures__/route_contexts.ts index 7f66602c61fff..42e8561c2ac54 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/route_contexts.ts @@ -6,7 +6,7 @@ */ import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; -import { createExternalCaseClient } from '../../../client'; +import { createExternalCasesClient } from '../../../client'; import { AlertService, CaseService, @@ -42,13 +42,13 @@ export const createRouteContext = async (client: any, badAuth = false) => { }, }, actions: { getActionsClient: () => actionsMock }, - case: { - getCaseClient: () => caseClient, + cases: { + getCasesClient: () => casesClient, }, } as unknown) as CasesRequestHandlerContext; const connectorMappingsService = await connectorMappingsServicePlugin.setup(); - const caseClient = createExternalCaseClient({ + const casesClient = createExternalCasesClient({ savedObjectsClient: client, user: authc.getCurrentUser(), caseService, diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts rename to x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/delete_all_comments.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/get_all_comment.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/get_comment.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts similarity index 90% rename from x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts rename to x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts index b8dc43dbf3fae..110a16a610014 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts @@ -28,17 +28,17 @@ export function initPostCommentApi({ router, logger }: RouteDeps) { }, }, async (context, request, response) => { - if (!context.case) { + if (!context.cases) { return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const caseId = request.query?.subCaseId ?? request.params.case_id; const comment = request.body as CommentRequest; try { return response.ok({ - body: await caseClient.addComment({ caseId, comment }), + body: await casesClient.addComment({ caseId, comment }), }); } catch (error) { logger.error( diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts similarity index 95% rename from x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts index ff4216a05ae58..f328844acfd00 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts @@ -20,7 +20,7 @@ import { initGetCaseConfigure } from './get_configure'; import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; import { mappings } from '../../../../client/configure/mock'; import { ConnectorTypes } from '../../../../../common/api/connectors'; -import { CaseClient } from '../../../../client'; +import { CasesClient } from '../../../../client'; describe('GET configuration', () => { let routeHandler: RequestHandler; @@ -141,15 +141,15 @@ describe('GET configuration', () => { ); const mockThrowContext = { ...context, - case: { - ...context.case, - getCaseClient: () => + cases: { + ...context.cases, + getCasesClient: () => ({ - ...context?.case?.getCaseClient(), + ...context?.cases?.getCasesClient(), getMappings: () => { throw new Error(); }, - } as CaseClient), + } as CasesClient), }, }; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts similarity index 94% rename from x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts index 2ca34d25482dd..c916bd8f4140b 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts @@ -29,16 +29,16 @@ export function initGetCaseConfigure({ caseConfigureService, router, logger }: R ?.attributes ?? { connector: null }; let mappings: ConnectorMappingsAttributes[] = []; if (connector != null) { - if (!context.case) { + if (!context.cases) { throw Boom.badRequest('RouteHandlerContext is not registered for cases'); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const actionsClient = context.actions?.getActionsClient(); if (actionsClient == null) { throw Boom.notFound('Action client not found'); } try { - mappings = await caseClient.getMappings({ + mappings = await casesClient.getMappings({ actionsClient, connectorId: connector.id, connectorType: connector.type, diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts similarity index 97% rename from x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts index f43f561e30e10..48d88e0f622f5 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts @@ -19,7 +19,7 @@ import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; import { initPatchCaseConfigure } from './patch_configure'; import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; import { ConnectorTypes } from '../../../../../common/api/connectors'; -import { CaseClient } from '../../../../client'; +import { CasesClient } from '../../../../client'; describe('PATCH configuration', () => { let routeHandler: RequestHandler; @@ -161,15 +161,15 @@ describe('PATCH configuration', () => { ); const mockThrowContext = { ...context, - case: { - ...context.case, - getCaseClient: () => + cases: { + ...context.cases, + getCasesClient: () => ({ - ...context?.case?.getCaseClient(), + ...context?.cases?.getCasesClient(), getMappings: () => { throw new Error(); }, - } as CaseClient), + } as CasesClient), }, }; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts similarity index 96% rename from x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts index cd764bb0e8a3e..ba0ea6eb17936 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts @@ -67,16 +67,16 @@ export function initPatchCaseConfigure({ let mappings: ConnectorMappingsAttributes[] = []; if (connector != null) { - if (!context.case) { + if (!context.cases) { throw Boom.badRequest('RouteHandlerContext is not registered for cases'); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const actionsClient = context.actions?.getActionsClient(); if (actionsClient == null) { throw Boom.notFound('Action client have not been found'); } try { - mappings = await caseClient.getMappings({ + mappings = await casesClient.getMappings({ actionsClient, connectorId: connector.id, connectorType: connector.type, diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts similarity index 98% rename from x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts index 7dcb7d1fa12ca..882a10742d733 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts @@ -20,7 +20,7 @@ import { initPostCaseConfigure } from './post_configure'; import { newConfiguration } from '../../__mocks__/request_responses'; import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; import { ConnectorTypes } from '../../../../../common/api/connectors'; -import { CaseClient } from '../../../../client'; +import { CasesClient } from '../../../../client'; describe('POST configuration', () => { let routeHandler: RequestHandler; @@ -81,15 +81,15 @@ describe('POST configuration', () => { ); const mockThrowContext = { ...context, - case: { - ...context.case, - getCaseClient: () => + cases: { + ...context.cases, + getCasesClient: () => ({ - ...context?.case?.getCaseClient(), + ...context?.cases?.getCasesClient(), getMappings: () => { throw new Error(); }, - } as CaseClient), + } as CasesClient), }, }; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts similarity index 96% rename from x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts rename to x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts index f619a727e2e7a..469151a126898 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts @@ -40,10 +40,10 @@ export function initPostCaseConfigure({ async (context, request, response) => { try { let error = null; - if (!context.case) { + if (!context.cases) { throw Boom.badRequest('RouteHandlerContext is not registered for cases'); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const actionsClient = context.actions?.getActionsClient(); if (actionsClient == null) { throw Boom.notFound('Action client not found'); @@ -68,7 +68,7 @@ export function initPostCaseConfigure({ const creationDate = new Date().toISOString(); let mappings: ConnectorMappingsAttributes[] = []; try { - mappings = await caseClient.getMappings({ + mappings = await casesClient.getMappings({ actionsClient, connectorId: query.connector.id, connectorType: query.connector.type, diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/delete_cases.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/delete_cases.ts rename to x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts b/x-pack/plugins/cases/server/routes/api/cases/find_cases.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/find_cases.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/find_cases.ts rename to x-pack/plugins/cases/server/routes/api/cases/find_cases.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/get_case.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/get_case.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts similarity index 93% rename from x-pack/plugins/case/server/routes/api/cases/get_case.ts rename to x-pack/plugins/cases/server/routes/api/cases/get_case.ts index 8a34e3a5b2431..f464f7e47fe7a 100644 --- a/x-pack/plugins/case/server/routes/api/cases/get_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts @@ -27,11 +27,11 @@ export function initGetCaseApi({ router, logger }: RouteDeps) { }, async (context, request, response) => { try { - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const id = request.params.case_id; return response.ok({ - body: await caseClient.get({ + body: await casesClient.get({ id, includeComments: request.query.includeComments, includeSubCaseComments: request.query.includeSubCaseComments, diff --git a/x-pack/plugins/case/server/routes/api/cases/helpers.test.ts b/x-pack/plugins/cases/server/routes/api/cases/helpers.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/helpers.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/helpers.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/helpers.ts b/x-pack/plugins/cases/server/routes/api/cases/helpers.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/helpers.ts rename to x-pack/plugins/cases/server/routes/api/cases/helpers.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/patch_cases.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts similarity index 88% rename from x-pack/plugins/case/server/routes/api/cases/patch_cases.ts rename to x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts index 2bff6000d5d6a..8e779087bcafe 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts @@ -20,15 +20,15 @@ export function initPatchCasesApi({ router, logger }: RouteDeps) { }, async (context, request, response) => { try { - if (!context.case) { + if (!context.cases) { return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const cases = request.body as CasesPatchRequest; return response.ok({ - body: await caseClient.update(cases), + body: await casesClient.update(cases), }); } catch (error) { logger.error(`Failed to patch cases in route: ${error}`); diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/cases/server/routes/api/cases/post_case.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/post_case.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/post_case.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.ts b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts similarity index 87% rename from x-pack/plugins/case/server/routes/api/cases/post_case.ts rename to x-pack/plugins/cases/server/routes/api/cases/post_case.ts index 1328d95826130..e2d71c5837353 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts @@ -21,14 +21,14 @@ export function initPostCaseApi({ router, logger }: RouteDeps) { }, async (context, request, response) => { try { - if (!context.case) { + if (!context.cases) { return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const theCase = request.body as CasePostRequest; return response.ok({ - body: await caseClient.create({ ...theCase }), + body: await casesClient.create({ ...theCase }), }); } catch (error) { logger.error(`Failed to post case in route: ${error}`); diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.test.ts b/x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts similarity index 98% rename from x-pack/plugins/case/server/routes/api/cases/push_case.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts index 0c3ebe67d227a..fb0ba5e3b5d9a 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts @@ -126,12 +126,12 @@ describe('Push case', () => { }) ); - const caseClient = context.case.getCaseClient(); - caseClient.getAlerts = jest.fn().mockResolvedValue([]); + const casesClient = context.cases.getCasesClient(); + casesClient.getAlerts = jest.fn().mockResolvedValue([]); const response = await routeHandler(context, request, kibanaResponseFactory); expect(response.status).toEqual(200); - expect(caseClient.getAlerts).toHaveBeenCalledWith({ + expect(casesClient.getAlerts).toHaveBeenCalledWith({ alertsInfo: [{ id: 'test-id', index: 'test-index' }], }); }); @@ -426,7 +426,7 @@ describe('Push case', () => { const betterContext = ({ ...context, - case: null, + cases: null, } as unknown) as CasesRequestHandlerContext; const res = await routeHandler(betterContext, request, kibanaResponseFactory); diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.ts b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts similarity index 92% rename from x-pack/plugins/case/server/routes/api/cases/push_case.ts rename to x-pack/plugins/cases/server/routes/api/cases/push_case.ts index cfd2f6b9a61ad..7395758210cf4 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts @@ -27,11 +27,11 @@ export function initPushCaseApi({ router, logger }: RouteDeps) { }, async (context, request, response) => { try { - if (!context.case) { + if (!context.cases) { return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const actionsClient = context.actions?.getActionsClient(); if (actionsClient == null) { @@ -44,7 +44,7 @@ export function initPushCaseApi({ router, logger }: RouteDeps) { ); return response.ok({ - body: await caseClient.push({ + body: await casesClient.push({ actionsClient, caseId: params.case_id, connectorId: params.connector_id, diff --git a/x-pack/plugins/case/server/routes/api/cases/reporters/get_reporters.ts b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/reporters/get_reporters.ts rename to x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/status/get_status.test.ts b/x-pack/plugins/cases/server/routes/api/cases/status/get_status.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/status/get_status.test.ts rename to x-pack/plugins/cases/server/routes/api/cases/status/get_status.test.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/status/get_status.ts b/x-pack/plugins/cases/server/routes/api/cases/status/get_status.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/status/get_status.ts rename to x-pack/plugins/cases/server/routes/api/cases/status/get_status.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/sub_case/delete_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/delete_sub_cases.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/sub_case/delete_sub_cases.ts rename to x-pack/plugins/cases/server/routes/api/cases/sub_case/delete_sub_cases.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/sub_case/find_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/find_sub_cases.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/sub_case/find_sub_cases.ts rename to x-pack/plugins/cases/server/routes/api/cases/sub_case/find_sub_cases.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/sub_case/get_sub_case.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/get_sub_case.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/sub_case/get_sub_case.ts rename to x-pack/plugins/cases/server/routes/api/cases/sub_case/get_sub_case.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts similarity index 97% rename from x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts rename to x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts index da7ec956cad1d..08836615e1d39 100644 --- a/x-pack/plugins/case/server/routes/api/cases/sub_case/patch_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts @@ -17,7 +17,7 @@ import { Logger, } from 'kibana/server'; -import { CaseClient } from '../../../../client'; +import { CasesClient } from '../../../../client'; import { CASE_COMMENT_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { CaseServiceSetup, CaseUserActionServiceSetup } from '../../../../services'; import { @@ -55,7 +55,7 @@ interface UpdateArgs { caseService: CaseServiceSetup; userActionService: CaseUserActionServiceSetup; request: KibanaRequest; - caseClient: CaseClient; + casesClient: CasesClient; subCases: SubCasesPatchRequest; logger: Logger; } @@ -218,13 +218,13 @@ async function updateAlerts({ subCasesToSync, caseService, client, - caseClient, + casesClient, logger, }: { subCasesToSync: SubCasePatchRequest[]; caseService: CaseServiceSetup; client: SavedObjectsClientContract; - caseClient: CaseClient; + casesClient: CasesClient; logger: Logger; }) { try { @@ -251,7 +251,7 @@ async function updateAlerts({ [] ); - await caseClient.updateAlertsStatus({ alerts: alertsToUpdate }); + await casesClient.updateAlertsStatus({ alerts: alertsToUpdate }); } catch (error) { throw createCaseError({ message: `Failed to update alert status while updating sub cases: ${JSON.stringify( @@ -268,7 +268,7 @@ async function update({ caseService, userActionService, request, - caseClient, + casesClient, subCases, logger, }: UpdateArgs): Promise { @@ -360,7 +360,7 @@ async function update({ await updateAlerts({ caseService, client, - caseClient, + casesClient, subCasesToSync: subCasesToSyncAlertsFor, logger, }); @@ -425,14 +425,14 @@ export function initPatchSubCasesApi({ }, async (context, request, response) => { try { - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const subCases = request.body as SubCasesPatchRequest; return response.ok({ body: await update({ request, subCases, - caseClient, + casesClient, client: context.core.savedObjects.client, caseService, userActionService, diff --git a/x-pack/plugins/case/server/routes/api/cases/tags/get_tags.ts b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/cases/tags/get_tags.ts rename to x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts diff --git a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts b/x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts similarity index 86% rename from x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts rename to x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts index 2efef9ac67f80..b5c564648c185 100644 --- a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts @@ -23,15 +23,15 @@ export function initGetAllCaseUserActionsApi({ router, logger }: RouteDeps) { }, async (context, request, response) => { try { - if (!context.case) { + if (!context.cases) { return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const caseId = request.params.case_id; return response.ok({ - body: await caseClient.getUserActions({ caseId }), + body: await casesClient.getUserActions({ caseId }), }); } catch (error) { logger.error( @@ -56,16 +56,16 @@ export function initGetAllSubCaseUserActionsApi({ router, logger }: RouteDeps) { }, async (context, request, response) => { try { - if (!context.case) { + if (!context.cases) { return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); } - const caseClient = context.case.getCaseClient(); + const casesClient = context.cases.getCasesClient(); const caseId = request.params.case_id; const subCaseId = request.params.sub_case_id; return response.ok({ - body: await caseClient.getUserActions({ caseId, subCaseId }), + body: await casesClient.getUserActions({ caseId, subCaseId }), }); } catch (error) { logger.error( diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/cases/server/routes/api/index.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/index.ts rename to x-pack/plugins/cases/server/routes/api/index.ts diff --git a/x-pack/plugins/case/server/routes/api/types.ts b/x-pack/plugins/cases/server/routes/api/types.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/types.ts rename to x-pack/plugins/cases/server/routes/api/types.ts diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/cases/server/routes/api/utils.test.ts similarity index 100% rename from x-pack/plugins/case/server/routes/api/utils.test.ts rename to x-pack/plugins/cases/server/routes/api/utils.test.ts diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/cases/server/routes/api/utils.ts similarity index 99% rename from x-pack/plugins/case/server/routes/api/utils.ts rename to x-pack/plugins/cases/server/routes/api/utils.ts index 37bffafa4377d..8e8862f4157f1 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/cases/server/routes/api/utils.ts @@ -34,7 +34,7 @@ import { excess, throwErrors, CaseStatuses, - CaseClientPostRequest, + CasesClientPostRequest, AssociationType, SubCaseAttributes, SubCaseResponse, @@ -79,7 +79,7 @@ export const transformNewCase = ({ createdDate: string; email?: string | null; full_name?: string | null; - newCase: CaseClientPostRequest; + newCase: CasesClientPostRequest; username?: string | null; }): ESCaseAttributes => ({ ...newCase, diff --git a/x-pack/plugins/case/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/cases.ts rename to x-pack/plugins/cases/server/saved_object_types/cases.ts diff --git a/x-pack/plugins/case/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/comments.ts rename to x-pack/plugins/cases/server/saved_object_types/comments.ts diff --git a/x-pack/plugins/case/server/saved_object_types/configure.ts b/x-pack/plugins/cases/server/saved_object_types/configure.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/configure.ts rename to x-pack/plugins/cases/server/saved_object_types/configure.ts diff --git a/x-pack/plugins/case/server/saved_object_types/connector_mappings.ts b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/connector_mappings.ts rename to x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts diff --git a/x-pack/plugins/case/server/saved_object_types/index.ts b/x-pack/plugins/cases/server/saved_object_types/index.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/index.ts rename to x-pack/plugins/cases/server/saved_object_types/index.ts diff --git a/x-pack/plugins/case/server/saved_object_types/migrations.ts b/x-pack/plugins/cases/server/saved_object_types/migrations.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/migrations.ts rename to x-pack/plugins/cases/server/saved_object_types/migrations.ts diff --git a/x-pack/plugins/case/server/saved_object_types/sub_case.ts b/x-pack/plugins/cases/server/saved_object_types/sub_case.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/sub_case.ts rename to x-pack/plugins/cases/server/saved_object_types/sub_case.ts diff --git a/x-pack/plugins/case/server/saved_object_types/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts similarity index 100% rename from x-pack/plugins/case/server/saved_object_types/user_actions.ts rename to x-pack/plugins/cases/server/saved_object_types/user_actions.ts diff --git a/x-pack/plugins/case/server/scripts/README.md b/x-pack/plugins/cases/server/scripts/README.md similarity index 96% rename from x-pack/plugins/case/server/scripts/README.md rename to x-pack/plugins/cases/server/scripts/README.md index 2c35eb305282a..08387a73870c6 100644 --- a/x-pack/plugins/case/server/scripts/README.md +++ b/x-pack/plugins/cases/server/scripts/README.md @@ -35,7 +35,7 @@ source ~/.zshrc Restart Kibana and ensure that you are using `--no-base-path` as changing the base path is a feature but will get in the way of the CURL scripts written as is. -Go to the scripts folder `cd kibana/x-pack/plugins/case/server/scripts` and run: +Go to the scripts folder `cd kibana/x-pack/plugins/cases/server/scripts` and run: ```sh ./hard_reset.sh diff --git a/x-pack/plugins/case/server/scripts/check_env_variables.sh b/x-pack/plugins/cases/server/scripts/check_env_variables.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/check_env_variables.sh rename to x-pack/plugins/cases/server/scripts/check_env_variables.sh diff --git a/x-pack/plugins/case/server/scripts/delete_cases.sh b/x-pack/plugins/cases/server/scripts/delete_cases.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/delete_cases.sh rename to x-pack/plugins/cases/server/scripts/delete_cases.sh diff --git a/x-pack/plugins/case/server/scripts/delete_comment.sh b/x-pack/plugins/cases/server/scripts/delete_comment.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/delete_comment.sh rename to x-pack/plugins/cases/server/scripts/delete_comment.sh diff --git a/x-pack/plugins/case/server/scripts/find_cases.sh b/x-pack/plugins/cases/server/scripts/find_cases.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/find_cases.sh rename to x-pack/plugins/cases/server/scripts/find_cases.sh diff --git a/x-pack/plugins/case/server/scripts/find_cases_by_filter.sh b/x-pack/plugins/cases/server/scripts/find_cases_by_filter.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/find_cases_by_filter.sh rename to x-pack/plugins/cases/server/scripts/find_cases_by_filter.sh diff --git a/x-pack/plugins/case/server/scripts/find_cases_sort.sh b/x-pack/plugins/cases/server/scripts/find_cases_sort.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/find_cases_sort.sh rename to x-pack/plugins/cases/server/scripts/find_cases_sort.sh diff --git a/x-pack/plugins/case/server/scripts/generate_case_and_comment_data.sh b/x-pack/plugins/cases/server/scripts/generate_case_and_comment_data.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/generate_case_and_comment_data.sh rename to x-pack/plugins/cases/server/scripts/generate_case_and_comment_data.sh diff --git a/x-pack/plugins/case/server/scripts/generate_case_data.sh b/x-pack/plugins/cases/server/scripts/generate_case_data.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/generate_case_data.sh rename to x-pack/plugins/cases/server/scripts/generate_case_data.sh diff --git a/x-pack/plugins/case/server/scripts/get_case.sh b/x-pack/plugins/cases/server/scripts/get_case.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/get_case.sh rename to x-pack/plugins/cases/server/scripts/get_case.sh diff --git a/x-pack/plugins/case/server/scripts/get_case_comments.sh b/x-pack/plugins/cases/server/scripts/get_case_comments.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/get_case_comments.sh rename to x-pack/plugins/cases/server/scripts/get_case_comments.sh diff --git a/x-pack/plugins/case/server/scripts/get_comment.sh b/x-pack/plugins/cases/server/scripts/get_comment.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/get_comment.sh rename to x-pack/plugins/cases/server/scripts/get_comment.sh diff --git a/x-pack/plugins/case/server/scripts/get_reporters.sh b/x-pack/plugins/cases/server/scripts/get_reporters.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/get_reporters.sh rename to x-pack/plugins/cases/server/scripts/get_reporters.sh diff --git a/x-pack/plugins/case/server/scripts/get_status.sh b/x-pack/plugins/cases/server/scripts/get_status.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/get_status.sh rename to x-pack/plugins/cases/server/scripts/get_status.sh diff --git a/x-pack/plugins/case/server/scripts/get_tags.sh b/x-pack/plugins/cases/server/scripts/get_tags.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/get_tags.sh rename to x-pack/plugins/cases/server/scripts/get_tags.sh diff --git a/x-pack/plugins/case/server/scripts/hard_reset.sh b/x-pack/plugins/cases/server/scripts/hard_reset.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/hard_reset.sh rename to x-pack/plugins/cases/server/scripts/hard_reset.sh diff --git a/x-pack/plugins/case/server/scripts/mock/case/post_case.json b/x-pack/plugins/cases/server/scripts/mock/case/post_case.json similarity index 100% rename from x-pack/plugins/case/server/scripts/mock/case/post_case.json rename to x-pack/plugins/cases/server/scripts/mock/case/post_case.json diff --git a/x-pack/plugins/case/server/scripts/mock/case/post_case_v2.json b/x-pack/plugins/cases/server/scripts/mock/case/post_case_v2.json similarity index 100% rename from x-pack/plugins/case/server/scripts/mock/case/post_case_v2.json rename to x-pack/plugins/cases/server/scripts/mock/case/post_case_v2.json diff --git a/x-pack/plugins/case/server/scripts/mock/comment/post_comment.json b/x-pack/plugins/cases/server/scripts/mock/comment/post_comment.json similarity index 100% rename from x-pack/plugins/case/server/scripts/mock/comment/post_comment.json rename to x-pack/plugins/cases/server/scripts/mock/comment/post_comment.json diff --git a/x-pack/plugins/case/server/scripts/mock/comment/post_comment_v2.json b/x-pack/plugins/cases/server/scripts/mock/comment/post_comment_v2.json similarity index 100% rename from x-pack/plugins/case/server/scripts/mock/comment/post_comment_v2.json rename to x-pack/plugins/cases/server/scripts/mock/comment/post_comment_v2.json diff --git a/x-pack/plugins/case/server/scripts/patch_cases.sh b/x-pack/plugins/cases/server/scripts/patch_cases.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/patch_cases.sh rename to x-pack/plugins/cases/server/scripts/patch_cases.sh diff --git a/x-pack/plugins/case/server/scripts/patch_comment.sh b/x-pack/plugins/cases/server/scripts/patch_comment.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/patch_comment.sh rename to x-pack/plugins/cases/server/scripts/patch_comment.sh diff --git a/x-pack/plugins/case/server/scripts/post_case.sh b/x-pack/plugins/cases/server/scripts/post_case.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/post_case.sh rename to x-pack/plugins/cases/server/scripts/post_case.sh diff --git a/x-pack/plugins/case/server/scripts/post_comment.sh b/x-pack/plugins/cases/server/scripts/post_comment.sh similarity index 100% rename from x-pack/plugins/case/server/scripts/post_comment.sh rename to x-pack/plugins/cases/server/scripts/post_comment.sh diff --git a/x-pack/plugins/case/server/scripts/sub_cases/README.md b/x-pack/plugins/cases/server/scripts/sub_cases/README.md similarity index 95% rename from x-pack/plugins/case/server/scripts/sub_cases/README.md rename to x-pack/plugins/cases/server/scripts/sub_cases/README.md index 92873b8f037f3..389fc53856db8 100644 --- a/x-pack/plugins/case/server/scripts/sub_cases/README.md +++ b/x-pack/plugins/cases/server/scripts/sub_cases/README.md @@ -2,7 +2,7 @@ This script makes interacting with sub cases easier (creating, deleting, retrieving, etc). -To run the script, first `cd x-pack/plugins/case/server/scripts` +To run the script, first `cd x-pack/plugins/cases/server/scripts` ## Showing the help diff --git a/x-pack/plugins/case/server/scripts/sub_cases/generator.js b/x-pack/plugins/cases/server/scripts/sub_cases/generator.js similarity index 100% rename from x-pack/plugins/case/server/scripts/sub_cases/generator.js rename to x-pack/plugins/cases/server/scripts/sub_cases/generator.js diff --git a/x-pack/plugins/case/server/scripts/sub_cases/index.ts b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts similarity index 100% rename from x-pack/plugins/case/server/scripts/sub_cases/index.ts rename to x-pack/plugins/cases/server/scripts/sub_cases/index.ts diff --git a/x-pack/plugins/case/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts similarity index 100% rename from x-pack/plugins/case/server/services/alerts/index.test.ts rename to x-pack/plugins/cases/server/services/alerts/index.test.ts diff --git a/x-pack/plugins/case/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts similarity index 100% rename from x-pack/plugins/case/server/services/alerts/index.ts rename to x-pack/plugins/cases/server/services/alerts/index.ts diff --git a/x-pack/plugins/case/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts similarity index 100% rename from x-pack/plugins/case/server/services/configure/index.ts rename to x-pack/plugins/cases/server/services/configure/index.ts diff --git a/x-pack/plugins/case/server/services/connector_mappings/index.ts b/x-pack/plugins/cases/server/services/connector_mappings/index.ts similarity index 100% rename from x-pack/plugins/case/server/services/connector_mappings/index.ts rename to x-pack/plugins/cases/server/services/connector_mappings/index.ts diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/cases/server/services/index.ts similarity index 100% rename from x-pack/plugins/case/server/services/index.ts rename to x-pack/plugins/cases/server/services/index.ts diff --git a/x-pack/plugins/case/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts similarity index 100% rename from x-pack/plugins/case/server/services/mocks.ts rename to x-pack/plugins/cases/server/services/mocks.ts diff --git a/x-pack/plugins/case/server/services/reporters/read_reporters.ts b/x-pack/plugins/cases/server/services/reporters/read_reporters.ts similarity index 100% rename from x-pack/plugins/case/server/services/reporters/read_reporters.ts rename to x-pack/plugins/cases/server/services/reporters/read_reporters.ts diff --git a/x-pack/plugins/case/server/services/tags/read_tags.ts b/x-pack/plugins/cases/server/services/tags/read_tags.ts similarity index 100% rename from x-pack/plugins/case/server/services/tags/read_tags.ts rename to x-pack/plugins/cases/server/services/tags/read_tags.ts diff --git a/x-pack/plugins/case/server/services/user_actions/helpers.ts b/x-pack/plugins/cases/server/services/user_actions/helpers.ts similarity index 100% rename from x-pack/plugins/case/server/services/user_actions/helpers.ts rename to x-pack/plugins/cases/server/services/user_actions/helpers.ts diff --git a/x-pack/plugins/case/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts similarity index 100% rename from x-pack/plugins/case/server/services/user_actions/index.ts rename to x-pack/plugins/cases/server/services/user_actions/index.ts diff --git a/x-pack/plugins/case/server/types.ts b/x-pack/plugins/cases/server/types.ts similarity index 89% rename from x-pack/plugins/case/server/types.ts rename to x-pack/plugins/cases/server/types.ts index d01aedeaaba4c..31d73ea999163 100644 --- a/x-pack/plugins/case/server/types.ts +++ b/x-pack/plugins/cases/server/types.ts @@ -8,17 +8,17 @@ import type { IRouter, RequestHandlerContext } from 'src/core/server'; import type { AppRequestContext } from '../../security_solution/server'; import type { ActionsApiRequestHandlerContext } from '../../actions/server'; -import { CaseClient } from './client'; +import { CasesClient } from './client'; export interface CaseRequestContext { - getCaseClient: () => CaseClient; + getCasesClient: () => CasesClient; } /** * @internal */ export interface CasesRequestHandlerContext extends RequestHandlerContext { - case: CaseRequestContext; + cases: CaseRequestContext; actions: ActionsApiRequestHandlerContext; // TODO: Remove when triggers_ui do not import case's types. // PR https://github.com/elastic/kibana/pull/84587. diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts index 52de4c68e512d..e662d51500333 100644 --- a/x-pack/plugins/cloud/public/user_menu_links.ts +++ b/x-pack/plugins/cloud/public/user_menu_links.ts @@ -16,11 +16,12 @@ export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => if (resetPasswordUrl) { userMenuLinks.push({ label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { - defaultMessage: 'Cloud profile', + defaultMessage: 'Profile', }), - iconType: 'logoCloud', + iconType: 'user', href: resetPasswordUrl, order: 100, + setAsProfile: true, }); } diff --git a/x-pack/plugins/code/tsconfig.json b/x-pack/plugins/code/tsconfig.json deleted file mode 100644 index 9c0b0ed21330f..0000000000000 --- a/x-pack/plugins/code/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "composite": true, - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": [ - "server/**/*" - ], - "references": [ - { "path": "../../../src/core/tsconfig.json" }, - ] -} diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index ecc7b991f0761..2579d0b728c15 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -80,7 +80,7 @@ export const setMockActions = (actions: object) => { * const { mount, unmount } = new LogicMounter(SomeLogic); * * it('some test', () => { - * mount({ someValue: 'hello' }); + * mount({ someValue: 'hello' }, { someProp: 'world' }); * unmount(); * }); */ @@ -88,6 +88,7 @@ import { resetContext, Logic, LogicInput } from 'kea'; interface LogicFile { inputs: Array>; + build(props?: object): void; mount(): Function; } export class LogicMounter { @@ -110,8 +111,10 @@ export class LogicMounter { }; // Automatically reset context & mount the logic file - public mount = (values?: object) => { + public mount = (values?: object, props?: object) => { this.resetContext(values); + if (props) this.logicFile.build(props); + const unmount = this.logicFile.mount(); this.unmountFn = unmount; return unmount; // Keep Kea behavior of returning an unmount fn from mount diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts index 920dfbf5f25de..f6497e5f5266b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts @@ -6,24 +6,19 @@ */ import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__'; - -import { resetContext } from 'kea'; +import { LogicMounter } from '../__mocks__'; import { AppLogic } from './app_logic'; describe('AppLogic', () => { - const mount = (props = {}) => { - AppLogic({ ...DEFAULT_INITIAL_APP_DATA, ...props }); - AppLogic.mount(); - }; + const { mount } = new LogicMounter(AppLogic); beforeEach(() => { jest.clearAllMocks(); - resetContext({}); }); it('sets values from props', () => { - mount(); + mount({}, DEFAULT_INITIAL_APP_DATA); expect(AppLogic.values).toEqual({ ilmEnabled: true, @@ -53,7 +48,7 @@ describe('AppLogic', () => { describe('actions', () => { describe('setOnboardingComplete()', () => { it('sets true', () => { - mount({ appSearch: { onboardingComplete: false } }); + mount({}, { ...DEFAULT_INITIAL_APP_DATA, appSearch: { onboardingComplete: false } }); AppLogic.actions.setOnboardingComplete(); expect(AppLogic.values.account.onboardingComplete).toEqual(true); @@ -64,7 +59,7 @@ describe('AppLogic', () => { describe('selectors', () => { describe('myRole', () => { it('falls back to an empty object if role is missing', () => { - mount({ appSearch: {} }); + mount({}, { ...DEFAULT_INITIAL_APP_DATA, appSearch: {} }); expect(AppLogic.values.myRole).toEqual({}); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curation_queries/curation_queries_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curation_queries/curation_queries_logic.test.ts index 157e97433d2b6..766ab78b283be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curation_queries/curation_queries_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curation_queries/curation_queries_logic.test.ts @@ -5,11 +5,13 @@ * 2.0. */ -import { resetContext } from 'kea'; +import { LogicMounter } from '../../../../../__mocks__'; import { CurationQueriesLogic } from './curation_queries_logic'; describe('CurationQueriesLogic', () => { + const { mount } = new LogicMounter(CurationQueriesLogic); + const MOCK_QUERIES = ['a', 'b', 'c']; const DEFAULT_PROPS = { queries: MOCK_QUERIES }; @@ -19,18 +21,12 @@ describe('CurationQueriesLogic', () => { hasOnlyOneQuery: false, }; - const mount = (props = {}) => { - CurationQueriesLogic({ ...DEFAULT_PROPS, ...props }); - CurationQueriesLogic.mount(); - }; - beforeEach(() => { jest.clearAllMocks(); - resetContext({}); }); it('has expected default values passed from props', () => { - mount(); + mount({}, DEFAULT_PROPS); expect(CurationQueriesLogic.values).toEqual(DEFAULT_VALUES); }); @@ -42,7 +38,7 @@ describe('CurationQueriesLogic', () => { describe('addQuery', () => { it('appends an empty string to the queries array', () => { - mount(); + mount(DEFAULT_VALUES); CurationQueriesLogic.actions.addQuery(); expect(CurationQueriesLogic.values).toEqual({ @@ -55,7 +51,7 @@ describe('CurationQueriesLogic', () => { describe('deleteQuery', () => { it('deletes the query string at the specified array index', () => { - mount(); + mount(DEFAULT_VALUES); CurationQueriesLogic.actions.deleteQuery(1); expect(CurationQueriesLogic.values).toEqual({ @@ -67,7 +63,7 @@ describe('CurationQueriesLogic', () => { describe('editQuery', () => { it('edits the query string at the specified array index', () => { - mount(); + mount(DEFAULT_VALUES); CurationQueriesLogic.actions.editQuery(2, 'z'); expect(CurationQueriesLogic.values).toEqual({ @@ -81,7 +77,7 @@ describe('CurationQueriesLogic', () => { describe('selectors', () => { describe('hasEmptyQueries', () => { it('returns true if queries has any empty strings', () => { - mount({ queries: ['', '', ''] }); + mount({}, { queries: ['', '', ''] }); expect(CurationQueriesLogic.values.hasEmptyQueries).toEqual(true); }); @@ -89,7 +85,7 @@ describe('CurationQueriesLogic', () => { describe('hasOnlyOneQuery', () => { it('returns true if queries only has one item', () => { - mount({ queries: ['test'] }); + mount({}, { queries: ['test'] }); expect(CurationQueriesLogic.values.hasOnlyOneQuery).toEqual(true); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts index c1f26ce08bc8d..133e5a065da25 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts @@ -19,6 +19,10 @@ export const CREATE_NEW_CURATION_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.create.title', { defaultMessage: 'Create new curation' } ); +export const MANAGE_CURATION_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.manage.title', + { defaultMessage: 'Manage curation' } +); export const DELETE_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.deleteConfirmation', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx new file mode 100644 index 0000000000000..6e6b614580713 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../../__mocks__/react_router_history.mock'; +import '../../../../__mocks__/shallow_useeffect.mock'; +import { setMockActions, setMockValues, rerender } from '../../../../__mocks__'; + +import React from 'react'; +import { useParams } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { EuiPageHeader } from '@elastic/eui'; + +import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; +import { Loading } from '../../../../shared/loading'; + +jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() })); +import { CurationLogic } from './curation_logic'; + +import { Curation } from './'; + +describe('Curation', () => { + const props = { + curationsBreadcrumb: ['Engines', 'some-engine', 'Curations'], + }; + const values = { + dataLoading: false, + curation: { + id: 'cur-123456789', + queries: ['query A', 'query B'], + }, + }; + const actions = { + loadCuration: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('Manage curation'); + expect(wrapper.find(SetPageChrome).prop('trail')).toEqual([ + ...props.curationsBreadcrumb, + 'query A, query B', + ]); + }); + + it('renders a loading component on page load', () => { + setMockValues({ ...values, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('initializes CurationLogic with a curationId prop from URL param', () => { + (useParams as jest.Mock).mockReturnValueOnce({ curationId: 'hello-world' }); + shallow(); + + expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' }); + }); + + it('calls loadCuration on page load & whenever the curationId URL param changes', () => { + (useParams as jest.Mock).mockReturnValueOnce({ curationId: 'cur-123456789' }); + const wrapper = shallow(); + expect(actions.loadCuration).toHaveBeenCalledTimes(1); + + (useParams as jest.Mock).mockReturnValueOnce({ curationId: 'cur-987654321' }); + rerender(wrapper); + expect(actions.loadCuration).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx new file mode 100644 index 0000000000000..f37c7ed559c33 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; + +import { useValues, useActions } from 'kea'; + +import { EuiPageHeader, EuiSpacer } from '@elastic/eui'; + +import { FlashMessages } from '../../../../shared/flash_messages'; +import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; +import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs'; +import { Loading } from '../../../../shared/loading'; + +import { MANAGE_CURATION_TITLE } from '../constants'; + +import { CurationLogic } from './curation_logic'; + +interface Props { + curationsBreadcrumb: BreadcrumbTrail; +} + +export const Curation: React.FC = ({ curationsBreadcrumb }) => { + const { curationId } = useParams() as { curationId: string }; + const { loadCuration } = useActions(CurationLogic({ curationId })); + const { dataLoading, curation } = useValues(CurationLogic({ curationId })); + + useEffect(() => { + loadCuration(); + }, [curationId]); + + if (dataLoading) return ; + + return ( + <> + + + + {/* TODO: Active query switcher / Manage queries modal */} + + + + + {/* TODO: PromotedDocuments section */} + {/* TODO: OrganicDocuments section */} + {/* TODO: HiddenDocuments section */} + + {/* TODO: AddResult flyout */} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts new file mode 100644 index 0000000000000..bf271be2c0957 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + LogicMounter, + mockHttpValues, + mockKibanaValues, + mockFlashMessageHelpers, +} from '../../../../__mocks__'; +import '../../../__mocks__/engine_logic.mock'; + +import { nextTick } from '@kbn/test/jest'; + +import { CurationLogic } from './'; + +describe('CurationLogic', () => { + const { mount } = new LogicMounter(CurationLogic); + const { http } = mockHttpValues; + const { navigateToUrl } = mockKibanaValues; + const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers; + + const MOCK_CURATION_RESPONSE = { + id: 'cur-123456789', + last_updated: 'some timestamp', + queries: ['some search'], + promoted: [{ id: 'some-promoted-document' }], + organic: [ + { + id: { raw: 'some-organic-document', snippet: null }, + _meta: { id: 'some-organic-document', engine: 'some-engine' }, + }, + ], + hidden: [{ id: 'some-hidden-document' }], + }; + + const DEFAULT_VALUES = { + dataLoading: true, + curation: { + id: '', + last_updated: '', + queries: [], + promoted: [], + organic: [], + hidden: [], + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(CurationLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('onCurationLoad', () => { + it('should set curation state & dataLoading to false', () => { + mount(); + + CurationLogic.actions.onCurationLoad(MOCK_CURATION_RESPONSE); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + curation: MOCK_CURATION_RESPONSE, + dataLoading: false, + }); + }); + }); + }); + + describe('listeners', () => { + describe('loadCuration', () => { + it('should set dataLoading state', () => { + mount({ dataLoading: false }, { curationId: 'cur-123456789' }); + + CurationLogic.actions.loadCuration(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + }); + }); + + it('should make an API call and set curation state', async () => { + http.get.mockReturnValueOnce(Promise.resolve(MOCK_CURATION_RESPONSE)); + mount({}, { curationId: 'cur-123456789' }); + jest.spyOn(CurationLogic.actions, 'onCurationLoad'); + + CurationLogic.actions.loadCuration(); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith( + '/api/app_search/engines/some-engine/curations/cur-123456789' + ); + expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); + }); + + it('handles errors/404s with a redirect to the Curations view', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount({}, { curationId: 'cur-404' }); + + CurationLogic.actions.loadCuration(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error', { isQueued: true }); + expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); + }); + }); + + describe('updateCuration', () => { + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + + it('should make a PUT API call with queries and promoted/hidden IDs to update', async () => { + http.put.mockReturnValueOnce(Promise.resolve(MOCK_CURATION_RESPONSE)); + mount({}, { curationId: 'cur-123456789' }); + jest.spyOn(CurationLogic.actions, 'onCurationLoad'); + + CurationLogic.actions.updateCuration(); + jest.runAllTimers(); + await nextTick(); + + expect(http.put).toHaveBeenCalledWith( + '/api/app_search/engines/some-engine/curations/cur-123456789', + { + body: '{"queries":[],"query":"","promoted":[],"hidden":[]}', // Uses state currently in CurationLogic + } + ); + expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); + }); + + it('should allow passing a custom queries param', async () => { + http.put.mockReturnValueOnce(Promise.resolve(MOCK_CURATION_RESPONSE)); + mount({}, { curationId: 'cur-123456789' }); + jest.spyOn(CurationLogic.actions, 'onCurationLoad'); + + CurationLogic.actions.updateCuration({ queries: ['hello', 'world'] }); + jest.runAllTimers(); + await nextTick(); + + expect(http.put).toHaveBeenCalledWith( + '/api/app_search/engines/some-engine/curations/cur-123456789', + { + body: '{"queries":["hello","world"],"query":"","promoted":[],"hidden":[]}', + } + ); + expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); + }); + + it('handles errors', async () => { + http.put.mockReturnValueOnce(Promise.reject('error')); + mount({}, { curationId: 'cur-123456789' }); + + CurationLogic.actions.updateCuration(); + jest.runAllTimers(); + await nextTick(); + + expect(clearFlashMessages).toHaveBeenCalled(); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts new file mode 100644 index 0000000000000..ec966da9ff65b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { clearFlashMessages, flashAPIErrors } from '../../../../shared/flash_messages'; +import { HttpLogic } from '../../../../shared/http'; +import { KibanaLogic } from '../../../../shared/kibana'; +import { ENGINE_CURATIONS_PATH } from '../../../routes'; +import { EngineLogic, generateEnginePath } from '../../engine'; + +import { Curation } from '../types'; + +interface CurationValues { + dataLoading: boolean; + curation: Curation; +} + +interface CurationActions { + loadCuration(): void; + onCurationLoad(curation: Curation): { curation: Curation }; + updateCuration(options?: { queries?: string[] }): { queries?: string[] }; +} + +interface CurationProps { + curationId: Curation['id']; +} + +export const CurationLogic = kea>({ + path: ['enterprise_search', 'app_search', 'curation_logic'], + actions: () => ({ + loadCuration: true, + onCurationLoad: (curation) => ({ curation }), + updateCuration: ({ queries } = {}) => ({ queries }), + }), + reducers: () => ({ + dataLoading: [ + true, + { + loadCuration: () => true, + onCurationLoad: () => false, + }, + ], + curation: [ + { + id: '', + last_updated: '', + queries: [], + promoted: [], + organic: [], + hidden: [], + }, + { + onCurationLoad: (_, { curation }) => curation, + }, + ], + }), + listeners: ({ actions, values, props }) => ({ + loadCuration: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + try { + const response = await http.get( + `/api/app_search/engines/${engineName}/curations/${props.curationId}` + ); + actions.onCurationLoad(response); + } catch (e) { + const { navigateToUrl } = KibanaLogic.values; + + flashAPIErrors(e, { isQueued: true }); + navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); + } + }, + updateCuration: async ({ queries }, breakpoint) => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + await breakpoint(100); + clearFlashMessages(); + + try { + const response = await http.put( + `/api/app_search/engines/${engineName}/curations/${props.curationId}`, + { + body: JSON.stringify({ + queries: queries || values.curation.queries, + query: '', // TODO: activeQuery state + promoted: [], // TODO: promotedIds state + hidden: [], // TODO: hiddenIds state + }), + } + ); + actions.onCurationLoad(response); + } catch (e) { + flashAPIErrors(e); + } + }, + }), +}); diff --git a/x-pack/plugins/fleet/server/saved_objects/security_solution.js b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/index.ts similarity index 68% rename from x-pack/plugins/fleet/server/saved_objects/security_solution.js rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/index.ts index 63f70ba783c0c..7e458959b2aa1 100644 --- a/x-pack/plugins/fleet/server/saved_objects/security_solution.js +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/index.ts @@ -5,7 +5,5 @@ * 2.0. */ -export { - migratePackagePolicyToV7110, - migratePackagePolicyToV7120, -} from '../../../security_solution/common'; +export { CurationLogic } from './curation_logic'; +export { Curation } from './curation'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx index 047d00ad98a0d..f0eafb13bb9b0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx @@ -17,6 +17,6 @@ describe('CurationsRouter', () => { const wrapper = shallow(); expect(wrapper.find(Switch)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(5); + expect(wrapper.find(Route)).toHaveLength(4); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx index 634736bca4c65..e080f7de13390 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx @@ -16,10 +16,10 @@ import { ENGINE_CURATIONS_PATH, ENGINE_CURATIONS_NEW_PATH, ENGINE_CURATION_PATH, - ENGINE_CURATION_ADD_RESULT_PATH, } from '../../routes'; import { CURATIONS_TITLE, CREATE_NEW_CURATION_TITLE } from './constants'; +import { Curation } from './curation'; import { Curations, CurationCreation } from './views'; interface Props { @@ -38,15 +38,8 @@ export const CurationsRouter: React.FC = ({ engineBreadcrumb }) => { - - - TODO: Curation view (+ show a NotFound view if ID is invalid) - - - - TODO: Curation Add Result view + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts index a6cfcb57564c4..a6631b62494b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts @@ -6,17 +6,24 @@ */ import { Meta } from '../../../../../common/types'; +import { Result } from '../result/types'; export interface Curation { id: string; last_updated: string; queries: string[]; - promoted: object[]; - hidden: object[]; - organic: object[]; + promoted: CurationResult[]; + hidden: CurationResult[]; + organic: Result[]; } export interface CurationsAPIResponse { results: Curation[]; meta: Meta; } + +export interface CurationResult { + // TODO: Consider updating our internal API to return more standard Result data in the future + id: string; + [key: string]: string | string[]; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.scss index f05e029ec8f8b..50ff1ce74f850 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.scss @@ -1,4 +1,7 @@ .dataPanel { + position: relative; + overflow: hidden; + // TODO: This CSS can be removed once EUI supports tables in `subdued` panels &--filled { .euiTable { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx index e8f480bce9ee7..c111383816e36 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiIcon, EuiButton } from '@elastic/eui'; +import { LoadingOverlay } from '../../../shared/loading'; + import { DataPanel } from './data_panel'; describe('DataPanel', () => { @@ -80,6 +82,18 @@ describe('DataPanel', () => { expect(wrapper.prop('className')).toEqual('dataPanel dataPanel--filled'); }); + it('renders a loading overlay based on isLoading flag', () => { + const wrapper = shallow(Test} />); + + expect(wrapper.prop('aria-busy')).toBeFalsy(); + expect(wrapper.find(LoadingOverlay)).toHaveLength(0); + + wrapper.setProps({ isLoading: true }); + + expect(wrapper.prop('aria-busy')).toBeTruthy(); + expect(wrapper.find(LoadingOverlay)).toHaveLength(1); + }); + it('passes class names', () => { const wrapper = shallow(Test} className="testing" />); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx index f6a474689b3af..825311fa1652a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx @@ -19,6 +19,8 @@ import { EuiTitle, } from '@elastic/eui'; +import { LoadingOverlay } from '../../../shared/loading'; + import './data_panel.scss'; interface Props { @@ -27,6 +29,7 @@ interface Props { iconType?: string; action?: React.ReactNode; filled?: boolean; + isLoading?: boolean; className?: string; } @@ -36,6 +39,7 @@ export const DataPanel: React.FC = ({ iconType, action, filled, + isLoading, className, children, ...props // e.g., data-test-subj @@ -45,29 +49,36 @@ export const DataPanel: React.FC = ({ }); return ( - + - + {iconType && ( - + )} {title} - {subtitle && ( - -

{subtitle}

-
- )}
+ {subtitle && ( + +

{subtitle}

+
+ )}
{action && {action}}
{children} + {isLoading && }
); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index a4ce724fdb097..f3a67c0d10389 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -229,8 +229,7 @@ export const EngineNav: React.FC = () => { )} {canManageEngineResultSettings && ( {RESULT_SETTINGS_TITLE} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index e6b829a43dcc1..7355ee148814c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -20,6 +20,7 @@ import { AnalyticsRouter } from '../analytics'; import { CurationsRouter } from '../curations'; import { EngineOverview } from '../engine_overview'; import { RelevanceTuning } from '../relevance_tuning'; +import { ResultSettings } from '../result_settings'; import { EngineRouter } from './engine_router'; @@ -111,4 +112,11 @@ describe('EngineRouter', () => { expect(wrapper.find(RelevanceTuning)).toHaveLength(1); }); + + it('renders a result settings view', () => { + setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } }); + const wrapper = shallow(); + + expect(wrapper.find(ResultSettings)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index 305bdf74ae501..8eb50626fcb2b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -29,7 +29,7 @@ import { ENGINE_RELEVANCE_TUNING_PATH, // ENGINE_SYNONYMS_PATH, ENGINE_CURATIONS_PATH, - // ENGINE_RESULT_SETTINGS_PATH, + ENGINE_RESULT_SETTINGS_PATH, // ENGINE_SEARCH_UI_PATH, // ENGINE_API_LOGS_PATH, } from '../../routes'; @@ -41,6 +41,8 @@ import { EngineOverview } from '../engine_overview'; import { ENGINES_TITLE } from '../engines'; import { RelevanceTuning } from '../relevance_tuning'; +import { ResultSettings } from '../result_settings'; + import { EngineLogic } from './'; export const EngineRouter: React.FC = () => { @@ -54,7 +56,7 @@ export const EngineRouter: React.FC = () => { canManageEngineRelevanceTuning, // canManageEngineSynonyms, canManageEngineCurations, - // canManageEngineResultSettings, + canManageEngineResultSettings, // canManageEngineSearchUi, // canViewEngineApiLogs, }, @@ -108,6 +110,11 @@ export const EngineRouter: React.FC = () => {
)} + {canManageEngineResultSettings && ( + + + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index e0c5823503445..3b9b6e6c6a778 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -54,7 +54,7 @@ export const EnginesTable: React.FC = ({ const { navigateToUrl } = useValues(KibanaLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); - const generteEncodedEnginePath = (engineName: string) => + const generateEncodedEnginePath = (engineName: string) => generateEncodedPath(ENGINE_PATH, { engineName }); const sendEngineTableLinkClickTelemetry = () => sendAppSearchTelemetry({ @@ -71,7 +71,7 @@ export const EnginesTable: React.FC = ({ render: (name: string) => ( {name} @@ -159,7 +159,7 @@ export const EnginesTable: React.FC = ({ icon: 'eye', onClick: (engineDetails) => { sendEngineTableLinkClickTelemetry(); - navigateToUrl(generteEncodedEnginePath(engineDetails.name)); + navigateToUrl(generateEncodedEnginePath(engineDetails.name)); }, }, { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx index f76ad78c847d1..3f72199d12805 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { EuiSpacer, @@ -18,7 +18,7 @@ import { import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { Schema } from '../../../shared/types'; -import { Result } from '../result/result'; +import { Result } from '../result'; export const Library: React.FC = () => { const props = { @@ -70,6 +70,16 @@ export const Library: React.FC = () => { length: 'number', }; + const [isActionButtonFilled, setIsActionButtonFilled] = useState(false); + const actions = [ + { + title: 'Fill this action button', + onClick: () => setIsActionButtonFilled(!isActionButtonFilled), + iconType: isActionButtonFilled ? 'starFilled' : 'starEmpty', + iconColor: 'primary', + }, + ]; + return ( <> @@ -202,6 +212,22 @@ export const Library: React.FC = () => { + + +

With custom actions

+
+ + + + + + +

With custom actions and a link

+
+ + + +

With field value type highlights

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx index f83ec99acb1ac..7b9dd6b26cbb2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx @@ -13,7 +13,7 @@ import { EuiButton, EuiFormRow, EuiPanel, EuiRange, EuiSpacer } from '@elastic/e import { i18n } from '@kbn/i18n'; import { RelevanceTuningLogic } from '../..'; -import { Boost, BoostType } from '../../types'; +import { Boost, BoostType, FunctionalBoost, ProximityBoost, ValueBoost } from '../../types'; import { FunctionalBoostForm } from './functional_boost_form'; import { ProximityBoostForm } from './proximity_boost_form'; @@ -32,11 +32,11 @@ export const BoostItemContent: React.FC = ({ boost, index, name }) => { const getBoostForm = () => { switch (type) { case BoostType.Value: - return ; + return ; case BoostType.Functional: - return ; + return ; case BoostType.Proximity: - return ; + return ; } }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx index 11a224a71d7f8..feb4328e5adea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx @@ -13,12 +13,13 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { EuiSelect } from '@elastic/eui'; -import { Boost, BoostOperation, BoostType, FunctionalBoostFunction } from '../../types'; +import { FunctionalBoost, BoostOperation, BoostType, FunctionalBoostFunction } from '../../types'; import { FunctionalBoostForm } from './functional_boost_form'; describe('FunctionalBoostForm', () => { - const boost: Boost = { + const boost: FunctionalBoost = { + value: undefined, factor: 2, type: 'functional' as BoostType, function: 'logarithmic' as FunctionalBoostFunction, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx index d677fe5cbc069..ebd826dcd27ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx @@ -19,7 +19,7 @@ import { FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP, } from '../../constants'; import { - Boost, + FunctionalBoost, BoostFunction, BoostOperation, BoostType, @@ -27,7 +27,7 @@ import { } from '../../types'; interface Props { - boost: Boost; + boost: FunctionalBoost; index: number; name: string; } @@ -39,7 +39,7 @@ const functionOptions = Object.values(FunctionalBoostFunction).map((boostFunctio const operationOptions = Object.values(BoostOperation).map((boostOperation) => ({ value: boostOperation, - text: BOOST_OPERATION_DISPLAY_MAP[boostOperation as BoostOperation], + text: BOOST_OPERATION_DISPLAY_MAP[boostOperation], })); export const FunctionalBoostForm: React.FC = ({ boost, index, name }) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx index 6abbcc3d98862..0ed914abb3ab5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx @@ -13,12 +13,14 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { EuiFieldText, EuiSelect } from '@elastic/eui'; -import { Boost, BoostType, ProximityBoostFunction } from '../../types'; +import { ProximityBoost, BoostType, ProximityBoostFunction } from '../../types'; import { ProximityBoostForm } from './proximity_boost_form'; describe('ProximityBoostForm', () => { - const boost: Boost = { + const boost: ProximityBoost = { + value: undefined, + operation: undefined, factor: 2, type: 'proximity' as BoostType, function: 'linear' as ProximityBoostFunction, @@ -46,8 +48,8 @@ describe('ProximityBoostForm', () => { describe('various boost values', () => { const renderWithBoostValues = (boostValues: { - center?: Boost['center']; - function?: Boost['function']; + center?: ProximityBoost['center']; + function?: ProximityBoost['function']; }) => { return shallow( { - const boost: Boost = { + const boost: ValueBoost = { + operation: undefined, + function: undefined, factor: 2, type: 'value' as BoostType, value: ['bar', '', 'baz'], diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx index 7fcd07d9a07aa..cd65f2842a5f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx @@ -20,10 +20,10 @@ import { import { i18n } from '@kbn/i18n'; import { RelevanceTuningLogic } from '../..'; -import { Boost } from '../../types'; +import { ValueBoost } from '../../types'; interface Props { - boost: Boost; + boost: ValueBoost; index: number; name: string; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx index 8a355b97e7b9f..9f6e194f92735 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx @@ -78,6 +78,24 @@ describe('Boosts', () => { expect(select.prop('options').map((o: any) => o.value)).toEqual(['add-boost', 'proximity']); }); + it('will not render functional option if "type" prop is "date"', () => { + const wrapper = shallow( + + ); + + const select = wrapper.find(EuiSuperSelect); + expect(select.prop('options').map((o: any) => o.value)).toEqual([ + 'add-boost', + 'proximity', + 'value', + ]); + }); + it('will add a boost of the selected type when a selection is made', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx index 4268e21110277..0aa1753938f46 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx @@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle, EuiSuperSelect } from '@ import { i18n } from '@kbn/i18n'; -import { GEOLOCATION, TEXT } from '../../../../shared/constants/field_types'; +import { GEOLOCATION, TEXT, DATE } from '../../../../shared/constants/field_types'; import { SchemaTypes } from '../../../../shared/types'; import { BoostIcon } from '../boost_icon'; @@ -70,6 +70,8 @@ const filterInvalidOptions = (value: BoostType, type: SchemaTypes) => { if (type === TEXT && [BoostType.Proximity, BoostType.Functional].includes(value)) return false; // Value and Functional boost types are not valid for geolocation fields if (type === GEOLOCATION && [BoostType.Functional, BoostType.Value].includes(value)) return false; + // Functional boosts are not valid for date fields + if (type === DATE && value === BoostType.Functional) return false; return true; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts index 8131a6a3a57c6..181ecad9e9990 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts @@ -10,8 +10,11 @@ import { i18n } from '@kbn/i18n'; import { BoostOperation, BoostType, + FunctionalBoost, FunctionalBoostFunction, + ProximityBoost, ProximityBoostFunction, + ValueBoost, } from './types'; export const FIELD_FILTER_CUTOFF = 10; @@ -77,6 +80,38 @@ export const BOOST_TYPE_TO_ICON_MAP = { [BoostType.Proximity]: 'tokenGeo', }; +const EMPTY_VALUE_BOOST: ValueBoost = { + type: BoostType.Value, + factor: 1, + newBoost: true, + function: undefined, + operation: undefined, +}; + +const EMPTY_FUNCTIONAL_BOOST: FunctionalBoost = { + value: undefined, + type: BoostType.Functional, + factor: 1, + newBoost: true, + function: FunctionalBoostFunction.Logarithmic, + operation: BoostOperation.Multiply, +}; + +const EMPTY_PROXIMITY_BOOST: ProximityBoost = { + value: undefined, + type: BoostType.Proximity, + factor: 1, + newBoost: true, + operation: undefined, + function: ProximityBoostFunction.Gaussian, +}; + +export const BOOST_TYPE_TO_EMPTY_BOOST = { + [BoostType.Value]: EMPTY_VALUE_BOOST, + [BoostType.Functional]: EMPTY_FUNCTIONAL_BOOST, + [BoostType.Proximity]: EMPTY_PROXIMITY_BOOST, +}; + export const ADD_DISPLAY = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addOperationDropDownOptionLabel', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index bcb8ad8a584a5..f0fe98f3f0a87 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -719,6 +719,9 @@ describe('RelevanceTuningLogic', () => { factor: 1, newBoost: true, type: BoostType.Functional, + function: 'logarithmic', + operation: 'multiply', + value: undefined, }, ], }, @@ -744,6 +747,9 @@ describe('RelevanceTuningLogic', () => { factor: 1, newBoost: true, type: BoostType.Functional, + function: 'logarithmic', + operation: 'multiply', + value: undefined, }, ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index 0d30296de285f..588b100416d10 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -20,15 +20,9 @@ import { RESET_CONFIRMATION_MESSAGE, DELETE_SUCCESS_MESSAGE, DELETE_CONFIRMATION_MESSAGE, + BOOST_TYPE_TO_EMPTY_BOOST, } from './constants'; -import { - BaseBoost, - Boost, - BoostFunction, - BoostOperation, - BoostType, - SearchSettings, -} from './types'; +import { Boost, BoostFunction, BoostOperation, BoostType, SearchSettings } from './types'; import { filterIfTerm, parseBoostCenter, @@ -87,12 +81,12 @@ interface RelevanceTuningActions { updateBoostSelectOption( name: string, boostIndex: number, - optionType: keyof BaseBoost, + optionType: keyof Pick, value: BoostOperation | BoostFunction ): { name: string; boostIndex: number; - optionType: keyof BaseBoost; + optionType: keyof Pick; value: string; }; updateSearchValue(query: string): string; @@ -376,7 +370,7 @@ export const RelevanceTuningLogic = kea< addBoost: ({ name, type }) => { const { searchSettings } = values; const { boosts } = searchSettings; - const emptyBoost = { type, factor: 1, newBoost: true }; + const emptyBoost = BOOST_TYPE_TO_EMPTY_BOOST[type]; let boostArray; if (Array.isArray(boosts[name])) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts index 16da5868da681..ec42df218878f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts @@ -29,26 +29,38 @@ export enum BoostOperation { Add = 'add', Multiply = 'multiply', } - -export interface BaseBoost { +export interface Boost { + type: BoostType; operation?: BoostOperation; function?: BoostFunction; + newBoost?: boolean; + center?: string | number; + factor: number; + value?: string[]; } // A boost that comes from the server, before we normalize it has a much looser schema -export interface RawBoost extends BaseBoost { - type: BoostType; - newBoost?: boolean; - center?: string | number; +export interface RawBoost extends Omit { value?: string | number | boolean | object | Array; - factor: number; } -// We normalize raw boosts to make them safer and easier to work with -export interface Boost extends RawBoost { +export interface ValueBoost extends Boost { value?: string[]; + operation: undefined; + function: undefined; } +export interface FunctionalBoost extends Boost { + value: undefined; + operation: BoostOperation; + function: FunctionalBoostFunction; +} + +export interface ProximityBoost extends Boost { + value: undefined; + operation: undefined; + function: ProximityBoostFunction; +} export interface SearchField { weight: number; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts index e46bee43c3933..89909c1e51d3f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts @@ -6,3 +6,4 @@ */ export { ResultFieldValue } from './result_field_value'; +export { Result } from './result'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index 41428999b1e40..86b71229f3785 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -96,6 +96,39 @@ describe('Result', () => { }); }); + describe('actions', () => { + const actions = [ + { + title: 'Hide', + onClick: jest.fn(), + iconType: 'eyeClosed', + iconColor: 'danger', + }, + { + title: 'Bookmark', + onClick: jest.fn(), + iconType: 'starFilled', + iconColor: 'primary', + }, + ]; + + it('will render an action button for each action passed', () => { + const wrapper = shallow(); + expect(wrapper.find('.appSearchResult__actionButton')).toHaveLength(2); + + wrapper.find('.appSearchResult__actionButton').first().simulate('click'); + expect(actions[0].onClick).toHaveBeenCalled(); + + wrapper.find('.appSearchResult__actionButton').last().simulate('click'); + expect(actions[1].onClick).toHaveBeenCalled(); + }); + + it('will render custom actions seamlessly next to the document detail link', () => { + const wrapper = shallow(); + expect(wrapper.find('.appSearchResult__actionButton')).toHaveLength(3); + }); + }); + it('will render field details with type highlights if schemaForTypeHighlights has been provided', () => { const wrapper = shallow( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 7288fdf39f3ff..2812b596e87fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -22,7 +22,7 @@ import { generateEncodedPath } from '../../utils/encode_path_params'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; -import { FieldValue, Result as ResultType } from './types'; +import { FieldValue, Result as ResultType, ResultAction } from './types'; interface Props { result: ResultType; @@ -30,6 +30,7 @@ interface Props { showScore?: boolean; shouldLinkToDetailPage?: boolean; schemaForTypeHighlights?: Schema; + actions?: ResultAction[]; } const RESULT_CUTOFF = 5; @@ -40,6 +41,7 @@ export const Result: React.FC = ({ showScore = false, shouldLinkToDetailPage = false, schemaForTypeHighlights, + actions = [], }) => { const [isOpen, setIsOpen] = useState(false); @@ -142,6 +144,18 @@ export const Result: React.FC = ({ )} + {actions.map(({ onClick, title, iconType, iconColor }) => ( + + ))} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts index afea65de95db8..96a135b0db36e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts @@ -33,3 +33,10 @@ export type Result = { // You'll need to cast it to FieldValue whenever you use it. [key: string]: object; }; + +export interface ResultAction { + onClick(): void; + title: string; + iconType: string; + iconColor?: string; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts index 07020105f2096..b605aa5714e91 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts @@ -6,3 +6,4 @@ */ export { RESULT_SETTINGS_TITLE } from './constants'; +export { ResultSettings } from './result_settings'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx new file mode 100644 index 0000000000000..8ef7076927307 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { ResultSettings } from './result_settings'; + +describe('RelevanceTuning', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx new file mode 100644 index 0000000000000..6d6d5b2609898 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiPageHeader, EuiPageContentBody, EuiPageContent } from '@elastic/eui'; + +import { FlashMessages } from '../../../shared/flash_messages'; +import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; + +import { RESULT_SETTINGS_TITLE } from './constants'; + +interface Props { + engineBreadcrumb: string[]; +} + +export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { + return ( + <> + + + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/utils.test.ts new file mode 100644 index 0000000000000..e72f2b90758ac --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/utils.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { generateRoleMappingPath } from './utils'; + +describe('generateRoleMappingPath', () => { + it('generates paths with roleId filled', () => { + const roleId = 'role123'; + + expect(generateRoleMappingPath(roleId)).toEqual(`/role_mappings/${roleId}`); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/utils.ts new file mode 100644 index 0000000000000..109d3de1b86db --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/utils.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ROLE_MAPPING_PATH } from '../../routes'; +import { generateEncodedPath } from '../../utils/encode_path_params'; + +export const generateRoleMappingPath = (roleId: string) => + generateEncodedPath(ROLE_MAPPING_PATH, { roleId }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 6827f4f5e827d..4da71ec9a135b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -156,7 +156,7 @@ describe('AppSearchNav', () => { const wrapper = shallow(); expect(wrapper.find(SideNavLink).last().prop('to')).toEqual( - 'http://localhost:3002/as#/role-mappings' + 'http://localhost:3002/as/role_mappings' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 907a27c8660d2..8b4f0f70039d3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -14,7 +14,10 @@ export const SETUP_GUIDE_PATH = '/setup_guide'; export const LIBRARY_PATH = '/library'; export const SETTINGS_PATH = '/settings/account'; export const CREDENTIALS_PATH = '/credentials'; -export const ROLE_MAPPINGS_PATH = '#/role-mappings'; // This page seems to 404 if the # isn't included + +export const ROLE_MAPPINGS_PATH = '/role_mappings'; +export const ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/:roleId`; +export const ROLE_MAPPING_NEW_PATH = `${ROLE_MAPPINGS_PATH}/new`; export const ENGINES_PATH = '/engines'; export const ENGINE_CREATION_PATH = '/engine_creation'; @@ -50,7 +53,6 @@ export const ENGINE_RESULT_SETTINGS_PATH = `${ENGINE_PATH}/result-settings`; export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`; export const ENGINE_CURATIONS_NEW_PATH = `${ENGINE_CURATIONS_PATH}/new`; export const ENGINE_CURATION_PATH = `${ENGINE_CURATIONS_PATH}/:curationId`; -export const ENGINE_CURATION_ADD_RESULT_PATH = `${ENGINE_CURATIONS_PATH}/:curationId/add_result`; export const ENGINE_SEARCH_UI_PATH = `${ENGINE_PATH}/reference_application/new`; export const ENGINE_API_LOGS_PATH = `${ENGINE_PATH}/api-logs`; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts index 0918d2025f732..ecbf885ac3b5c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts @@ -7,7 +7,7 @@ import { roleHasScopedEngines } from './'; -describe('roleHasScopedEngines()', () => { +describe('roleHasScopedEngines', () => { it('returns false for owner and admin roles', () => { expect(roleHasScopedEngines('owner')).toEqual(false); expect(roleHasScopedEngines('admin')).toEqual(false); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/loading/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/loading/index.ts index 3840aa26cef5f..7d6867cfc0401 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/loading/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/loading/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { Loading } from './loading'; +export { Loading, LoadingOverlay } from './loading'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.scss b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.scss index 45fc89c7a181e..ff712be8ec3b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.scss +++ b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.scss @@ -6,8 +6,19 @@ */ .enterpriseSearchLoading { + z-index: $euiZLevel2; position: absolute; top: 50%; left: 50%; transform: translate(-50%); } + +.enterpriseSearchLoadingOverlay { + z-index: $euiZLevel1; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba($euiColorEmptyShade, .75); +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx index eab5694a27968..4ed242c6ed677 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx @@ -11,11 +11,20 @@ import { shallow } from 'enzyme'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { Loading } from './'; +import { Loading, LoadingOverlay } from './'; describe('Loading', () => { it('renders', () => { const wrapper = shallow(); + expect(wrapper.hasClass('enterpriseSearchLoading')).toBe(true); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1); }); }); + +describe('LoadingOverlay', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.hasClass('enterpriseSearchLoadingOverlay')).toBe(true); + expect(wrapper.find(Loading)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx index 27a4dfdec0c07..627d8386dc1c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx @@ -16,3 +16,9 @@ export const Loading: React.FC = () => ( ); + +export const LoadingOverlay: React.FC = () => ( +
+ +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts index 6e9c867b15679..1576fa178cfa9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts @@ -5,9 +5,11 @@ * 2.0. */ +import { AttributeName } from '../../types'; + export const asRoleMapping = { id: null, - attributeName: 'role', + attributeName: 'role' as AttributeName, attributeValue: ['superuser'], authProvider: ['*'], roleType: 'owner', @@ -23,7 +25,7 @@ export const asRoleMapping = { export const wsRoleMapping = { id: '602d4ba85foobarbaz123', - attributeName: 'username', + attributeName: 'username' as AttributeName, attributeValue: 'user', authProvider: ['*', 'other_auth'], roleType: 'admin', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx index bc31732527b0e..b5d1ebb899ba1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx @@ -11,7 +11,9 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { EuiComboBox, EuiFieldText } from '@elastic/eui'; -import { AttributeSelector, AttributeName } from './attribute_selector'; +import { AttributeName } from '../types'; + +import { AttributeSelector } from './attribute_selector'; import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL } from './constants'; const handleAttributeSelectorChange = jest.fn(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx index d19107b534fb7..48d1447e9bd0f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx @@ -20,6 +20,8 @@ import { EuiTitle, } from '@elastic/eui'; +import { AttributeName, AttributeExamples } from '../types'; + import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, @@ -31,8 +33,6 @@ import { ATTRIBUTE_VALUE_LABEL, } from './constants'; -export type AttributeName = keyof AttributeExamples | 'role'; - interface Props { attributeName: AttributeName; attributeValue?: string; @@ -47,12 +47,6 @@ interface Props { handleAuthProviderChange?(value: string[]): void; } -interface AttributeExamples { - username: string; - email: string; - metadata: string; -} - interface ParentOption extends EuiComboBoxOptionOption { label: string; options: ChildOption[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts index 1fbbc172dcf69..9eae2ce06efa2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts @@ -107,3 +107,25 @@ export const MANAGE_ROLE_MAPPING_BUTTON = i18n.translate( defaultMessage: 'Manage', } ); + +export const ROLE_MAPPINGS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.roleMapping.roleMappingsTitle', + { + defaultMessage: 'Users & roles', + } +); + +export const EMPTY_ROLE_MAPPINGS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.roleMapping.emptyRoleMappingsTitle', + { + defaultMessage: 'No role mappings yet', + } +); + +export const ROLE_MAPPINGS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.roleMapping.roleMappingsDescription', + { + defaultMessage: + 'Define role mappings for elasticsearch-native and elasticsearch-saml authentication.', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 16a9e3b54aea5..e026e2f592e75 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -51,9 +51,17 @@ export interface RoleRules { metadata?: string; } +export interface AttributeExamples { + username: string; + email: string; + metadata: string; +} + +export type AttributeName = keyof AttributeExamples | 'role'; + export interface RoleMapping { id: string; - attributeName: string; + attributeName: AttributeName; attributeValue: string; authProvider: string[]; roleType: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg index 23eff13915401..7aac36a6fe3c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg deleted file mode 100644 index d241989f1aff1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg index f8f6415dea22b..cc07fbbc50877 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg deleted file mode 100644 index 40b65df3a1ce3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg index d16f293fde6dc..01e5a7735de12 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg index c4b4176560d5b..aa9c3e5b45146 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg index ae068feb7133d..31fe60c6a63f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg deleted file mode 100644 index 22630f533dcbf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg index c684cecb71235..f3fe82cd3cd98 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts index 347dee11670c9..e6a994d05f3ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts @@ -7,24 +7,18 @@ import box from './box.svg'; import confluence from './confluence.svg'; -import crawler from './crawler.svg'; import custom from './custom.svg'; -import drive from './drive.svg'; import dropbox from './dropbox.svg'; import github from './github.svg'; import gmail from './gmail.svg'; -import google from './google.svg'; import googleDrive from './google_drive.svg'; import jira from './jira.svg'; import jiraServer from './jira_server.svg'; import loadingSmall from './loading_small.svg'; -import office365 from './office365.svg'; -import oneDrive from './one_drive.svg'; -import outlook from './outlook.svg'; -import people from './people.svg'; +import oneDrive from './onedrive.svg'; import salesforce from './salesforce.svg'; -import serviceNow from './service_now.svg'; -import sharePoint from './share_point.svg'; +import serviceNow from './servicenow.svg'; +import sharePoint from './sharepoint.svg'; import slack from './slack.svg'; import zendesk from './zendesk.svg'; @@ -33,23 +27,17 @@ export const images = { confluence, confluenceCloud: confluence, confluenceServer: confluence, - crawler, custom, - drive, dropbox, github, githubEnterpriseServer: github, gmail, googleDrive, - google, jira, jiraServer, jiraCloud: jira, loadingSmall, - office365, oneDrive, - outlook, - people, salesforce, salesforceSandbox: salesforce, serviceNow, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg index 224bb822a581c..c12e55798d889 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg index 71750fb6e25a0..4dfd0fd910079 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg deleted file mode 100644 index fdce5d02da3cd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg deleted file mode 100644 index 1856e5e3ce1af..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/onedrive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/onedrive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg deleted file mode 100644 index 2680bc99cc367..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg deleted file mode 100644 index 4500c494c23b7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg index 510c438a28195..ef6d552949424 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg deleted file mode 100644 index 2d0c09db4e1c3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/servicenow.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/servicenow.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg index f8d2ea1e634f6..39c40a65dfa70 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg deleted file mode 100644 index 8724be9da88cf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/sharepoint.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/sharepoint.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg index 14dbd0289da84..8f6fc0c987eaa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg index f7bc1fda0c9ac..8afd143dd9a7c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/box.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/box.svg deleted file mode 100644 index 827f8cf0a55ec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/box.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg deleted file mode 100644 index 7aac36a6fe3c5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg deleted file mode 100644 index cc07fbbc50877..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg deleted file mode 100644 index 01e5a7735de12..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg deleted file mode 100644 index aa9c3e5b45146..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg deleted file mode 100644 index 31fe60c6a63f9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg deleted file mode 100644 index f3fe82cd3cd98..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts deleted file mode 100644 index d9a0975abef7c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import box from './box.svg'; -import confluence from './confluence.svg'; -import custom from './custom.svg'; -import dropbox from './dropbox.svg'; -import github from './github.svg'; -import gmail from './gmail.svg'; -import googleDrive from './google_drive.svg'; -import jira from './jira.svg'; -import jiraServer from './jira_server.svg'; -import oneDrive from './onedrive.svg'; -import salesforce from './salesforce.svg'; -import serviceNow from './servicenow.svg'; -import sharePoint from './sharepoint.svg'; -import slack from './slack.svg'; -import zendesk from './zendesk.svg'; - -export const imagesFull = { - box, - confluence, - confluenceCloud: confluence, - confluenceServer: confluence, - custom, - dropbox, - github, - githubEnterpriseServer: github, - gmail, - googleDrive, - jira, - jiraServer, - jiraCloud: jira, - oneDrive, - salesforce, - salesforceSandbox: salesforce, - serviceNow, - sharePoint, - slack, - zendesk, -} as { [key: string]: string }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg deleted file mode 100644 index c12e55798d889..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg deleted file mode 100644 index 4dfd0fd910079..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg deleted file mode 100644 index ef6d552949424..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg deleted file mode 100644 index 8f6fc0c987eaa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg deleted file mode 100644 index 8afd143dd9a7c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx index 3bea6f224dc2b..ee079970a5ebb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx @@ -26,10 +26,4 @@ describe('SourceIcon', () => { expect(wrapper.find('.wrapped-icon')).toHaveLength(1); }); - - it('renders a full bleed icon', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiIcon).prop('type')).toEqual('test-file-stub'); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx index 03106dd7d8b8f..1d1462542a3f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -14,14 +14,12 @@ import { EuiIcon, IconSize } from '@elastic/eui'; import './source_icon.scss'; import { images } from '../assets/source_icons'; -import { imagesFull } from '../assets/sources_full_bleed'; interface SourceIconProps { serviceType: string; name: string; className?: string; wrapped?: boolean; - fullBleed?: boolean; size?: IconSize; } @@ -30,16 +28,10 @@ export const SourceIcon: React.FC = ({ serviceType, className, wrapped, - fullBleed = false, size = 'xxl', }) => { const icon = ( - + ); return wrapped ? (
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx index bf472240d3c89..2ecb3c98565b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx @@ -34,12 +34,7 @@ export const AddSourceHeader: React.FC = ({ responsive={false} > - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx index feebc7f8d445e..c1f526e24b8e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx @@ -9,7 +9,6 @@ import '../../../../../__mocks__/shallow_useeffect.mock'; import { mockKibanaValues } from '../../../../../__mocks__'; import { setMockValues, setMockActions } from '../../../../../__mocks__'; -import { unmountHandler } from '../../../../../__mocks__/shallow_useeffect.mock'; import { exampleResult } from '../../../../__mocks__/content_sources.mock'; import React from 'react'; @@ -19,6 +18,7 @@ import { shallow } from 'enzyme'; import { EuiButton, EuiTabbedContent } from '@elastic/eui'; import { Loading } from '../../../../../shared/loading'; +import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { DisplaySettings } from './display_settings'; @@ -53,6 +53,7 @@ describe('DisplaySettings', () => { it('renders', () => { const wrapper = shallow(); + expect(wrapper.find(UnsavedChangesPrompt)).toHaveLength(1); expect(wrapper.find('form')).toHaveLength(1); }); @@ -63,24 +64,6 @@ describe('DisplaySettings', () => { expect(wrapper.find(Loading)).toHaveLength(1); }); - it('handles window.onbeforeunload change', () => { - setMockValues({ ...values, unsavedChanges: true }); - shallow(); - - unmountHandler(); - - expect(window.onbeforeunload).toEqual(null); - }); - - it('handles window.onbeforeunload unmount', () => { - setMockValues({ ...values, unsavedChanges: true }); - shallow(); - - expect(window.onbeforeunload!({} as any)).toEqual( - 'Your display settings have not been saved. Are you sure you want to leave?' - ); - }); - describe('tabbed content', () => { const tabs = [ { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx index 29266cdefe584..681c7a21463c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx @@ -22,9 +22,11 @@ import { import { clearFlashMessages } from '../../../../../shared/flash_messages'; import { KibanaLogic } from '../../../../../shared/kibana'; import { Loading } from '../../../../../shared/loading'; +import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; import { AppLogic } from '../../../../app_logic'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { SAVE_BUTTON } from '../../../../constants'; + import { DISPLAY_SETTINGS_RESULT_DETAIL_PATH, DISPLAY_SETTINGS_SEARCH_RESULT_PATH, @@ -69,13 +71,6 @@ export const DisplaySettings: React.FC = ({ tabId }) => { return clearFlashMessages; }, []); - useEffect(() => { - window.onbeforeunload = hasDocuments && unsavedChanges ? () => UNSAVED_MESSAGE : null; - return () => { - window.onbeforeunload = null; - }; - }, [unsavedChanges]); - if (dataLoading) return ; const tabs = [ @@ -107,6 +102,10 @@ export const DisplaySettings: React.FC = ({ tabId }) => { return ( <> +
= ({ className="content-source-meta__icon" serviceType={sourceType} name={sourceType} - fullBleed size="l" /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts index a4b4e4a1bfd29..a44144666d139 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts @@ -59,13 +59,6 @@ export const GROUP_ASSIGNMENT_ALL_GROUPS_LABEL = i18n.translate( } ); -export const EMPTY_ROLE_MAPPINGS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.roleMapping.emptyRoleMappingsTitle', - { - defaultMessage: 'No role mappings yet', - } -); - export const EMPTY_ROLE_MAPPINGS_BODY = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.roleMapping.emptyRoleMappingsBody', { @@ -80,18 +73,3 @@ export const ROLE_MAPPINGS_TABLE_HEADER = i18n.translate( defaultMessage: 'Group Access', } ); - -export const ROLE_MAPPINGS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsTitle', - { - defaultMessage: 'Users & roles', - } -); - -export const ROLE_MAPPINGS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsDescription', - { - defaultMessage: - 'Define role mappings for elasticsearch-native and elasticsearch-saml authentication.', - } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx index e47b2646459df..842c59e683f06 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx @@ -14,16 +14,15 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; import { Loading } from '../../../shared/loading'; import { AddRoleMappingButton, RoleMappingsTable } from '../../../shared/role_mapping'; -import { ViewContentHeader } from '../../components/shared/view_content_header'; -import { getRoleMappingPath, ROLE_MAPPING_NEW_PATH } from '../../routes'; - import { EMPTY_ROLE_MAPPINGS_TITLE, - EMPTY_ROLE_MAPPINGS_BODY, - ROLE_MAPPINGS_TABLE_HEADER, ROLE_MAPPINGS_TITLE, ROLE_MAPPINGS_DESCRIPTION, -} from './constants'; +} from '../../../shared/role_mapping/constants'; +import { ViewContentHeader } from '../../components/shared/view_content_header'; +import { getRoleMappingPath, ROLE_MAPPING_NEW_PATH } from '../../routes'; + +import { EMPTY_ROLE_MAPPINGS_BODY, ROLE_MAPPINGS_TABLE_HEADER } from './constants'; import { RoleMappingsLogic } from './role_mappings_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts index 6fc3867d7ab1e..b43bda3bb228e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts @@ -10,8 +10,8 @@ import { kea, MakeLogicType } from 'kea'; import { clearFlashMessages, flashAPIErrors } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; -import { AttributeName } from '../../../shared/role_mapping/attribute_selector'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; +import { AttributeName } from '../../../shared/types'; import { ROLE_MAPPINGS_PATH } from '../../routes'; import { RoleGroup, WSRoleMapping, Role } from '../../types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.test.tsx index 4eed6a6fefe68..51346a69eeec2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ +import '../../../__mocks__/shallow_useeffect.mock'; import { setMockValues, setMockActions } from '../../../__mocks__'; -import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; import React from 'react'; @@ -15,6 +15,7 @@ import { shallow } from 'enzyme'; import { EuiSwitch, EuiConfirmModal } from '@elastic/eui'; import { Loading } from '../../../shared/loading'; +import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; import { ViewContentHeader } from '../../components/shared/view_content_header'; import { Security } from './security'; @@ -56,6 +57,7 @@ describe('Security', () => { setMockValues({ ...mockValues, hasPlatinumLicense: false }); const wrapper = shallow(); + expect(wrapper.find(UnsavedChangesPrompt)).toHaveLength(1); expect(wrapper.find(ViewContentHeader)).toHaveLength(1); expect(wrapper.find(EuiSwitch).prop('disabled')).toEqual(true); }); @@ -63,6 +65,7 @@ describe('Security', () => { it('renders on Platinum license', () => { const wrapper = shallow(); + expect(wrapper.find(UnsavedChangesPrompt)).toHaveLength(1); expect(wrapper.find(ViewContentHeader)).toHaveLength(1); expect(wrapper.find(EuiSwitch).prop('disabled')).toEqual(false); }); @@ -74,24 +77,6 @@ describe('Security', () => { expect(wrapper.find(Loading)).toHaveLength(1); }); - it('handles window.onbeforeunload change', () => { - setMockValues({ ...mockValues, unsavedChanges: true }); - shallow(); - - expect(window.onbeforeunload!({} as any)).toEqual( - 'Your private sources settings have not been saved. Are you sure you want to leave?' - ); - }); - - it('handles window.onbeforeunload unmount', () => { - setMockValues({ ...mockValues, unsavedChanges: true }); - shallow(); - - unmountHandler(); - - expect(window.onbeforeunload).toEqual(null); - }); - it('handles switch click', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx index f0f0cd8de6c70..a81ac93ab69dd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx @@ -25,6 +25,7 @@ import { import { FlashMessages } from '../../../shared/flash_messages'; import { LicensingLogic } from '../../../shared/licensing'; import { Loading } from '../../../shared/loading'; +import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; import { LicenseCallout } from '../../components/shared/license_callout'; import { ViewContentHeader } from '../../components/shared/view_content_header'; import { @@ -69,13 +70,6 @@ export const Security: React.FC = () => { initializeSourceRestrictions(); }, []); - useEffect(() => { - window.onbeforeunload = unsavedChanges ? () => SECURITY_UNSAVED_CHANGES_MESSAGE : null; - return () => { - window.onbeforeunload = null; - }; - }, [unsavedChanges]); - if (dataLoading) return ; const panelClass = classNames('euiPanel--noShadow', { @@ -183,6 +177,10 @@ export const Security: React.FC = () => { return ( <> + {header} {allSourcesToggle} {!hasPlatinumLicense && platinumLicenseCallout} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts index 28896809bc81a..08e123a98cd31 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts @@ -130,6 +130,71 @@ describe('curations routes', () => { }); }); + describe('GET /api/app_search/engines/{engineName}/curations/{curationId}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/curations/{curationId}', + }); + + registerCurationsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/curations/:curationId', + }); + }); + }); + + describe('PUT /api/app_search/engines/{engineName}/curations/{curationId}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/app_search/engines/{engineName}/curations/{curationId}', + }); + + registerCurationsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/curations/:curationId', + }); + }); + + describe('validates', () => { + it('required body', () => { + const request = { + body: { + query: 'hello', + queries: ['hello', 'world'], + promoted: ['some-doc-id'], + hidden: ['another-doc-id'], + }, + }; + mockRouter.shouldValidate(request); + }); + + it('missing body', () => { + const request = { body: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); + describe('GET /api/app_search/engines/{engineName}/curations/find_or_create', () => { let mockRouter: MockRouter; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts index 2d7f09e1aeb8d..3cacab96d1968 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts @@ -63,6 +63,42 @@ export function registerCurationsRoutes({ }) ); + router.get( + { + path: '/api/app_search/engines/{engineName}/curations/{curationId}', + validate: { + params: schema.object({ + engineName: schema.string(), + curationId: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/curations/:curationId', + }) + ); + + router.put( + { + path: '/api/app_search/engines/{engineName}/curations/{curationId}', + validate: { + params: schema.object({ + engineName: schema.string(), + curationId: schema.string(), + }), + body: schema.object({ + query: schema.string(), + queries: schema.arrayOf(schema.string()), + promoted: schema.arrayOf(schema.string()), + hidden: schema.arrayOf(schema.string()), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/curations/:curationId', + }) + ); + router.get( { path: '/api/app_search/engines/{engineName}/curations/find_or_create', diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 90b86138a4a6d..74f13a05aa7e6 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -12,6 +12,7 @@ import { registerCredentialsRoutes } from './credentials'; import { registerCurationsRoutes } from './curations'; import { registerDocumentsRoutes, registerDocumentRoutes } from './documents'; import { registerEnginesRoutes } from './engines'; +import { registerRoleMappingsRoutes } from './role_mappings'; import { registerSearchSettingsRoutes } from './search_settings'; import { registerSettingsRoutes } from './settings'; @@ -24,4 +25,5 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerDocumentRoutes(dependencies); registerCurationsRoutes(dependencies); registerSearchSettingsRoutes(dependencies); + registerRoleMappingsRoutes(dependencies); }; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts new file mode 100644 index 0000000000000..53368035af225 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { + registerRoleMappingsRoute, + registerRoleMappingRoute, + registerNewRoleMappingRoute, + registerResetRoleMappingRoute, +} from './role_mappings'; + +const roleMappingBaseSchema = { + rules: { username: 'user' }, + roleType: 'owner', + engines: ['e1', 'e2'], + accessAllEngines: false, + authProvider: ['*'], +}; + +describe('role mappings routes', () => { + describe('GET /api/app_search/role_mappings', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/role_mappings', + }); + + registerRoleMappingsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings', + }); + }); + }); + + describe('POST /api/app_search/role_mappings', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/role_mappings', + }); + + registerRoleMappingsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: roleMappingBaseSchema }; + mockRouter.shouldValidate(request); + }); + + it('missing required fields', () => { + const request = { body: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); + + describe('GET /api/app_search/role_mappings/{id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/role_mappings/{id}', + }); + + registerRoleMappingRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings/:id', + }); + }); + }); + + describe('PUT /api/app_search/role_mappings/{id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/app_search/role_mappings/{id}', + }); + + registerRoleMappingRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings/:id', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + ...roleMappingBaseSchema, + id: '123', + }, + }; + mockRouter.shouldValidate(request); + }); + + it('missing required fields', () => { + const request = { body: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); + + describe('DELETE /api/app_search/role_mappings/{id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'delete', + path: '/api/app_search/role_mappings/{id}', + }); + + registerRoleMappingRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings/:id', + }); + }); + }); + + describe('GET /api/app_search/role_mappings/new', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/role_mappings/new', + }); + + registerNewRoleMappingRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings/new', + }); + }); + }); + + describe('GET /api/app_search/role_mappings/reset', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/role_mappings/reset', + }); + + registerResetRoleMappingRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/role_mappings/reset', + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts new file mode 100644 index 0000000000000..4b77c8614a52c --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +const roleMappingBaseSchema = { + rules: schema.recordOf(schema.string(), schema.string()), + roleType: schema.string(), + engines: schema.arrayOf(schema.string()), + accessAllEngines: schema.boolean(), + authProvider: schema.arrayOf(schema.string()), +}; + +export function registerRoleMappingsRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/role_mappings', + validate: false, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings', + }) + ); + + router.post( + { + path: '/api/app_search/role_mappings', + validate: { + body: schema.object(roleMappingBaseSchema), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings', + }) + ); +} + +export function registerRoleMappingRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/role_mappings/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings/:id', + }) + ); + + router.put( + { + path: '/api/app_search/role_mappings/{id}', + validate: { + body: schema.object({ + ...roleMappingBaseSchema, + id: schema.string(), + }), + params: schema.object({ + id: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings/:id', + }) + ); + + router.delete( + { + path: '/api/app_search/role_mappings/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings/:id', + }) + ); +} + +export function registerNewRoleMappingRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/role_mappings/new', + validate: false, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings/new', + }) + ); +} + +export function registerResetRoleMappingRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.post( + { + path: '/api/app_search/role_mappings/reset', + validate: false, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/role_mappings/reset', + }) + ); +} + +export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => { + registerRoleMappingsRoute(dependencies); + registerRoleMappingRoute(dependencies); + registerNewRoleMappingRoute(dependencies); + registerResetRoleMappingRoute(dependencies); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index 5c1ef8ed62982..b56c5880dba43 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -857,9 +857,10 @@ export function registerOauthConnectorParamsRoute({ validate: { query: schema.object({ kibana_host: schema.string(), - code: schema.string(), - session_state: schema.string(), + code: schema.maybe(schema.string()), + session_state: schema.maybe(schema.string()), state: schema.string(), + oauth_token: schema.maybe(schema.string()), oauth_verifier: schema.maybe(schema.string()), }), }, diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index abf6b3e1cbbd7..5598e63219776 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -24,10 +24,12 @@ export const SO_SEARCH_LIMIT = 10000; export const FLEET_SERVER_INDICES_VERSION = 1; +export const FLEET_SERVER_ARTIFACTS_INDEX = '.fleet-artifacts'; + export const FLEET_SERVER_INDICES = [ '.fleet-actions', '.fleet-agents', - '.fleet-artifacts', + FLEET_SERVER_ARTIFACTS_INDEX, '.fleet-enrollment-api-keys', '.fleet-policies', '.fleet-policies-leader', diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index 4e1c4649aaf6f..421f986ee8848 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -6,25 +6,24 @@ */ import Boom, { isBoom } from '@hapi/boom'; -import { KibanaRequest } from 'src/core/server'; import type { - RequestHandlerContext, IKibanaResponse, KibanaResponseFactory, + RequestHandlerContext, } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { errors as LegacyESErrors } from 'elasticsearch'; -import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { appContextService } from '../services'; import { + AgentNotFoundError, + AgentPolicyNameExistsError, + ConcurrentInstallOperationError, IngestManagerError, - RegistryError, PackageNotFoundError, - AgentPolicyNameExistsError, PackageUnsupportedMediaTypeError, - ConcurrentInstallOperationError, - AgentNotFoundError, + RegistryError, } from './index'; type IngestErrorHandler = ( @@ -59,10 +58,6 @@ export const isLegacyESClientError = (error: any): error is LegacyESClientError return error instanceof LegacyESErrors._Abstract; }; -export function isESClientError(error: unknown): error is ResponseError { - return error instanceof ResponseError; -} - const getHTTPResponseCode = (error: IngestManagerError): number => { if (error instanceof RegistryError) { return 502; // Bad Gateway diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index f6be638c86b6d..1c9491189bcf2 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -6,13 +6,16 @@ */ /* eslint-disable max-classes-per-file */ +import { isESClientError } from './utils'; + export { defaultIngestErrorHandler, ingestErrorToResponseOptions, isLegacyESClientError, - isESClientError, } from './handlers'; +export { isESClientError } from './utils'; + export class IngestManagerError extends Error { constructor(message?: string) { super(message); @@ -42,3 +45,34 @@ export class ConcurrentInstallOperationError extends IngestManagerError {} export class AgentReassignmentError extends IngestManagerError {} export class AgentUnenrollmentError extends IngestManagerError {} export class AgentPolicyDeletionError extends IngestManagerError {} + +export class ArtifactsClientError extends IngestManagerError {} +export class ArtifactsClientAccessDeniedError extends IngestManagerError { + constructor(deniedPackageName: string, allowedPackageName: string) { + super( + `Access denied. Artifact package name (${deniedPackageName}) does not match ${allowedPackageName}` + ); + } +} +export class ArtifactsElasticsearchError extends IngestManagerError { + readonly requestDetails: string; + + constructor(public readonly meta: Error) { + super( + `${ + isESClientError(meta) && meta.meta.body?.error?.reason + ? meta.meta.body?.error?.reason + : `Elasticsearch error while working with artifacts: ${meta.message}` + }` + ); + + if (isESClientError(meta)) { + const { method, path, querystring = '', body = '' } = meta.meta.meta.request.params; + this.requestDetails = `${method} ${path}${querystring ? `?${querystring}` : ''}${ + body ? `\n${body}` : '' + }`; + } else { + this.requestDetails = 'unable to determine request details'; + } + } +} diff --git a/x-pack/plugins/fleet/server/errors/utils.ts b/x-pack/plugins/fleet/server/errors/utils.ts new file mode 100644 index 0000000000000..6d7d4aaffa2a3 --- /dev/null +++ b/x-pack/plugins/fleet/server/errors/utils.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ResponseError } from '@elastic/elasticsearch/lib/errors'; + +export function isESClientError(error: unknown): error is ResponseError { + return error instanceof ResponseError; +} diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 0c9fc7dc27d1b..719a7dba599e9 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -24,6 +24,8 @@ export { getRegistryUrl, PackageService, AgentPolicyServiceInterface, + ArtifactsClientInterface, + Artifact, } from './services'; export { FleetSetupContract, FleetSetupDeps, FleetStartContract, ExternalCallback } from './plugin'; export { AgentNotFoundError } from './errors'; diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index cff80f533d5e3..d4b6f007feb4d 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -17,6 +17,9 @@ import type { PackagePolicyServiceInterface } from '../services/package_policy'; import type { AgentPolicyServiceInterface, AgentService } from '../services'; import type { FleetAppContext } from '../plugin'; +// Export all mocks from artifacts +export * from '../services/artifacts/mocks'; + export const createAppContextStartContractMock = (): FleetAppContext => { return { elasticsearch: elasticsearchServiceMock.createStart(), diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 775bef45ea79c..cd7afc63ab2ed 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -86,6 +86,7 @@ import { registerFleetUsageCollector } from './collectors/register'; import { getInstallation } from './services/epm/packages'; import { makeRouterEnforcingSuperuser } from './routes/security'; import { startFleetServerSetup } from './services/fleet_server'; +import { FleetArtifactsClient } from './services/artifacts'; export interface FleetSetupDeps { licensing: LicensingPluginSetup; @@ -168,6 +169,12 @@ export interface FleetStartContract { * @param args */ registerExternalCallback: (...args: ExternalCallback) => void; + + /** + * Create a Fleet Artifact Client instance + * @param packageName + */ + createArtifactsClient: (packageName: string) => FleetArtifactsClient; } export class FleetPlugin @@ -328,6 +335,9 @@ export class FleetPlugin registerExternalCallback: (type: ExternalCallback[0], callback: ExternalCallback[1]) => { return appContextService.addExternalCallback(type, callback); }, + createArtifactsClient(packageName: string) { + return new FleetArtifactsClient(core.elasticsearch.client.asInternalUser, packageName); + }, }; } diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 05e8fecdcaad1..923f4704aa652 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -22,20 +22,22 @@ import { } from '../constants'; import { - migratePackagePolicyToV7110, - migratePackagePolicyToV7120, - // @ts-expect-error -} from './security_solution'; -import { - migrateAgentToV7100, + migrateAgentActionToV7100, migrateAgentEventToV7100, migrateAgentPolicyToV7100, + migrateAgentToV7100, migrateEnrollmentApiKeysToV7100, migratePackagePolicyToV7100, migrateSettingsToV7100, - migrateAgentActionToV7100, } from './migrations/to_v7_10_0'; -import { migrateAgentToV7120, migrateAgentPolicyToV7120 } from './migrations/to_v7_12_0'; + +import { migratePackagePolicyToV7110 } from './migrations/to_v7_11_0'; + +import { + migrateAgentPolicyToV7120, + migrateAgentToV7120, + migratePackagePolicyToV7120, +} from './migrations/to_v7_12_0'; /* * Saved object types and mappings diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts new file mode 100644 index 0000000000000..bbdd3f14fe22f --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { migratePackagePolicyToV7110 } from './to_v7_11_0'; +export { migratePackagePolicyToV7120 } from './to_v7_12_0'; diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts similarity index 98% rename from x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.test.ts rename to x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts index 251539113fee1..71a87793727a8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.test.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts @@ -6,7 +6,9 @@ */ import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; -import { PackagePolicy } from '../../../../../fleet/common'; + +import type { PackagePolicy } from '../../../../common'; + import { migratePackagePolicyToV7110 } from './to_v7_11_0'; describe('7.11.0 Endpoint Package Policy migration', () => { diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts similarity index 95% rename from x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts rename to x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts index d0dc4f547332a..445b84995353b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts @@ -7,7 +7,8 @@ import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { cloneDeep } from 'lodash'; -import { PackagePolicy } from '../../../../../fleet/common'; + +import type { PackagePolicy } from '../../../../common'; export const migratePackagePolicyToV7110: SavedObjectMigrationFn = ( packagePolicyDoc diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts similarity index 91% rename from x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts rename to x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts index 936d90cc1aa9c..e2f73270efbca 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts @@ -6,14 +6,15 @@ */ import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; -import { PackagePolicy } from '../../../../../fleet/common'; -import { PolicyData, ProtectionModes } from '../../types'; + +import type { PackagePolicy } from '../../../../common'; + import { migratePackagePolicyToV7120 } from './to_v7_12_0'; describe('7.12.0 Endpoint Package Policy migration', () => { const migration = migratePackagePolicyToV7120; it('adds ransomware option and notification customization', () => { - const doc: SavedObjectUnsanitizedDoc = { + const doc = { id: 'mock-saved-object-id', attributes: { name: 'Some Policy Name', @@ -41,7 +42,6 @@ describe('7.12.0 Endpoint Package Policy migration', () => { policy: { value: { windows: { - // @ts-expect-error popup: { malware: { message: '', @@ -58,9 +58,7 @@ describe('7.12.0 Endpoint Package Policy migration', () => { type: ' nested', }; - expect( - migration(doc, {} as SavedObjectMigrationContext) as SavedObjectUnsanitizedDoc - ).toEqual({ + expect(migration(doc, {} as SavedObjectMigrationContext)).toEqual({ attributes: { name: 'Some Policy Name', package: { @@ -88,7 +86,7 @@ describe('7.12.0 Endpoint Package Policy migration', () => { value: { windows: { ransomware: { - mode: ProtectionModes.off, + mode: 'off', }, popup: { malware: { diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts similarity index 88% rename from x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts rename to x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts index 06d505a71025f..c10a89fea6b18 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts @@ -7,8 +7,8 @@ import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { cloneDeep } from 'lodash'; -import { PackagePolicy } from '../../../../../fleet/common'; -import { ProtectionModes } from '../../types'; + +import type { PackagePolicy } from '../../../../common'; export const migratePackagePolicyToV7120: SavedObjectMigrationFn = ( packagePolicyDoc @@ -19,7 +19,7 @@ export const migratePackagePolicyToV7120: SavedObjectMigrationFn = ( agentDoc ) => { diff --git a/x-pack/plugins/fleet/server/services/agents/crud_so.ts b/x-pack/plugins/fleet/server/services/agents/crud_so.ts index 028210b620a13..92ac7c9f6c08e 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud_so.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud_so.ts @@ -76,29 +76,42 @@ export async function listAgents( if (showInactive === false) { filters.push(ACTIVE_AGENT_CONDITION); } - let { saved_objects: agentSOs, total } = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - filter: _joinFilters(filters) || '', - sortField, - sortOrder, - page, - perPage, - }); - // filtering for a range on the version string will not work, - // nor does filtering on a flattened field (local_metadata), so filter here - if (showUpgradeable) { - agentSOs = agentSOs.filter((agent) => - isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion()) - ); - total = agentSOs.length; + try { + let { saved_objects: agentSOs, total } = await soClient.find({ + type: AGENT_SAVED_OBJECT_TYPE, + filter: _joinFilters(filters) || '', + sortField, + sortOrder, + page, + perPage, + }); + // filtering for a range on the version string will not work, + // nor does filtering on a flattened field (local_metadata), so filter here + if (showUpgradeable) { + agentSOs = agentSOs.filter((agent) => + isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion()) + ); + total = agentSOs.length; + } + + return { + agents: agentSOs.map(savedObjectToAgent), + total, + page, + perPage, + }; + } catch (e) { + if (e.output?.payload?.message?.startsWith('The key is empty')) { + return { + agents: [], + total: 0, + page: 0, + perPage: 0, + }; + } else { + throw e; + } } - - return { - agents: agentSOs.map(savedObjectToAgent), - total, - page, - perPage, - }; } export async function listAllAgents( diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts new file mode 100644 index 0000000000000..985699efc4379 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { elasticsearchServiceMock } from 'src/core/server/mocks'; + +import { ResponseError } from '@elastic/elasticsearch/lib/errors'; + +import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; + +import { ArtifactsElasticsearchError } from '../../errors'; + +import { + generateArtifactEsGetSingleHitMock, + generateArtifactEsSearchResultHitsMock, + generateArtifactMock, + generateEsRequestErrorApiResponseMock, + setEsClientMethodResponseToError, +} from './mocks'; +import { + createArtifact, + deleteArtifact, + encodeArtifactContent, + generateArtifactContentHash, + getArtifact, + listArtifacts, +} from './artifacts'; + +import { NewArtifact } from './types'; + +describe('When using the artifacts services', () => { + let esClientMock: ReturnType; + + beforeEach(() => { + esClientMock = elasticsearchServiceMock.createInternalClient(); + }); + + describe('and calling `getArtifact()`', () => { + it('should get artifact using id', async () => { + esClientMock.get.mockImplementation(() => { + return elasticsearchServiceMock.createSuccessTransportRequestPromise( + generateArtifactEsGetSingleHitMock() + ); + }); + + expect(await getArtifact(esClientMock, '123')).toEqual(generateArtifactMock()); + expect(esClientMock.get).toHaveBeenCalledWith({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + id: '123', + }); + }); + + it('should return undefined if artifact is not found', async () => { + setEsClientMethodResponseToError(esClientMock, 'get', { statusCode: 404 }); + expect(await getArtifact(esClientMock, '123')).toBeUndefined(); + }); + + it('should throw an ArtifactElasticsearchError if one is encountered', async () => { + esClientMock.get.mockImplementation(() => { + return elasticsearchServiceMock.createErrorTransportRequestPromise( + new ResponseError(generateEsRequestErrorApiResponseMock()) + ); + }); + + await expect(getArtifact(esClientMock, '123')).rejects.toBeInstanceOf( + ArtifactsElasticsearchError + ); + }); + }); + + describe('and calling `createArtifact()`', () => { + let newArtifact: NewArtifact; + + beforeEach(() => { + const { id, created, ...artifact } = generateArtifactMock(); + newArtifact = artifact; + }); + + it('should create and return artifact', async () => { + const artifact = await createArtifact(esClientMock, newArtifact); + + expect(esClientMock.create).toHaveBeenCalledWith({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + id: expect.any(String), + body: { + ...newArtifact, + created: expect.any(String), + }, + refresh: 'wait_for', + }); + + expect(artifact).toEqual({ + ...newArtifact, + id: expect.any(String), + created: expect.any(String), + }); + }); + + it('should throw an ArtifactElasticsearchError if one is encountered', async () => { + setEsClientMethodResponseToError(esClientMock, 'create'); + await expect(createArtifact(esClientMock, newArtifact)).rejects.toBeInstanceOf( + ArtifactsElasticsearchError + ); + }); + }); + + describe('and calling `deleteArtifact()`', () => { + it('should delete the artifact', async () => { + deleteArtifact(esClientMock, '123'); + + expect(esClientMock.delete).toHaveBeenCalledWith({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + id: '123', + }); + }); + + it('should throw an ArtifactElasticsearchError if one is encountered', async () => { + setEsClientMethodResponseToError(esClientMock, 'delete'); + + await expect(deleteArtifact(esClientMock, '123')).rejects.toBeInstanceOf( + ArtifactsElasticsearchError + ); + }); + }); + + describe('and calling `listArtifacts()`', () => { + beforeEach(() => { + esClientMock.search.mockImplementation(() => { + return elasticsearchServiceMock.createSuccessTransportRequestPromise( + generateArtifactEsSearchResultHitsMock() + ); + }); + }); + + it('should use defaults when options is not provided', async () => { + const results = await listArtifacts(esClientMock); + + expect(esClientMock.search).toHaveBeenCalledWith({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + sort: 'created:asc', + q: '', + from: 0, + size: 20, + }); + + expect(results).toEqual({ + items: [ + { + ...generateArtifactMock(), + id: expect.any(String), + created: expect.any(String), + }, + ], + page: 1, + perPage: 20, + total: 1, + }); + }); + + it('should allow for options to be defined', async () => { + const { items, ...listMeta } = await listArtifacts(esClientMock, { + perPage: 50, + page: 10, + kuery: 'packageName:endpoint', + sortField: 'identifier', + sortOrder: 'desc', + }); + + expect(esClientMock.search).toHaveBeenCalledWith({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + sort: 'identifier:desc', + q: 'packageName:endpoint', + from: 450, + size: 50, + }); + + expect(listMeta).toEqual({ + perPage: 50, + page: 10, + total: 1, + }); + }); + + it('should throw an ArtifactElasticsearchError if one is encountered', async () => { + setEsClientMethodResponseToError(esClientMock, 'search'); + + await expect(listArtifacts(esClientMock)).rejects.toBeInstanceOf(ArtifactsElasticsearchError); + }); + }); + + describe('and calling `generateArtifactContentHash()`', () => { + it('should return a sha256 string', () => { + expect(generateArtifactContentHash('eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==')).toBe( + 'e40a028b3dab7e567135b80ed69934a52be5b4c2d901faa8e0997b256c222473' + ); + }); + }); + + describe('and calling `encodeArtifactContent()`', () => { + it('should encode content', async () => { + expect(await encodeArtifactContent('{"key": "value"}')).toEqual({ + body: 'eJyrVspOrVSyUlAqS8wpTVWqBQArrwVB', + compressionAlgorithm: 'zlib', + decodedSha256: '9724c1e20e6e3e4d7f57ed25f9d4efb006e508590d528c90da597f6a775c13e5', + decodedSize: 16, + encodedSha256: 'b411ccf0a7bf4e015d849ee82e3512683d72c5a3c9bd233db9c885b229b8adf4', + encodedSize: 24, + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts new file mode 100644 index 0000000000000..ab82a3cb335bd --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { deflate } from 'zlib'; +import { promisify } from 'util'; + +import { createHash, BinaryLike } from 'crypto'; + +import uuid from 'uuid'; +import { ElasticsearchClient } from 'kibana/server'; + +import { FLEET_SERVER_ARTIFACTS_INDEX, ListResult } from '../../../common'; +import { ESSearchHit, ESSearchResponse } from '../../../../../typings/elasticsearch'; + +import { ArtifactsElasticsearchError } from '../../errors'; + +import { isElasticsearchItemNotFoundError } from './utils'; +import { + Artifact, + ArtifactElasticsearchProperties, + ArtifactEncodedMetadata, + ArtifactsClientCreateOptions, + ListArtifactsProps, + NewArtifact, +} from './types'; +import { esSearchHitToArtifact } from './mappings'; + +const deflateAsync = promisify(deflate); + +export const getArtifact = async ( + esClient: ElasticsearchClient, + id: string +): Promise => { + try { + const esData = await esClient.get>({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + id, + }); + + return esSearchHitToArtifact(esData.body); + } catch (e) { + if (isElasticsearchItemNotFoundError(e)) { + return; + } + + throw new ArtifactsElasticsearchError(e); + } +}; + +export const createArtifact = async ( + esClient: ElasticsearchClient, + artifact: NewArtifact +): Promise => { + const id = uuid.v4(); + const newArtifactData: ArtifactElasticsearchProperties = { + ...artifact, + created: new Date().toISOString(), + }; + + try { + await esClient.create({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + id, + body: newArtifactData, + refresh: 'wait_for', + }); + + return { + ...newArtifactData, + id, + }; + } catch (e) { + throw new ArtifactsElasticsearchError(e); + } +}; + +export const deleteArtifact = async (esClient: ElasticsearchClient, id: string): Promise => { + try { + await esClient.delete({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + id, + }); + } catch (e) { + throw new ArtifactsElasticsearchError(e); + } +}; + +export const listArtifacts = async ( + esClient: ElasticsearchClient, + options: ListArtifactsProps = {} +): Promise> => { + const { perPage = 20, page = 1, kuery = '', sortField = 'created', sortOrder = 'asc' } = options; + + try { + const searchResult = await esClient.search< + ESSearchResponse + >({ + index: FLEET_SERVER_ARTIFACTS_INDEX, + sort: `${sortField}:${sortOrder}`, + q: kuery, + from: (page - 1) * perPage, + size: perPage, + }); + + return { + items: searchResult.body.hits.hits.map((hit) => esSearchHitToArtifact(hit)), + page, + perPage, + total: searchResult.body.hits.total.value, + }; + } catch (e) { + throw new ArtifactsElasticsearchError(e); + } +}; + +export const generateArtifactContentHash = (content: BinaryLike): string => { + return createHash('sha256').update(content).digest('hex'); +}; + +export const encodeArtifactContent = async ( + content: ArtifactsClientCreateOptions['content'] +): Promise => { + const decodedContentBuffer = Buffer.from(content); + const encodedContentBuffer = await deflateAsync(decodedContentBuffer); + + const encodedArtifact: ArtifactEncodedMetadata = { + compressionAlgorithm: 'zlib', + decodedSha256: generateArtifactContentHash(decodedContentBuffer.toString()), + decodedSize: decodedContentBuffer.byteLength, + encodedSha256: generateArtifactContentHash(encodedContentBuffer), + encodedSize: encodedContentBuffer.byteLength, + body: encodedContentBuffer.toString('base64'), + }; + + return encodedArtifact; +}; diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.test.ts b/x-pack/plugins/fleet/server/services/artifacts/client.test.ts new file mode 100644 index 0000000000000..da1387b24cab0 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/client.test.ts @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { elasticsearchServiceMock } from 'src/core/server/mocks'; + +import { ArtifactsClientAccessDeniedError, ArtifactsClientError } from '../../errors'; + +import { FleetArtifactsClient } from './client'; +import { + generateArtifactEsGetSingleHitMock, + generateArtifactEsSearchResultHitsMock, + generateArtifactMock, + setEsClientMethodResponseToError, +} from './mocks'; + +describe('When using the Fleet Artifacts Client', () => { + let esClientMock: ReturnType; + let artifactClient: FleetArtifactsClient; + + const setEsClientGetMock = (withInvalidArtifact?: boolean) => { + const singleHit = generateArtifactEsGetSingleHitMock(); + + if (withInvalidArtifact) { + singleHit._source.packageName = 'not endpoint'; + } + + esClientMock.get.mockImplementation(() => { + return elasticsearchServiceMock.createSuccessTransportRequestPromise(singleHit); + }); + }; + + beforeEach(() => { + esClientMock = elasticsearchServiceMock.createInternalClient(); + artifactClient = new FleetArtifactsClient(esClientMock, 'endpoint'); + }); + + it('should error if input argument is not set', () => { + expect(() => new FleetArtifactsClient(esClientMock, '')).toThrow(ArtifactsClientError); + }); + + describe('and calling `getArtifact()`', () => { + it('should retrieve artifact', async () => { + setEsClientGetMock(); + expect(await artifactClient.getArtifact('123')).toEqual(generateArtifactMock()); + }); + + it('should throw error if artifact is not for packageName', async () => { + setEsClientGetMock(true); + await expect(artifactClient.getArtifact('123')).rejects.toBeInstanceOf( + ArtifactsClientAccessDeniedError + ); + }); + }); + + describe('and calling `createArtifact()`', () => { + it('should create a new artifact', async () => { + expect( + await artifactClient.createArtifact({ + content: '{ "key": "value" }', + identifier: 'some-identifier', + type: 'type A', + }) + ).toEqual({ + ...generateArtifactMock(), + body: 'eJyrVlDKTq1UslJQKkvMKU1VUqgFADNPBYE=', + created: expect.any(String), + decodedSha256: '05d13b11501327cc43f9a29165f1b4cab5c65783d86227536fcf798e6fa45586', + decodedSize: 18, + encodedSha256: '373d059bac3b51b05af96128cdaf013abd0c59d3d50579589937068059690a68', + encodedSize: 26, + id: expect.any(String), + identifier: 'some-identifier', + relative_url: + '/api/fleet/artifacts/some-identifier/05d13b11501327cc43f9a29165f1b4cab5c65783d86227536fcf798e6fa45586', + type: 'type A', + }); + }); + }); + + describe('and calling `deleteArtifact()`', () => { + it('should delete the artifact', async () => { + setEsClientGetMock(); + await artifactClient.deleteArtifact('123'); + expect(esClientMock.delete).toHaveBeenCalledWith(expect.objectContaining({ id: '123' })); + }); + + it('should throw error if artifact is not for packageName', async () => { + setEsClientGetMock(true); + await expect(artifactClient.deleteArtifact('123')).rejects.toThrow( + ArtifactsClientAccessDeniedError + ); + }); + + it('should do nothing if artifact does not exist', async () => { + setEsClientMethodResponseToError(esClientMock, 'get', { statusCode: 404 }); + await artifactClient.deleteArtifact('123'); + expect(esClientMock.delete).not.toHaveBeenCalled(); + }); + }); + + describe('and calling `listArtifacts()`', () => { + beforeEach(() => { + esClientMock.search.mockImplementation(() => { + return elasticsearchServiceMock.createSuccessTransportRequestPromise( + generateArtifactEsSearchResultHitsMock() + ); + }); + }); + + it('should retrieve list bound to packageName', async () => { + expect( + await artifactClient.listArtifacts({ + sortField: 'created', + sortOrder: 'desc', + kuery: 'identifier: one', + page: 2, + perPage: 100, + }) + ).toEqual({ + items: [generateArtifactMock()], + total: 1, + perPage: 100, + page: 2, + }); + + expect(esClientMock.search).toHaveBeenCalledWith( + expect.objectContaining({ + q: '(packageName: "endpoint") AND identifier: one', + }) + ); + }); + + it('should add packageName kuery to every call', async () => { + expect(await artifactClient.listArtifacts()).toEqual({ + items: [generateArtifactMock()], + total: 1, + perPage: 20, + page: 1, + }); + expect(esClientMock.search).toHaveBeenCalledWith( + expect.objectContaining({ + q: '(packageName: "endpoint")', + }) + ); + }); + }); + + describe('and calling `generateHash()`', () => { + it('should return a hash', () => { + expect(artifactClient.generateHash('{ "key": "value" }')).toBe( + '05d13b11501327cc43f9a29165f1b4cab5c65783d86227536fcf798e6fa45586' + ); + }); + }); + + describe('and calling `encodeContent()`', () => { + it('should encode content', async () => { + expect(await artifactClient.encodeContent('{ "key": "value" }')).toEqual({ + body: 'eJyrVlDKTq1UslJQKkvMKU1VUqgFADNPBYE=', + compressionAlgorithm: 'zlib', + decodedSha256: '05d13b11501327cc43f9a29165f1b4cab5c65783d86227536fcf798e6fa45586', + decodedSize: 18, + encodedSha256: '373d059bac3b51b05af96128cdaf013abd0c59d3d50579589937068059690a68', + encodedSize: 26, + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.ts b/x-pack/plugins/fleet/server/services/artifacts/client.ts new file mode 100644 index 0000000000000..58837cb525766 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/client.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ElasticsearchClient } from 'kibana/server'; + +import { ListResult } from '../../../common'; + +import { ArtifactsClientAccessDeniedError, ArtifactsClientError } from '../../errors'; + +import { + Artifact, + ArtifactsClientCreateOptions, + ArtifactEncodedMetadata, + ArtifactsClientInterface, + NewArtifact, + ListArtifactsProps, +} from './types'; +import { relativeDownloadUrlFromArtifact } from './mappings'; + +import { + createArtifact, + deleteArtifact, + encodeArtifactContent, + generateArtifactContentHash, + getArtifact, + listArtifacts, +} from './artifacts'; + +/** + * Exposes an interface for access artifacts from within the context of a single integration (`packageName`) + */ +export class FleetArtifactsClient implements ArtifactsClientInterface { + constructor(private esClient: ElasticsearchClient, private packageName: string) { + if (!packageName) { + throw new ArtifactsClientError('packageName is required'); + } + } + + private validate(artifact: Artifact): Artifact { + if (artifact.packageName !== this.packageName) { + throw new ArtifactsClientAccessDeniedError(artifact.packageName, this.packageName); + } + + return artifact; + } + + async getArtifact(id: string): Promise { + const artifact = await getArtifact(this.esClient, id); + return artifact ? this.validate(artifact) : undefined; + } + + /** + * Creates a new artifact. Content will be compress and stored in binary form. + */ + async createArtifact({ + content, + type = '', + identifier = this.packageName, + }: ArtifactsClientCreateOptions): Promise { + const encodedMetaData = await this.encodeContent(content); + const newArtifactData: NewArtifact = { + type, + identifier, + packageName: this.packageName, + encryptionAlgorithm: 'none', + relative_url: relativeDownloadUrlFromArtifact({ + identifier, + decodedSha256: encodedMetaData.decodedSha256, + }), + ...encodedMetaData, + }; + + return createArtifact(this.esClient, newArtifactData); + } + + async deleteArtifact(id: string) { + // get the artifact first, which will also ensure its validated + const artifact = await this.getArtifact(id); + + if (artifact) { + await deleteArtifact(this.esClient, id); + } + } + + async listArtifacts({ kuery, ...options }: ListArtifactsProps = {}): Promise< + ListResult + > { + // All filtering for artifacts should be bound to the `packageName`, so we insert + // that into the KQL value and use `AND` to add the defined `kuery` (if any) to it. + const filter = `(packageName: "${this.packageName}")${kuery ? ` AND ${kuery}` : ''}`; + + return listArtifacts(this.esClient, { + ...options, + kuery: filter, + }); + } + + generateHash(content: string): string { + return generateArtifactContentHash(content); + } + + async encodeContent( + content: ArtifactsClientCreateOptions['content'] + ): Promise { + return encodeArtifactContent(content); + } +} diff --git a/x-pack/plugins/fleet/server/services/artifacts/constants.ts b/x-pack/plugins/fleet/server/services/artifacts/constants.ts new file mode 100644 index 0000000000000..27e6234b8487b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ARTIFACT_DOWNLOAD_RELATIVE_PATH = '/api/fleet/artifacts/{identifier}/{decodedSha256}'; diff --git a/x-pack/plugins/fleet/server/services/artifacts/index.ts b/x-pack/plugins/fleet/server/services/artifacts/index.ts new file mode 100644 index 0000000000000..d134f93ac00e1 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './artifacts'; +export * from './client'; +export * from './types'; diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts new file mode 100644 index 0000000000000..b4fbc2c575cd1 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Artifact, ArtifactElasticsearchProperties } from './types'; +import { ESSearchHit } from '../../../../../typings/elasticsearch'; +import { ARTIFACT_DOWNLOAD_RELATIVE_PATH } from './constants'; + +export const esSearchHitToArtifact = < + T extends Pick, '_id' | '_source'> +>( + searchHit: T +): Artifact => { + return { + ...searchHit._source, + id: searchHit._id, + }; +}; + +export const relativeDownloadUrlFromArtifact = < + T extends Pick +>({ + identifier, + decodedSha256, +}: T): string => { + return ARTIFACT_DOWNLOAD_RELATIVE_PATH.replace('{identifier}', identifier).replace( + '{decodedSha256}', + decodedSha256 + ); +}; diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts new file mode 100644 index 0000000000000..6763292f2fb44 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { URL } from 'url'; +import { ApiResponse } from '@elastic/elasticsearch'; +import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; +import { ESSearchHit, ESSearchResponse } from '../../../../../typings/elasticsearch'; +import { Artifact, ArtifactElasticsearchProperties, ArtifactsClientInterface } from './types'; + +export const createArtifactsClientMock = (): jest.Mocked => { + return { + getArtifact: jest.fn().mockResolvedValue(generateArtifactMock()), + createArtifact: jest.fn().mockResolvedValue(generateArtifactMock()), + deleteArtifact: jest.fn(), + listArtifacts: jest.fn().mockResolvedValue({ + items: [generateArtifactMock()], + total: 1, + perPage: 20, + page: 1, + }), + generateHash: jest + .fn() + .mockResolvedValue('e40a028b3dab7e567135b80ed69934a52be5b4c2d901faa8e0997b256c222473'), + encodeContent: jest.fn().mockResolvedValue({ + body: 'eJyrVspOrVSyUlAqS8wpTVWqBQArrwVB', + compressionAlgorithm: 'zlib', + decodedSha256: '9724c1e20e6e3e4d7f57ed25f9d4efb006e508590d528c90da597f6a775c13e5', + decodedSize: 16, + encodedSha256: '446086d1609189c3ad93a943976e4b7474c028612e5ec4810a81cc01a631f0f9', + encodedSize: 24, + }), + }; +}; + +export const generateArtifactMock = (): Artifact => { + return { + id: '123', + type: 'trustlist', + identifier: 'trustlist-v1', + packageName: 'endpoint', + encryptionAlgorithm: 'none', + relative_url: '/api/fleet/artifacts/trustlist-v1/d801aa1fb', + compressionAlgorithm: 'zlib', + decodedSha256: 'd801aa1fb', + decodedSize: 14, + encodedSha256: 'd29238d40', + encodedSize: 22, + body: 'eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==', + created: '2021-03-08T14:47:13.714Z', + }; +}; + +export interface GenerateEsRequestErrorApiResponseMockProps { + statusCode?: number; +} + +export const generateEsRequestErrorApiResponseMock = ( + { statusCode = 500 }: GenerateEsRequestErrorApiResponseMockProps = { statusCode: 500 } +): ApiResponse => { + return generateEsApiResponseMock( + { + _index: '.fleet-artifacts_1', + _id: '123', + found: false, + }, + { + statusCode, + } + ); +}; + +export const generateArtifactEsGetSingleHitMock = (): ESSearchHit => { + const { id, ..._source } = generateArtifactMock(); + + return { + _index: '.fleet-artifacts_1', + _id: id, + _version: 1, + _type: '', + _score: 1, + _source, + }; +}; + +export const generateArtifactEsSearchResultHitsMock = (): ESSearchResponse< + ArtifactElasticsearchProperties, + {} +> => { + return { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 2, + hits: [generateArtifactEsGetSingleHitMock()], + }, + }; +}; + +export const generateEsApiResponseMock = >( + body: TBody, + otherProps: Partial> = {} +): ApiResponse => { + return elasticsearchServiceMock.createApiResponse({ + body, + headers: { + 'content-type': 'application/json', + 'content-length': '697', + }, + meta: { + context: null, + request: { + params: { + method: 'GET', + path: '/.fleet-artifacts/_doc/02d38f4b-24cf-486e-b17e-9f727cfde23c', + body: undefined, + querystring: '', + }, + options: {}, + id: 7160, + }, + name: 'elasticsearch-js', + // There are some properties missing below which is not important for this mock + // @ts-ignore + connection: { + url: new URL('http://localhost:9200/'), + id: 'http://localhost:9200/', + headers: {}, + deadCount: 0, + resurrectTimeout: 0, + _openRequests: 0, + status: 'alive', + roles: { + master: true, + data: true, + ingest: true, + ml: false, + }, + }, + attempts: 0, + aborted: false, + }, + ...otherProps, + }); +}; + +type EsClientMock = ReturnType; +type EsClientMockMethods = keyof Pick; + +export const setEsClientMethodResponseToError = ( + esClientMock: EsClientMock, + method: EsClientMockMethods, + options?: GenerateEsRequestErrorApiResponseMockProps +) => { + esClientMock[method].mockImplementation(() => { + return elasticsearchServiceMock.createErrorTransportRequestPromise( + new ResponseError(generateEsRequestErrorApiResponseMock(options)) + ); + }); +}; diff --git a/x-pack/plugins/fleet/server/services/artifacts/types.ts b/x-pack/plugins/fleet/server/services/artifacts/types.ts new file mode 100644 index 0000000000000..fee2e4768d42d --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/types.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ListResult } from '../../../common'; +import { ListWithKuery } from '../../types'; + +export interface NewArtifact { + compressionAlgorithm: 'none' | 'zlib'; + encryptionAlgorithm: 'none'; + decodedSha256: string; + decodedSize: number; + encodedSha256: string; + encodedSize: number; + /** + * An identifier for the Artifact download. Value is used in download URL route, thus it should + * not include spaces and it should be all lowercase. Defaults to the `packageName` if no value + * is set during artifact creation. + */ + identifier: string; + /** The integration name that owns the artifact */ + packageName: string; + /** The relative URL to download this artifact from fleet-server */ + relative_url: string; + /** The encoded (binary) content of the artifact as BASE64 string */ + body: string; + type?: string; +} + +export interface Artifact extends NewArtifact { + id: string; + created: string; +} + +/** + * The set of Properties in Artifact that are actually stored in the Artifact document defined by the schema + */ +export type ArtifactElasticsearchProperties = Omit; + +export type ArtifactEncodedMetadata = Pick< + Artifact, + | 'decodedSha256' + | 'decodedSize' + | 'encodedSha256' + | 'encodedSize' + | 'compressionAlgorithm' + | 'body' +>; + +type ArtifactUserDefinedMetadata = Pick; + +export type ArtifactsClientCreateOptions = Partial & { + /** the artifact content. This value will be compressed and then stored as the `body` of the artifact */ + content: string; +}; + +export type ListArtifactsProps = Pick & { + sortField?: string | keyof ArtifactElasticsearchProperties; +}; + +/** + * The interface exposed out of Fleet's Artifact service via the client class + */ +export interface ArtifactsClientInterface { + getArtifact(id: string): Promise; + createArtifact(options: ArtifactsClientCreateOptions): Promise; + deleteArtifact(id: string): Promise; + listArtifacts(options?: ListWithKuery): Promise>; + encodeContent(content: ArtifactsClientCreateOptions['content']): Promise; + generateHash(content: string): string; +} diff --git a/x-pack/plugins/fleet/server/services/artifacts/utils.ts b/x-pack/plugins/fleet/server/services/artifacts/utils.ts new file mode 100644 index 0000000000000..bce6b1a1e815b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/artifacts/utils.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isESClientError } from '../../errors'; + +export const isElasticsearchItemNotFoundError = (error: Error): boolean => { + return isESClientError(error) && error.meta.statusCode === 404 && error.meta.body.found === false; +}; diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json index 01a2c82b71861..f6e1bd39ed873 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_artifacts.json @@ -22,16 +22,14 @@ "index": false }, "decodedSha256": { - "type": "keyword", - "index": false + "type": "keyword" }, "decodedSize": { "type": "long", "index": false }, "created": { - "type": "date", - "index": false + "type": "date" }, "packageName": { "type": "keyword" @@ -39,6 +37,9 @@ "type": { "type": "keyword" }, + "relative_url": { + "type": "keyword" + }, "body": { "type": "binary" } diff --git a/x-pack/plugins/fleet/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts index 43ff74c14fcf0..dec8ecb2b39cd 100644 --- a/x-pack/plugins/fleet/server/services/index.ts +++ b/x-pack/plugins/fleet/server/services/index.ts @@ -80,3 +80,6 @@ export { settingsService }; // Plugin services export { appContextService } from './app_context'; export { licenseService } from './license'; + +// Artifacts services +export * from './artifacts'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index b692d7fe69cd4..2b5319cafb1d4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -208,8 +208,8 @@ export const setup = async (arg?: { }; }; - const setFreeze = createFormToggleAction('freezeSwitch'); - const freezeExists = () => exists('freezeSwitch'); + const createSetFreeze = (phase: Phases) => createFormToggleAction(`${phase}-freezeSwitch`); + const createFreezeExists = (phase: Phases) => () => exists(`${phase}-freezeSwitch`); const createReadonlyActions = (phase: Phases) => { const toggleSelector = `${phase}-readonlySwitch`; @@ -275,21 +275,21 @@ export const setup = async (arg?: { const dataTierSelector = `${controlsSelector}.dataTierSelect`; const nodeAttrsSelector = `${phase}-selectedNodeAttrs`; + const openNodeAttributesSection = async () => { + await act(async () => { + find(dataTierSelector).simulate('click'); + }); + component.update(); + }; + return { hasDataTierAllocationControls: () => exists(controlsSelector), - openNodeAttributesSection: async () => { - await act(async () => { - find(dataTierSelector).simulate('click'); - }); - component.update(); - }, + openNodeAttributesSection, hasNodeAttributesSelect: (): boolean => exists(nodeAttrsSelector), getNodeAttributesSelectOptions: () => find(nodeAttrsSelector).find('option'), setDataAllocation: async (value: DataTierAllocationType) => { - act(() => { - find(dataTierSelector).simulate('click'); - }); - component.update(); + await openNodeAttributesSection(); + await act(async () => { switch (value) { case 'node_roles': @@ -359,6 +359,7 @@ export const setup = async (arg?: { hasHotPhase: () => exists('ilmTimelineHotPhase'), hasWarmPhase: () => exists('ilmTimelineWarmPhase'), hasColdPhase: () => exists('ilmTimelineColdPhase'), + hasFrozenPhase: () => exists('ilmTimelineFrozenPhase'), hasDeletePhase: () => exists('ilmTimelineDeletePhase'), }, hot: { @@ -390,13 +391,19 @@ export const setup = async (arg?: { enable: enable('cold'), ...createMinAgeActions('cold'), setReplicas: setReplicas('cold'), - setFreeze, - freezeExists, + setFreeze: createSetFreeze('cold'), + freezeExists: createFreezeExists('cold'), hasErrorIndicator: () => exists('phaseErrorIndicator-cold'), ...createIndexPriorityActions('cold'), ...createSearchableSnapshotActions('cold'), ...createNodeAllocationActions('cold'), }, + frozen: { + enable: enable('frozen'), + ...createMinAgeActions('frozen'), + hasErrorIndicator: () => exists('phaseErrorIndicator-frozen'), + ...createSearchableSnapshotActions('frozen'), + }, delete: { isShown: () => exists('delete-phaseContent'), ...createToggleDeletePhaseActions(), diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts new file mode 100644 index 0000000000000..3103a4198fc3b --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-dom/test-utils'; + +import { licensingMock } from '../../../../../licensing/public/mocks'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; +import { getDefaultHotPhasePolicy } from '../constants'; + +describe(' frozen phase', () => { + let testBed: EditPolicyTestBed; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: { data: ['node1'] }, + nodesByAttributes: { 'attribute:true': ['node1'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + httpRequestsMockHelpers.setNodesDetails('attribute:true', [ + { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, + ]); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('shows timing only when enabled', async () => { + const { actions, exists } = testBed; + + expect(exists('frozen-phase')).toBe(true); + expect(actions.frozen.hasMinAgeInput()).toBeFalsy(); + await actions.frozen.enable(true); + expect(actions.frozen.hasMinAgeInput()).toBeTruthy(); + }); + + describe('on non-enterprise license', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); + httpRequestsMockHelpers.setListNodes({ + isUsingDeprecatedDataRoleConfig: false, + nodesByAttributes: { test: ['123'] }, + nodesByRoles: { data: ['123'] }, + }); + httpRequestsMockHelpers.setListSnapshotRepos({ repositories: ['my-repo'] }); + + await act(async () => { + testBed = await setup({ + appServicesContext: { + license: licensingMock.createLicense({ license: { type: 'basic' } }), + }, + }); + }); + + const { component } = testBed; + component.update(); + }); + + test('should not be available', async () => { + const { exists } = testBed; + expect(exists('frozen-phase')).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation.test.ts index 13e55a1f39e2c..832963827663d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation.test.ts @@ -216,6 +216,7 @@ describe(' node allocation', () => { test('shows view node attributes link when attribute selected and shows flyout when clicked', async () => { const { actions, component } = testBed; + await actions.cold.enable(true); expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -250,23 +251,37 @@ describe(' node allocation', () => { expect(actions.cold.hasDefaultAllocationWarning()).toBeTruthy(); }); - test('shows default allocation notice when warm or hot tiers exists, but not cold tier', async () => { - httpRequestsMockHelpers.setListNodes({ - nodesByAttributes: {}, + [ + { + nodesByRoles: { data_hot: ['test'] }, + previousActiveRole: 'hot', + }, + { nodesByRoles: { data_hot: ['test'], data_warm: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); + previousActiveRole: 'warm', + }, + ].forEach(({ nodesByRoles, previousActiveRole }) => { + test(`shows default allocation notice when ${previousActiveRole} tiers exists, but not cold tier`, async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles, + isUsingDeprecatedDataRoleConfig: false, + }); - await act(async () => { - testBed = await setup(); - }); - const { actions, component } = testBed; + await act(async () => { + testBed = await setup(); + }); + const { actions, component, find } = testBed; - component.update(); - await actions.cold.enable(true); + component.update(); + await actions.cold.enable(true); - expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(actions.cold.hasDefaultAllocationNotice()).toBeTruthy(); + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.cold.hasDefaultAllocationNotice()).toBeTruthy(); + expect(find('defaultAllocationNotice').text()).toContain( + `This policy will move data in the cold phase to ${previousActiveRole} tier nodes` + ); + }); }); test(`doesn't show default allocation notice when node with "data" role exists`, async () => { @@ -366,7 +381,7 @@ describe(' node allocation', () => { expect(find('cloudDataTierCallout').exists()).toBeFalsy(); }); - test('shows cloud notice when cold tier nodes do not exist', async () => { + test(`shows cloud notice when cold tier nodes do not exist`, async () => { httpRequestsMockHelpers.setListNodes({ nodesByAttributes: {}, nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, @@ -375,13 +390,17 @@ describe(' node allocation', () => { await act(async () => { testBed = await setup({ appServicesContext: { cloud: { isCloudEnabled: true } } }); }); - const { actions, component, exists } = testBed; + const { actions, component, exists, find } = testBed; component.update(); await actions.cold.enable(true); expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(exists('cloudMissingColdTierCallout')).toBeTruthy(); + expect(exists('cloudMissingTierCallout')).toBeTruthy(); + expect(find('cloudMissingTierCallout').text()).toContain( + `Edit your Elastic Cloud deployment to set up a cold tier` + ); + // Assert that other notices are not showing expect(actions.cold.hasDefaultAllocationNotice()).toBeFalsy(); expect(actions.cold.hasNoNodeAttrsWarning()).toBeFalsy(); @@ -480,6 +499,7 @@ describe(' node allocation', () => { const { find } = testBed; expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).toBe('Custom'); }); + test('detecting use of the "off" allocation type', () => { const { find } = testBed; expect(find('cold-dataTierAllocationControls.dataTierSelect').text()).toContain('Off'); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.test.ts index e2b67efbf588d..506ac4cece032 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.test.ts @@ -45,6 +45,7 @@ describe(' timeline', () => { const { actions } = testBed; expect(actions.hot.forceMergeFieldExists()).toBeTruthy(); }); + test('hides forcemerge when rollover is disabled', async () => { const { actions } = testBed; await actions.hot.toggleDefaultRollover(false); @@ -56,22 +57,26 @@ describe(' timeline', () => { const { actions } = testBed; expect(actions.hot.shrinkExists()).toBeTruthy(); }); + test('hides shrink input when rollover is disabled', async () => { const { actions } = testBed; await actions.hot.toggleDefaultRollover(false); await actions.hot.toggleRollover(false); expect(actions.hot.shrinkExists()).toBeFalsy(); }); + test('shows readonly input when rollover enabled', async () => { const { actions } = testBed; expect(actions.hot.readonlyExists()).toBeTruthy(); }); + test('hides readonly input when rollover is disabled', async () => { const { actions } = testBed; await actions.hot.toggleDefaultRollover(false); await actions.hot.toggleRollover(false); expect(actions.hot.readonlyExists()).toBeFalsy(); }); + test('hides and disables searchable snapshot field', async () => { const { actions } = testBed; await actions.hot.toggleDefaultRollover(false); @@ -86,12 +91,15 @@ describe(' timeline', () => { await actions.warm.enable(true); await actions.cold.enable(true); + await actions.frozen.enable(true); await actions.delete.enablePhase(); expect(actions.warm.hasRolloverTipOnMinAge()).toBeTruthy(); expect(actions.cold.hasRolloverTipOnMinAge()).toBeTruthy(); + expect(actions.frozen.hasRolloverTipOnMinAge()).toBeTruthy(); expect(actions.delete.hasRolloverTipOnMinAge()).toBeTruthy(); }); + test('hiding rollover tip on minimum age', async () => { const { actions } = testBed; await actions.hot.toggleDefaultRollover(false); @@ -99,10 +107,12 @@ describe(' timeline', () => { await actions.warm.enable(true); await actions.cold.enable(true); + await actions.frozen.enable(true); await actions.delete.enablePhase(); expect(actions.warm.hasRolloverTipOnMinAge()).toBeFalsy(); expect(actions.cold.hasRolloverTipOnMinAge()).toBeFalsy(); + expect(actions.frozen.hasRolloverTipOnMinAge()).toBeFalsy(); expect(actions.delete.hasRolloverTipOnMinAge()).toBeFalsy(); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts index ed678a6b217ae..44e03564cb89a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts @@ -94,6 +94,7 @@ describe(' searchable snapshots', () => { ).toBe(true); }); }); + describe('existing policy', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); @@ -124,6 +125,7 @@ describe(' searchable snapshots', () => { }); }); }); + describe('on non-enterprise license', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); @@ -145,11 +147,13 @@ describe(' searchable snapshots', () => { const { component } = testBed; component.update(); }); + test('disable setting searchable snapshots', async () => { const { actions } = testBed; - expect(actions.cold.searchableSnapshotsExists()).toBeFalsy(); expect(actions.hot.searchableSnapshotsExists()).toBeFalsy(); + expect(actions.cold.searchableSnapshotsExists()).toBeFalsy(); + expect(actions.frozen.searchableSnapshotsExists()).toBeFalsy(); await actions.cold.enable(true); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts index 6fe968edb9b05..56f5815633a1d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts @@ -21,5 +21,6 @@ export type TestSubjects = | 'policyTablePolicyNameLink' | 'policyTitle' | 'createPolicyButton' - | 'freezeSwitch' + | 'cold-freezeSwitch' + | 'frozen-freezeSwitch' | string; diff --git a/x-pack/plugins/index_lifecycle_management/common/constants/data_tiers.ts b/x-pack/plugins/index_lifecycle_management/common/constants/data_tiers.ts index dcf8ce51a65ad..b00bb617b0be8 100644 --- a/x-pack/plugins/index_lifecycle_management/common/constants/data_tiers.ts +++ b/x-pack/plugins/index_lifecycle_management/common/constants/data_tiers.ts @@ -13,7 +13,15 @@ const WARM_PHASE_NODE_PREFERENCE: DataTierRole[] = ['data_warm', 'data_hot']; const COLD_PHASE_NODE_PREFERENCE: DataTierRole[] = ['data_cold', 'data_warm', 'data_hot']; +const FROZEN_PHASE_NODE_PREFERENCE: DataTierRole[] = [ + 'data_frozen', + 'data_cold', + 'data_warm', + 'data_hot', +]; + export const phaseToNodePreferenceMap: Record = Object.freeze({ warm: WARM_PHASE_NODE_PREFERENCE, cold: COLD_PHASE_NODE_PREFERENCE, + frozen: FROZEN_PHASE_NODE_PREFERENCE, }); diff --git a/x-pack/plugins/index_lifecycle_management/common/types/index.ts b/x-pack/plugins/index_lifecycle_management/common/types/index.ts index 3cd01f975d3b3..bc7e881a8c230 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/index.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/index.ts @@ -12,7 +12,7 @@ export * from './policies'; /** * These roles reflect how nodes are stratified into different data tiers. */ -export type DataTierRole = 'data_hot' | 'data_warm' | 'data_cold'; +export type DataTierRole = 'data_hot' | 'data_warm' | 'data_cold' | 'data_frozen'; /** * The "data_content" role can store all data the ES stack uses for feature diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 9f65286dc9b30..d3fec300d2d5f 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -7,7 +7,7 @@ import { Index as IndexInterface } from '../../../index_management/common/types'; -export type PhaseWithAllocation = 'warm' | 'cold'; +export type PhaseWithAllocation = 'warm' | 'cold' | 'frozen'; export interface SerializedPolicy { name: string; @@ -18,6 +18,7 @@ export interface Phases { hot?: SerializedHotPhase; warm?: SerializedWarmPhase; cold?: SerializedColdPhase; + frozen?: SerializedFrozenPhase; delete?: SerializedDeletePhase; } @@ -51,6 +52,8 @@ export interface SerializedActionWithAllocation { migrate?: MigrateAction; } +export type SearchableSnapshotStorage = 'full_copy' | 'shared_cache'; + export interface SearchableSnapshotAction { snapshot_repository: string; /** @@ -58,6 +61,12 @@ export interface SearchableSnapshotAction { * not suit the vast majority of cases. */ force_merge_index?: boolean; + /** + * This configuration lets the user create full or partial searchable snapshots. + * Full searchable snapshots store primary data locally and store replica data in the snapshot. + * Partial searchable snapshots store no data locally. + */ + storage?: SearchableSnapshotStorage; } export interface RolloverAction { @@ -111,6 +120,21 @@ export interface SerializedColdPhase extends SerializedPhase { }; } +export interface SerializedFrozenPhase extends SerializedPhase { + actions: { + freeze?: {}; + allocate?: AllocateAction; + set_priority?: { + priority: number | null; + }; + migrate?: MigrateAction; + /** + * Only available on enterprise license + */ + searchable_snapshot?: SearchableSnapshotAction; + }; +} + export interface SerializedDeletePhase extends SerializedPhase { actions: { wait_for_snapshot?: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index 39c52391927a0..6e3134f9f2428 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -11,6 +11,7 @@ export const defaultIndexPriority = { hot: '100', warm: '50', cold: '0', + frozen: '0', }; export const defaultRolloverAction: RolloverAction = { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_icon/phase_icon.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_icon/phase_icon.scss index 7c6a5aefdde6e..5bd6790dda572 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_icon/phase_icon.scss +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_icon/phase_icon.scss @@ -23,6 +23,9 @@ &__inner--cold { fill: $euiColorVis1_behindText; } + &__inner--frozen { + fill: $euiColorVis4_behindText; + } &__inner--delete { fill: $euiColorDarkShade; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx index 1dbc30674eaa5..bc22516e6c996 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -6,20 +6,15 @@ */ import React, { FunctionComponent } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiTextColor } from '@elastic/eui'; - import { useConfigurationIssues } from '../../../form'; - -import { LearnMoreLink, ToggleFieldWithDescribedFormRow } from '../../'; - import { DataTierAllocationField, SearchableSnapshotField, IndexPriorityField, ReplicasField, + FreezeField, } from '../shared_fields'; import { Phase } from '../phase'; @@ -41,35 +36,7 @@ export const ColdPhase: FunctionComponent = () => { {/* Freeze section */} - {!isUsingSearchableSnapshotInHotPhase && ( - - - - } - description={ - - {' '} - - - } - fullWidth - titleSize="xs" - switchProps={{ - 'data-test-subj': 'freezeSwitch', - path: '_meta.cold.freezeEnabled', - }} - > -
- - )} + {!isUsingSearchableSnapshotInHotPhase && } {/* Data tier allocation section */} { ); return ( - } - className="ilmDeletePhase ilmPhase" - timelineIcon={} - > - - {i18nTexts.editPolicy.descriptions.delete} - + <> - - + } + className="ilmDeletePhase ilmPhase" + timelineIcon={} + > + + {i18nTexts.editPolicy.descriptions.delete} + + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/frozen_phase/frozen_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/frozen_phase/frozen_phase.tsx new file mode 100644 index 0000000000000..41cdb2a5f10fa --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/frozen_phase/frozen_phase.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; + +import { SearchableSnapshotField } from '../shared_fields'; +import { Phase } from '../phase'; + +export const FrozenPhase: FunctionComponent = () => { + return ( + } + /> + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/frozen_phase/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/frozen_phase/index.ts new file mode 100644 index 0000000000000..e0f5ac9189adc --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/frozen_phase/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { FrozenPhase } from './frozen_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts index 62807d9499243..b248c15817548 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts @@ -11,6 +11,8 @@ export { WarmPhase } from './warm_phase'; export { ColdPhase } from './cold_phase'; +export { FrozenPhase } from './frozen_phase'; + export { DeletePhase } from './delete_phase'; export { Phase } from './phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx index 3a057f6204e24..040baf3625eb8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx @@ -23,16 +23,13 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { PhasesExceptDelete } from '../../../../../../../common/types'; import { ToggleField, useFormData } from '../../../../../../shared_imports'; import { i18nTexts } from '../../../i18n_texts'; - import { FormInternal } from '../../../types'; - import { UseField } from '../../../form'; - -import { PhaseErrorIndicator } from './phase_error_indicator'; - import { MinAgeField } from '../shared_fields'; import { PhaseIcon } from '../../phase_icon'; import { PhaseFooter } from '../../phase_footer'; +import { PhaseErrorIndicator } from './phase_error_indicator'; + import './phase.scss'; interface Props { @@ -99,6 +96,7 @@ export const Phase: FunctionComponent = ({ children, topLevelSettings, ph actions={minAge} timelineIcon={} className={`ilmPhase ${enabled ? 'ilmPhase--enabled' : ''}`} + data-test-subj={`${phase}-phase`} > {i18nTexts.editPolicy.descriptions[phase]} @@ -115,20 +113,28 @@ export const Phase: FunctionComponent = ({ children, topLevelSettings, ph )} - - } - buttonClassName="ilmSettingsButton" - extraAction={} - > - - {children} - + {children ? ( + + } + buttonClassName="ilmSettingsButton" + extraAction={} + > + + {children} + + ) : ( + + + + + + )} )} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx index b7a437d85add0..254063ac1a9ea 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx @@ -24,6 +24,17 @@ import './data_tier_allocation.scss'; type SelectOptions = EuiSuperSelectOption; +const customTexts = { + inputDisplay: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.customOption.input', + { defaultMessage: 'Custom' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.customOption.helpText', + { defaultMessage: 'Move data based on node attributes.' } + ), +}; + const i18nTexts = { allocationOptions: { warm: { @@ -47,16 +58,7 @@ const i18nTexts = { { defaultMessage: 'Do not move data in the warm phase.' } ), }, - custom: { - inputDisplay: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.input', - { defaultMessage: 'Custom' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.helpText', - { defaultMessage: 'Move data based on node attributes.' } - ), - }, + custom: customTexts, }, cold: { default: { @@ -79,16 +81,30 @@ const i18nTexts = { { defaultMessage: 'Do not move data in the cold phase.' } ), }, - custom: { + custom: customTexts, + }, + frozen: { + default: { + input: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.frozen.defaultOption.input', + { defaultMessage: 'Use frozen nodes (recommended)' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.frozen.defaultOption.helpText', + { defaultMessage: 'Move data to nodes in the frozen tier.' } + ), + }, + none: { inputDisplay: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input', - { defaultMessage: 'Custom' } + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.frozen.noneOption.input', + { defaultMessage: 'Off' } ), helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText', - { defaultMessage: 'Move data based on node attributes.' } + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.frozen.noneOption.helpText', + { defaultMessage: 'Do not move data in the frozen phase.' } ), }, + custom: customTexts, }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx index e43b750849774..b09d09d254085 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx @@ -21,6 +21,9 @@ const i18nTextsNodeRoleToDataTier: Record = { data_cold: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierColdLabel', { defaultMessage: 'cold', }), + data_frozen: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierFrozenLabel', { + defaultMessage: 'frozen', + }), }; const i18nTexts = { @@ -49,6 +52,21 @@ const i18nTexts = { values: { tier: i18nTextsNodeRoleToDataTier[nodeRole] }, }), }, + frozen: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.frozen.title', + { defaultMessage: 'No nodes assigned to the frozen tier' } + ), + body: (nodeRole: DataTierRole) => + i18n.translate( + 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.frozen', + { + defaultMessage: + 'This policy will move data in the frozen phase to {tier} tier nodes instead.', + values: { tier: i18nTextsNodeRoleToDataTier[nodeRole] }, + } + ), + }, }, warning: { warm: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_warning.tsx index a194f3c07f900..649eb9f2fcb7f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_warning.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_warning.tsx @@ -39,6 +39,19 @@ const i18nTexts = { } ), }, + frozen: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.frozenPhase.dataTier.defaultAllocationNotAvailableTitle', + { defaultMessage: 'No nodes assigned to the frozen tier' } + ), + body: i18n.translate( + 'xpack.indexLifecycleMgmt.frozenPhase.dataTier.defaultAllocationNotAvailableBody', + { + defaultMessage: + 'Assign at least one node to the frozen, cold, warm, or hot tier to use role-based allocation. The policy will fail to complete allocation if there are no available nodes.', + } + ), + }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts index 938e0a850f933..dacec1df52e2e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts @@ -17,7 +17,7 @@ export { DefaultAllocationWarning } from './default_allocation_warning'; export { NoNodeAttributesWarning } from './no_node_attributes_warning'; -export { MissingColdTierCallout } from './missing_cold_tier_callout'; +export { MissingCloudTierCallout } from './missing_cloud_tier_callout'; export { CloudDataTierCallout } from './cloud_data_tier_callout'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/missing_cold_tier_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/missing_cloud_tier_callout.tsx similarity index 70% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/missing_cold_tier_callout.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/missing_cloud_tier_callout.tsx index 21b8850e0b088..09d3135cde469 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/missing_cold_tier_callout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/missing_cloud_tier_callout.tsx @@ -9,20 +9,23 @@ import { i18n } from '@kbn/i18n'; import React, { FunctionComponent } from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; -const i18nTexts = { - title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudMissingColdTierCallout.title', { - defaultMessage: 'Create a cold tier', +const geti18nTexts = (tier: 'cold' | 'frozen') => ({ + title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudMissingTierCallout.title', { + defaultMessage: 'Create a {tier} tier', + values: { tier }, }), - body: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudMissingColdTierCallout.body', { - defaultMessage: 'Edit your Elastic Cloud deployment to set up a cold tier.', + body: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudMissingTierCallout.body', { + defaultMessage: 'Edit your Elastic Cloud deployment to set up a {tier} tier.', + values: { tier }, }), linkText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.cloudMissingColdTierCallout.linkToCloudDeploymentDescription', + 'xpack.indexLifecycleMgmt.editPolicy.cloudMissingTierCallout.linkToCloudDeploymentDescription', { defaultMessage: 'View cloud deployment' } ), -}; +}); interface Props { + phase: 'cold' | 'frozen'; linkToCloudDeployment?: string; } @@ -31,9 +34,14 @@ interface Props { * This may need to be change when we have autoscaling enabled on a cluster because nodes may not * yet exist, but will automatically be provisioned. */ -export const MissingColdTierCallout: FunctionComponent = ({ linkToCloudDeployment }) => { +export const MissingCloudTierCallout: FunctionComponent = ({ + phase, + linkToCloudDeployment, +}) => { + const i18nTexts = geti18nTexts(phase); + return ( - + {i18nTexts.body}{' '} {Boolean(linkToCloudDeployment) && ( diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx index c4ca0c5e495e1..e6cadf7049962 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx @@ -33,6 +33,15 @@ const i18nTexts = { } ), }, + frozen: { + body: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.frozen.nodeAttributesMissingDescription', + { + defaultMessage: + 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation.', + } + ), + }, }; export const NoNodeAttributesWarning: FunctionComponent<{ phase: PhaseWithAllocation }> = ({ diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx index 7a660e0379a8d..ef0e82063ce20 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx @@ -25,7 +25,7 @@ import { DefaultAllocationNotice, DefaultAllocationWarning, NoNodeAttributesWarning, - MissingColdTierCallout, + MissingCloudTierCallout, CloudDataTierCallout, LoadingError, } from './components'; @@ -71,17 +71,21 @@ export const DataTierAllocationField: FunctionComponent = ({ phase, descr switch (allocationType) { case 'node_roles': /** - * We'll drive Cloud users to add a cold tier to their deployment if there are no nodes with the cold node role. + * We'll drive Cloud users to add a cold or frozen tier to their deployment if there are no nodes with that role. */ - if (isCloudEnabled && phase === 'cold' && !isUsingDeprecatedDataRoleConfig) { - const hasNoNodesWithNodeRole = !nodesByRoles.data_cold?.length; + if ( + isCloudEnabled && + !isUsingDeprecatedDataRoleConfig && + (phase === 'cold' || phase === 'frozen') + ) { + const hasNoNodesWithNodeRole = !nodesByRoles[`data_${phase}` as const]?.length; if (hasDataNodeRoles && hasNoNodesWithNodeRole) { // Tell cloud users they can deploy nodes on cloud. return ( <> - + ); } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/freeze_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/freeze_field.tsx new file mode 100644 index 0000000000000..8db1829f03764 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/freeze_field.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { FunctionComponent } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTextColor } from '@elastic/eui'; + +import { LearnMoreLink, ToggleFieldWithDescribedFormRow } from '../../'; + +interface Props { + phase: 'cold' | 'frozen'; +} + +export const FreezeField: FunctionComponent = ({ phase }) => { + return ( + + + + } + description={ + + {' '} + + + } + fullWidth + titleSize="xs" + switchProps={{ + 'data-test-subj': `${phase}-freezeSwitch`, + path: `_meta.${phase}.freezeEnabled`, + }} + > +
+ + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts index 220f0bd8e941a..91faf5c66df81 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts @@ -22,3 +22,5 @@ export { ReadonlyField } from './readonly_field'; export { ReplicasField } from './replicas_field'; export { IndexPriorityField } from './index_priority_field'; + +export { FreezeField } from './freeze_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx index 507a99c4754b8..47d2aa6ba92df 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx @@ -18,7 +18,7 @@ import { UseField } from '../../../form'; import { LearnMoreLink, DescribedFormRow } from '../..'; interface Props { - phase: 'hot' | 'warm' | 'cold'; + phase: 'hot' | 'warm' | 'cold' | 'frozen'; } export const IndexPriorityField: FunctionComponent = ({ phase }) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx index 4f005eb4fd201..ea05e401822ce 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx @@ -16,7 +16,7 @@ import { UseField } from '../../../form'; import { DescribedFormRow } from '../../described_form_row'; interface Props { - phase: 'warm' | 'cold'; + phase: 'warm' | 'cold' | 'frozen'; } export const ReplicasField: FunctionComponent = ({ phase }) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx index 3fc7064575555..816e1aaec31d7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx @@ -17,28 +17,18 @@ import { EuiLink, } from '@elastic/eui'; -import { - ComboBoxField, - useKibana, - fieldValidators, - useFormData, -} from '../../../../../../../shared_imports'; +import { ComboBoxField, useKibana, useFormData } from '../../../../../../../shared_imports'; import { useEditPolicyContext } from '../../../../edit_policy_context'; -import { useConfigurationIssues, UseField } from '../../../../form'; - -import { i18nTexts } from '../../../../i18n_texts'; - +import { useConfigurationIssues, UseField, searchableSnapshotFields } from '../../../../form'; import { FieldLoadingError, DescribedFormRow, LearnMoreLink } from '../../../'; - import { SearchableSnapshotDataProvider } from './searchable_snapshot_data_provider'; import './_searchable_snapshot_field.scss'; -const { emptyField } = fieldValidators; - export interface Props { - phase: 'hot' | 'cold'; + phase: 'hot' | 'cold' | 'frozen'; + canBeDisabled?: boolean; } /** @@ -47,29 +37,62 @@ export interface Props { */ const CLOUD_DEFAULT_REPO = 'found-snapshots'; -export const SearchableSnapshotField: FunctionComponent = ({ phase }) => { +const geti18nTexts = (phase: Props['phase']) => ({ + title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldTitle', { + defaultMessage: 'Searchable snapshot', + }), + description: + phase === 'frozen' ? ( + + ), + }} + /> + ) : ( + , + }} + /> + ), +}); + +export const SearchableSnapshotField: FunctionComponent = ({ + phase, + canBeDisabled = true, +}) => { const { services: { cloud }, } = useKibana(); const { getUrlForApp, policy, license, isNewPolicy } = useEditPolicyContext(); const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues(); - const searchableSnapshotPath = `phases.${phase}.actions.searchable_snapshot.snapshot_repository`; + const searchableSnapshotRepoPath = `phases.${phase}.actions.searchable_snapshot.snapshot_repository`; - const [formData] = useFormData({ watch: searchableSnapshotPath }); - const searchableSnapshotRepo = get(formData, searchableSnapshotPath); + const [formData] = useFormData({ watch: searchableSnapshotRepoPath }); + const searchableSnapshotRepo = get(formData, searchableSnapshotRepoPath); const isColdPhase = phase === 'cold'; + const isFrozenPhase = phase === 'frozen'; + const isColdOrFrozenPhase = isColdPhase || isFrozenPhase; const isDisabledDueToLicense = !license.canUseSearchableSnapshot(); const [isFieldToggleChecked, setIsFieldToggleChecked] = useState(() => Boolean( - // New policy on cloud should have searchable snapshot on in cold phase - (isColdPhase && isNewPolicy && cloud?.isCloudEnabled) || + // New policy on cloud should have searchable snapshot on in cold and frozen phase + (isColdOrFrozenPhase && isNewPolicy && cloud?.isCloudEnabled) || policy.phases[phase]?.actions?.searchable_snapshot?.snapshot_repository ) ); + const i18nTexts = geti18nTexts(phase); + useEffect(() => { if (isDisabledDueToLicense) { setIsFieldToggleChecked(false); @@ -180,17 +203,10 @@ export const SearchableSnapshotField: FunctionComponent = ({ phase }) =>
config={{ - label: i18nTexts.editPolicy.searchableSnapshotsFieldLabel, + ...searchableSnapshotFields.snapshot_repository, defaultValue: cloud?.isCloudEnabled ? CLOUD_DEFAULT_REPO : undefined, - validations: [ - { - validator: emptyField( - i18nTexts.editPolicy.errors.searchableSnapshotRepoRequired - ), - }, - ], }} - path={searchableSnapshotPath} + path={searchableSnapshotRepoPath} > {(field) => { const singleSelectionArray: [selectedSnapshot?: string] = field.value @@ -289,34 +305,24 @@ export const SearchableSnapshotField: FunctionComponent = ({ phase }) => return ( - {i18n.translate('xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldTitle', { - defaultMessage: 'Searchable snapshot', - })} - + switchProps={ + canBeDisabled + ? { + checked: isFieldToggleChecked, + disabled: isDisabledDueToLicense, + onChange: setIsFieldToggleChecked, + 'data-test-subj': 'searchableSnapshotToggle', + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotsToggleLabel', + { defaultMessage: 'Create searchable snapshot' } + ), + } + : undefined } + title={

{i18nTexts.title}

} description={ <> - - , - }} - /> - + {i18nTexts.description} } fieldNotices={renderInfoCallout()} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index 2f5d00082cc8a..93547fdebffe5 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -42,6 +42,7 @@ const prettifyFormJson = (policy: SerializedPolicy): SerializedPolicy => ({ hot: policy.phases.hot, warm: policy.phases.warm, cold: policy.phases.cold, + frozen: policy.phases.frozen, delete: policy.phases.delete, }, }); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.container.tsx index c2aa011f582c7..88d9d2de03d89 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.container.tsx @@ -26,6 +26,7 @@ export const Timeline: FunctionComponent = () => { hotPhaseMinAge={timings.hot.min_age} warmPhaseMinAge={timings.warm?.min_age} coldPhaseMinAge={timings.cold?.min_age} + frozenPhaseMinAge={timings.frozen?.min_age} deletePhaseMinAge={timings.delete?.min_age} isUsingRollover={isUsingRollover} hasDeletePhase={Boolean(formData._meta?.delete?.enabled)} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss index de49e665ed933..983ef0ab20f69 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss @@ -80,4 +80,12 @@ $ilmTimelineBarHeight: $euiSizeS; background-color: $euiColorVis1; } } + + &__frozenPhase { + width: var(--ilm-timeline-frozen-phase-width); + + &__colorBar { + background-color: $euiColorVis4; + } + } } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx index c996c45171d2f..8a0028dcb8b19 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx @@ -62,6 +62,9 @@ const i18nTexts = { coldPhase: i18n.translate('xpack.indexLifecycleMgmt.timeline.coldPhaseSectionTitle', { defaultMessage: 'Cold phase', }), + frozenPhase: i18n.translate('xpack.indexLifecycleMgmt.timeline.frozenPhaseSectionTitle', { + defaultMessage: 'Frozen phase', + }), deleteIcon: { toolTipContent: i18n.translate('xpack.indexLifecycleMgmt.timeline.deleteIconToolTipContent', { defaultMessage: 'Policy deletes the index after lifecycle phases complete.', @@ -84,12 +87,17 @@ const calculateWidths = (inputs: PhaseAgeInMilliseconds) => { inputs.phases.cold != null ? msTimeToOverallPercent(inputs.phases.cold, inputs.total) + SCORE_BUFFER_AMOUNT : 0; + const frozenScore = + inputs.phases.frozen != null + ? msTimeToOverallPercent(inputs.phases.frozen, inputs.total) + SCORE_BUFFER_AMOUNT + : 0; - const totalScore = hotScore + warmScore + coldScore; + const totalScore = hotScore + warmScore + coldScore + frozenScore; return { hot: `${toPercent(hotScore, totalScore)}%`, warm: `${toPercent(warmScore, totalScore)}%`, cold: `${toPercent(coldScore, totalScore)}%`, + frozen: `${toPercent(frozenScore, totalScore)}%`, }; }; @@ -102,6 +110,7 @@ interface Props { isUsingRollover: boolean; warmPhaseMinAge?: string; coldPhaseMinAge?: string; + frozenPhaseMinAge?: string; deletePhaseMinAge?: string; } @@ -115,6 +124,9 @@ export const Timeline: FunctionComponent = memo( hot: { min_age: phasesMinAge.hotPhaseMinAge }, warm: phasesMinAge.warmPhaseMinAge ? { min_age: phasesMinAge.warmPhaseMinAge } : undefined, cold: phasesMinAge.coldPhaseMinAge ? { min_age: phasesMinAge.coldPhaseMinAge } : undefined, + frozen: phasesMinAge.frozenPhaseMinAge + ? { min_age: phasesMinAge.frozenPhaseMinAge } + : undefined, delete: phasesMinAge.deletePhaseMinAge ? { min_age: phasesMinAge.deletePhaseMinAge } : undefined, @@ -157,6 +169,7 @@ export const Timeline: FunctionComponent = memo( el.style.setProperty('--ilm-timeline-hot-phase-width', widths.hot); el.style.setProperty('--ilm-timeline-warm-phase-width', widths.warm ?? null); el.style.setProperty('--ilm-timeline-cold-phase-width', widths.cold ?? null); + el.style.setProperty('--ilm-timeline-frozen-phase-width', widths.frozen ?? null); } }} > @@ -198,6 +211,18 @@ export const Timeline: FunctionComponent = memo( />
)} + {exists(phaseAgeInMilliseconds.phases.frozen) && ( +
+
+ +
+ )}
{hasDeletePhase && ( diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index befb8faf51aa1..ed165e8638843 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -43,6 +43,7 @@ import { ColdPhase, DeletePhase, HotPhase, + FrozenPhase, PolicyJsonFlyout, WarmPhase, Timeline, @@ -72,6 +73,7 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { policy: currentPolicy, existingPolicies, policyName, + license, } = useEditPolicyContext(); const serializer = useMemo(() => { @@ -80,6 +82,7 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { const [saveAsNew, setSaveAsNew] = useState(false); const originalPolicyName: string = isNewPolicy ? '' : policyName!; + const isAllowedByLicense = license.canUseSearchableSnapshot(); const { form } = useForm({ schema, @@ -243,13 +246,21 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { - - + {isAllowedByLicense && ( + <> + + + + )} + + {/* We can't add the here as it breaks the layout + and makes the connecting line go further that it needs to. + There is an issue in EUI to fix this (https://github.com/elastic/eui/issues/4492) */}
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx index 7210dc6b7ce2b..34999368ffab2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx @@ -23,6 +23,7 @@ const isXPhaseField = (phase: keyof Phases) => (fieldPath: string): boolean => const isHotPhaseField = isXPhaseField('hot'); const isWarmPhaseField = isXPhaseField('warm'); const isColdPhaseField = isXPhaseField('cold'); +const isFrozenPhaseField = isXPhaseField('frozen'); const isDeletePhaseField = isXPhaseField('delete'); const determineFieldPhase = (fieldPath: string): keyof Phases | 'other' => { @@ -35,6 +36,9 @@ const determineFieldPhase = (fieldPath: string): keyof Phases | 'other' => { if (isColdPhaseField(fieldPath)) { return 'cold'; } + if (isFrozenPhaseField(fieldPath)) { + return 'frozen'; + } if (isDeletePhaseField(fieldPath)) { return 'delete'; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx index cc021f101cfb5..c2e55f7aa6e61 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx @@ -25,6 +25,7 @@ export interface ConfigurationIssues { * See https://github.com/elastic/elasticsearch/blob/master/docs/reference/ilm/actions/ilm-searchable-snapshot.asciidoc. */ isUsingSearchableSnapshotInHotPhase: boolean; + isUsingSearchableSnapshotInColdPhase: boolean; } const ConfigurationIssuesContext = createContext(null as any); @@ -32,10 +33,14 @@ const ConfigurationIssuesContext = createContext(null as an const pathToHotPhaseSearchableSnapshot = 'phases.hot.actions.searchable_snapshot.snapshot_repository'; +const pathToColdPhaseSearchableSnapshot = + 'phases.cold.actions.searchable_snapshot.snapshot_repository'; + export const ConfigurationIssuesProvider: FunctionComponent = ({ children }) => { const [formData] = useFormData({ watch: [ pathToHotPhaseSearchableSnapshot, + pathToColdPhaseSearchableSnapshot, isUsingCustomRolloverPath, isUsingDefaultRolloverPath, ], @@ -50,6 +55,8 @@ export const ConfigurationIssuesProvider: FunctionComponent = ({ children }) => isUsingRollover: isUsingDefaultRollover === false ? isUsingCustomRollover : true, isUsingSearchableSnapshotInHotPhase: get(formData, pathToHotPhaseSearchableSnapshot) != null, + isUsingSearchableSnapshotInColdPhase: + get(formData, pathToColdPhaseSearchableSnapshot) != null, }} > {children} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts index 3e70cbb533653..227f135ca7b72 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts @@ -17,7 +17,7 @@ import { FormInternal } from '../types'; export const deserializer = (policy: SerializedPolicy): FormInternal => { const { - phases: { hot, warm, cold, delete: deletePhase }, + phases: { hot, warm, cold, frozen, delete: deletePhase }, } = policy; const _meta: FormInternal['_meta'] = { @@ -41,6 +41,11 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { dataTierAllocationType: determineDataTierAllocationType(cold?.actions), freezeEnabled: Boolean(cold?.actions?.freeze), }, + frozen: { + enabled: Boolean(frozen), + dataTierAllocationType: determineDataTierAllocationType(frozen?.actions), + freezeEnabled: Boolean(frozen?.actions?.freeze), + }, delete: { enabled: Boolean(deletePhase), }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx index 9877a2ea9449c..b4aab0ffdea60 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx @@ -26,6 +26,7 @@ interface Errors { hot: ErrorGroup; warm: ErrorGroup; cold: ErrorGroup; + frozen: ErrorGroup; delete: ErrorGroup; /** * Errors that are not specific to a phase should go here. @@ -46,6 +47,7 @@ const createEmptyErrors = (): Errors => ({ hot: {}, warm: {}, cold: {}, + frozen: {}, delete: {}, other: {}, }); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts index 734a12a72bd30..6deb4d7fd4711 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts @@ -9,7 +9,7 @@ export { deserializer } from './deserializer'; export { createSerializer } from './serializer'; -export { schema } from './schema'; +export { schema, searchableSnapshotFields } from './schema'; export * from './validations'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/phase_timings_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/phase_timings_context.tsx index 92cc8eeead91a..98ffb7e2dd7af 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/phase_timings_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/phase_timings_context.tsx @@ -23,19 +23,24 @@ const getPhaseTimingConfiguration = ( hot: PhaseTimingConfiguration; warm: PhaseTimingConfiguration; cold: PhaseTimingConfiguration; + frozen: PhaseTimingConfiguration; } => { const isWarmPhaseEnabled = formData?._meta?.warm?.enabled; const isColdPhaseEnabled = formData?._meta?.cold?.enabled; + const isFrozenPhaseEnabled = formData?._meta?.frozen?.enabled; + return { - hot: { isFinalDataPhase: !isWarmPhaseEnabled && !isColdPhaseEnabled }, - warm: { isFinalDataPhase: isWarmPhaseEnabled && !isColdPhaseEnabled }, - cold: { isFinalDataPhase: isColdPhaseEnabled }, + hot: { isFinalDataPhase: !isWarmPhaseEnabled && !isColdPhaseEnabled && !isFrozenPhaseEnabled }, + warm: { isFinalDataPhase: isWarmPhaseEnabled && !isColdPhaseEnabled && !isFrozenPhaseEnabled }, + cold: { isFinalDataPhase: isColdPhaseEnabled && !isFrozenPhaseEnabled }, + frozen: { isFinalDataPhase: isFrozenPhaseEnabled }, }; }; export interface PhaseTimings { hot: PhaseTimingConfiguration; warm: PhaseTimingConfiguration; cold: PhaseTimingConfiguration; + frozen: PhaseTimingConfiguration; isDeletePhaseEnabled: boolean; setDeletePhaseEnabled: (enabled: boolean) => void; } @@ -44,7 +49,12 @@ const PhaseTimingsContext = createContext(null as any); export const PhaseTimingsProvider: FunctionComponent = ({ children }) => { const [formData] = useFormData({ - watch: ['_meta.warm.enabled', '_meta.cold.enabled', '_meta.delete.enabled'], + watch: [ + '_meta.warm.enabled', + '_meta.cold.enabled', + '_meta.frozen.enabled', + '_meta.delete.enabled', + ], }); return ( @@ -65,6 +75,7 @@ export const PhaseTimingsProvider: FunctionComponent = ({ children }) => { ); }; + export const usePhaseTimings = () => { const ctx = useContext(PhaseTimingsContext); if (!ctx) throw new Error('Cannot use phase timings outside of phase timings context'); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 65fc82b7ccc68..5861c7b320de1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -30,6 +30,90 @@ const serializers = { stringToNumber: (v: string): any => (v != null ? parseInt(v, 10) : undefined), }; +const maxNumSegmentsField = { + label: i18nTexts.editPolicy.maxNumSegmentsFieldLabel, + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.forcemerge.numberOfSegmentsRequiredError', + { defaultMessage: 'A value for number of segments is required.' } + ) + ), + }, + { + validator: ifExistsNumberGreaterThanZero, + }, + ], + serializer: serializers.stringToNumber, +}; + +export const searchableSnapshotFields = { + snapshot_repository: { + label: i18nTexts.editPolicy.searchableSnapshotsRepoFieldLabel, + validations: [ + { validator: emptyField(i18nTexts.editPolicy.errors.searchableSnapshotRepoRequired) }, + ], + }, + storage: { + label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageLabel', { + defaultMessage: 'Storage', + }), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageHelpText', + { + defaultMessage: + "Type of snapshot mounted for the searchable snapshot. This is an advanced option. Only change it if you know what you're doing.", + } + ), + }, +}; + +const numberOfReplicasField = { + label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.numberOfReplicasLabel', { + defaultMessage: 'Number of replicas', + }), + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { + validator: ifExistsNumberNonNegative, + }, + ], + serializer: serializers.stringToNumber, +}; + +const numberOfShardsField = { + label: i18n.translate('xpack.indexLifecycleMgmt.shrink.numberOfPrimaryShardsLabel', { + defaultMessage: 'Number of primary shards', + }), + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { + validator: numberGreaterThanField({ + message: i18nTexts.editPolicy.errors.numberGreatThan0Required, + than: 0, + }), + }, + ], + serializer: serializers.stringToNumber, +}; + +const getPriorityField = (phase: 'hot' | 'warm' | 'cold' | 'frozen') => ({ + defaultValue: defaultIndexPriority[phase] as any, + label: i18nTexts.editPolicy.indexPriorityFieldLabel, + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { validator: ifExistsNumberNonNegative }, + ], + serializer: serializers.stringToNumber, +}); + export const schema: FormSchema = { _meta: { hot: { @@ -110,6 +194,30 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, }, + frozen: { + enabled: { + defaultValue: false, + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.frozenPhase.activateFrozenPhaseSwitchLabel', + { defaultMessage: 'Activate frozen phase' } + ), + }, + freezeEnabled: { + defaultValue: false, + label: i18n.translate('xpack.indexLifecycleMgmt.frozePhase.freezeIndexLabel', { + defaultMessage: 'Freeze index', + }), + }, + minAgeUnit: { + defaultValue: 'd', + }, + dataTierAllocationType: { + label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, + }, + allocationNodeAttribute: { + label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, + }, + }, delete: { enabled: { defaultValue: false, @@ -172,55 +280,13 @@ export const schema: FormSchema = { }, }, forcemerge: { - max_num_segments: { - label: i18nTexts.editPolicy.maxNumSegmentsFieldLabel, - validations: [ - { - validator: emptyField( - i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.forcemerge.numberOfSegmentsRequiredError', - { defaultMessage: 'A value for number of segments is required.' } - ) - ), - }, - { - validator: ifExistsNumberGreaterThanZero, - }, - ], - serializer: serializers.stringToNumber, - }, + max_num_segments: maxNumSegmentsField, }, shrink: { - number_of_shards: { - label: i18n.translate('xpack.indexLifecycleMgmt.shrink.numberOfPrimaryShardsLabel', { - defaultMessage: 'Number of primary shards', - }), - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { - validator: numberGreaterThanField({ - message: i18nTexts.editPolicy.errors.numberGreatThan0Required, - than: 0, - }), - }, - ], - serializer: serializers.stringToNumber, - }, + number_of_shards: numberOfShardsField, }, set_priority: { - priority: { - defaultValue: defaultIndexPriority.hot as any, - label: i18nTexts.editPolicy.indexPriorityFieldLabel, - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { validator: ifExistsNumberNonNegative }, - ], - serializer: serializers.stringToNumber, - }, + priority: getPriorityField('hot'), }, }, }, @@ -235,71 +301,16 @@ export const schema: FormSchema = { }, actions: { allocate: { - number_of_replicas: { - label: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel', { - defaultMessage: 'Number of replicas', - }), - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { - validator: ifExistsNumberNonNegative, - }, - ], - serializer: serializers.stringToNumber, - }, + number_of_replicas: numberOfReplicasField, }, shrink: { - number_of_shards: { - label: i18n.translate('xpack.indexLifecycleMgmt.shrink.numberOfPrimaryShardsLabel', { - defaultMessage: 'Number of primary shards', - }), - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { - validator: numberGreaterThanField({ - message: i18nTexts.editPolicy.errors.numberGreatThan0Required, - than: 0, - }), - }, - ], - serializer: serializers.stringToNumber, - }, + number_of_shards: numberOfShardsField, }, forcemerge: { - max_num_segments: { - label: i18nTexts.editPolicy.maxNumSegmentsFieldLabel, - validations: [ - { - validator: emptyField( - i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.forcemerge.numberOfSegmentsRequiredError', - { defaultMessage: 'A value for number of segments is required.' } - ) - ), - }, - { - validator: ifExistsNumberGreaterThanZero, - }, - ], - serializer: serializers.stringToNumber, - }, + max_num_segments: maxNumSegmentsField, }, set_priority: { - priority: { - defaultValue: defaultIndexPriority.warm as any, - label: i18nTexts.editPolicy.indexPriorityFieldLabel, - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { validator: ifExistsNumberNonNegative }, - ], - serializer: serializers.stringToNumber, - }, + priority: getPriorityField('warm'), }, }, }, @@ -314,42 +325,31 @@ export const schema: FormSchema = { }, actions: { allocate: { - number_of_replicas: { - label: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel', { - defaultMessage: 'Number of replicas', - }), - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { - validator: ifExistsNumberNonNegative, - }, - ], - serializer: serializers.stringToNumber, - }, + number_of_replicas: numberOfReplicasField, }, set_priority: { - priority: { - defaultValue: defaultIndexPriority.cold as any, - label: i18nTexts.editPolicy.indexPriorityFieldLabel, - validations: [ - { - validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), - }, - { validator: ifExistsNumberNonNegative }, - ], - serializer: serializers.stringToNumber, - }, + priority: getPriorityField('cold'), }, - searchable_snapshot: { - snapshot_repository: { - label: i18nTexts.editPolicy.searchableSnapshotsFieldLabel, - validations: [ - { validator: emptyField(i18nTexts.editPolicy.errors.searchableSnapshotRepoRequired) }, - ], + searchable_snapshot: searchableSnapshotFields, + }, + }, + frozen: { + min_age: { + defaultValue: '0', + validations: [ + { + validator: minAgeValidator, }, + ], + }, + actions: { + allocate: { + number_of_replicas: numberOfReplicasField, + }, + set_priority: { + priority: getPriorityField('frozen'), }, + searchable_snapshot: searchableSnapshotFields, }, }, delete: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index 746ba4eeb801f..b21545ce1739c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -241,6 +241,23 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( delete draft.phases.cold; } + /** + * FROZEN PHASE SERIALIZATION + */ + if (_meta.frozen.enabled) { + draft.phases.frozen!.actions = draft.phases.frozen?.actions ?? {}; + const frozenPhase = draft.phases.frozen!; + + /** + * FROZEN PHASE SEARCHABLE SNAPSHOT + */ + if (!updatedPolicy.phases.frozen?.actions?.searchable_snapshot) { + delete frozenPhase.actions.searchable_snapshot; + } + } else { + delete draft.phases.frozen; + } + /** * DELETE PHASE SERIALIZATION */ diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 1d75fb5031216..47585fba38768 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -77,12 +77,18 @@ export const i18nTexts = { defaultMessage: 'Select a node attribute', } ), - searchableSnapshotsFieldLabel: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldLabel', + searchableSnapshotsRepoFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotRepoFieldLabel', { defaultMessage: 'Searchable snapshot repository', } ), + searchableSnapshotsStorageFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotStorageFieldLabel', + { + defaultMessage: 'Searchable snapshot storage', + } + ), errors: { numberRequired: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.errors.numberRequiredErrorMessage', @@ -188,6 +194,9 @@ export const i18nTexts = { cold: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseTitle', { defaultMessage: 'Cold phase', }), + frozen: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.frozenPhase.frozenPhaseTitle', { + defaultMessage: 'Frozen phase', + }), delete: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.deletePhase.deletePhaseTitle', { defaultMessage: 'Delete phase', }), @@ -205,6 +214,13 @@ export const i18nTexts = { defaultMessage: 'Move data to the cold tier, which is optimized for cost savings over search performance. Data is normally read-only in the cold phase.', }), + frozen: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.frozenPhase.frozenPhaseDescription', + { + defaultMessage: + 'Archive data as searchable snapshots in the frozen tier. The frozen tier is optimized for maximum cost savings. Data in the frozen tier is rarely accessed and never updated.', + } + ), delete: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.deletePhase.deletePhaseDescription', { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts index 2974a88c22343..5d71bc057966e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts @@ -28,11 +28,11 @@ import { FormInternal } from '../types'; /* -===- Private functions and types -===- */ -type MinAgePhase = 'warm' | 'cold' | 'delete'; +type MinAgePhase = 'warm' | 'cold' | 'frozen' | 'delete'; type Phase = 'hot' | MinAgePhase; -const phaseOrder: Phase[] = ['hot', 'warm', 'cold', 'delete']; +const phaseOrder: Phase[] = ['hot', 'warm', 'cold', 'frozen', 'delete']; const getMinAge = (phase: MinAgePhase, formData: FormInternal) => ({ min_age: formData.phases?.[phase]?.min_age @@ -69,6 +69,9 @@ export interface AbsoluteTimings { cold?: { min_age: string; }; + frozen?: { + min_age: string; + }; delete?: { min_age: string; }; @@ -80,6 +83,7 @@ export interface PhaseAgeInMilliseconds { hot: number; warm?: number; cold?: number; + frozen?: number; }; } @@ -92,6 +96,7 @@ export const formDataToAbsoluteTimings = (formData: FormInternal): AbsoluteTimin hot: { min_age: undefined }, warm: _meta.warm.enabled ? getMinAge('warm', formData) : undefined, cold: _meta.cold.enabled ? getMinAge('cold', formData) : undefined, + frozen: _meta.frozen?.enabled ? getMinAge('frozen', formData) : undefined, delete: _meta.delete.enabled ? getMinAge('delete', formData) : undefined, }; }; @@ -139,6 +144,7 @@ export const calculateRelativeFromAbsoluteMilliseconds = ( hot: 0, warm: inputs.warm ? 0 : undefined, cold: inputs.cold ? 0 : undefined, + frozen: inputs.frozen ? 0 : undefined, }, } ); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 7aabf5d48e4fd..4330cde378b6d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -53,6 +53,11 @@ interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { freezeEnabled: boolean; } +interface FrozenPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { + enabled: boolean; + freezeEnabled: boolean; +} + interface DeletePhaseMetaFields extends MinAgeField { enabled: boolean; } @@ -69,6 +74,7 @@ export interface FormInternal extends SerializedPolicy { hot: HotPhaseMetaFields; warm: WarmPhaseMetaFields; cold: ColdPhaseMetaFields; + frozen: FrozenPhaseMetaFields; delete: DeletePhaseMetaFields; }; } diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts index b46b77f2776c9..accd8993abc62 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts @@ -75,6 +75,7 @@ export function registerListRoute({ 'ml.enabled', 'ml.machine_memory', 'ml.max_open_jobs', + 'ml.max_jvm_size', // Used by ML to identify nodes that have transform enabled: // https://github.com/elastic/elasticsearch/pull/52712/files#diff-225cc2c1291b4c60a8c3412a619094e1R147 'transform.node', diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts index 1cca7d29874d6..7a4795f8e370b 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts @@ -37,6 +37,7 @@ const bodySchema = schema.object({ hot: schema.any(), warm: schema.maybe(schema.any()), cold: schema.maybe(schema.any()), + frozen: schema.maybe(schema.any()), delete: schema.maybe(schema.any()), }), }); diff --git a/x-pack/plugins/infra/common/inventory_models/pod/metrics/snapshot/memory.ts b/x-pack/plugins/infra/common/inventory_models/pod/metrics/snapshot/memory.ts index fef40b109941d..480fdc055a03f 100644 --- a/x-pack/plugins/infra/common/inventory_models/pod/metrics/snapshot/memory.ts +++ b/x-pack/plugins/infra/common/inventory_models/pod/metrics/snapshot/memory.ts @@ -8,5 +8,27 @@ import { MetricsUIAggregation } from '../../../types'; export const memory: MetricsUIAggregation = { - memory: { avg: { field: 'kubernetes.pod.memory.usage.node.pct' } }, + memory_with_limit: { + avg: { + field: 'kubernetes.pod.memory.usage.limit.pct', + }, + }, + memory_without_limit: { + avg: { + field: 'kubernetes.pod.memory.usage.node.pct', + }, + }, + memory: { + bucket_script: { + buckets_path: { + with_limit: 'memory_with_limit', + without_limit: 'memory_without_limit', + }, + script: { + source: 'params.with_limit > 0.0 ? params.with_limit : params.without_limit', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, }; diff --git a/x-pack/plugins/infra/common/inventory_models/pod/metrics/tsvb/pod_memory_usage.ts b/x-pack/plugins/infra/common/inventory_models/pod/metrics/tsvb/pod_memory_usage.ts index 9964d49e3a957..9c774e1b18ed0 100644 --- a/x-pack/plugins/infra/common/inventory_models/pod/metrics/tsvb/pod_memory_usage.ts +++ b/x-pack/plugins/infra/common/inventory_models/pod/metrics/tsvb/pod_memory_usage.ts @@ -25,9 +25,23 @@ export const podMemoryUsage: TSVBMetricModelCreator = ( metrics: [ { field: 'kubernetes.pod.memory.usage.node.pct', - id: 'avg-memory-usage', + id: 'avg-memory-without', type: 'avg', }, + { + field: 'kubernetes.pod.memory.usage.limit.pct', + id: 'avg-memory-with', + type: 'avg', + }, + { + id: 'memory-usage', + type: 'calculation', + variables: [ + { id: 'memory_with', name: 'with_limit', field: 'avg-memory-with' }, + { id: 'memory_without', name: 'without_limit', field: 'avg-memory-without' }, + ], + script: 'params.with_limit > 0.0 ? params.with_limit : params.without_limit', + }, ], }, ], diff --git a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx index bcda3c0ec96dc..3b9193db65e1d 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx @@ -14,6 +14,7 @@ import { EuiContextMenuPanelDescriptor, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { PrefilledInventoryAlertFlyout } from '../../inventory/components/alert_flyout'; import { PrefilledThresholdAlertFlyout } from '../../metric_threshold/components/alert_flyout'; import { useLinkProps } from '../../../hooks/use_link_props'; @@ -23,73 +24,100 @@ type VisibleFlyoutType = 'inventory' | 'threshold' | null; export const MetricsAlertDropdown = () => { const [popoverOpen, setPopoverOpen] = useState(false); const [visibleFlyoutType, setVisibleFlyoutType] = useState(null); + const uiCapabilities = useKibana().services.application?.capabilities; + + const canCreateAlerts = useMemo(() => Boolean(uiCapabilities?.infrastructure?.save), [ + uiCapabilities, + ]); const closeFlyout = useCallback(() => setVisibleFlyoutType(null), [setVisibleFlyoutType]); + const infrastructureAlertsPanel = useMemo( + () => ({ + id: 1, + title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', { + defaultMessage: 'Infrastructure alerts', + }), + items: [ + { + name: i18n.translate('xpack.infra.alerting.createInventoryAlertButton', { + defaultMessage: 'Create inventory alert', + }), + onClick: () => setVisibleFlyoutType('inventory'), + }, + ], + }), + [setVisibleFlyoutType] + ); + + const metricsAlertsPanel = useMemo( + () => ({ + id: 2, + title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', { + defaultMessage: 'Metrics alerts', + }), + items: [ + { + name: i18n.translate('xpack.infra.alerting.createThresholdAlertButton', { + defaultMessage: 'Create threshold alert', + }), + onClick: () => setVisibleFlyoutType('threshold'), + }, + ], + }), + [setVisibleFlyoutType] + ); + const manageAlertsLinkProps = useLinkProps({ app: 'management', pathname: '/insightsAndAlerting/triggersActions/alerts', }); + const manageAlertsMenuItem = useMemo( + () => ({ + name: i18n.translate('xpack.infra.alerting.manageAlerts', { + defaultMessage: 'Manage alerts', + }), + icon: 'tableOfContents', + onClick: manageAlertsLinkProps.onClick, + }), + [manageAlertsLinkProps] + ); + + const firstPanelMenuItems: EuiContextMenuPanelDescriptor['items'] = useMemo( + () => + canCreateAlerts + ? [ + { + name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', { + defaultMessage: 'Infrastructure', + }), + panel: 1, + }, + { + name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', { + defaultMessage: 'Metrics', + }), + panel: 2, + }, + manageAlertsMenuItem, + ] + : [manageAlertsMenuItem], + [canCreateAlerts, manageAlertsMenuItem] + ); + const panels: EuiContextMenuPanelDescriptor[] = useMemo( - () => [ - { - id: 0, - title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', { - defaultMessage: 'Alerts', - }), - items: [ - { - name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', { - defaultMessage: 'Infrastructure', - }), - panel: 1, - }, - { - name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', { - defaultMessage: 'Metrics', - }), - panel: 2, - }, - { - name: i18n.translate('xpack.infra.alerting.manageAlerts', { - defaultMessage: 'Manage alerts', - }), - icon: 'tableOfContents', - onClick: manageAlertsLinkProps.onClick, - }, - ], - }, - { - id: 1, - title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', { - defaultMessage: 'Infrastructure alerts', - }), - items: [ - { - name: i18n.translate('xpack.infra.alerting.createInventoryAlertButton', { - defaultMessage: 'Create inventory alert', - }), - onClick: () => setVisibleFlyoutType('inventory'), - }, - ], - }, - { - id: 2, - title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', { - defaultMessage: 'Metrics alerts', - }), - items: [ - { - name: i18n.translate('xpack.infra.alerting.createThresholdAlertButton', { - defaultMessage: 'Create threshold alert', - }), - onClick: () => setVisibleFlyoutType('threshold'), - }, - ], - }, - ], - [manageAlertsLinkProps, setVisibleFlyoutType] + () => + [ + { + id: 0, + title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', { + defaultMessage: 'Alerts', + }), + items: firstPanelMenuItems, + }, + ].concat(canCreateAlerts ? [infrastructureAlertsPanel, metricsAlertsPanel] : []), + [infrastructureAlertsPanel, metricsAlertsPanel, firstPanelMenuItems, canCreateAlerts] ); const closePopover = useCallback(() => { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx index e313cf0762af2..f21a9d1e1f591 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { EuiOutsideClickDetector } from '@elastic/eui'; import { EuiIcon, EuiButtonIcon } from '@elastic/eui'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib'; import { InventoryItemType } from '../../../../../../common/inventory_models/types'; import { MetricsTab } from './tabs/metrics/metrics'; @@ -46,6 +47,10 @@ export const NodeContextPopover = ({ const tabConfigs = [MetricsTab, LogsTab, ProcessesTab, PropertiesTab]; const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; + const uiCapabilities = useKibana().services.application?.capabilities; + const canCreateAlerts = useMemo(() => Boolean(uiCapabilities?.infrastructure?.save), [ + uiCapabilities, + ]); const tabs = useMemo(() => { return tabConfigs.map((m) => { @@ -96,20 +101,22 @@ export const NodeContextPopover = ({ - - - - - + {canCreateAlerts && ( + + + + + + )} = withTheme const showUptimeLink = inventoryModel.crosslinkSupport.uptime && (['pod', 'container'].includes(nodeType) || node.ip); + const showCreateAlertLink = uiCapabilities?.infrastructure?.save; const inventoryId = useMemo(() => { if (nodeType === 'host') { @@ -155,10 +156,10 @@ export const NodeContextMenu: React.FC = withTheme label: i18n.translate('xpack.infra.nodeContextMenu.createAlertLink', { defaultMessage: 'Create alert', }), - style: { color: theme?.eui.euiLinkColor || '#006BB4', fontWeight: 500, padding: 0 }, onClick: () => { setFlyoutVisible(true); }, + isDisabled: !showCreateAlertLink, }; return ( diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts index 3fa6bc065e7c1..91f1859899177 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts @@ -78,9 +78,8 @@ export const useWaffleViewState = () => { region: newState.region, legend: newState.legend, }); - // if this is the "Default View" view, don't update the time range to the view's time range, - // this way it will use the global Kibana time or the default time already set - if (newState.time && newState.id !== '0') { + + if (newState.time) { setWaffleTimeState({ currentTime: newState.time, isAutoReloading: newState.autoReload, @@ -102,5 +101,4 @@ export type WaffleViewState = WaffleOptionsState & { time: number; autoReload: boolean; filterQuery: WaffleFiltersState; - id?: string; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.test.tsx index 1a5f084fc909e..49568bdd8404c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.test.tsx @@ -25,6 +25,7 @@ const uiCapabilities: Capabilities = { management: { fake: { show: false } }, catalogue: { show: false }, visualize: { show: true }, + infrastructure: { save: true }, }; const getTestSubject = (component: ReactWrapper, name: string) => { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx index 9fdea02c7b334..f5970cffa157d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx @@ -152,20 +152,21 @@ export const MetricsExplorerChartContextMenu: React.FC = ({ ] : []; - const itemPanels = [ - ...filterByItem, - ...openInVisualize, - ...viewNodeDetail, - { - name: i18n.translate('xpack.infra.metricsExplorer.alerts.createAlertButton', { - defaultMessage: 'Create alert', - }), - icon: 'bell', - onClick() { - setFlyoutVisible(true); - }, - }, - ]; + const createAlert = uiCapabilities?.infrastructure?.save + ? [ + { + name: i18n.translate('xpack.infra.metricsExplorer.alerts.createAlertButton', { + defaultMessage: 'Create alert', + }), + icon: 'bell', + onClick() { + setFlyoutVisible(true); + }, + }, + ] + : []; + + const itemPanels = [...filterByItem, ...openInVisualize, ...viewNodeDetail, ...createAlert]; // If there are no itemPanels then there is no reason to show the actions button. if (itemPanels.length === 0) return null; diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 477bd0a3f0eee..38bcf8a377bf2 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -83,6 +83,7 @@ function createMockSearchService() { session: { start: jest.fn(() => `sessionId-${sessionIdCounter++}`), clear: jest.fn(), + getSessionId: jest.fn(() => `sessionId-${sessionIdCounter}`), }, }; } diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index bacb426b02838..07bebaf5800b8 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -120,7 +120,8 @@ export function App({ const { resolvedDateRange, from: fromDate, to: toDate } = useTimeRange( data, state.lastKnownDoc, - setState + setState, + state.searchSessionId ); const onError = useCallback( diff --git a/x-pack/plugins/lens/public/app_plugin/time_range.ts b/x-pack/plugins/lens/public/app_plugin/time_range.ts index 447a012a8d582..c9e507f3e6f13 100644 --- a/x-pack/plugins/lens/public/app_plugin/time_range.ts +++ b/x-pack/plugins/lens/public/app_plugin/time_range.ts @@ -26,15 +26,16 @@ const TIME_LAG_PERCENTAGE_LIMIT = 0.02; * @param data data plugin contract to manage current now value, time range and session * @param lastKnownDoc Current state of the editor * @param setState state setter for Lens app state + * @param searchSessionId current session id */ export function useTimeRange( data: DataPublicPluginStart, lastKnownDoc: Document | undefined, - setState: React.Dispatch> + setState: React.Dispatch>, + searchSessionId: string ) { const timefilter = data.query.timefilter.timefilter; const { from, to } = data.query.timefilter.timefilter.getTime(); - const currentNow = data.nowProvider.get(); // Need a stable reference for the frame component of the dateRange const resolvedDateRange = useMemo(() => { @@ -43,10 +44,10 @@ export function useTimeRange( to, }); return { fromDate: min?.toISOString() || from, toDate: max?.toISOString() || to }; - // recalculate current date range if current "now" value changes because calculateBounds - // depends on it internally + // recalculate current date range if the session gets updated because it + // might change "now" and calculateBounds depends on it internally // eslint-disable-next-line react-hooks/exhaustive-deps - }, [timefilter, currentNow, from, to]); + }, [timefilter, searchSessionId, from, to]); useEffect(() => { const unresolvedTimeRange = timefilter.getTime(); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap index 992301af13ad0..afc69c2e8861f 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap +++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap @@ -537,7 +537,7 @@ exports[`DatatableComponent it should not render actions on header when it is in Array [ Object { "actions": Object { - "additional": undefined, + "additional": Array [], "showHide": false, "showMoveLeft": false, "showMoveRight": false, @@ -551,7 +551,7 @@ exports[`DatatableComponent it should not render actions on header when it is in }, Object { "actions": Object { - "additional": undefined, + "additional": Array [], "showHide": false, "showMoveLeft": false, "showMoveRight": false, @@ -565,7 +565,7 @@ exports[`DatatableComponent it should not render actions on header when it is in }, Object { "actions": Object { - "additional": undefined, + "additional": Array [], "showHide": false, "showMoveLeft": false, "showMoveRight": false, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx index fdb05599c38e9..ba24da8309ed7 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx @@ -7,8 +7,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; -import type { Datatable, DatatableColumnMeta } from 'src/plugins/expressions'; +import { + EuiDataGridColumn, + EuiDataGridColumnCellActionProps, + EuiListGroupItemProps, +} from '@elastic/eui'; +import type { Datatable, DatatableColumn, DatatableColumnMeta } from 'src/plugins/expressions'; import type { FormatFactory } from '../../types'; import { ColumnConfig } from './table_basic'; @@ -22,6 +26,10 @@ export const createGridColumns = ( rowIndex: number, negate?: boolean ) => void, + handleTransposedColumnClick: ( + bucketValues: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>, + negate?: boolean + ) => void, isReadOnly: boolean, columnConfig: ColumnConfig, visibleColumns: string[], @@ -135,9 +143,63 @@ export const createGridColumns = ( ] : undefined; - const column = columnConfig.columns.find(({ columnId }) => columnId === field); - const initialWidth = column?.width; - const isHidden = column?.hidden; + const columnArgs = columnConfig.columns.find(({ columnId }) => columnId === field); + const isTransposed = Boolean(columnArgs?.originalColumnId); + const initialWidth = columnArgs?.width; + const isHidden = columnArgs?.hidden; + const originalColumnId = columnArgs?.originalColumnId; + + const additionalActions: EuiListGroupItemProps[] = []; + + if (!isReadOnly) { + additionalActions.push({ + color: 'text', + size: 'xs', + onClick: () => onColumnResize({ columnId: originalColumnId || field, width: undefined }), + iconType: 'empty', + label: i18n.translate('xpack.lens.table.resize.reset', { + defaultMessage: 'Reset width', + }), + 'data-test-subj': 'lensDatatableResetWidth', + isDisabled: initialWidth == null, + }); + if (!isTransposed) { + additionalActions.push({ + color: 'text', + size: 'xs', + onClick: () => onColumnHide({ columnId: originalColumnId || field }), + iconType: 'eyeClosed', + label: i18n.translate('xpack.lens.table.hide.hideLabel', { + defaultMessage: 'Hide', + }), + 'data-test-subj': 'lensDatatableHide', + isDisabled: !isHidden && visibleColumns.length <= 1, + }); + } else if (columnArgs?.bucketValues) { + const bucketValues = columnArgs?.bucketValues; + additionalActions.push({ + color: 'text', + size: 'xs', + onClick: () => handleTransposedColumnClick(bucketValues, false), + iconType: 'plusInCircle', + label: i18n.translate('xpack.lens.table.columnFilter.filterForValueText', { + defaultMessage: 'Filter for column', + }), + 'data-test-subj': 'lensDatatableHide', + }); + + additionalActions.push({ + color: 'text', + size: 'xs', + onClick: () => handleTransposedColumnClick(bucketValues, true), + iconType: 'minusInCircle', + label: i18n.translate('xpack.lens.table.columnFilter.filterOutValueText', { + defaultMessage: 'Filter out column', + }), + 'data-test-subj': 'lensDatatableHide', + }); + } + } const columnDefinition: EuiDataGridColumn = { id: field, @@ -162,32 +224,7 @@ export const createGridColumns = ( defaultMessage: 'Sort descending', }), }, - additional: isReadOnly - ? undefined - : [ - { - color: 'text', - size: 'xs', - onClick: () => onColumnResize({ columnId: field, width: undefined }), - iconType: 'empty', - label: i18n.translate('xpack.lens.table.resize.reset', { - defaultMessage: 'Reset width', - }), - 'data-test-subj': 'lensDatatableResetWidth', - isDisabled: initialWidth == null, - }, - { - color: 'text', - size: 'xs', - onClick: () => onColumnHide({ columnId: field }), - iconType: 'eyeClosed', - label: i18n.translate('xpack.lens.table.hide.hideLabel', { - defaultMessage: 'Hide', - }), - 'data-test-subj': 'lensDatatableHide', - isDisabled: !isHidden && visibleColumns.length <= 1, - }, - ], + additional: additionalActions, }, }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 9c60cd47af3e3..672b29846d760 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; import { VisualizationDimensionEditorProps } from '../../types'; import { DatatableVisualizationState } from '../visualization'; +import { getOriginalId } from '../transpose_helpers'; const idPrefix = htmlIdGenerator()(); @@ -20,13 +21,15 @@ export function TableDimensionEditor( const column = state.columns.find(({ columnId }) => accessor === columnId); if (!column) return null; + if (column.isTransposed) return null; // either read config state or use same logic as chart itself const currentAlignment = column?.alignment || (frame.activeData && - frame.activeData[state.layerId].columns.find((col) => col.id === accessor)?.meta.type === - 'number' + frame.activeData[state.layerId].columns.find( + (col) => col.id === accessor || getOriginalId(col.id) === accessor + )?.meta.type === 'number' ? 'right' : 'left'); @@ -89,39 +92,41 @@ export function TableDimensionEditor( }} /> - + display="columnCompressedSwitch" + > + { + const newState = { + ...state, + columns: state.columns.map((currentColumn) => { + if (currentColumn.columnId === accessor) { + return { + ...currentColumn, + hidden: !column.hidden, + }; + } else { + return currentColumn; + } + }), + }; + setState(newState); + }} + /> + + )} ); } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts index 68416ac9a60aa..8490d33f83444 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts @@ -15,9 +15,11 @@ import { createGridResizeHandler, createGridSortingConfig, createGridHideHandler, + createTransposeColumnFilterHandler, } from './table_actions'; import { LensGridDirection } from './types'; import { ColumnConfig } from './table_basic'; +import { LensMultiTable } from '../../types'; function getDefaultConfig(): ColumnConfig { return { @@ -48,6 +50,19 @@ function createTableRef( }; } +function createUntransposedRef(options?: { + withDate: boolean; +}): React.MutableRefObject { + return { + current: { + type: 'lens_multitable', + tables: { + first: createTableRef(options).current, + }, + }, + }; +} + describe('Table actions', () => { const onEditAction = jest.fn(); @@ -132,6 +147,138 @@ describe('Table actions', () => { }); }); }); + + describe('Transposed column filtering', () => { + it('should set a filter on click with the correct configuration', () => { + const onClickValue = jest.fn(); + const tableRef = createUntransposedRef({ withDate: true }); + tableRef.current.tables.first.rows = [{ a: 123456 }]; + const filterHandle = createTransposeColumnFilterHandler(onClickValue, tableRef); + + filterHandle( + [ + { + originalBucketColumn: tableRef.current.tables.first.columns[0], + value: 123456, + }, + ], + false + ); + expect(onClickValue).toHaveBeenCalledWith({ + data: [ + { + column: 0, + row: 0, + table: tableRef.current.tables.first, + value: 123456, + }, + ], + negate: false, + timeFieldName: 'a', + }); + }); + + it('should set a negate filter on click with the correct configuration', () => { + const onClickValue = jest.fn(); + const tableRef = createUntransposedRef({ withDate: true }); + tableRef.current.tables.first.rows = [{ a: 123456 }]; + const filterHandle = createTransposeColumnFilterHandler(onClickValue, tableRef); + + filterHandle( + [ + { + originalBucketColumn: tableRef.current.tables.first.columns[0], + value: 123456, + }, + ], + true + ); + expect(onClickValue).toHaveBeenCalledWith({ + data: [ + { + column: 0, + row: 0, + table: tableRef.current.tables.first, + value: 123456, + }, + ], + negate: true, + timeFieldName: undefined, + }); + }); + + it('should set a multi filter and look up positions of the values', () => { + const onClickValue = jest.fn(); + const tableRef = createUntransposedRef({ withDate: false }); + const filterHandle = createTransposeColumnFilterHandler(onClickValue, tableRef); + tableRef.current.tables.first.columns = [ + { + id: 'a', + name: 'a', + meta: { + type: 'string', + }, + }, + { + id: 'b', + name: 'b', + meta: { + type: 'string', + }, + }, + ]; + tableRef.current.tables.first.rows = [ + { + a: 'a1', + b: 'b1', + }, + { + a: 'a2', + b: 'b2', + }, + { + a: 'a3', + b: 'b3', + }, + { + a: 'a4', + b: 'b4', + }, + ]; + + filterHandle( + [ + { + originalBucketColumn: tableRef.current.tables.first.columns[0], + value: 'a2', + }, + { + originalBucketColumn: tableRef.current.tables.first.columns[1], + value: 'b3', + }, + ], + false + ); + expect(onClickValue).toHaveBeenCalledWith({ + data: [ + { + column: 0, + row: 1, + table: tableRef.current.tables.first, + value: 'a2', + }, + { + column: 1, + row: 2, + table: tableRef.current.tables.first, + value: 'b3', + }, + ], + negate: false, + timeFieldName: undefined, + }); + }); + }); describe('Table sorting', () => { it('should create the right configuration for all types of sorting', () => { const configs: Array<{ diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts index 4f0271b758ffb..0d44ae3aa6dec 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts @@ -6,8 +6,8 @@ */ import type { EuiDataGridSorting } from '@elastic/eui'; -import type { Datatable } from 'src/plugins/expressions'; -import type { LensFilterEvent } from '../../types'; +import type { Datatable, DatatableColumn } from 'src/plugins/expressions'; +import type { LensFilterEvent, LensMultiTable } from '../../types'; import type { LensGridDirection, LensResizeAction, @@ -17,18 +17,20 @@ import type { import { ColumnConfig } from './table_basic'; import { desanitizeFilterContext } from '../../utils'; +import { getOriginalId } from '../transpose_helpers'; export const createGridResizeHandler = ( columnConfig: ColumnConfig, setColumnConfig: React.Dispatch>, onEditAction: (data: LensResizeAction['data']) => void ) => (eventData: { columnId: string; width: number | undefined }) => { + const originalColumnId = getOriginalId(eventData.columnId); // directly set the local state of the component to make sure the visualization re-renders immediately, // re-layouting and taking up all of the available space. setColumnConfig({ ...columnConfig, columns: columnConfig.columns.map((column) => { - if (column.columnId === eventData.columnId) { + if (column.columnId === eventData.columnId || column.originalColumnId === originalColumnId) { return { ...column, width: eventData.width }; } return column; @@ -36,7 +38,7 @@ export const createGridResizeHandler = ( }); return onEditAction({ action: 'resize', - columnId: eventData.columnId, + columnId: originalColumnId, width: eventData.width, }); }; @@ -46,11 +48,12 @@ export const createGridHideHandler = ( setColumnConfig: React.Dispatch>, onEditAction: (data: LensToggleAction['data']) => void ) => (eventData: { columnId: string }) => { + const originalColumnId = getOriginalId(eventData.columnId); // directly set the local state of the component to make sure the visualization re-renders immediately setColumnConfig({ ...columnConfig, columns: columnConfig.columns.map((column) => { - if (column.columnId === eventData.columnId) { + if (column.columnId === eventData.columnId || column.originalColumnId === originalColumnId) { return { ...column, hidden: true }; } return column; @@ -58,7 +61,7 @@ export const createGridHideHandler = ( }); return onEditAction({ action: 'toggle', - columnId: eventData.columnId, + columnId: originalColumnId, }); }; @@ -92,6 +95,39 @@ export const createGridFilterHandler = ( onClickValue(desanitizeFilterContext(data)); }; +export const createTransposeColumnFilterHandler = ( + onClickValue: (data: LensFilterEvent['data']) => void, + untransposedDataRef: React.MutableRefObject +) => ( + bucketValues: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>, + negate: boolean = false +) => { + if (!untransposedDataRef.current) return; + const originalTable = Object.values(untransposedDataRef.current.tables)[0]; + const timeField = bucketValues.find( + ({ originalBucketColumn }) => originalBucketColumn.meta.type === 'date' + )?.originalBucketColumn; + const isDate = Boolean(timeField); + const timeFieldName = negate && isDate ? undefined : timeField?.meta?.field; + + const data: LensFilterEvent['data'] = { + negate, + data: bucketValues.map(({ originalBucketColumn, value }) => { + const columnIndex = originalTable.columns.findIndex((c) => c.id === originalBucketColumn.id); + const rowIndex = originalTable.rows.findIndex((r) => r[originalBucketColumn.id] === value); + return { + row: rowIndex, + column: columnIndex, + value, + table: originalTable, + }; + }), + timeFieldName, + }; + + onClickValue(desanitizeFilterContext(data)); +}; + export const createGridSortingConfig = ( sortBy: string | undefined, sortDirection: LensGridDirection, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index e1687ba28f07b..24cde07cebaa0 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -38,6 +38,7 @@ import { createGridHideHandler, createGridResizeHandler, createGridSortingConfig, + createTransposeColumnFilterHandler, } from './table_actions'; export const DataContext = React.createContext({}); @@ -82,6 +83,9 @@ export const DatatableComponent = (props: DatatableRenderProps) => { const firstTableRef = useRef(firstLocalTable); firstTableRef.current = firstLocalTable; + const untransposedDataRef = useRef(props.untransposedData); + untransposedDataRef.current = props.untransposedData; + const hasAtLeastOneRowClickAction = props.rowHasRowClickTriggerActions?.some((x) => x); const { getType, dispatchEvent, renderMode, formatFactory } = props; @@ -125,6 +129,11 @@ export const DatatableComponent = (props: DatatableRenderProps) => { onClickValue, ]); + const handleTransposedColumnClick = useMemo( + () => createTransposeColumnFilterHandler(onClickValue, untransposedDataRef), + [onClickValue, untransposedDataRef] + ); + const bucketColumns = useMemo( () => columnConfig.columns @@ -172,6 +181,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { bucketColumns, firstLocalTable, handleFilterClick, + handleTransposedColumnClick, isReadOnlySorted, columnConfig, visibleColumns, @@ -183,6 +193,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { bucketColumns, firstLocalTable, handleFilterClick, + handleTransposedColumnClick, isReadOnlySorted, columnConfig, visibleColumns, diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx index 3ee41d4e9aeed..3ba448b49afc9 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx @@ -73,7 +73,7 @@ function sampleArgs() { type: 'lens_datatable_column', }, ], - sortingColumnId: '', + sortingColumnId: undefined, sortingDirection: 'none', }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index f6a38541cda27..7d879217abf8b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -7,11 +7,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { cloneDeep } from 'lodash'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import type { IAggType } from 'src/plugins/data/public'; -import type { +import { DatatableColumnMeta, ExpressionFunctionDefinition, ExpressionRenderDefinition, @@ -23,8 +24,9 @@ import { ColumnState } from './visualization'; import type { FormatFactory, ILensInterpreterRenderHandlers, LensMultiTable } from '../types'; import type { DatatableRender } from './components/types'; +import { transposeTable } from './transpose_helpers'; -interface Args { +export interface Args { title: string; description?: string; columns: Array; @@ -34,6 +36,7 @@ interface Args { export interface DatatableProps { data: LensMultiTable; + untransposedData?: LensMultiTable; args: Args; } @@ -78,6 +81,7 @@ export const getDatatable = ({ }, }, fn(data, args, context) { + let untransposedData: LensMultiTable | undefined; // do the sorting at this level to propagate it also at CSV download const [firstTable] = Object.values(data.tables); const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); @@ -86,6 +90,15 @@ export const getDatatable = ({ firstTable.columns.forEach((column) => { formatters[column.id] = formatFactory(column.meta?.params); }); + + const hasTransposedColumns = args.columns.some((c) => c.isTransposed); + if (hasTransposedColumns) { + // store original shape of data separately + untransposedData = cloneDeep(data); + // transposes table and args inplace + transposeTable(args, firstTable, formatters); + } + const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; const columnsReverseLookup = firstTable.columns.reduce< @@ -95,7 +108,7 @@ export const getDatatable = ({ return memo; }, {}); - if (sortBy && sortDirection !== 'none') { + if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { // Sort on raw values for these types, while use the formatted value for the rest const sortingCriteria = getSortingCriteria( isRange(columnsReverseLookup[sortBy]?.meta) @@ -111,12 +124,16 @@ export const getDatatable = ({ .sort(sortingCriteria); // replace also the local copy firstTable.rows = context.inspectorAdapters.tables[layerId].rows; + } else { + args.sortingColumnId = undefined; + args.sortingDirection = 'none'; } return { type: 'render', as: 'lens_datatable_renderer', value: { data, + untransposedData, args, }, }; @@ -141,6 +158,8 @@ export const datatableColumn: ExpressionFunctionDefinition< alignment: { types: ['string'], help: '' }, hidden: { types: ['boolean'], help: '' }, width: { types: ['number'], help: '' }, + isTransposed: { types: ['boolean'], help: '' }, + transposable: { types: ['boolean'], help: '' }, }, fn: function fn(input: unknown, args: ColumnState) { return { diff --git a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.test.ts b/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.test.ts new file mode 100644 index 0000000000000..91559a1778f4f --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.test.ts @@ -0,0 +1,293 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FieldFormat } from 'src/plugins/data/public'; +import type { Datatable } from 'src/plugins/expressions'; + +import { Args } from './expression'; +import { transposeTable } from './transpose_helpers'; + +describe('transpose_helpes', () => { + function buildTable(): Datatable { + // 3 buckets, 2 metrics + // first bucket goes A/B/C + // second buckets goes D/E/F + // third bucket goes X/Y/Z (all combinations) + // metric values count up from 1 + return { + type: 'datatable', + columns: [ + { id: 'bucket1', name: 'bucket1', meta: { type: 'string' } }, + { id: 'bucket2', name: 'bucket2', meta: { type: 'string' } }, + { id: 'bucket3', name: 'bucket3', meta: { type: 'string' } }, + { id: 'metric1', name: 'metric1', meta: { type: 'number' } }, + { id: 'metric2', name: 'metric2', meta: { type: 'number' } }, + ], + rows: [ + { bucket1: 'A', bucket2: 'D', bucket3: 'X', metric1: 1, metric2: 2 }, + { bucket1: 'A', bucket2: 'D', bucket3: 'Y', metric1: 3, metric2: 4 }, + { bucket1: 'A', bucket2: 'D', bucket3: 'Z', metric1: 5, metric2: 6 }, + { bucket1: 'A', bucket2: 'E', bucket3: 'X', metric1: 7, metric2: 8 }, + { bucket1: 'A', bucket2: 'E', bucket3: 'Y', metric1: 9, metric2: 10 }, + { bucket1: 'A', bucket2: 'E', bucket3: 'Z', metric1: 11, metric2: 12 }, + { bucket1: 'A', bucket2: 'F', bucket3: 'X', metric1: 13, metric2: 14 }, + { bucket1: 'A', bucket2: 'F', bucket3: 'Y', metric1: 15, metric2: 16 }, + { bucket1: 'A', bucket2: 'F', bucket3: 'Z', metric1: 17, metric2: 18 }, + { bucket1: 'B', bucket2: 'D', bucket3: 'X', metric1: 19, metric2: 20 }, + { bucket1: 'B', bucket2: 'D', bucket3: 'Y', metric1: 21, metric2: 22 }, + { bucket1: 'B', bucket2: 'D', bucket3: 'Z', metric1: 23, metric2: 24 }, + { bucket1: 'B', bucket2: 'E', bucket3: 'X', metric1: 25, metric2: 26 }, + { bucket1: 'B', bucket2: 'E', bucket3: 'Y', metric1: 27, metric2: 28 }, + { bucket1: 'B', bucket2: 'E', bucket3: 'Z', metric1: 29, metric2: 30 }, + { bucket1: 'B', bucket2: 'F', bucket3: 'X', metric1: 31, metric2: 32 }, + { bucket1: 'B', bucket2: 'F', bucket3: 'Y', metric1: 33, metric2: 34 }, + { bucket1: 'B', bucket2: 'F', bucket3: 'Z', metric1: 35, metric2: 36 }, + { bucket1: 'C', bucket2: 'D', bucket3: 'X', metric1: 37, metric2: 38 }, + { bucket1: 'C', bucket2: 'D', bucket3: 'Y', metric1: 39, metric2: 40 }, + { bucket1: 'C', bucket2: 'D', bucket3: 'Z', metric1: 41, metric2: 42 }, + { bucket1: 'C', bucket2: 'E', bucket3: 'X', metric1: 43, metric2: 44 }, + { bucket1: 'C', bucket2: 'E', bucket3: 'Y', metric1: 45, metric2: 46 }, + { bucket1: 'C', bucket2: 'E', bucket3: 'Z', metric1: 47, metric2: 48 }, + { bucket1: 'C', bucket2: 'F', bucket3: 'X', metric1: 49, metric2: 50 }, + { bucket1: 'C', bucket2: 'F', bucket3: 'Y', metric1: 51, metric2: 52 }, + { bucket1: 'C', bucket2: 'F', bucket3: 'Z', metric1: 53, metric2: 54 }, + ], + }; + } + + function buildArgs(): Args { + return { + title: 'Table', + sortingColumnId: undefined, + sortingDirection: 'none', + columns: [ + { + type: 'lens_datatable_column', + columnId: 'bucket1', + isTransposed: false, + transposable: false, + }, + { + type: 'lens_datatable_column', + columnId: 'bucket2', + isTransposed: false, + transposable: false, + }, + { + type: 'lens_datatable_column', + columnId: 'bucket3', + isTransposed: false, + transposable: false, + }, + { + type: 'lens_datatable_column', + columnId: 'metric1', + isTransposed: false, + transposable: true, + }, + { + type: 'lens_datatable_column', + columnId: 'metric2', + isTransposed: false, + transposable: true, + }, + ], + }; + } + + function buildFormatters() { + return ({ + bucket1: { convert: (x: unknown) => x }, + bucket2: { convert: (x: unknown) => x }, + bucket3: { convert: (x: unknown) => x }, + metric1: { convert: (x: unknown) => x }, + metric2: { convert: (x: unknown) => x }, + } as unknown) as Record; + } + + it('should transpose table by one column', () => { + const table = buildTable(); + const args = buildArgs(); + args.columns[0].isTransposed = true; + transposeTable(args, table, buildFormatters()); + + // one metric for each unique value of bucket1 + expect(table.columns.map((c) => c.id)).toEqual([ + 'bucket2', + 'bucket3', + 'A---metric1', + 'B---metric1', + 'C---metric1', + 'A---metric2', + 'B---metric2', + 'C---metric2', + ]); + + // order is different for args to visually group unique values + const expectedColumns = [ + 'bucket2', + 'bucket3', + 'A---metric1', + 'A---metric2', + 'B---metric1', + 'B---metric2', + 'C---metric1', + 'C---metric2', + ]; + + // args should be in sync + expect(args.columns.map((c) => c.columnId)).toEqual(expectedColumns); + // original column id should stay preserved + expect(args.columns.slice(2).map((c) => c.originalColumnId)).toEqual([ + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + ]); + + // data should stay consistent + expect(table.rows.length).toEqual(9); + table.rows.forEach((row, index) => { + expect(row['A---metric1']).toEqual(index * 2 + 1); + expect(row['A---metric2']).toEqual(index * 2 + 2); + // B metrics start with offset 18 because there are 18 A metrics (2 metrics * 3 bucket2 metrics * 3 bucket3 metrics) + expect(row['B---metric1']).toEqual(18 + index * 2 + 1); + expect(row['B---metric2']).toEqual(18 + index * 2 + 2); + // B metrics start with offset 36 because there are 18 A metrics and 18 B metrics (2 metrics * 3 bucket2 values * 3 bucket3 values) + expect(row['C---metric1']).toEqual(36 + index * 2 + 1); + expect(row['C---metric2']).toEqual(36 + index * 2 + 2); + }); + + // visible name should use separator + expect(table.columns[2].name).toEqual(`A › metric1`); + }); + + it('should transpose table by two columns', () => { + const table = buildTable(); + const args = buildArgs(); + args.columns[0].isTransposed = true; + args.columns[1].isTransposed = true; + transposeTable(args, table, buildFormatters()); + + // one metric for each unique value of bucket1 + expect(table.columns.map((c) => c.id)).toEqual([ + 'bucket3', + 'A---D---metric1', + 'B---D---metric1', + 'C---D---metric1', + 'A---E---metric1', + 'B---E---metric1', + 'C---E---metric1', + 'A---F---metric1', + 'B---F---metric1', + 'C---F---metric1', + 'A---D---metric2', + 'B---D---metric2', + 'C---D---metric2', + 'A---E---metric2', + 'B---E---metric2', + 'C---E---metric2', + 'A---F---metric2', + 'B---F---metric2', + 'C---F---metric2', + ]); + + // order is different for args to visually group unique values + const expectedColumns = [ + 'bucket3', + 'A---D---metric1', + 'A---D---metric2', + 'A---E---metric1', + 'A---E---metric2', + 'A---F---metric1', + 'A---F---metric2', + 'B---D---metric1', + 'B---D---metric2', + 'B---E---metric1', + 'B---E---metric2', + 'B---F---metric1', + 'B---F---metric2', + 'C---D---metric1', + 'C---D---metric2', + 'C---E---metric1', + 'C---E---metric2', + 'C---F---metric1', + 'C---F---metric2', + ]; + + // args should be in sync + expect(args.columns.map((c) => c.columnId)).toEqual(expectedColumns); + // original column id should stay preserved + expect(args.columns.slice(1).map((c) => c.originalColumnId)).toEqual([ + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + 'metric1', + 'metric2', + ]); + + // data should stay consistent + expect(table.rows.length).toEqual(3); + table.rows.forEach((row, index) => { + // each metric block has an additional offset of 6 because there are 6 metrics for each bucket1/bucket2 combination (2 metrics * 3 bucket3 values) + expect(row['A---D---metric1']).toEqual(index * 2 + 1); + expect(row['A---D---metric2']).toEqual(index * 2 + 2); + expect(row['A---E---metric1']).toEqual(index * 2 + 6 + 1); + expect(row['A---E---metric2']).toEqual(index * 2 + 6 + 2); + expect(row['A---F---metric1']).toEqual(index * 2 + 12 + 1); + expect(row['A---F---metric2']).toEqual(index * 2 + 12 + 2); + + expect(row['B---D---metric1']).toEqual(index * 2 + 18 + 1); + expect(row['B---D---metric2']).toEqual(index * 2 + 18 + 2); + expect(row['B---E---metric1']).toEqual(index * 2 + 24 + 1); + expect(row['B---E---metric2']).toEqual(index * 2 + 24 + 2); + expect(row['B---F---metric1']).toEqual(index * 2 + 30 + 1); + expect(row['B---F---metric2']).toEqual(index * 2 + 30 + 2); + + expect(row['C---D---metric1']).toEqual(index * 2 + 36 + 1); + expect(row['C---D---metric2']).toEqual(index * 2 + 36 + 2); + expect(row['C---E---metric1']).toEqual(index * 2 + 42 + 1); + expect(row['C---E---metric2']).toEqual(index * 2 + 42 + 2); + expect(row['C---F---metric1']).toEqual(index * 2 + 48 + 1); + expect(row['C---F---metric2']).toEqual(index * 2 + 48 + 2); + }); + }); + + it('should be able to handle missing values', () => { + const table = buildTable(); + const args = buildArgs(); + args.columns[0].isTransposed = true; + args.columns[1].isTransposed = true; + // delete A-E-Z bucket + table.rows.splice(5, 1); + transposeTable(args, table, buildFormatters()); + expect(args.columns.length).toEqual(19); + expect(table.columns.length).toEqual(19); + expect(table.rows.length).toEqual(3); + expect(table.rows[2]['A---E---metric1']).toEqual(undefined); + expect(table.rows[2]['A---E---metric2']).toEqual(undefined); + // 1 bucket column and 2 missing from the regular 18 columns + expect(Object.values(table.rows[2]).filter((val) => val !== undefined).length).toEqual( + 1 + 18 - 2 + ); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts b/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts new file mode 100644 index 0000000000000..6e29e018b481e --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FieldFormat } from 'src/plugins/data/public'; +import type { Datatable, DatatableColumn, DatatableRow } from 'src/plugins/expressions'; + +import { Args } from './expression'; +import { ColumnState } from './visualization'; + +const TRANSPOSE_SEPARATOR = '---'; + +const TRANSPOSE_VISUAL_SEPARATOR = '›'; + +export function getTransposeId(value: string, columnId: string) { + return `${value}${TRANSPOSE_SEPARATOR}${columnId}`; +} + +export function getOriginalId(id: string) { + if (id.includes(TRANSPOSE_SEPARATOR)) { + const idParts = id.split(TRANSPOSE_SEPARATOR); + return idParts[idParts.length - 1]; + } + return id; +} + +/** + * Transposes the columns of the given table as defined in the arguments. + * This function modifies the passed in args and firstTable objects. + * This process consists out of three parts: + * * Calculating the new column arguments + * * Calculating the new datatable columns + * * Calculating the new rows + * + * If the table is tranposed by multiple columns, this process is repeated on top of the previous transformation. + * + * @param args Arguments for the table visualization + * @param firstTable datatable object containing the actual data + * @param formatters Formatters for all columns to transpose columns by actual display values + */ +export function transposeTable( + args: Args, + firstTable: Datatable, + formatters: Record +) { + args.columns + .filter((columnArgs) => columnArgs.isTransposed) + // start with the inner nested transposed column and work up to preserve column grouping + .reverse() + .forEach(({ columnId: transposedColumnId }) => { + const datatableColumnIndex = firstTable.columns.findIndex((c) => c.id === transposedColumnId); + const datatableColumn = firstTable.columns[datatableColumnIndex]; + const transposedColumnFormatter = formatters[datatableColumn.id]; + const { uniqueValues, uniqueRawValues } = getUniqueValues( + firstTable, + transposedColumnFormatter, + transposedColumnId + ); + const metricsColumnArgs = args.columns.filter((c) => c.transposable); + const bucketsColumnArgs = args.columns.filter( + (c) => !c.transposable && c.columnId !== transposedColumnId + ); + firstTable.columns.splice(datatableColumnIndex, 1); + + transposeColumns( + args, + bucketsColumnArgs, + metricsColumnArgs, + firstTable, + uniqueValues, + uniqueRawValues, + datatableColumn + ); + transposeRows( + firstTable, + bucketsColumnArgs, + formatters, + transposedColumnFormatter, + transposedColumnId, + metricsColumnArgs + ); + }); +} + +function transposeRows( + firstTable: Datatable, + bucketsColumnArgs: Array, + formatters: Record, + transposedColumnFormatter: FieldFormat, + transposedColumnId: string, + metricsColumnArgs: Array +) { + const rowsByBucketColumns: Record = groupRowsByBucketColumns( + firstTable, + bucketsColumnArgs, + formatters + ); + firstTable.rows = mergeRowGroups( + rowsByBucketColumns, + bucketsColumnArgs, + transposedColumnFormatter, + transposedColumnId, + metricsColumnArgs + ); +} + +/** + * Updates column args by adding bucket column args first, then adding transposed metric columns + * grouped by unique value + */ +function updateColumnArgs( + args: Args, + bucketsColumnArgs: Array, + transposedColumnGroups: Array> +) { + args.columns = [...bucketsColumnArgs]; + // add first column from each group, then add second column for each group, ... + transposedColumnGroups[0].forEach((_, index) => { + transposedColumnGroups.forEach((transposedColumnGroup) => { + args.columns.push(transposedColumnGroup[index]); + }); + }); +} + +/** + * Finds all unique values in a column in order of first occurence + * @param table Table to search through + * @param formatter formatter for the column + * @param columnId column + */ +function getUniqueValues(table: Datatable, formatter: FieldFormat, columnId: string) { + const values = new Map(); + table.rows.forEach((row) => { + const rawValue = row[columnId]; + values.set(formatter.convert(row[columnId]), rawValue); + }); + const uniqueValues = [...values.keys()]; + const uniqueRawValues = [...values.values()]; + return { uniqueValues, uniqueRawValues }; +} + +/** + * Calculate transposed column objects of the datatable object and puts them into the datatable. + * Returns args for additional columns grouped by metric + * @param metricColumns + * @param firstTable + * @param uniqueValues + */ +function transposeColumns( + args: Args, + bucketsColumnArgs: Array, + metricColumns: Array, + firstTable: Datatable, + uniqueValues: string[], + uniqueRawValues: unknown[], + transposingDatatableColumn: DatatableColumn +) { + const columnGroups = metricColumns.map((metricColumn) => { + const originalDatatableColumn = firstTable.columns.find((c) => c.id === metricColumn.columnId)!; + const datatableColumns = uniqueValues.map((uniqueValue) => { + return { + ...originalDatatableColumn, + id: getTransposeId(uniqueValue, metricColumn.columnId), + name: `${uniqueValue} ${TRANSPOSE_VISUAL_SEPARATOR} ${originalDatatableColumn.name}`, + }; + }); + firstTable.columns.splice( + firstTable.columns.findIndex((c) => c.id === metricColumn.columnId), + 1, + ...datatableColumns + ); + return uniqueValues.map((uniqueValue, valueIndex) => { + return { + ...metricColumn, + columnId: getTransposeId(uniqueValue, metricColumn.columnId), + originalColumnId: metricColumn.originalColumnId || metricColumn.columnId, + originalName: metricColumn.originalName || originalDatatableColumn.name, + bucketValues: [ + ...(metricColumn.bucketValues || []), + { + originalBucketColumn: transposingDatatableColumn, + value: uniqueRawValues[valueIndex], + }, + ], + }; + }); + }); + updateColumnArgs(args, bucketsColumnArgs, columnGroups); +} + +/** + * Merge groups of rows together by creating separate columns for unique values of the column to transpose by. + */ +function mergeRowGroups( + rowsByBucketColumns: Record, + bucketColumns: ColumnState[], + formatter: FieldFormat, + transposedColumnId: string, + metricColumns: ColumnState[] +) { + return Object.values(rowsByBucketColumns).map((rows) => { + const mergedRow: DatatableRow = {}; + bucketColumns.forEach((c) => { + mergedRow[c.columnId] = rows[0][c.columnId]; + }); + rows.forEach((row) => { + const transposalValue = formatter.convert(row[transposedColumnId]); + metricColumns.forEach((c) => { + mergedRow[getTransposeId(transposalValue, c.columnId)] = row[c.columnId]; + }); + }); + return mergedRow; + }); +} + +/** + * Groups rows of the data table by the values of bucket columns which are not transposed by. + * All rows ending up in a group have the same bucket column value, but have different values of the column to transpose by. + */ +function groupRowsByBucketColumns( + firstTable: Datatable, + bucketColumns: ColumnState[], + formatters: Record +) { + const rowsByBucketColumns: Record = {}; + firstTable.rows.forEach((row) => { + const key = bucketColumns.map((c) => formatters[c.columnId].convert(row[c.columnId])).join(','); + if (!rowsByBucketColumns[key]) { + rowsByBucketColumns[key] = []; + } + rowsByBucketColumns[key].push(row); + }); + return rowsByBucketColumns; +} diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx index 92136c557ad38..1848565114dea 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx @@ -9,7 +9,13 @@ import { Ast } from '@kbn/interpreter/common'; import { buildExpression } from '../../../../../src/plugins/expressions/public'; import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_service/mocks'; import { DatatableVisualizationState, datatableVisualization } from './visualization'; -import { Operation, DataType, FramePublicAPI, TableSuggestionColumn } from '../types'; +import { + Operation, + DataType, + FramePublicAPI, + TableSuggestionColumn, + VisualizationDimensionGroupConfig, +} from '../types'; function mockFrame(): FramePublicAPI { return { @@ -132,9 +138,9 @@ describe('Datatable Visualization', () => { expect(suggestions.length).toBeGreaterThan(0); expect(suggestions[0].state.columns).toEqual([ - { columnId: 'col1', width: 123 }, - { columnId: 'col2', hidden: true }, - { columnId: 'col3' }, + { columnId: 'col1', width: 123, isTransposed: false }, + { columnId: 'col2', hidden: true, isTransposed: false }, + { columnId: 'col3', isTransposed: false }, ]); expect(suggestions[0].state.sorting).toEqual({ columnId: 'col1', @@ -226,39 +232,45 @@ describe('Datatable Visualization', () => { }, frame, }).groups - ).toHaveLength(2); + ).toHaveLength(3); }); - it('allows only bucket operations one category', () => { + it('allows only bucket operations for splitting columns and rows', () => { const datasource = createMockDatasource('test'); const frame = mockFrame(); frame.datasourceLayers = { first: datasource.publicAPIMock }; - - const filterOperations = datatableVisualization.getConfiguration({ + const groups = datatableVisualization.getConfiguration({ layerId: 'first', state: { layerId: 'first', columns: [], }, frame, - }).groups[0].filterOperations; + }).groups; - const baseOperation: Operation = { - dataType: 'string', - isBucketed: true, - label: '', - }; - expect(filterOperations({ ...baseOperation })).toEqual(true); - expect(filterOperations({ ...baseOperation, dataType: 'number' })).toEqual(true); - expect(filterOperations({ ...baseOperation, dataType: 'date' })).toEqual(true); - expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(true); - expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(true); - expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual( - false - ); - expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual( - false - ); + function testGroup(group: VisualizationDimensionGroupConfig) { + const baseOperation: Operation = { + dataType: 'string', + isBucketed: true, + label: '', + }; + expect(group.filterOperations({ ...baseOperation })).toEqual(true); + expect(group.filterOperations({ ...baseOperation, dataType: 'number' })).toEqual(true); + expect(group.filterOperations({ ...baseOperation, dataType: 'date' })).toEqual(true); + expect(group.filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(true); + expect(group.filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual( + true + ); + expect( + group.filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false }) + ).toEqual(false); + expect( + group.filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false }) + ).toEqual(false); + } + + testGroup(groups[0]); + testGroup(groups[1]); }); it('allows only metric operations in one category', () => { @@ -273,7 +285,7 @@ describe('Datatable Visualization', () => { columns: [], }, frame, - }).groups[1].filterOperations; + }).groups[2].filterOperations; const baseOperation: Operation = { dataType: 'string', @@ -307,7 +319,7 @@ describe('Datatable Visualization', () => { columns: [{ columnId: 'b' }, { columnId: 'c' }], }, frame, - }).groups[1].accessors + }).groups[2].accessors ).toEqual([{ columnId: 'c' }, { columnId: 'b' }]); }); }); @@ -368,7 +380,7 @@ describe('Datatable Visualization', () => { }) ).toEqual({ layerId: 'layer1', - columns: [{ columnId: 'b' }, { columnId: 'c' }, { columnId: 'd' }], + columns: [{ columnId: 'b' }, { columnId: 'c' }, { columnId: 'd', isTransposed: false }], }); }); @@ -382,7 +394,7 @@ describe('Datatable Visualization', () => { }) ).toEqual({ layerId: 'layer1', - columns: [{ columnId: 'b' }, { columnId: 'c' }], + columns: [{ columnId: 'b', isTransposed: false }, { columnId: 'c' }], }); }); }); @@ -419,12 +431,16 @@ describe('Datatable Visualization', () => { columnId: ['c'], hidden: [], width: [], + isTransposed: [], + transposable: [true], alignment: [], }); expect(columnArgs[1].arguments).toEqual({ columnId: ['b'], hidden: [], width: [], + isTransposed: [], + transposable: [true], alignment: [], }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index b7ff23cdb6e35..4094ecee74e1c 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -10,6 +10,7 @@ import { render } from 'react-dom'; import { Ast } from '@kbn/interpreter/common'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { DatatableColumn } from 'src/plugins/expressions/public'; import { SuggestionRequest, Visualization, @@ -23,6 +24,13 @@ export interface ColumnState { columnId: string; width?: number; hidden?: boolean; + isTransposed?: boolean; + // These flags are necessary to transpose columns and map them back later + // They are set automatically and are not user-editable + transposable?: boolean; + originalColumnId?: string; + originalName?: string; + bucketValues?: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>; alignment?: 'left' | 'right' | 'center'; } @@ -108,6 +116,11 @@ export const datatableVisualization: Visualization oldColumnSettings[column.columnId] = column; }); } + const lastTransposedColumnIndex = table.columns.findIndex((c) => + !oldColumnSettings[c.columnId] ? false : !oldColumnSettings[c.columnId]?.isTransposed + ); + const usesTransposing = state?.columns.some((c) => c.isTransposed); + const title = table.changeType === 'unchanged' ? i18n.translate('xpack.lens.datatable.suggestionLabel', { @@ -138,8 +151,9 @@ export const datatableVisualization: Visualization state: { ...(state || {}), layerId: table.layerId, - columns: table.columns.map((col) => ({ + columns: table.columns.map((col, columnIndex) => ({ ...(oldColumnSettings[col.columnId] || {}), + isTransposed: usesTransposing && columnIndex < lastTransposedColumnIndex, columnId: col.columnId, })), }, @@ -166,21 +180,55 @@ export const datatableVisualization: Visualization return { groups: [ { - groupId: 'columns', - groupLabel: i18n.translate('xpack.lens.datatable.breakdown', { - defaultMessage: 'Break down by', + groupId: 'rows', + groupLabel: i18n.translate('xpack.lens.datatable.breakdownRows', { + defaultMessage: 'Split rows', + }), + groupTooltip: i18n.translate('xpack.lens.datatable.breakdownRows.description', { + defaultMessage: + 'Split table rows by field. This is recommended for high cardinality breakdowns.', }), layerId: state.layerId, accessors: sortedColumns - .filter((c) => datasource!.getOperationForColumnId(c)?.isBucketed) + .filter( + (c) => + datasource!.getOperationForColumnId(c)?.isBucketed && + !state.columns.find((col) => col.columnId === c)?.isTransposed + ) .map((accessor) => ({ columnId: accessor, triggerIcon: columnMap[accessor].hidden ? 'invisible' : undefined, })), supportsMoreColumns: true, filterOperations: (op) => op.isBucketed, - dataTestSubj: 'lnsDatatable_column', + dataTestSubj: 'lnsDatatable_rows', + enableDimensionEditor: true, + hideGrouping: true, + nestingOrder: 1, + }, + { + groupId: 'columns', + groupLabel: i18n.translate('xpack.lens.datatable.breakdownColumns', { + defaultMessage: 'Split columns', + }), + groupTooltip: i18n.translate('xpack.lens.datatable.breakdownColumns.description', { + defaultMessage: + "Split metric columns by field. It's recommended to keep the number of columns low to avoid horizontal scrolling.", + }), + layerId: state.layerId, + accessors: sortedColumns + .filter( + (c) => + datasource!.getOperationForColumnId(c)?.isBucketed && + state.columns.find((col) => col.columnId === c)?.isTransposed + ) + .map((accessor) => ({ columnId: accessor })), + supportsMoreColumns: true, + filterOperations: (op) => op.isBucketed, + dataTestSubj: 'lnsDatatable_columns', enableDimensionEditor: true, + hideGrouping: true, + nestingOrder: 0, }, { groupId: 'metrics', @@ -204,13 +252,26 @@ export const datatableVisualization: Visualization }; }, - setDimension({ prevState, columnId }) { - if (prevState.columns.some((column) => column.columnId === columnId)) { - return prevState; + setDimension({ prevState, columnId, groupId, previousColumn }) { + if ( + prevState.columns.some( + (column) => + column.columnId === columnId || (previousColumn && column.columnId === previousColumn) + ) + ) { + return { + ...prevState, + columns: prevState.columns.map((column) => { + if (column.columnId === columnId || column.columnId === previousColumn) { + return { ...column, columnId, isTransposed: groupId === 'columns' }; + } + return column; + }), + }; } return { ...prevState, - columns: [...prevState.columns, { columnId }], + columns: [...prevState.columns, { columnId, isTransposed: groupId === 'columns' }], }; }, removeDimension({ prevState, columnId }) { @@ -268,6 +329,11 @@ export const datatableVisualization: Visualization columnId: [column.columnId], hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden], width: typeof column.width === 'undefined' ? [] : [column.width], + isTransposed: + typeof column.isTransposed === 'undefined' ? [] : [column.isTransposed], + transposable: [ + !datasource!.getOperationForColumnId(column.columnId)?.isBucketed, + ], alignment: typeof column.alignment === 'undefined' ? [] : [column.alignment], }, }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx index 04ab1318a12e0..8449727a9e79d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx @@ -40,6 +40,7 @@ export function DraggableDimensionButton({ layerIndex, columnId, group, + groups, onDrop, children, layerDatasourceDropProps, @@ -55,6 +56,7 @@ export function DraggableDimensionButton({ dropType?: DropType ) => void; group: VisualizationDimensionGroupConfig; + groups: VisualizationDimensionGroupConfig[]; label: string; children: React.ReactElement; layerDatasource: Datasource; @@ -71,6 +73,7 @@ export function DraggableDimensionButton({ columnId, filterOperations: group.filterOperations, groupId: group.groupId, + dimensionGroups: groups, }); const dropType = dropProps?.dropType; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx index 664e24b989836..a6ccac1427fbf 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx @@ -27,6 +27,7 @@ const getAdditionalClassesOnDroppable = (dropType?: string) => { export function EmptyDimensionButton({ group, + groups, layerDatasource, layerDatasourceDropProps, layerId, @@ -45,6 +46,8 @@ export function EmptyDimensionButton({ dropType?: DropType ) => void; group: VisualizationDimensionGroupConfig; + groups: VisualizationDimensionGroupConfig[]; + layerDatasource: Datasource; layerDatasourceDropProps: LayerDatasourceDropProps; }) { @@ -63,6 +66,7 @@ export function EmptyDimensionButton({ columnId: newColumnId, filterOperations: group.filterOperations, groupId: group.groupId, + dimensionGroups: groups, }); const dropType = dropProps?.dropType; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 1d75e873f9b18..14063aea02665 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -8,7 +8,14 @@ import './layer_panel.scss'; import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; +import { + EuiPanel, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIconTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { NativeRenderer } from '../../../native_renderer'; import { StateSetter, Visualization, DraggedOperation, DropType } from '../../../types'; @@ -151,6 +158,8 @@ export function LayerPanel( columnId, layerId: targetLayerId, filterOperations, + dimensionGroups: groups, + groupId, dropType, }); if (dropResult) { @@ -159,6 +168,7 @@ export function LayerPanel( groupId, layerId: targetLayerId, prevState: props.visualizationState, + previousColumn: typeof droppedItem.column === 'string' ? droppedItem.column : undefined, }); if (typeof dropResult === 'object') { @@ -254,7 +264,26 @@ export function LayerPanel( : 'lnsLayerPanel__row lnsLayerPanel__row--notSupportsMoreColumns' } fullWidth - label={
{group.groupLabel}
} + label={ +
+ {group.groupLabel} + {group.groupTooltip && ( + <> + {' '} + + + )} +
+ } labelType="legend" key={group.groupId} isInvalid={isMissing} @@ -281,6 +310,7 @@ export function LayerPanel( accessorIndex={accessorIndex} columnId={columnId} group={group} + groups={groups} groupIndex={groupIndex} key={columnId} layerDatasourceDropProps={layerDatasourceDropProps} @@ -325,6 +355,7 @@ export function LayerPanel( nativeProps={{ ...layerDatasourceConfigProps, columnId: accessorConfig.columnId, + groupId: group.groupId, filterOperations: group.filterOperations, }} /> @@ -338,6 +369,7 @@ export function LayerPanel( { instance.update(); expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` - Object { - "chain": Array [ - Object { - "arguments": Object {}, - "function": "kibana", - "type": "function", - }, - Object { - "arguments": Object { - "layerIds": Array [ - "first", - ], - "tables": Array [ - Object { - "chain": Array [ - Object { - "arguments": Object {}, - "function": "datasource", - "type": "function", - }, - ], - "type": "expression", - }, - ], - }, - "function": "lens_merge_tables", - "type": "function", - }, - Object { - "arguments": Object {}, - "function": "vis", - "type": "function", - }, - ], - "type": "expression", - } + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={datasource} + | vis" `); }); @@ -525,7 +493,9 @@ describe('editor_frame', () => { instance.update(); - expect(instance.find(expressionRendererMock).prop('expression')).toEqual({ + expect( + fromExpression(instance.find(expressionRendererMock).prop('expression') as string) + ).toEqual({ type: 'expression', chain: expect.arrayContaining([ expect.objectContaining({ @@ -533,7 +503,8 @@ describe('editor_frame', () => { }), ]), }); - expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` + expect(fromExpression(instance.find(expressionRendererMock).prop('expression') as string)) + .toMatchInlineSnapshot(` Object { "chain": Array [ Object { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index ab718a99843c8..d5fd7100258f3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -27,7 +27,7 @@ import { WorkspacePanel, WorkspacePanelProps } from './workspace_panel'; import { mountWithIntl as mount } from '@kbn/test/jest'; import { ReactWrapper } from 'enzyme'; import { DragDrop, ChildDragDropProvider } from '../../../drag_drop'; -import { Ast } from '@kbn/interpreter/common'; +import { fromExpression } from '@kbn/interpreter/common'; import { coreMock } from 'src/core/public/mocks'; import { DataPublicPluginStart, @@ -177,42 +177,9 @@ describe('workspace_panel', () => { ); expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` - Object { - "chain": Array [ - Object { - "arguments": Object {}, - "function": "kibana", - "type": "function", - }, - Object { - "arguments": Object { - "layerIds": Array [ - "first", - ], - "tables": Array [ - Object { - "chain": Array [ - Object { - "arguments": Object {}, - "function": "datasource", - "type": "function", - }, - ], - "type": "expression", - }, - ], - }, - "function": "lens_merge_tables", - "type": "function", - }, - Object { - "arguments": Object {}, - "function": "vis", - "type": "function", - }, - ], - "type": "expression", - } + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={datasource} + | vis" `); }); @@ -346,12 +313,10 @@ describe('workspace_panel', () => { /> ); - expect( - (instance.find(expressionRendererMock).prop('expression') as Ast).chain[1].arguments.layerIds - ).toEqual(['first', 'second', 'third']); - expect( - (instance.find(expressionRendererMock).prop('expression') as Ast).chain[1].arguments.tables - ).toMatchInlineSnapshot(` + const ast = fromExpression(instance.find(expressionRendererMock).prop('expression') as string); + + expect(ast.chain[1].arguments.layerIds).toEqual(['first', 'second', 'third']); + expect(ast.chain[1].arguments.tables).toMatchInlineSnapshot(` Array [ Object { "chain": Array [ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 486c6f120d4a8..c0b249e9d22f0 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -8,7 +8,7 @@ import React, { useState, useEffect, useMemo, useContext, useCallback } from 'react'; import classNames from 'classnames'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Ast } from '@kbn/interpreter/common'; +import { toExpression } from '@kbn/interpreter/common'; import { i18n } from '@kbn/i18n'; import { EuiEmptyPrompt, @@ -159,13 +159,21 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ () => { if (!configurationValidationError?.length) { try { - return buildExpression({ + const ast = buildExpression({ visualization: activeVisualization, visualizationState, datasourceMap, datasourceStates, datasourceLayers: framePublicAPI.datasourceLayers, }); + if (ast) { + // expression has to be turned into a string for dirty checking - if the ast is rebuilt, + // turning it into a string will make sure the expression renderer only re-renders if the + // expression actually changed. + return toExpression(ast); + } else { + return null; + } } catch (e) { const buildMessages = activeVisualization?.getErrorMessages(visualizationState); const defaultMessage = { @@ -347,7 +355,7 @@ export const InnerVisualizationWrapper = ({ ExpressionRendererComponent, dispatch, }: { - expression: Ast | null | undefined; + expression: string | null | undefined; framePublicAPI: FramePublicAPI; timefilter: TimefilterContract; onEvent: (event: ExpressionRendererEvent) => void; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 362866207801d..f1ec38d968864 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -299,6 +299,45 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(2); }); + it('should re-render once if session id changes and ', async () => { + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + ast: { + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }, + errors: undefined, + }), + }, + { id: '123' } as LensEmbeddableInput + ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + expect(expressionRenderer).toHaveBeenCalledTimes(1); + + embeddable.updateInput({ + searchSessionId: 'newSession', + }); + embeddable.reload(); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(expressionRenderer).toHaveBeenCalledTimes(2); + }); + it('should re-render when dashboard view/edit mode changes', async () => { const embeddable = new Embeddable( { diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index c484b9b9b5470..f397dffc03262 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -178,7 +178,8 @@ export class Embeddable }); // Update search context and reload on changes related to search - input$ + this.getUpdated$() + .pipe(map(() => this.getInput())) .pipe( distinctUntilChanged((a, b) => isEqual( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 8f5da64fcc9a8..08842fb755888 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -116,6 +116,7 @@ export function DimensionEditor(props: DimensionEditorProps) { currentIndexPattern, hideGrouping, dateRange, + dimensionGroups, } = props; const services = { data: props.data, @@ -173,6 +174,7 @@ export function DimensionEditor(props: DimensionEditorProps) { indexPattern: currentIndexPattern, field: currentField || undefined, filterOperations: props.filterOperations, + visualizationGroups: dimensionGroups, }), disabledStatus: definition.getDisabledStatus && @@ -250,6 +252,8 @@ export function DimensionEditor(props: DimensionEditorProps) { indexPattern: currentIndexPattern, columnId, op: operationType, + visualizationGroups: dimensionGroups, + targetGroup: props.groupId, }); setStateWrapper(newLayer); trackUiEvent(`indexpattern_dimension_operation_${operationType}`); @@ -265,6 +269,8 @@ export function DimensionEditor(props: DimensionEditorProps) { columnId, op: operationType, field: currentIndexPattern.getFieldByName(possibleFields.values().next().value), + visualizationGroups: dimensionGroups, + targetGroup: props.groupId, }) ); } else { @@ -275,6 +281,8 @@ export function DimensionEditor(props: DimensionEditorProps) { columnId, op: operationType, field: undefined, + visualizationGroups: dimensionGroups, + targetGroup: props.groupId, }) ); } @@ -297,6 +305,7 @@ export function DimensionEditor(props: DimensionEditorProps) { field: hasField(selectedColumn) ? currentIndexPattern.getFieldByName(selectedColumn.sourceField) : undefined, + visualizationGroups: dimensionGroups, }); setStateWrapper(newLayer); }, @@ -363,6 +372,7 @@ export function DimensionEditor(props: DimensionEditorProps) { uiSettings: props.uiSettings, currentColumn: state.layers[layerId].columns[columnId], })} + dimensionGroups={dimensionGroups} {...services} /> ); @@ -424,6 +434,8 @@ export function DimensionEditor(props: DimensionEditorProps) { indexPattern: currentIndexPattern, op: choice.operationType, field: currentIndexPattern.getFieldByName(choice.field), + visualizationGroups: dimensionGroups, + targetGroup: props.groupId, }) ); }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 5eaa798f459e3..a6d2361be21d4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -197,6 +197,7 @@ describe('IndexPatternDimensionEditorPanel', () => { } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, dimensionGroups: [], + groupId: 'a', }; jest.clearAllMocks(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index 12df14f81cb67..82b6434e50aac 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -7,12 +7,13 @@ import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; import { onDrop, getDropProps } from './droppable'; +import { DraggingIdentifier } from '../../drag_drop'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup, CoreSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; import { OperationMetadata, DropType } from '../../types'; -import { IndexPatternColumn } from '../operations'; +import { IndexPatternColumn, MedianIndexPatternColumn } from '../operations'; import { getFieldByNameFactory } from '../pure_helpers'; const fields = [ @@ -48,6 +49,22 @@ const fields = [ searchable: true, exists: true, }, + { + name: 'src', + displayName: 'src', + type: 'string', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + exists: true, + }, documentField, ]; @@ -144,6 +161,7 @@ describe('IndexPatternDimensionEditorPanel', () => { columnId: 'col1', layerId: 'first', uniqueLabel: 'stuff', + groupId: 'group1', filterOperations: () => true, storage: {} as IStorageWrapper, uiSettings: {} as IUiSettingsClient, @@ -572,36 +590,458 @@ describe('IndexPatternDimensionEditorPanel', () => { }); }); - it('copies a dimension if dropType is duplicate_in_group, respecting bucket metric order', () => { - const testState = { ...state }; - testState.layers.first = { - indexPatternId: 'foo', - columnOrder: ['col1', 'col2', 'col3'], - columns: { - col1: testState.layers.first.columns.col1, + describe('dimension group aware ordering and copying', () => { + let dragging: DraggingIdentifier; + let testState: IndexPatternPrivateState; + beforeEach(() => { + dragging = { + columnId: 'col2', + groupId: 'b', + layerId: 'first', + id: 'col2', + humanData: { + label: '', + }, + }; + testState = { ...state }; + testState.layers.first = { + indexPatternId: 'foo', + columnOrder: ['col1', 'col2', 'col3', 'col4'], + columns: { + col1: testState.layers.first.columns.col1, + col2: { + label: 'Top values of src', + dataType: 'string', + isBucketed: true, - col2: { - label: 'Top values of src', - dataType: 'string', - isBucketed: true, + // Private + operationType: 'terms', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'desc', + size: 10, + }, + sourceField: 'src', + }, + col3: { + label: 'Top values of dest', + dataType: 'string', + isBucketed: true, - // Private - operationType: 'terms', - params: { - orderBy: { type: 'column', columnId: 'col3' }, - orderDirection: 'desc', - size: 10, + // Private + operationType: 'terms', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'desc', + size: 10, + }, + sourceField: 'dest', + }, + col4: { + label: 'Median of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'median', + sourceField: 'bytes', }, - sourceField: 'src', }, - col3: { - label: 'Count', - dataType: 'number', - isBucketed: false, + }; + }); + const dimensionGroups = [ + { + accessors: [], + groupId: 'a', + supportsMoreColumns: true, + hideGrouping: true, + groupLabel: '', + filterOperations: () => false, + }, + { + accessors: [{ columnId: 'col1' }, { columnId: 'col2' }, { columnId: 'col3' }], + groupId: 'b', + supportsMoreColumns: true, + hideGrouping: true, + groupLabel: '', + filterOperations: () => false, + }, + { + accessors: [{ columnId: 'col4' }], + groupId: 'c', + supportsMoreColumns: true, + hideGrouping: true, + groupLabel: '', + filterOperations: () => false, + }, + ]; - // Private - operationType: 'count', - sourceField: 'Records', + it('respects groups on moving operations from one group to another', () => { + // config: + // a: + // b: col1, col2, col3 + // c: col4 + // dragging col2 into newCol in group a + onDrop({ + ...defaultProps, + columnId: 'newCol', + droppedItem: dragging, + state: testState, + groupId: 'a', + dimensionGroups, + dropType: 'move_compatible', + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...testState, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['newCol', 'col1', 'col3', 'col4'], + columns: { + newCol: testState.layers.first.columns.col2, + col1: testState.layers.first.columns.col1, + col3: testState.layers.first.columns.col3, + col4: testState.layers.first.columns.col4, + }, + }, + }, + }); + }); + + it('respects groups on moving operations from one group to another with overwrite', () => { + // config: + // a: col1, + // b: col2, col3 + // c: col4 + // dragging col3 onto col1 in group a + const draggingCol3 = { + columnId: 'col3', + groupId: 'b', + layerId: 'first', + id: 'col3', + humanData: { + label: '', + }, + }; + onDrop({ + ...defaultProps, + columnId: 'col1', + droppedItem: draggingCol3, + state: testState, + groupId: 'a', + dimensionGroups: [ + { ...dimensionGroups[0], accessors: [{ columnId: 'col1' }] }, + { ...dimensionGroups[1], accessors: [{ columnId: 'col2' }, { columnId: 'col3' }] }, + { ...dimensionGroups[2] }, + ], + dropType: 'move_compatible', + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...testState, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['col1', 'col2', 'col4'], + columns: { + col1: testState.layers.first.columns.col3, + col2: testState.layers.first.columns.col2, + col4: testState.layers.first.columns.col4, + }, + }, + }, + }); + }); + + it('moves newly created dimension to the bottom of the current group', () => { + // config: + // a: col1 + // b: col2, col3 + // c: col4 + // dragging col1 into newCol in group b + const draggingCol1 = { + columnId: 'col1', + groupId: 'a', + layerId: 'first', + id: 'col1', + humanData: { + label: '', + }, + }; + onDrop({ + ...defaultProps, + columnId: 'newCol', + dropType: 'move_compatible', + droppedItem: draggingCol1, + state: testState, + groupId: 'b', + dimensionGroups: [ + { ...dimensionGroups[0], accessors: [{ columnId: 'col1' }] }, + { ...dimensionGroups[1], accessors: [{ columnId: 'col2' }, { columnId: 'col3' }] }, + { ...dimensionGroups[2] }, + ], + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...testState, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['col2', 'col3', 'newCol', 'col4'], + columns: { + newCol: testState.layers.first.columns.col1, + col2: testState.layers.first.columns.col2, + col3: testState.layers.first.columns.col3, + col4: testState.layers.first.columns.col4, + }, + }, + }, + }); + }); + + it('appends the dropped column in the right place when a field is dropped', () => { + // config: + // a: + // b: col1, col2, col3 + // c: col4 + // dragging field into newCol in group a + const draggingBytesField = { + field: { type: 'number', name: 'bytes', aggregatable: true }, + indexPatternId: 'foo', + id: 'bar', + humanData: { + label: '', + }, + }; + + onDrop({ + ...defaultProps, + droppedItem: draggingBytesField, + columnId: 'newCol', + filterOperations: (op: OperationMetadata) => op.dataType === 'number', + groupId: 'a', + dimensionGroups, + dropType: 'field_add', + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['newCol', 'col1', 'col2', 'col3', 'col4'], + columns: { + newCol: expect.objectContaining({ + dataType: 'number', + sourceField: 'bytes', + }), + col1: testState.layers.first.columns.col1, + col2: testState.layers.first.columns.col2, + col3: testState.layers.first.columns.col3, + col4: testState.layers.first.columns.col4, + }, + incompleteColumns: {}, + }, + }, + }); + }); + + it('appends the dropped column in the right place respecting custom nestingOrder', () => { + // config: + // a: + // b: col1, col2, col3 + // c: col4 + // dragging field into newCol in group a + const draggingBytesField = { + field: { type: 'number', name: 'bytes', aggregatable: true }, + indexPatternId: 'foo', + id: 'bar', + humanData: { + label: '', + }, + }; + + onDrop({ + ...defaultProps, + droppedItem: draggingBytesField, + columnId: 'newCol', + filterOperations: (op: OperationMetadata) => op.dataType === 'number', + groupId: 'a', + dimensionGroups: [ + // a and b are ordered in reverse visually, but nesting order keeps them in place for column order + { ...dimensionGroups[1], nestingOrder: 1 }, + { ...dimensionGroups[0], nestingOrder: 0 }, + { ...dimensionGroups[2] }, + ], + dropType: 'field_add', + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['newCol', 'col1', 'col2', 'col3', 'col4'], + columns: { + newCol: expect.objectContaining({ + dataType: 'number', + sourceField: 'bytes', + }), + col1: testState.layers.first.columns.col1, + col2: testState.layers.first.columns.col2, + col3: testState.layers.first.columns.col3, + col4: testState.layers.first.columns.col4, + }, + incompleteColumns: {}, + }, + }, + }); + }); + + it('copies column to the bottom of the current group', () => { + // config: + // a: col1 + // b: col2, col3 + // c: col4 + // copying col1 within group a + const draggingCol1 = { + columnId: 'col1', + groupId: 'a', + layerId: 'first', + id: 'col1', + humanData: { + label: '', + }, + }; + onDrop({ + ...defaultProps, + columnId: 'newCol', + dropType: 'duplicate_in_group', + droppedItem: draggingCol1, + state: testState, + groupId: 'a', + dimensionGroups: [ + { ...dimensionGroups[0], accessors: [{ columnId: 'col1' }] }, + { ...dimensionGroups[1], accessors: [{ columnId: 'col2' }, { columnId: 'col3' }] }, + { ...dimensionGroups[2] }, + ], + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...testState, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['col1', 'newCol', 'col2', 'col3', 'col4'], + columns: { + col1: testState.layers.first.columns.col1, + newCol: testState.layers.first.columns.col1, + col2: testState.layers.first.columns.col2, + col3: testState.layers.first.columns.col3, + col4: testState.layers.first.columns.col4, + }, + }, + }, + }); + }); + + it('moves incompatible column to the bottom of the target group', () => { + // config: + // a: col1 + // b: col2, col3 + // c: col4 + // dragging col4 into newCol in group a + const draggingCol4 = { + columnId: 'col4', + groupId: 'c', + layerId: 'first', + id: 'col4', + humanData: { + label: '', + }, + }; + onDrop({ + ...defaultProps, + columnId: 'newCol', + dropType: 'move_incompatible', + droppedItem: draggingCol4, + state: testState, + groupId: 'a', + dimensionGroups: [ + { ...dimensionGroups[0], accessors: [{ columnId: 'col1' }] }, + { ...dimensionGroups[1], accessors: [{ columnId: 'col2' }, { columnId: 'col3' }] }, + { ...dimensionGroups[2] }, + ], + }); + + expect(setState).toBeCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + ...testState, + layers: { + first: { + ...testState.layers.first, + columnOrder: ['col1', 'newCol', 'col2', 'col3'], + columns: { + col1: testState.layers.first.columns.col1, + newCol: expect.objectContaining({ + sourceField: (testState.layers.first.columns.col4 as MedianIndexPatternColumn) + .sourceField, + }), + col2: testState.layers.first.columns.col2, + col3: testState.layers.first.columns.col3, + }, + incompleteColumns: {}, + }, + }, + }); + }); + }); + + it('if dnd is reorder, it correctly reorders columns', () => { + const testState: IndexPatternPrivateState = { + ...state, + layers: { + first: { + indexPatternId: 'foo', + columnOrder: ['col1', 'col2', 'col3'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + params: { + interval: '1d', + }, + sourceField: 'timestamp', + }, + col2: { + label: 'Top values of bar', + dataType: 'number', + isBucketed: true, + operationType: 'terms', + sourceField: 'bar', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + size: 5, + }, + }, + col3: { + operationType: 'avg', + sourceField: 'memory', + label: 'average of memory', + dataType: 'number', + isBucketed: false, + }, + }, }, }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts index a7d4774d8aa3d..e846db718f1d3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts @@ -17,6 +17,8 @@ import { insertOrReplaceColumn, deleteColumn, getOperationTypesForField, + getColumnOrder, + reorderByGroups, getOperationDisplay, } from '../operations'; import { mergeLayer } from '../state_helpers'; @@ -191,7 +193,7 @@ function onReorderDrop({ } function onMoveDropToNonCompatibleGroup(props: DropHandlerProps) { - const { columnId, setState, state, layerId, droppedItem } = props; + const { columnId, setState, state, layerId, droppedItem, dimensionGroups, groupId } = props; const layer = state.layers[layerId]; const op = { ...layer.columns[droppedItem.columnId] }; @@ -225,6 +227,8 @@ function onMoveDropToNonCompatibleGroup(props: DropHandlerProps) { const layer = state.layers[layerId]; @@ -258,19 +264,29 @@ function onSameGroupDuplicateDrop({ const newColumnOrder = [...layer.columnOrder]; // put a new bucketed dimension just in front of the metric dimensions, a metric dimension in the back of the array - // TODO this logic does not take into account groups - we probably need to pass the current - // group config to this position to place the column right + // then reorder based on dimension groups if necessary const insertionIndex = op.isBucketed ? newColumnOrder.findIndex((id) => !newColumns[id].isBucketed) : newColumnOrder.length; newColumnOrder.splice(insertionIndex, 0, columnId); + + const newLayer = { + ...layer, + columnOrder: newColumnOrder, + columns: newColumns, + }; + + const updatedColumnOrder = getColumnOrder(newLayer); + + reorderByGroups(dimensionGroups, groupId, updatedColumnOrder, columnId); + // Time to replace setState( mergeLayer({ state, layerId, newLayer: { - columnOrder: newColumnOrder, + columnOrder: updatedColumnOrder, columns: newColumns, }, }) @@ -284,6 +300,8 @@ function onMoveDropToCompatibleGroup({ state, layerId, droppedItem, + dimensionGroups, + groupId, }: DropHandlerProps) { const layer = state.layers[layerId]; const op = { ...layer.columns[droppedItem.columnId] }; @@ -296,18 +314,31 @@ function onMoveDropToCompatibleGroup({ const newIndex = newColumnOrder.findIndex((c) => c === columnId); if (newIndex === -1) { - newColumnOrder[oldIndex] = columnId; - } else { + // for newly created columns, remove the old entry and add the last one to the end newColumnOrder.splice(oldIndex, 1); + newColumnOrder.push(columnId); + } else { + // for drop to replace, reuse the same index + newColumnOrder[oldIndex] = columnId; } + const newLayer = { + ...layer, + columnOrder: newColumnOrder, + columns: newColumns, + }; + + const updatedColumnOrder = getColumnOrder(newLayer); + + reorderByGroups(dimensionGroups, groupId, updatedColumnOrder, columnId); // Time to replace setState( mergeLayer({ state, layerId, + newLayer: { - columnOrder: newColumnOrder, + columnOrder: updatedColumnOrder, columns: newColumns, }, }) @@ -316,7 +347,7 @@ function onMoveDropToCompatibleGroup({ } function onFieldDrop(props: DropHandlerProps) { - const { columnId, setState, state, layerId, droppedItem } = props; + const { columnId, setState, state, layerId, droppedItem, groupId, dimensionGroups } = props; const operationsForNewField = getOperationTypesForField( droppedItem.field, @@ -343,6 +374,8 @@ function onFieldDrop(props: DropHandlerProps) { indexPattern: currentIndexPattern, op: fieldIsCompatibleWithCurrent ? selectedColumn.operationType : operationsForNewField[0], field: droppedItem.field, + visualizationGroups: dimensionGroups, + targetGroup: groupId, }); trackUiEvent('drop_onto_dimension'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx index dddebfbff1466..9ad6a2d20a4c2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx @@ -50,6 +50,7 @@ describe('reference editor', () => { savedObjectsClient: {} as SavedObjectsClientContract, http: {} as HttpSetup, data: {} as DataPublicPluginStart, + dimensionGroups: [], }; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index 87be81f66e8e7..353bba9652eff 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -35,6 +35,7 @@ import { FieldSelect } from './field_select'; import { hasField } from '../utils'; import type { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; +import { VisualizationDimensionGroupConfig } from '../../types'; const operationPanels = getOperationDisplay(); @@ -48,6 +49,7 @@ export interface ReferenceEditorProps { existingFields: IndexPatternPrivateState['existingFields']; dateRange: DateRange; labelAppend?: EuiFormRowProps['labelAppend']; + dimensionGroups: VisualizationDimensionGroupConfig[]; // Services uiSettings: IUiSettingsClient; @@ -68,6 +70,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { selectionStyle, dateRange, labelAppend, + dimensionGroups, ...services } = props; @@ -168,6 +171,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { op: operationType, indexPattern: currentIndexPattern, field: currentIndexPattern.getFieldByName(column.sourceField), + visualizationGroups: dimensionGroups, }) ); } else { @@ -185,6 +189,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { op: operationType, indexPattern: currentIndexPattern, field: possibleField, + visualizationGroups: dimensionGroups, }) ); } @@ -257,7 +262,11 @@ export function ReferenceEditor(props: ReferenceEditorProps) { onChange={(choices) => { if (choices.length === 0) { updateLayer( - deleteColumn({ layer, columnId, indexPattern: currentIndexPattern }) + deleteColumn({ + layer, + columnId, + indexPattern: currentIndexPattern, + }) ); return; } @@ -298,7 +307,13 @@ export function ReferenceEditor(props: ReferenceEditorProps) { incompleteOperation={incompleteOperation} markAllFieldsCompatible={selectionStyle === 'field'} onDeleteColumn={() => { - updateLayer(deleteColumn({ layer, columnId, indexPattern: currentIndexPattern })); + updateLayer( + deleteColumn({ + layer, + columnId, + indexPattern: currentIndexPattern, + }) + ); }} onChoose={(choice) => { updateLayer( @@ -308,6 +323,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { indexPattern: currentIndexPattern, op: choice.operationType, field: currentIndexPattern.getFieldByName(choice.field), + visualizationGroups: dimensionGroups, }) ); }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index cd7cfc6e8a1b2..64da5e4fb9f74 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -171,7 +171,11 @@ export function getIndexPatternDatasource({ return mergeLayer({ state: prevState, layerId, - newLayer: deleteColumn({ layer: prevState.layers[layerId], columnId, indexPattern }), + newLayer: deleteColumn({ + layer: prevState.layers[layerId], + columnId, + indexPattern, + }), }); }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index e62764cbfef8d..bde07c182555e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -182,6 +182,7 @@ function getExistingLayerSuggestionsForField( field, op: usableAsBucketOperation, columnId: previousDate, + visualizationGroups: [], }), layerId, changeType: 'initial', @@ -197,6 +198,7 @@ function getExistingLayerSuggestionsForField( field, op: usableAsBucketOperation, columnId: generateId(), + visualizationGroups: [], }), layerId, changeType: 'extended', @@ -214,6 +216,7 @@ function getExistingLayerSuggestionsForField( field, columnId: generateId(), op: metricOperation.type, + visualizationGroups: [], }); if (layerWithNewMetric) { suggestions.push( @@ -235,6 +238,7 @@ function getExistingLayerSuggestionsForField( field, columnId: metrics[0], op: metricOperation.type, + visualizationGroups: [], }); if (layerWithReplacedMetric) { suggestions.push( @@ -302,10 +306,12 @@ function createNewLayerWithBucketAggregation( columnId: generateId(), field: documentField, indexPattern, + visualizationGroups: [], }), columnId: generateId(), field, indexPattern, + visualizationGroups: [], }); } @@ -327,10 +333,12 @@ function createNewLayerWithMetricAggregation( columnId: generateId(), field, indexPattern, + visualizationGroups: [], }), columnId: generateId(), field: dateField, indexPattern, + visualizationGroups: [], }); } @@ -483,6 +491,7 @@ function createMetricSuggestion( op: operation.type, field: operation.type === 'count' ? documentField : field, indexPattern, + visualizationGroups: [], }), }); } @@ -525,6 +534,7 @@ function createAlternativeMetricSuggestions( field, columnId, op: possibleOperations[0].type, + visualizationGroups: [], }); if (layerWithNewMetric) { suggestions.push( @@ -558,6 +568,7 @@ function createSuggestionWithDefaultDateHistogram( field: timeField, op: 'date_histogram', columnId: generateId(), + visualizationGroups: [], }), label: i18n.translate('xpack.lens.indexpattern.suggestions.overTimeLabel', { defaultMessage: 'Over time', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 1961a4f957d81..4f915160a52a8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -104,6 +104,7 @@ describe('state_helpers', () => { indexPattern, op: 'missing' as OperationType, columnId: 'none', + visualizationGroups: [], }); }).toThrow(); }); @@ -130,6 +131,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col2', op: 'filters', + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col2', 'col1'] })); }); @@ -157,6 +159,7 @@ describe('state_helpers', () => { columnId: 'col2', op: 'date_histogram', field: indexPattern.fields[0], + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col2', 'col1'] })); }); @@ -187,6 +190,7 @@ describe('state_helpers', () => { columnId: 'col2', op: 'count', field: documentField, + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2'] })); }); @@ -225,6 +229,7 @@ describe('state_helpers', () => { columnId: 'col2', op: 'count', field: documentField, + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2', 'col3'] })); }); @@ -263,6 +268,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col2', op: 'filters', + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2', 'col3'] })); }); @@ -275,6 +281,7 @@ describe('state_helpers', () => { indexPattern, op: 'terms', field: indexPattern.fields[0], + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -310,6 +317,7 @@ describe('state_helpers', () => { indexPattern, op: 'terms', field: indexPattern.fields[2], + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col2', 'col1'] })); }); @@ -339,6 +347,7 @@ describe('state_helpers', () => { indexPattern, op: 'date_histogram', field: indexPattern.fields[0], + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2'] })); }); @@ -374,6 +383,7 @@ describe('state_helpers', () => { indexPattern, op: 'sum', field: indexPattern.fields[2], + visualizationGroups: [], }) ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2', 'col3'] })); }); @@ -395,6 +405,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col2', op: 'testReference' as OperationType, + visualizationGroups: [], }); }).toThrow(); }); @@ -406,6 +417,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col2', op: 'testReference' as OperationType, + visualizationGroups: [], }); expect(operationDefinitionMap.testReference.buildColumn).toHaveBeenCalledWith( @@ -470,6 +482,7 @@ describe('state_helpers', () => { columnId: 'ref1', op: 'count', field: documentField, + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -492,6 +505,7 @@ describe('state_helpers', () => { op: 'count', field: documentField, columnId: 'none', + visualizationGroups: [], }); }).toThrow(); }); @@ -503,6 +517,7 @@ describe('state_helpers', () => { indexPattern, op: 'missing' as OperationType, columnId: 'none', + visualizationGroups: [], }); }).toThrow(); }); @@ -539,6 +554,7 @@ describe('state_helpers', () => { columnId: 'col2', op: 'date_histogram', field: indexPattern.fields[0], // date + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -572,6 +588,7 @@ describe('state_helpers', () => { indexPattern, op: 'date_histogram', field: indexPattern.fields[0], + visualizationGroups: [], }); }).toThrow(); }); @@ -600,6 +617,7 @@ describe('state_helpers', () => { columnId: 'col1', indexPattern, op: 'terms', + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -636,6 +654,7 @@ describe('state_helpers', () => { indexPattern, op: 'date_histogram', field: indexPattern.fields[1], + visualizationGroups: [], }).columns.col1 ).toEqual( expect.objectContaining({ @@ -671,6 +690,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col1', op: 'filters', + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -706,6 +726,7 @@ describe('state_helpers', () => { columnId: 'col1', op: 'date_histogram', field: indexPattern.fields[0], + visualizationGroups: [], }).columns.col1 ).toEqual( expect.objectContaining({ @@ -740,6 +761,7 @@ describe('state_helpers', () => { columnId: 'col1', op: 'date_histogram', field: indexPattern.fields[1], + visualizationGroups: [], }).columns.col1 ).toEqual( expect.objectContaining({ @@ -775,6 +797,7 @@ describe('state_helpers', () => { columnId: 'col1', op: 'terms', field: indexPattern.fields[0], + visualizationGroups: [], }).columns.col1 ).toEqual( expect.objectContaining({ @@ -819,6 +842,7 @@ describe('state_helpers', () => { columnId: 'col2', op: 'avg', field: indexPattern.fields[2], // bytes field + visualizationGroups: [], }); expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith( @@ -876,6 +900,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'willBeReference', op: 'cumulative_sum', + visualizationGroups: [], }); expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith( @@ -925,6 +950,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col1', op: 'testReference' as OperationType, + visualizationGroups: [], }); expect(operationDefinitionMap.testReference.buildColumn).toHaveBeenCalledWith( @@ -1333,6 +1359,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'col2', op: 'filters', + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -1376,6 +1403,7 @@ describe('state_helpers', () => { columnId: 'col2', op: 'count', field: documentField, + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ @@ -1414,6 +1442,7 @@ describe('state_helpers', () => { indexPattern, columnId: 'ref', op: 'sum', + visualizationGroups: [], }); expect(result.columnOrder).toEqual(['ref']); @@ -1466,6 +1495,7 @@ describe('state_helpers', () => { columnId: 'col1', op: 'count', field: documentField, + visualizationGroups: [], }) ).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 15acdcd52860a..3a67e8e464323 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -6,7 +6,7 @@ */ import _, { partition } from 'lodash'; -import type { OperationMetadata } from '../../types'; +import type { OperationMetadata, VisualizationDimensionGroupConfig } from '../../types'; import { operationDefinitionMap, operationDefinitions, @@ -25,6 +25,8 @@ interface ColumnChange { columnId: string; indexPattern: IndexPattern; field?: IndexPatternField; + visualizationGroups: VisualizationDimensionGroupConfig[]; + targetGroup?: string; } export function insertOrReplaceColumn(args: ColumnChange): IndexPatternLayer { @@ -42,6 +44,8 @@ export function insertNewColumn({ columnId, field, indexPattern, + visualizationGroups, + targetGroup, }: ColumnChange): IndexPatternLayer { const operationDefinition = operationDefinitionMap[op]; @@ -63,7 +67,13 @@ export function insertNewColumn({ const isBucketed = Boolean(possibleOperation.isBucketed); const addOperationFn = isBucketed ? addBucket : addMetric; return updateDefaultLabels( - addOperationFn(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId), + addOperationFn( + layer, + operationDefinition.buildColumn({ ...baseOptions, layer }), + columnId, + visualizationGroups, + targetGroup + ), indexPattern ); } @@ -97,6 +107,8 @@ export function insertNewColumn({ columnId: newId, op: def.type, indexPattern, + visualizationGroups, + targetGroup, }); } else if (validFields.length === 1) { // Recursively update the layer for each new reference @@ -106,6 +118,8 @@ export function insertNewColumn({ op: def.type, indexPattern, field: validFields[0], + visualizationGroups, + targetGroup, }); } else { tempLayer = { @@ -133,7 +147,9 @@ export function insertNewColumn({ addOperationFn( tempLayer, operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, referenceIds }), - columnId + columnId, + visualizationGroups, + targetGroup ), indexPattern ); @@ -155,7 +171,9 @@ export function insertNewColumn({ addBucket( layer, operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }), - columnId + columnId, + visualizationGroups, + targetGroup ), indexPattern ); @@ -196,7 +214,9 @@ export function insertNewColumn({ addOperationFn( layer, operationDefinition.buildColumn({ ...baseOptions, layer, field }), - columnId + columnId, + visualizationGroups, + targetGroup ), indexPattern ); @@ -208,6 +228,7 @@ export function replaceColumn({ indexPattern, op, field, + visualizationGroups, }: ColumnChange): IndexPatternLayer { const previousColumn = layer.columns[columnId]; if (!previousColumn) { @@ -240,6 +261,7 @@ export function replaceColumn({ previousColumn, op, indexPattern, + visualizationGroups, }); } @@ -297,7 +319,11 @@ export function replaceColumn({ // This logic comes after the transitions because they need to look at previous columns if (previousDefinition.input === 'fullReference') { (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { - tempLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern }); + tempLayer = deleteColumn({ + layer: tempLayer, + columnId: id, + indexPattern, + }); }); } @@ -385,6 +411,7 @@ export function canTransition({ field, indexPattern, filterOperations, + visualizationGroups, }: ColumnChange & { filterOperations: (meta: OperationMetadata) => boolean; }): boolean { @@ -398,7 +425,14 @@ export function canTransition({ } try { - const newLayer = replaceColumn({ layer, columnId, op, field, indexPattern }); + const newLayer = replaceColumn({ + layer, + columnId, + op, + field, + indexPattern, + visualizationGroups, + }); const newDefinition = operationDefinitionMap[op]; const newColumn = newLayer.columns[columnId]; return ( @@ -437,12 +471,14 @@ function applyReferenceTransition({ previousColumn, op, indexPattern, + visualizationGroups, }: { layer: IndexPatternLayer; columnId: string; previousColumn: IndexPatternColumn; op: OperationType; indexPattern: IndexPattern; + visualizationGroups: VisualizationDimensionGroupConfig[]; }): IndexPatternLayer { const operationDefinition = operationDefinitionMap[op]; @@ -499,6 +535,7 @@ function applyReferenceTransition({ columnId: newId, op: validOperations[0].type, indexPattern, + visualizationGroups, }); return newId; } @@ -536,6 +573,7 @@ function applyReferenceTransition({ op: defWithField[0].type, indexPattern, field: indexPattern.getFieldByName(previousColumn.sourceField), + visualizationGroups, }); return newId; } else if (defIgnoringfield.length === 1) { @@ -578,6 +616,7 @@ function applyReferenceTransition({ op: defWithField[0].type, indexPattern, field: previousField, + visualizationGroups, }); return newId; } @@ -633,7 +672,9 @@ function copyCustomLabel(newColumn: IndexPatternColumn, previousColumn: IndexPat function addBucket( layer: IndexPatternLayer, column: IndexPatternColumn, - addedColumnId: string + addedColumnId: string, + visualizationGroups: VisualizationDimensionGroupConfig[], + targetGroup?: string ): IndexPatternLayer { const [buckets, metrics, references] = getExistingColumnGroups(layer); @@ -656,6 +697,7 @@ function addBucket( // they already had, with an extra level of detail. updatedColumnOrder = [...buckets, addedColumnId, ...metrics, ...references]; } + reorderByGroups(visualizationGroups, targetGroup, updatedColumnOrder, addedColumnId); const tempLayer = { ...resetIncomplete(layer, addedColumnId), columns: { ...layer.columns, [addedColumnId]: column }, @@ -664,6 +706,43 @@ function addBucket( return { ...tempLayer, columnOrder: getColumnOrder(tempLayer) }; } +export function reorderByGroups( + visualizationGroups: VisualizationDimensionGroupConfig[], + targetGroup: string | undefined, + updatedColumnOrder: string[], + addedColumnId: string +) { + const hidesColumnGrouping = + targetGroup && visualizationGroups.find((group) => group.groupId === targetGroup)?.hideGrouping; + + // if column grouping is disabled, keep bucket aggregations in the same order as the groups + // if grouping is known + if (hidesColumnGrouping) { + const orderedVisualizationGroups = [...visualizationGroups]; + orderedVisualizationGroups.sort((group1, group2) => { + if (typeof group1.nestingOrder === undefined) { + return -1; + } + if (typeof group2.nestingOrder === undefined) { + return 1; + } + return group1.nestingOrder! - group2.nestingOrder!; + }); + const columnGroupIndex: Record = {}; + updatedColumnOrder.forEach((columnId) => { + columnGroupIndex[columnId] = orderedVisualizationGroups.findIndex( + (group) => + (columnId === addedColumnId && group.groupId === targetGroup) || + group.accessors.some((acc) => acc.columnId === columnId) + ); + }); + + updatedColumnOrder.sort((a, b) => { + return columnGroupIndex[a] - columnGroupIndex[b]; + }); + } +} + function addMetric( layer: IndexPatternLayer, column: IndexPatternColumn, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index e42ca36e5cfc3..3b47792af4254 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -264,6 +264,7 @@ interface SharedDimensionProps { export type DatasourceDimensionProps = SharedDimensionProps & { layerId: string; columnId: string; + groupId: string; onRemove?: (accessor: string) => void; state: T; activeData?: Record; @@ -308,9 +309,11 @@ export function isDraggedOperation( export type DatasourceDimensionDropProps = SharedDimensionProps & { layerId: string; + groupId: string; columnId: string; state: T; setState: StateSetter; + dimensionGroups: VisualizationDimensionGroupConfig[]; }; export type DatasourceDimensionDropHandlerProps = DatasourceDimensionDropProps & { @@ -388,6 +391,7 @@ export interface AccessorConfig { export type VisualizationDimensionGroupConfig = SharedDimensionProps & { groupLabel: string; + groupTooltip?: string; /** ID is passed back to visualization. For example, `x` */ groupId: string; @@ -402,6 +406,10 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { * will render the extra tab for the dimension editor */ enableDimensionEditor?: boolean; + // if the visual order of dimension groups diverges from the intended nesting order, this property specifies the position of + // this dimension group in the hierarchy. If not specified, the position of the dimension in the array is used. specified nesting + // orders are always higher in the hierarchy than non-specified ones. + nestingOrder?: number; }; interface VisualizationDimensionChangeProps { @@ -592,7 +600,9 @@ export interface Visualization { * The frame is telling the visualization to update or set a dimension based on user interaction * groupId is coming from the groupId provided in getConfiguration */ - setDimension: (props: VisualizationDimensionChangeProps & { groupId: string }) => T; + setDimension: ( + props: VisualizationDimensionChangeProps & { groupId: string; previousColumn?: string } + ) => T; /** * The frame is telling the visualization to remove a dimension. The visualization needs to * look at its internal state to determine which dimension is being affected. diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index c5e7b0af9654f..0bf5c139e2403 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -648,6 +648,14 @@ export function XYChart({ layersAlreadyFormatted ); + const isStacked = seriesType.includes('stacked'); + const isPercentage = seriesType.includes('percentage'); + const isBarChart = seriesType.includes('bar'); + const enableHistogramMode = + isHistogram && + (isStacked || !splitAccessor) && + (isStacked || !isBarChart || !chartHasMoreThanOneBarSeries); + // For date histogram chart type, we're getting the rows that represent intervals without data. // To not display them in the legend, they need to be filtered out. const rows = tableConverted.rows.filter( @@ -674,7 +682,7 @@ export function XYChart({ const seriesProps: SeriesSpec = { splitSeriesAccessors: splitAccessor ? [splitAccessor] : [], - stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [], + stackAccessors: isStacked ? [xAccessor as string] : [], id: `${splitAccessor}-${accessor}`, xAccessor: xAccessor || 'unifiedX', yAccessors: [accessor], @@ -710,13 +718,8 @@ export function XYChart({ ); }, groupId: yAxis?.groupId, - enableHistogramMode: - isHistogram && - (seriesType.includes('stacked') || !splitAccessor) && - (seriesType.includes('stacked') || - !seriesType.includes('bar') || - !chartHasMoreThanOneBarSeries), - stackMode: seriesType.includes('percentage') ? StackMode.Percentage : undefined, + enableHistogramMode, + stackMode: isPercentage ? StackMode.Percentage : undefined, timeZone, areaSeriesStyle: { point: { @@ -797,7 +800,11 @@ export function XYChart({ case 'area_stacked': case 'area_percentage_stacked': return ( - + ); case 'area': return ( diff --git a/x-pack/plugins/security_solution/common/detection_engine/build_exceptions_filter.test.ts b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts similarity index 97% rename from x-pack/plugins/security_solution/common/detection_engine/build_exceptions_filter.test.ts rename to x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts index 689c60e67f14f..291831777e471 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/build_exceptions_filter.test.ts +++ b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts @@ -5,41 +5,35 @@ * 2.0. */ +import { getEntryMatchExcludeMock, getEntryMatchMock } from '../schemas/types/entry_match.mock'; import { - getEntryMatchMock, - getEntryMatchExcludeMock, -} from '../../../lists/common/schemas/types/entry_match.mock'; -import { - getEntryMatchAnyMock, getEntryMatchAnyExcludeMock, -} from '../../../lists/common/schemas/types/entry_match_any.mock'; -import { - getEntryExistsMock, - getEntryExistsExcludedMock, -} from '../../../lists/common/schemas/types/entry_exists.mock'; + getEntryMatchAnyMock, +} from '../schemas/types/entry_match_any.mock'; +import { getEntryExistsExcludedMock, getEntryExistsMock } from '../schemas/types/entry_exists.mock'; import { - getEntryNestedMock, getEntryNestedExcludeMock, getEntryNestedMixedEntries, -} from '../../../lists/common/schemas/types/entry_nested.mock'; + getEntryNestedMock, +} from '../schemas/types/entry_nested.mock'; import { getExceptionListItemSchemaMock, getExceptionListItemSchemaXMock, -} from '../../../lists/common/schemas/response/exception_list_item_schema.mock'; +} from '../schemas/response/exception_list_item_schema.mock'; +import { EntryMatchAny, ExceptionListItemSchema } from '../schemas'; import { + ExceptionItemSansLargeValueLists, + buildExceptionFilter, buildExceptionItemFilter, buildExclusionClause, buildExistsClause, buildMatchAnyClause, buildMatchClause, buildNestedClause, - createOrClauses, chunkExceptions, - buildExceptionFilter, - ExceptionItemSansLargeValueLists, + createOrClauses, } from './build_exceptions_filter'; -import { EntryMatchAny, ExceptionListItemSchema } from '../../common/shared_imports'; import { hasLargeValueList } from './utils'; const modifiedGetEntryMatchAnyMock = (): EntryMatchAny => ({ @@ -59,22 +53,22 @@ describe('build_exceptions_filter', () => { describe('buildExceptionFilter', () => { test('it should return undefined if no exception items', () => { const booleanFilter = buildExceptionFilter({ - lists: [], - excludeExceptions: false, chunkSize: 1, + excludeExceptions: false, + lists: [], }); expect(booleanFilter).toBeUndefined(); }); test('it should build a filter given an exception list', () => { const booleanFilter = buildExceptionFilter({ - lists: [getExceptionListItemSchemaMock()], - excludeExceptions: false, chunkSize: 1, + excludeExceptions: false, + lists: [getExceptionListItemSchemaMock()], }); expect(booleanFilter).toEqual({ - meta: { alias: null, negate: false, disabled: false }, + meta: { alias: null, disabled: false, negate: false }, query: { bool: { should: [ @@ -86,10 +80,10 @@ describe('build_exceptions_filter', () => { path: 'some.parentField', query: { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'some.parentField.nested.field': 'some value' } }, ], - minimum_should_match: 1, }, }, score_mode: 'none', @@ -97,8 +91,8 @@ describe('build_exceptions_filter', () => { }, { bool: { - should: [{ match_phrase: { 'some.not.nested.field': 'some value' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'some.not.nested.field': 'some value' } }], }, }, ], @@ -123,15 +117,15 @@ describe('build_exceptions_filter', () => { entries: [{ field: 'user.name', operator: 'included', type: 'match', value: 'name' }], }; const exceptionFilter = buildExceptionFilter({ - lists: [exceptionItem1, exceptionItem2], - excludeExceptions: true, chunkSize: 2, + excludeExceptions: true, + lists: [exceptionItem1, exceptionItem2], }); expect(exceptionFilter).toEqual({ meta: { alias: null, - negate: true, disabled: false, + negate: true, }, query: { bool: { @@ -201,16 +195,16 @@ describe('build_exceptions_filter', () => { entries: [{ field: 'file.path', operator: 'included', type: 'match', value: '/safe/path' }], }; const exceptionFilter = buildExceptionFilter({ - lists: [exceptionItem1, exceptionItem2, exceptionItem3], - excludeExceptions: true, chunkSize: 2, + excludeExceptions: true, + lists: [exceptionItem1, exceptionItem2, exceptionItem3], }); expect(exceptionFilter).toEqual({ meta: { alias: null, - negate: true, disabled: false, + negate: true, }, query: { bool: { @@ -298,13 +292,13 @@ describe('build_exceptions_filter', () => { ]; const booleanFilter = buildExceptionFilter({ - lists: exceptions, - excludeExceptions: true, chunkSize: 1, + excludeExceptions: true, + lists: exceptions, }); expect(booleanFilter).toEqual({ - meta: { alias: null, negate: true, disabled: false }, + meta: { alias: null, disabled: false, negate: true }, query: { bool: { should: [ @@ -319,21 +313,23 @@ describe('build_exceptions_filter', () => { filter: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'parent.field.host.name': 'some host name' }, }, ], - minimum_should_match: 1, }, }, { bool: { must_not: { bool: { + minimum_should_match: 1, should: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -341,11 +337,11 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -353,19 +349,17 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, ], - minimum_should_match: 1, }, }, }, }, { bool: { - should: [{ exists: { field: 'parent.field.host.name' } }], minimum_should_match: 1, + should: [{ exists: { field: 'parent.field.host.name' } }], }, }, ], @@ -382,21 +376,21 @@ describe('build_exceptions_filter', () => { should: [ { bool: { + minimum_should_match: 1, should: [ { bool: { - should: [{ match_phrase: { 'host.name': 'some "host" name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some "host" name' } }], }, }, { bool: { - should: [{ match_phrase: { 'host.name': 'some other host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some other host name' } }], }, }, ], - minimum_should_match: 1, }, }, ], @@ -412,16 +406,16 @@ describe('build_exceptions_filter', () => { bool: { must_not: { bool: { - should: [{ exists: { field: 'host.name' } }], minimum_should_match: 1, + should: [{ exists: { field: 'host.name' } }], }, }, }, }, { bool: { - should: [{ match_phrase: { 'host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some host name' } }], }, }, ], @@ -520,18 +514,18 @@ describe('build_exceptions_filter', () => { filter: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'parent.field.host.name': 'some host name' } }, ], - minimum_should_match: 1, }, }, { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'parent.field.host.name': 'some host name' } }, ], - minimum_should_match: 1, }, }, ], @@ -542,8 +536,8 @@ describe('build_exceptions_filter', () => { }, { bool: { - should: [{ match_phrase: { 'host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some host name' } }], }, }, ], @@ -560,19 +554,21 @@ describe('build_exceptions_filter', () => { filter: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'parent.field.host.name': 'some host name' } }, ], - minimum_should_match: 1, }, }, { bool: { must_not: { bool: { + minimum_should_match: 1, should: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -580,11 +576,11 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -592,19 +588,17 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, ], - minimum_should_match: 1, }, }, }, }, { bool: { - should: [{ exists: { field: 'parent.field.host.name' } }], minimum_should_match: 1, + should: [{ exists: { field: 'parent.field.host.name' } }], }, }, ], @@ -615,29 +609,29 @@ describe('build_exceptions_filter', () => { }, { bool: { + minimum_should_match: 1, should: [ { bool: { - should: [{ match_phrase: { 'host.name': 'some "host" name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some "host" name' } }], }, }, { bool: { - should: [{ match_phrase: { 'host.name': 'some other host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some other host name' } }], }, }, ], - minimum_should_match: 1, }, }, { bool: { must_not: { bool: { - should: [{ match_phrase: { 'host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some host name' } }], }, }, }, @@ -645,7 +639,7 @@ describe('build_exceptions_filter', () => { { bool: { must_not: { - bool: { should: [{ exists: { field: 'host.name' } }], minimum_should_match: 1 }, + bool: { minimum_should_match: 1, should: [{ exists: { field: 'host.name' } }] }, }, }, }, @@ -655,7 +649,7 @@ describe('build_exceptions_filter', () => { { bool: { must_not: { - bool: { should: [{ exists: { field: 'host.name' } }], minimum_should_match: 1 }, + bool: { minimum_should_match: 1, should: [{ exists: { field: 'host.name' } }] }, }, }, }, @@ -686,6 +680,7 @@ describe('build_exceptions_filter', () => { filter: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -693,16 +688,17 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, { bool: { must_not: { bool: { + minimum_should_match: 1, should: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -710,11 +706,11 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -722,19 +718,17 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, ], - minimum_should_match: 1, }, }, }, }, { bool: { - should: [{ exists: { field: 'parent.field.host.name' } }], minimum_should_match: 1, + should: [{ exists: { field: 'parent.field.host.name' } }], }, }, ], @@ -745,29 +739,29 @@ describe('build_exceptions_filter', () => { }, { bool: { + minimum_should_match: 1, should: [ { bool: { - should: [{ match_phrase: { 'host.name': 'some "host" name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some "host" name' } }], }, }, { bool: { - should: [{ match_phrase: { 'host.name': 'some other host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some other host name' } }], }, }, ], - minimum_should_match: 1, }, }, { bool: { must_not: { bool: { - should: [{ match_phrase: { 'host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some host name' } }], }, }, }, @@ -775,7 +769,7 @@ describe('build_exceptions_filter', () => { { bool: { must_not: { - bool: { should: [{ exists: { field: 'host.name' } }], minimum_should_match: 1 }, + bool: { minimum_should_match: 1, should: [{ exists: { field: 'host.name' } }] }, }, }, }, @@ -905,21 +899,21 @@ describe('build_exceptions_filter', () => { bool: { must_not: { bool: { + minimum_should_match: 1, should: [ { bool: { - should: [{ match_phrase: { 'host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some host name' } }], }, }, { bool: { - should: [{ match_phrase: { 'host.name': 'some other host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'host.name': 'some other host name' } }], }, }, ], - minimum_should_match: 1, }, }, }, @@ -961,14 +955,14 @@ describe('build_exceptions_filter', () => { filter: [ { bool: { - should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], }, }, { bool: { - should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], }, }, ], @@ -992,8 +986,8 @@ describe('build_exceptions_filter', () => { bool: { must_not: { bool: { - should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], }, }, }, @@ -1002,17 +996,19 @@ describe('build_exceptions_filter', () => { bool: { must_not: { bool: { + minimum_should_match: 1, should: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'parent.field.host.name': 'some host name' } }, ], - minimum_should_match: 1, }, }, { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -1020,11 +1016,9 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, ], - minimum_should_match: 1, }, }, }, @@ -1048,25 +1042,27 @@ describe('build_exceptions_filter', () => { filter: [ { bool: { - should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], minimum_should_match: 1, + should: [{ match_phrase: { 'parent.field.host.name': 'some host name' } }], }, }, { bool: { must_not: { bool: { + minimum_should_match: 1, should: [ { bool: { + minimum_should_match: 1, should: [ { match_phrase: { 'parent.field.host.name': 'some host name' } }, ], - minimum_should_match: 1, }, }, { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -1074,19 +1070,17 @@ describe('build_exceptions_filter', () => { }, }, ], - minimum_should_match: 1, }, }, ], - minimum_should_match: 1, }, }, }, }, { bool: { - should: [{ exists: { field: 'parent.field.host.name' } }], minimum_should_match: 1, + should: [{ exists: { field: 'parent.field.host.name' } }], }, }, ], diff --git a/x-pack/plugins/security_solution/common/detection_engine/build_exceptions_filter.ts b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts similarity index 99% rename from x-pack/plugins/security_solution/common/detection_engine/build_exceptions_filter.ts rename to x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts index e625254e49b23..0a9753b02a612 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/build_exceptions_filter.ts +++ b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts @@ -9,17 +9,18 @@ import { chunk } from 'lodash/fp'; import { Filter } from '../../../../../src/plugins/data/common'; import { - ExceptionListItemSchema, CreateExceptionListItemSchema, + EntryExists, EntryMatch, EntryMatchAny, EntryNested, + ExceptionListItemSchema, + entriesExists, entriesMatch, entriesMatchAny, - entriesExists, entriesNested, - EntryExists, -} from '../../../lists/common'; +} from '../schemas'; + import { BooleanFilter, NestedFilter } from './types'; import { hasLargeValueList } from './utils'; @@ -83,8 +84,8 @@ export const buildExceptionFilter = ({ const exceptionFilter: Filter = { meta: { alias: null, - negate: excludeExceptions, disabled: false, + negate: excludeExceptions, }, query: { bool: { @@ -108,8 +109,8 @@ export const buildExceptionFilter = ({ return { meta: { alias: null, - negate: false, disabled: false, + negate: false, }, query: { bool: { @@ -124,8 +125,8 @@ export const buildExceptionFilter = ({ return { meta: { alias: null, - negate: excludeExceptions, disabled: false, + negate: excludeExceptions, }, query: { bool: { @@ -148,6 +149,7 @@ export const buildMatchClause = (entry: EntryMatch): BooleanFilter => { const { field, operator, value } = entry; const matchClause = { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -155,7 +157,6 @@ export const buildMatchClause = (entry: EntryMatch): BooleanFilter => { }, }, ], - minimum_should_match: 1, }, }; @@ -172,6 +173,7 @@ export const getBaseMatchAnyClause = (entry: EntryMatchAny): BooleanFilter => { if (value.length === 1) { return { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -179,16 +181,17 @@ export const getBaseMatchAnyClause = (entry: EntryMatchAny): BooleanFilter => { }, }, ], - minimum_should_match: 1, }, }; } return { bool: { + minimum_should_match: 1, should: value.map((val) => { return { bool: { + minimum_should_match: 1, should: [ { match_phrase: { @@ -196,11 +199,9 @@ export const getBaseMatchAnyClause = (entry: EntryMatchAny): BooleanFilter => { }, }, ], - minimum_should_match: 1, }, }; }), - minimum_should_match: 1, }, }; }; @@ -220,6 +221,7 @@ export const buildExistsClause = (entry: EntryExists): BooleanFilter => { const { field, operator } = entry; const existsClause = { bool: { + minimum_should_match: 1, should: [ { exists: { @@ -227,7 +229,6 @@ export const buildExistsClause = (entry: EntryExists): BooleanFilter => { }, }, ], - minimum_should_match: 1, }, }; diff --git a/x-pack/plugins/lists/common/exceptions/index.ts b/x-pack/plugins/lists/common/exceptions/index.ts new file mode 100644 index 0000000000000..d9a9c47348e1a --- /dev/null +++ b/x-pack/plugins/lists/common/exceptions/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './build_exceptions_filter'; diff --git a/x-pack/plugins/lists/common/exceptions/types.ts b/x-pack/plugins/lists/common/exceptions/types.ts new file mode 100644 index 0000000000000..f4e0dffab3f25 --- /dev/null +++ b/x-pack/plugins/lists/common/exceptions/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface BooleanFilter { + bool: { + must?: unknown | unknown[]; + must_not?: unknown | unknown[]; + should?: unknown[]; + filter?: unknown | unknown[]; + minimum_should_match?: number; + }; +} + +export interface NestedFilter { + nested: { + path: string; + query: unknown | unknown[]; + score_mode: string; + }; +} diff --git a/x-pack/plugins/lists/common/exceptions/utils.ts b/x-pack/plugins/lists/common/exceptions/utils.ts new file mode 100644 index 0000000000000..d7dc706882cd5 --- /dev/null +++ b/x-pack/plugins/lists/common/exceptions/utils.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CreateExceptionListItemSchema, EntriesArray, ExceptionListItemSchema } from '../schemas'; + +export const hasLargeValueItem = ( + exceptionItems: Array +): boolean => { + return exceptionItems.some((exceptionItem) => hasLargeValueList(exceptionItem.entries)); +}; + +export const hasLargeValueList = (entries: EntriesArray): boolean => { + const found = entries.filter(({ type }) => type === 'list'); + return found.length > 0; +}; + +export const hasNestedEntry = (entries: EntriesArray): boolean => { + const found = entries.filter(({ type }) => type === 'nested'); + return found.length > 0; +}; diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index 4c4ee19d29bcd..23da48b35a9d4 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -44,8 +44,11 @@ export { namespaceType, ExceptionListType, Type, + osType, osTypeArray, OsTypeArray, } from './schemas'; +export { buildExceptionFilter } from './exceptions'; + export { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from './constants'; diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index cfceb3e8b422e..f1e0ac25aa127 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -131,15 +131,15 @@ export enum ES_SPATIAL_RELATIONS { WITHIN = 'WITHIN', } -export const GEO_JSON_TYPE = { - POINT: 'Point', - MULTI_POINT: 'MultiPoint', - LINE_STRING: 'LineString', - MULTI_LINE_STRING: 'MultiLineString', - POLYGON: 'Polygon', - MULTI_POLYGON: 'MultiPolygon', - GEOMETRY_COLLECTION: 'GeometryCollection', -}; +export enum GEO_JSON_TYPE { + POINT = 'Point', + MULTI_POINT = 'MultiPoint', + LINE_STRING = 'LineString', + MULTI_LINE_STRING = 'MultiLineString', + POLYGON = 'Polygon', + MULTI_POLYGON = 'MultiPolygon', + GEOMETRY_COLLECTION = 'GeometryCollection', +} export const POLYGON_COORDINATES_EXTERIOR_INDEX = 0; export const LON_INDEX = 0; diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts deleted file mode 100644 index 2a3741146d454..0000000000000 --- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FeatureCollection, GeoJsonProperties, Polygon } from 'geojson'; -import { MapExtent } from '../descriptor_types'; -import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants'; - -export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent; - -export function turfBboxToBounds(turfBbox: unknown): MapExtent; - -export function clampToLatBounds(lat: number): number; - -export function clampToLonBounds(lon: number): number; - -export function hitsToGeoJson( - hits: Array>, - flattenHit: (elasticSearchHit: Record) => GeoJsonProperties, - geoFieldName: string, - geoFieldType: ES_GEO_FIELD_TYPE, - epochMillisFields: string[] -): FeatureCollection; - -export interface ESBBox { - top_left: number[]; - bottom_right: number[]; -} - -export interface ESGeoBoundingBoxFilter { - geo_bounding_box: { - [geoFieldName: string]: ESBBox; - }; -} - -export interface ESPolygonFilter { - geo_shape: { - [geoFieldName: string]: { - shape: Polygon; - relation: ES_SPATIAL_RELATIONS.INTERSECTS; - }; - }; -} - -export function createExtentFilter( - mapExtent: MapExtent, - geoFieldName: string -): ESPolygonFilter | ESGeoBoundingBoxFilter; - -export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBox; diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js index 9983bb9b84588..22b8a86158a74 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js @@ -397,12 +397,10 @@ describe('createExtentFilter', () => { minLon: -89, }; const filter = createExtentFilter(mapExtent, geoFieldName); - expect(filter).toEqual({ - geo_bounding_box: { - location: { - top_left: [-89, 39], - bottom_right: [-83, 35], - }, + expect(filter.geo_bounding_box).toEqual({ + location: { + top_left: [-89, 39], + bottom_right: [-83, 35], }, }); }); @@ -415,12 +413,10 @@ describe('createExtentFilter', () => { minLon: -190, }; const filter = createExtentFilter(mapExtent, geoFieldName); - expect(filter).toEqual({ - geo_bounding_box: { - location: { - top_left: [-180, 89], - bottom_right: [180, -89], - }, + expect(filter.geo_bounding_box).toEqual({ + location: { + top_left: [-180, 89], + bottom_right: [180, -89], }, }); }); @@ -436,12 +432,10 @@ describe('createExtentFilter', () => { const leftLon = filter.geo_bounding_box.location.top_left[0]; const rightLon = filter.geo_bounding_box.location.bottom_right[0]; expect(leftLon).toBeGreaterThan(rightLon); - expect(filter).toEqual({ - geo_bounding_box: { - location: { - top_left: [100, 39], - bottom_right: [-160, 35], - }, + expect(filter.geo_bounding_box).toEqual({ + location: { + top_left: [100, 39], + bottom_right: [-160, 35], }, }); }); @@ -457,12 +451,10 @@ describe('createExtentFilter', () => { const leftLon = filter.geo_bounding_box.location.top_left[0]; const rightLon = filter.geo_bounding_box.location.bottom_right[0]; expect(leftLon).toBeGreaterThan(rightLon); - expect(filter).toEqual({ - geo_bounding_box: { - location: { - top_left: [160, 39], - bottom_right: [-100, 35], - }, + expect(filter.geo_bounding_box).toEqual({ + location: { + top_left: [160, 39], + bottom_right: [-100, 35], }, }); }); @@ -475,12 +467,10 @@ describe('createExtentFilter', () => { minLon: -191, }; const filter = createExtentFilter(mapExtent, geoFieldName); - expect(filter).toEqual({ - geo_bounding_box: { - location: { - top_left: [-180, 39], - bottom_right: [180, 35], - }, + expect(filter.geo_bounding_box).toEqual({ + location: { + top_left: [-180, 39], + bottom_right: [180, 35], }, }); }); diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts similarity index 67% rename from x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts index 47de8850c0e96..f2a8b95f7b643 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts @@ -7,7 +7,12 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +// @ts-expect-error import { parse } from 'wellknown'; +// @ts-expect-error +import turfCircle from '@turf/circle'; +import { Feature, FeatureCollection, Geometry, Polygon, Point, Position } from 'geojson'; +import { BBox } from '@turf/helpers'; import { DECIMAL_DEGREES_PRECISION, ES_GEO_FIELD_TYPE, @@ -18,14 +23,58 @@ import { LAT_INDEX, } from '../constants'; import { getEsSpatialRelationLabel } from '../i18n_getters'; -import { FILTERS } from '../../../../../src/plugins/data/common'; -import turfCircle from '@turf/circle'; +import { Filter, FilterMeta, FILTERS } from '../../../../../src/plugins/data/common'; +import { MapExtent } from '../descriptor_types'; const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER; -function ensureGeoField(type) { +type Coordinates = Position | Position[] | Position[][] | Position[][][]; + +// Elasticsearch stores more then just GeoJSON. +// 1) geometry.type as lower case string +// 2) circle and envelope types +interface ESGeometry { + type: string; + coordinates: Coordinates; +} + +export interface ESBBox { + top_left: number[]; + bottom_right: number[]; +} + +interface GeoShapeQueryBody { + shape?: Polygon; + relation?: ES_SPATIAL_RELATIONS; + indexed_shape?: PreIndexedShape; +} + +// Index signature explicitly states that anything stored in an object using a string conforms to the structure +// problem is that Elasticsearch signature also allows for other string keys to conform to other structures, like 'ignore_unmapped' +// Use intersection type to exclude certain properties from the index signature +// https://basarat.gitbook.io/typescript/type-system/index-signatures#excluding-certain-properties-from-the-index-signature +type GeoShapeQuery = { ignore_unmapped: boolean } & { [geoFieldName: string]: GeoShapeQueryBody }; + +export type GeoFilter = Filter & { + geo_bounding_box?: { + [geoFieldName: string]: ESBBox; + }; + geo_distance?: { + distance: string; + [geoFieldName: string]: Position | { lat: number; lon: number } | string; + }; + geo_shape?: GeoShapeQuery; +}; + +export interface PreIndexedShape { + index: string; + id: string | number; + path: string; +} + +function ensureGeoField(type: string) { const expectedTypes = [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE]; - if (!expectedTypes.includes(type)) { + if (!expectedTypes.includes(type as ES_GEO_FIELD_TYPE)) { const errorMessage = i18n.translate( 'xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage', { @@ -41,8 +90,8 @@ function ensureGeoField(type) { } } -function ensureGeometryType(type, expectedTypes) { - if (!expectedTypes.includes(type)) { +function ensureGeometryType(type: string, expectedTypes: GEO_JSON_TYPE[]) { + if (!expectedTypes.includes(type as GEO_JSON_TYPE)) { const errorMessage = i18n.translate( 'xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage', { @@ -68,36 +117,48 @@ function ensureGeometryType(type, expectedTypes) { * @param {string} geoFieldType Geometry field type ["geo_point", "geo_shape"] * @returns {number} */ -export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType, epochMillisFields) { - const features = []; - const tmpGeometriesAccumulator = []; +export function hitsToGeoJson( + hits: Array>, + flattenHit: (elasticSearchHit: Record) => Record, + geoFieldName: string, + geoFieldType: ES_GEO_FIELD_TYPE, + epochMillisFields: string[] +): FeatureCollection { + const features: Feature[] = []; + const tmpGeometriesAccumulator: Geometry[] = []; for (let i = 0; i < hits.length; i++) { const properties = flattenHit(hits[i]); - tmpGeometriesAccumulator.length = 0; //truncate accumulator + tmpGeometriesAccumulator.length = 0; // truncate accumulator ensureGeoField(geoFieldType); if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) { - geoPointToGeometry(properties[geoFieldName], tmpGeometriesAccumulator); + geoPointToGeometry( + properties[geoFieldName] as string | string[] | undefined, + tmpGeometriesAccumulator + ); } else { - geoShapeToGeometry(properties[geoFieldName], tmpGeometriesAccumulator); + geoShapeToGeometry( + properties[geoFieldName] as string | string[] | ESGeometry | ESGeometry[] | undefined, + tmpGeometriesAccumulator + ); } // There is a bug in Elasticsearch API where epoch_millis are returned as a string instead of a number // https://github.com/elastic/elasticsearch/issues/50622 // Convert these field values to integers. - for (let i = 0; i < epochMillisFields.length; i++) { - const fieldName = epochMillisFields[i]; + for (let k = 0; k < epochMillisFields.length; k++) { + const fieldName = epochMillisFields[k]; if (typeof properties[fieldName] === 'string') { - properties[fieldName] = parseInt(properties[fieldName]); + properties[fieldName] = parseInt(properties[fieldName] as string, 10); } } // don't include geometry field value in properties delete properties[geoFieldName]; - //create new geojson Feature for every individual geojson geometry. + // create new geojson Feature for every individual geojson geometry. for (let j = 0; j < tmpGeometriesAccumulator.length; j++) { features.push({ type: 'Feature', @@ -112,7 +173,7 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType, epoc return { type: 'FeatureCollection', - features: features, + features, }; } @@ -120,7 +181,10 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType, epoc // Either // 1) Array of latLon strings // 2) latLon string -export function geoPointToGeometry(value, accumulator) { +export function geoPointToGeometry( + value: string[] | string | undefined, + accumulator: Geometry[] +): void { if (!value) { return; } @@ -138,10 +202,10 @@ export function geoPointToGeometry(value, accumulator) { accumulator.push({ type: GEO_JSON_TYPE.POINT, coordinates: [lon, lat], - }); + } as Point); } -export function convertESShapeToGeojsonGeometry(value) { +export function convertESShapeToGeojsonGeometry(value: ESGeometry): Geometry { const geoJson = { type: value.type, coordinates: value.coordinates, @@ -183,12 +247,13 @@ export function convertESShapeToGeojsonGeometry(value) { ); throw new Error(invalidGeometrycollectionError); case 'envelope': + const envelopeCoords = geoJson.coordinates as Position[]; // format defined here https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html#_envelope const polygon = formatEnvelopeAsPolygon({ - minLon: geoJson.coordinates[0][0], - maxLon: geoJson.coordinates[1][0], - minLat: geoJson.coordinates[1][1], - maxLat: geoJson.coordinates[0][1], + minLon: envelopeCoords[0][0], + maxLon: envelopeCoords[1][0], + minLat: envelopeCoords[1][1], + maxLat: envelopeCoords[0][1], }); geoJson.type = polygon.type; geoJson.coordinates = polygon.coordinates; @@ -205,10 +270,10 @@ export function convertESShapeToGeojsonGeometry(value) { ); throw new Error(errorMessage); } - return geoJson; + return (geoJson as unknown) as Geometry; } -function convertWKTStringToGeojson(value) { +function convertWKTStringToGeojson(value: string): Geometry { try { return parse(value); } catch (e) { @@ -222,7 +287,10 @@ function convertWKTStringToGeojson(value) { } } -export function geoShapeToGeometry(value, accumulator) { +export function geoShapeToGeometry( + value: string | ESGeometry | string[] | ESGeometry[] | undefined, + accumulator: Geometry[] +): void { if (!value) { return; } @@ -243,8 +311,9 @@ export function geoShapeToGeometry(value, accumulator) { value.type === GEO_JSON_TYPE.GEOMETRY_COLLECTION || value.type === 'geometrycollection' ) { - for (let i = 0; i < value.geometries.length; i++) { - geoShapeToGeometry(value.geometries[i], accumulator); + const geometryCollection = (value as unknown) as { geometries: ESGeometry[] }; + for (let i = 0; i < geometryCollection.geometries.length; i++) { + geoShapeToGeometry(geometryCollection.geometries[i], accumulator); } } else { const geoJson = convertESShapeToGeojsonGeometry(value); @@ -252,7 +321,7 @@ export function geoShapeToGeometry(value, accumulator) { } } -export function makeESBbox({ maxLat, maxLon, minLat, minLon }) { +export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBox { const bottom = clampToLatBounds(minLat); const top = clampToLatBounds(maxLat); let esBbox; @@ -280,11 +349,16 @@ export function makeESBbox({ maxLat, maxLon, minLat, minLon }) { return esBbox; } -export function createExtentFilter(mapExtent, geoFieldName) { - const boundingBox = makeESBbox(mapExtent); +export function createExtentFilter(mapExtent: MapExtent, geoFieldName: string): GeoFilter { return { geo_bounding_box: { - [geoFieldName]: boundingBox, + [geoFieldName]: makeESBbox(mapExtent), + }, + meta: { + alias: null, + disabled: false, + negate: false, + key: geoFieldName, }, }; } @@ -297,7 +371,15 @@ export function createSpatialFilterWithGeometry({ geoFieldName, geoFieldType, relation = ES_SPATIAL_RELATIONS.INTERSECTS, -}) { +}: { + preIndexedShape?: PreIndexedShape; + geometry: Polygon; + geometryLabel: string; + indexPatternId: string; + geoFieldName: string; + geoFieldType: ES_GEO_FIELD_TYPE; + relation: ES_SPATIAL_RELATIONS; +}): GeoFilter { ensureGeoField(geoFieldType); const isGeoPoint = geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT; @@ -307,15 +389,16 @@ export function createSpatialFilterWithGeometry({ defaultMessage: 'in', }) : getEsSpatialRelationLabel(relation); - const meta = { + const meta: FilterMeta = { type: SPATIAL_FILTER_TYPE, negate: false, index: indexPatternId, key: geoFieldName, alias: `${geoFieldName} ${relationLabel} ${geometryLabel}`, + disabled: false, }; - const shapeQuery = { + const shapeQuery: GeoShapeQueryBody = { // geo_shape query with geo_point field only supports intersects relation relation: isGeoPoint ? ES_SPATIAL_RELATIONS.INTERSECTS : relation, }; @@ -328,6 +411,9 @@ export function createSpatialFilterWithGeometry({ return { meta, + // Currently no way to create an object with exclude property from index signature + // typescript error for "ignore_unmapped is not assignable to type 'GeoShapeQueryBody'" expected" + // @ts-expect-error geo_shape: { ignore_unmapped: true, [geoFieldName]: shapeQuery, @@ -341,8 +427,14 @@ export function createDistanceFilterWithMeta({ geoFieldName, indexPatternId, point, -}) { - const meta = { +}: { + alias: string; + distanceKm: number; + geoFieldName: string; + indexPatternId: string; + point: Position; +}): GeoFilter { + const meta: FilterMeta = { type: SPATIAL_FILTER_TYPE, negate: false, index: indexPatternId, @@ -357,6 +449,7 @@ export function createDistanceFilterWithMeta({ pointLabel: point.join(', '), }, }), + disabled: false, }; return { @@ -368,7 +461,7 @@ export function createDistanceFilterWithMeta({ }; } -export function roundCoordinates(coordinates) { +export function roundCoordinates(coordinates: Coordinates): void { for (let i = 0; i < coordinates.length; i++) { const value = coordinates[i]; if (Array.isArray(value)) { @@ -382,10 +475,10 @@ export function roundCoordinates(coordinates) { /* * returns Polygon geometry where coordinates define a bounding box that contains the input geometry */ -export function getBoundingBoxGeometry(geometry) { +export function getBoundingBoxGeometry(geometry: Geometry): Polygon { ensureGeometryType(geometry.type, [GEO_JSON_TYPE.POLYGON]); - const exterior = geometry.coordinates[POLYGON_COORDINATES_EXTERIOR_INDEX]; + const exterior = (geometry as Polygon).coordinates[POLYGON_COORDINATES_EXTERIOR_INDEX]; const extent = { minLon: exterior[0][LON_INDEX], minLat: exterior[0][LAT_INDEX], @@ -402,7 +495,7 @@ export function getBoundingBoxGeometry(geometry) { return formatEnvelopeAsPolygon(extent); } -export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) { +export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }: MapExtent): Polygon { // GeoJSON mandates that the outer polygon must be counterclockwise to avoid ambiguous polygons // when the shape crosses the dateline const lonDelta = maxLon - minLon; @@ -410,25 +503,25 @@ export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) { const right = lonDelta > 360 ? 180 : maxLon; const top = clampToLatBounds(maxLat); const bottom = clampToLatBounds(minLat); - const topLeft = [left, top]; - const bottomLeft = [left, bottom]; - const bottomRight = [right, bottom]; - const topRight = [right, top]; + const topLeft = [left, top] as Position; + const bottomLeft = [left, bottom] as Position; + const bottomRight = [right, bottom] as Position; + const topRight = [right, top] as Position; return { type: GEO_JSON_TYPE.POLYGON, coordinates: [[topLeft, bottomLeft, bottomRight, topRight, topLeft]], - }; + } as Polygon; } -export function clampToLatBounds(lat) { +export function clampToLatBounds(lat: number): number { return clamp(lat, -89, 89); } -export function clampToLonBounds(lon) { +export function clampToLonBounds(lon: number): number { return clamp(lon, -180, 180); } -export function clamp(val, min, max) { +export function clamp(val: number, min: number, max: number): number { if (val > max) { return max; } else if (val < min) { @@ -438,25 +531,26 @@ export function clamp(val, min, max) { } } -export function extractFeaturesFromFilters(filters) { - const features = []; +export function extractFeaturesFromFilters(filters: GeoFilter[]): Feature[] { + const features: Feature[] = []; filters .filter((filter) => { return filter.meta.key && filter.meta.type === SPATIAL_FILTER_TYPE; }) .forEach((filter) => { + const geoFieldName = filter.meta.key!; let geometry; - if (filter.geo_distance && filter.geo_distance[filter.meta.key]) { + if (filter.geo_distance && filter.geo_distance[geoFieldName]) { const distanceSplit = filter.geo_distance.distance.split('km'); const distance = parseFloat(distanceSplit[0]); - const circleFeature = turfCircle(filter.geo_distance[filter.meta.key], distance); + const circleFeature = turfCircle(filter.geo_distance[geoFieldName], distance); geometry = circleFeature.geometry; } else if ( filter.geo_shape && - filter.geo_shape[filter.meta.key] && - filter.geo_shape[filter.meta.key].shape + filter.geo_shape[geoFieldName] && + filter.geo_shape[geoFieldName].shape ) { - geometry = filter.geo_shape[filter.meta.key].shape; + geometry = filter.geo_shape[geoFieldName].shape; } else { // do not know how to convert spatial filter to geometry // this includes pre-indexed shapes @@ -475,7 +569,7 @@ export function extractFeaturesFromFilters(filters) { return features; } -export function scaleBounds(bounds, scaleFactor) { +export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent { const width = bounds.maxLon - bounds.minLon; const height = bounds.maxLat - bounds.minLat; return { @@ -486,7 +580,7 @@ export function scaleBounds(bounds, scaleFactor) { }; } -export function turfBboxToBounds(turfBbox) { +export function turfBboxToBounds(turfBbox: BBox): MapExtent { return { minLon: turfBbox[0], minLat: turfBbox[1], diff --git a/x-pack/plugins/maps/common/i18n_getters.ts b/x-pack/plugins/maps/common/i18n_getters.ts index 0e1923bb26545..4e9537a12647f 100644 --- a/x-pack/plugins/maps/common/i18n_getters.ts +++ b/x-pack/plugins/maps/common/i18n_getters.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; -import { $Values } from '@kbn/utility-types'; import { ES_SPATIAL_RELATIONS } from './constants'; export function getAppTitle() { @@ -34,7 +33,7 @@ export function getUrlLabel() { }); } -export function getEsSpatialRelationLabel(spatialRelation: $Values) { +export function getEsSpatialRelationLabel(spatialRelation: ES_SPATIAL_RELATIONS) { switch (spatialRelation) { case ES_SPATIAL_RELATIONS.INTERSECTS: return i18n.translate('xpack.maps.common.esSpatialRelation.intersectsLabel', { diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index d3a4fa4101ac9..d795315acbf50 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -298,10 +298,12 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { this.getSource(), this.getCurrentStyle() ); + const source = this.getSource(); const canSkipFetch = await canSkipSourceUpdate({ - source: this.getSource(), + source, prevDataRequest: this.getDataRequest(dataRequestId), nextMeta: searchFilters, + extentAware: source.isFilterByMapBounds(), }); let activeSource; diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx index b2bb6a94197f0..01902d1fec89d 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx @@ -37,7 +37,8 @@ const defaultConfig = { function createLayer( layerOptions: Partial = {}, - sourceOptions: Partial = {} + sourceOptions: Partial = {}, + isTimeAware: boolean = false ): TiledVectorLayer { const sourceDescriptor: TiledSingleLayerVectorSourceDescriptor = { type: SOURCE_TYPES.MVT_SINGLE_LAYER, @@ -47,6 +48,14 @@ function createLayer( ...sourceOptions, }; const mvtSource = new MVTSingleLayerVectorSource(sourceDescriptor); + if (isTimeAware) { + mvtSource.isTimeAware = async () => { + return true; + }; + mvtSource.getApplyGlobalTime = () => { + return true; + }; + } const defaultLayerOptions = { ...layerOptions, @@ -107,62 +116,75 @@ describe('syncData', () => { }); it('Should not resync when no changes to source params', async () => { - const layer1: TiledVectorLayer = createLayer({}, {}); - const syncContext1 = new MockSyncContext({ dataFilters: {} }); - - await layer1.syncData(syncContext1); - const dataRequestDescriptor: DataRequestDescriptor = { data: { ...defaultConfig }, dataId: 'source', }; - const layer2: TiledVectorLayer = createLayer( + const layer: TiledVectorLayer = createLayer( { __dataRequests: [dataRequestDescriptor], }, {} ); - const syncContext2 = new MockSyncContext({ dataFilters: {} }); - await layer2.syncData(syncContext2); + const syncContext = new MockSyncContext({ dataFilters: {} }); + await layer.syncData(syncContext); // @ts-expect-error - sinon.assert.notCalled(syncContext2.startLoading); + sinon.assert.notCalled(syncContext.startLoading); // @ts-expect-error - sinon.assert.notCalled(syncContext2.stopLoading); + sinon.assert.notCalled(syncContext.stopLoading); + }); + + it('Should resync when changes to syncContext', async () => { + const dataRequestDescriptor: DataRequestDescriptor = { + data: { ...defaultConfig }, + dataId: 'source', + }; + const layer: TiledVectorLayer = createLayer( + { + __dataRequests: [dataRequestDescriptor], + }, + {}, + true + ); + const syncContext = new MockSyncContext({ + dataFilters: { + timeFilters: { + from: 'now', + to: '30m', + mode: 'relative', + }, + }, + }); + await layer.syncData(syncContext); + // @ts-expect-error + sinon.assert.calledOnce(syncContext.startLoading); + // @ts-expect-error + sinon.assert.calledOnce(syncContext.stopLoading); }); describe('Should resync when changes to source params: ', () => { - [ - { layerName: 'barfoo' }, - { urlTemplate: 'https://sub.example.com/{z}/{x}/{y}.pbf' }, - { minSourceZoom: 1 }, - { maxSourceZoom: 12 }, - ].forEach((changes) => { + [{ layerName: 'barfoo' }, { minSourceZoom: 1 }, { maxSourceZoom: 12 }].forEach((changes) => { it(`change in ${Object.keys(changes).join(',')}`, async () => { - const layer1: TiledVectorLayer = createLayer({}, {}); - const syncContext1 = new MockSyncContext({ dataFilters: {} }); - - await layer1.syncData(syncContext1); - const dataRequestDescriptor: DataRequestDescriptor = { data: defaultConfig, dataId: 'source', }; - const layer2: TiledVectorLayer = createLayer( + const layer: TiledVectorLayer = createLayer( { __dataRequests: [dataRequestDescriptor], }, changes ); - const syncContext2 = new MockSyncContext({ dataFilters: {} }); - await layer2.syncData(syncContext2); + const syncContext = new MockSyncContext({ dataFilters: {} }); + await layer.syncData(syncContext); // @ts-expect-error - sinon.assert.calledOnce(syncContext2.startLoading); + sinon.assert.calledOnce(syncContext.startLoading); // @ts-expect-error - sinon.assert.calledOnce(syncContext2.stopLoading); + sinon.assert.calledOnce(syncContext.stopLoading); // @ts-expect-error - const call = syncContext2.stopLoading.getCall(0); + const call = syncContext.stopLoading.getCall(0); expect(call.args[2]).toEqual({ ...defaultConfig, ...changes }); }); }); 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 477b17ae03d7b..f2fe916953801 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 @@ -23,6 +23,7 @@ import { VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; import { MVTSingleLayerVectorSourceConfig } from '../../sources/mvt_single_layer_vector_source/types'; +import { canSkipSourceUpdate } from '../../util/can_skip_fetch'; export class TiledVectorLayer extends VectorLayer { static type = LAYER_TYPE.TILED_VECTOR; @@ -68,18 +69,22 @@ export class TiledVectorLayer extends VectorLayer { this._style as IVectorStyle ); const prevDataRequest = this.getSourceDataRequest(); - - const templateWithMeta = await this._source.getUrlTemplateWithMeta(searchFilters); + const dataRequest = await this._source.getUrlTemplateWithMeta(searchFilters); if (prevDataRequest) { const data: MVTSingleLayerVectorSourceConfig = prevDataRequest.getData() as MVTSingleLayerVectorSourceConfig; if (data) { - const canSkipBecauseNoChanges = + const noChangesInSourceState: boolean = data.layerName === this._source.getLayerName() && data.minSourceZoom === this._source.getMinZoom() && - data.maxSourceZoom === this._source.getMaxZoom() && - data.urlTemplate === templateWithMeta.urlTemplate; - - if (canSkipBecauseNoChanges) { + data.maxSourceZoom === this._source.getMaxZoom(); + const noChangesInSearchState: boolean = await canSkipSourceUpdate({ + extentAware: false, // spatial extent knowledge is already fully automated by tile-loading based on pan-zooming + source: this.getSource(), + prevDataRequest, + nextMeta: searchFilters, + }); + const canSkip = noChangesInSourceState && noChangesInSearchState; + if (canSkip) { return null; } } @@ -87,7 +92,7 @@ export class TiledVectorLayer extends VectorLayer { startLoading(SOURCE_DATA_REQUEST_ID, requestToken, searchFilters); try { - stopLoading(SOURCE_DATA_REQUEST_ID, requestToken, templateWithMeta, {}); + stopLoading(SOURCE_DATA_REQUEST_ID, requestToken, dataRequest, {}); } catch (error) { onLoadError(SOURCE_DATA_REQUEST_ID, requestToken, error.message); } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx index 91bdd74c158f9..e49339b6250b4 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx @@ -73,6 +73,7 @@ export async function syncVectorSource({ source, prevDataRequest, nextMeta: requestMeta, + extentAware: source.isFilterByMapBounds(), }); if (canSkipFetch) { return { diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index b21bff9922671..104d0b56578d1 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -332,6 +332,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { source: joinSource, prevDataRequest, nextMeta: searchFilters, + extentAware: false, // join-sources are term-aggs that are spatially unaware (e.g. ESTermSource/TableSource). }); if (canSkipFetch) { return { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index 6696140a5d852..5ac487c713173 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -206,6 +206,12 @@ describe('ESGeoGridSource', () => { expect(getProperty('filter')).toEqual([ { geo_bounding_box: { bar: { bottom_right: [180, -82.67628], top_left: [-180, 82.67628] } }, + meta: { + alias: null, + disabled: false, + key: 'bar', + negate: false, + }, }, ]); expect(getProperty('aggs')).toEqual({ @@ -277,7 +283,7 @@ describe('ESGeoGridSource', () => { expect(urlTemplateWithMeta.minSourceZoom).toBe(0); expect(urlTemplateWithMeta.maxSourceZoom).toBe(24); expect(urlTemplateWithMeta.urlTemplate).toBe( - "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" + "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628))),meta:(alias:!n,disabled:!f,key:bar,negate:!f)))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" ); }); @@ -288,7 +294,7 @@ describe('ESGeoGridSource', () => { }); expect(urlTemplateWithMeta.urlTemplate).toBe( - "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1" + "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628))),meta:(alias:!n,disabled:!f,key:bar,negate:!f)))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1" ); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 785b00c06dd54..b3ecdbf51f3c3 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -14,7 +14,12 @@ import { IFieldType, IndexPattern } from 'src/plugins/data/public'; import { GeoJsonProperties } from 'geojson'; import { AbstractESSource } from '../es_source'; import { getHttp, getSearchService } from '../../../kibana_services'; -import { addFieldToDSL, getField, hitsToGeoJson } from '../../../../common/elasticsearch_util'; +import { + addFieldToDSL, + getField, + hitsToGeoJson, + PreIndexedShape, +} from '../../../../common/elasticsearch_util'; // @ts-expect-error import { UpdateSourceEditor } from './update_source_editor'; @@ -43,7 +48,7 @@ import { VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; -import { ImmutableSourceProperty, PreIndexedShape, SourceEditorArgs } from '../source'; +import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { IField } from '../../fields/field'; import { GeoJsonWithMeta, diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 6b99f1f8860c0..0936cdc50b4c0 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -238,7 +238,6 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource : searchFilters.buffer; const extentFilter = createExtentFilter(buffer, geoField.name); - // @ts-expect-error allFilters.push(extentFilter); } if (searchFilters.applyGlobalTime && (await this.isTimeAware())) { diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index d9eda86428701..7c2aaf714c34e 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -18,6 +18,7 @@ import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; import { AbstractSourceDescriptor } from '../../../common/descriptor_types'; import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view'; import { LICENSED_FEATURES } from '../../licensed_features'; +import { PreIndexedShape } from '../../../common/elasticsearch_util'; export type SourceEditorArgs = { onChange: (...args: OnSourceChangeArgs[]) => void; @@ -35,12 +36,6 @@ export type Attribution = { label: string; }; -export type PreIndexedShape = { - index: string; - id: string | number; - path: string; -}; - export interface ISource { destroy(): void; getDisplayName(): Promise; diff --git a/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts index 961ca88719488..5f81a74ab03ce 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts @@ -29,7 +29,7 @@ export interface FeatureGeometry { } export interface RenderTooltipContentParams { - addFilters(filter: object): void; + addFilters(filter: object, actionId: string): void; closeTooltip(): void; features: TooltipFeature[]; isLocked: boolean; diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js index ce58cb7d019ed..1901b15e8f350 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js @@ -135,6 +135,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(true); @@ -154,6 +155,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(true); @@ -173,6 +175,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(false); @@ -189,6 +192,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(false); @@ -219,6 +223,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(false); @@ -238,6 +243,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(false); @@ -257,6 +263,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(false); @@ -273,6 +280,7 @@ describe('canSkipSourceUpdate', () => { source: queryAwareSourceMock, prevDataRequest, nextMeta, + extentAware: queryAwareSourceMock.isFilterByMapBounds(), }); expect(canSkipUpdate).toBe(false); diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts index 1b2fae413d909..575c99432f508 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts @@ -55,14 +55,15 @@ export async function canSkipSourceUpdate({ source, prevDataRequest, nextMeta, + extentAware, }: { source: ISource; prevDataRequest: DataRequest | undefined; nextMeta: DataMeta; + extentAware: boolean; }): Promise { const timeAware = await source.isTimeAware(); const refreshTimerAware = await source.isRefreshTimerAware(); - const extentAware = source.isFilterByMapBounds(); const isFieldAware = source.isFieldAware(); const isQueryAware = source.isQueryAware(); const isGeoGridPrecisionAware = source.isGeoGridPrecisionAware(); @@ -132,11 +133,12 @@ export async function canSkipSourceUpdate({ } let updateDueToPrecisionChange = false; + let updateDueToExtentChange = false; + if (isGeoGridPrecisionAware) { updateDueToPrecisionChange = !_.isEqual(prevMeta.geogridPrecision, nextMeta.geogridPrecision); } - let updateDueToExtentChange = false; if (extentAware) { updateDueToExtentChange = updateDueToExtent(prevMeta, nextMeta); } diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 390a8eebfad58..622aeae3cbb87 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -37,7 +37,7 @@ import 'mapbox-gl/dist/mapbox-gl.css'; const RENDER_COMPLETE_EVENT = 'renderComplete'; export interface Props { - addFilters: ((filters: Filter[]) => Promise) | null; + addFilters: ((filters: Filter[], actionId: string) => Promise) | null; getFilterActions?: () => Promise; getActionContext?: () => ActionExecutionContext; onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts index a4b076c0dd7f0..f0df797582bef 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts @@ -11,12 +11,17 @@ import turfDistance from '@turf/distance'; // @ts-expect-error import turfCircle from '@turf/circle'; +import { Position } from 'geojson'; + +export interface DrawCircleProperties { + center: Position; + radiusKm: number; +} type DrawCircleState = { circle: { - properties: { - center: {} | null; - radiusKm: number; + properties: Omit & { + center: Position | null; }; id: string | number; incomingCoords: (coords: unknown[]) => void; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx similarity index 63% rename from x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx index aaec7ecb2b34f..f68875dc81394 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx @@ -6,11 +6,18 @@ */ import _ from 'lodash'; -import React from 'react'; -import { DRAW_TYPE } from '../../../../common/constants'; +import React, { Component } from 'react'; +// @ts-expect-error import MapboxDraw from '@mapbox/mapbox-gl-draw'; +// @ts-expect-error import DrawRectangle from 'mapbox-gl-draw-rectangle-mode'; -import { DrawCircle } from './draw_circle'; +import { Map as MbMap } from 'mapbox-gl'; +import { i18n } from '@kbn/i18n'; +import { Filter } from 'src/plugins/data/public'; +import { Feature, Polygon } from 'geojson'; +import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../../../common/constants'; +import { DrawState } from '../../../../common/descriptor_types'; +import { DrawCircle, DrawCircleProperties } from './draw_circle'; import { createDistanceFilterWithMeta, createSpatialFilterWithGeometry, @@ -18,6 +25,7 @@ import { roundCoordinates, } from '../../../../common/elasticsearch_util'; import { DrawTooltip } from './draw_tooltip'; +import { getToasts } from '../../../kibana_services'; const DRAW_RECTANGLE = 'draw_rectangle'; const DRAW_CIRCLE = 'draw_circle'; @@ -26,15 +34,21 @@ const mbDrawModes = MapboxDraw.modes; mbDrawModes[DRAW_RECTANGLE] = DrawRectangle; mbDrawModes[DRAW_CIRCLE] = DrawCircle; -export class DrawControl extends React.Component { - constructor() { - super(); - this._mbDrawControl = new MapboxDraw({ - displayControlsDefault: false, - modes: mbDrawModes, - }); - this._mbDrawControlAdded = false; - } +export interface Props { + addFilters: (filters: Filter[], actionId: string) => Promise; + disableDrawState: () => void; + drawState?: DrawState; + isDrawingFilter: boolean; + mbMap: MbMap; +} + +export class DrawControl extends Component { + private _isMounted = false; + private _mbDrawControlAdded = false; + private _mbDrawControl = new MapboxDraw({ + displayControlsDefault: false, + modes: mbDrawModes, + }); componentDidUpdate() { this._syncDrawControl(); @@ -63,14 +77,19 @@ export class DrawControl extends React.Component { } }, 0); - _onDraw = async (e) => { - if (!e.features.length) { + _onDraw = async (e: { features: Feature[] }) => { + if ( + !e.features.length || + !this.props.drawState || + !this.props.drawState.geoFieldName || + !this.props.drawState.indexPatternId + ) { return; } - let filter; + let filter: Filter | undefined; if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) { - const circle = e.features[0]; + const circle = e.features[0] as Feature & { properties: DrawCircleProperties }; const distanceKm = _.round( circle.properties.radiusKm, circle.properties.radiusKm > 10 ? 0 : 2 @@ -85,7 +104,7 @@ export class DrawControl extends React.Component { precision = 3; } filter = createDistanceFilterWithMeta({ - alias: this.props.drawState.filterLabel, + alias: this.props.drawState.filterLabel ? this.props.drawState.filterLabel : '', distanceKm, geoFieldName: this.props.drawState.geoFieldName, indexPatternId: this.props.drawState.indexPatternId, @@ -95,7 +114,7 @@ export class DrawControl extends React.Component { ], }); } else { - const geometry = e.features[0].geometry; + const geometry = e.features[0].geometry as Polygon; // MapboxDraw returns coordinates with 12 decimals. Round to a more reasonable number roundCoordinates(geometry.coordinates); @@ -106,24 +125,34 @@ export class DrawControl extends React.Component { : geometry, indexPatternId: this.props.drawState.indexPatternId, geoFieldName: this.props.drawState.geoFieldName, - geoFieldType: this.props.drawState.geoFieldType, - geometryLabel: this.props.drawState.geometryLabel, - relation: this.props.drawState.relation, + geoFieldType: this.props.drawState.geoFieldType + ? this.props.drawState.geoFieldType + : ES_GEO_FIELD_TYPE.GEO_POINT, + geometryLabel: this.props.drawState.geometryLabel ? this.props.drawState.geometryLabel : '', + relation: this.props.drawState.relation + ? this.props.drawState.relation + : ES_SPATIAL_RELATIONS.INTERSECTS, }); } try { - await this.props.addFilters([filter], this.props.drawState.actionId); + await this.props.addFilters([filter!], this.props.drawState.actionId); } catch (error) { - // TODO notify user why filter was not created - console.error(error); + getToasts().addWarning( + i18n.translate('xpack.maps.drawControl.unableToCreatFilter', { + defaultMessage: `Unable to create filter, error: '{errorMsg}'.`, + values: { + errorMsg: error.message, + }, + }) + ); } finally { this.props.disableDrawState(); } }; _removeDrawControl() { - if (!this.props.mbMap || !this._mbDrawControlAdded) { + if (!this._mbDrawControlAdded) { return; } @@ -134,7 +163,7 @@ export class DrawControl extends React.Component { } _updateDrawControl() { - if (!this.props.mbMap) { + if (!this.props.drawState) { return; } @@ -159,7 +188,7 @@ export class DrawControl extends React.Component { } render() { - if (!this.props.mbMap || !this.props.isDrawingFilter) { + if (!this.props.isDrawingFilter || !this.props.drawState) { return null; } diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx similarity index 84% rename from x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx index 01e90d8e2daf4..099f409c91c21 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx @@ -6,25 +6,35 @@ */ import _ from 'lodash'; -import React, { Component } from 'react'; +import React, { Component, RefObject } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { Map as MbMap } from 'mapbox-gl'; import { DRAW_TYPE } from '../../../../common/constants'; +import { DrawState } from '../../../../common/descriptor_types'; const noop = () => {}; -export class DrawTooltip extends Component { - state = { +interface Props { + mbMap: MbMap; + drawState: DrawState; +} + +interface State { + x?: number; + y?: number; + isOpen: boolean; +} + +export class DrawTooltip extends Component { + private readonly _popoverRef: RefObject = React.createRef(); + + state: State = { x: undefined, y: undefined, isOpen: false, }; - constructor(props) { - super(props); - this._popoverRef = React.createRef(); - } - componentDidMount() { this.props.mbMap.on('mousemove', this._updateTooltipLocation); this.props.mbMap.on('mouseout', this._hideTooltip); @@ -43,6 +53,10 @@ export class DrawTooltip extends Component { } render() { + if (this.state.x === undefined || this.state.y === undefined) { + return null; + } + let instructions; if (this.props.drawState.drawType === DRAW_TYPE.BOUNDS) { instructions = i18n.translate('xpack.maps.drawTooltip.boundsInstructions', { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.ts similarity index 63% rename from x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.ts index 9324ebc0c9fe9..cc2f560c63d24 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.ts @@ -5,19 +5,22 @@ * 2.0. */ +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; import { DrawControl } from './draw_control'; import { updateDrawState } from '../../../actions'; import { getDrawState, isDrawingFilter } from '../../../selectors/map_selectors'; +import { MapStoreState } from '../../../reducers/store'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { isDrawingFilter: isDrawingFilter(state), drawState: getDrawState(state), }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: ThunkDispatch) { return { disableDrawState() { dispatch(updateDrawState(null)); @@ -25,5 +28,5 @@ function mapDispatchToProps(dispatch) { }; } -const connectedDrawControl = connect(mapStateToProps, mapDispatchToProps)(DrawControl); -export { connectedDrawControl as DrawControl }; +const connected = connect(mapStateToProps, mapDispatchToProps)(DrawControl); +export { connected as DrawControl }; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js index fa07460a584a7..3950c6ef124be 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js @@ -9,6 +9,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { URL_MAX_LENGTH } from '../../../../../../../src/core/public'; +import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../../../src/plugins/data/public'; import { createSpatialFilterWithGeometry } from '../../../../common/elasticsearch_util'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; @@ -90,7 +91,7 @@ export class FeatureGeometryFilterForm extends Component { return; } - this.props.addFilters([filter]); + this.props.addFilters([filter], ACTION_GLOBAL_APPLY_FILTER); this.props.onClose(); }; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js index 996c530dcae94..2bd1d5c9cacf5 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js @@ -192,7 +192,7 @@ export class FeatureProperties extends React.Component { onClick={async () => { this.props.onCloseTooltip(); const filters = await tooltipProperty.getESFilters(); - this.props.addFilters(filters); + this.props.addFilters(filters, ACTION_GLOBAL_APPLY_FILTER); }} aria-label={i18n.translate('xpack.maps.tooltip.filterOnPropertyAriaLabel', { defaultMessage: 'Filter on property', diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 5dbe2f97ee6d3..fae89a0484f11 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -17,7 +17,6 @@ import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { Adapters } from 'src/plugins/inspector/public'; import { Filter } from 'src/plugins/data/public'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; -// @ts-expect-error import { DrawControl } from './draw_control'; import { ScaleControl } from './scale_control'; // @ts-expect-error @@ -67,7 +66,7 @@ export interface Props { clearMouseCoordinates: () => void; clearGoto: () => void; setMapInitError: (errorMessage: string) => void; - addFilters: ((filters: Filter[]) => Promise) | null; + addFilters: ((filters: Filter[], actionId: string) => Promise) | null; getFilterActions?: () => Promise; getActionContext?: () => ActionExecutionContext; onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void; @@ -418,7 +417,9 @@ export class MBMap extends Component { let tooltipControl; let scaleControl; if (this.state.mbMap) { - drawControl = ; + drawControl = this.props.addFilters ? ( + + ) : null; tooltipControl = !this.props.settings.disableTooltipControl ? ( ): GeoJsonProperties { - const flat: GeoJsonProperties = {}; +export function flattenHit( + geometryField: string, + hit: Record +): Record { + const flat: Record = {}; if (hit) { flattenSource(flat, '', hit._source as Record, geometryField); if (hit.fields) { @@ -30,11 +32,11 @@ export function flattenHit(geometryField: string, hit: Record): } function flattenSource( - accum: GeoJsonProperties, + accum: Record, path: string, properties: Record = {}, geometryField: string -): GeoJsonProperties { +): Record { accum = accum || {}; for (const key in properties) { if (properties.hasOwnProperty(key)) { @@ -58,7 +60,7 @@ function flattenSource( return accum; } -function flattenFields(accum: GeoJsonProperties = {}, fields: Array>) { +function flattenFields(accum: Record = {}, fields: Array>) { accum = accum || {}; for (const key in fields) { if (fields.hasOwnProperty(key)) { diff --git a/x-pack/plugins/ml/common/types/modules.ts b/x-pack/plugins/ml/common/types/modules.ts index 7c9623d3e68ec..617000d017025 100644 --- a/x-pack/plugins/ml/common/types/modules.ts +++ b/x-pack/plugins/ml/common/types/modules.ts @@ -16,6 +16,7 @@ export interface ModuleJob { export interface ModuleDatafeed { id: string; + job_id: string; config: Omit; } @@ -48,7 +49,8 @@ export interface Module { title: string; description: string; type: string; - logoFile: string; + logoFile?: string; + logo?: Logo; defaultIndexPattern: string; query: any; jobs: ModuleJob[]; @@ -56,6 +58,18 @@ export interface Module { kibana: KibanaObjects; } +export interface FileBasedModule extends Omit { + jobs: Array<{ file: string; id: string }>; + datafeeds: Array<{ file: string; job_id: string; id: string }>; + kibana: { + search: Array<{ file: string; id: string }>; + visualization: Array<{ file: string; id: string }>; + dashboard: Array<{ file: string; id: string }>; + }; +} + +export type Logo = { icon: string } | null; + export interface ResultItem { id: string; success?: boolean; diff --git a/x-pack/plugins/ml/common/types/saved_objects.ts b/x-pack/plugins/ml/common/types/saved_objects.ts index f40eefa2167c9..c90707d39ab14 100644 --- a/x-pack/plugins/ml/common/types/saved_objects.ts +++ b/x-pack/plugins/ml/common/types/saved_objects.ts @@ -7,6 +7,7 @@ export type JobType = 'anomaly-detector' | 'data-frame-analytics'; export const ML_SAVED_OBJECT_TYPE = 'ml-job'; +export const ML_MODULE_SAVED_OBJECT_TYPE = 'ml-module'; export interface SavedObjectResult { [jobId: string]: { success: boolean; error?: any }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/failures.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/failures.tsx new file mode 100644 index 0000000000000..498320b1b792d --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/failures.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { Component } from 'react'; + +import { EuiAccordion, EuiPagination } from '@elastic/eui'; + +const PAGE_SIZE = 100; + +export interface DocFailure { + item: number; + reason: string; + doc: { + message: string; + }; +} + +interface Props { + failedDocs: DocFailure[]; +} + +interface State { + page: number; +} + +export class Failures extends Component { + state: State = { page: 0 }; + + _renderPaginationControl() { + return this.props.failedDocs.length > PAGE_SIZE ? ( + this.setState({ page })} + compressed + /> + ) : null; + } + + render() { + const lastDocIndex = this.props.failedDocs.length - 1; + const startIndex = this.state.page * PAGE_SIZE; + const endIndex = startIndex + PAGE_SIZE > lastDocIndex ? lastDocIndex : startIndex + PAGE_SIZE; + return ( + + } + paddingSize="m" + > +
+ {this._renderPaginationControl()} + {this.props.failedDocs.slice(startIndex, endIndex).map(({ item, reason, doc }) => ( +
+
+ {item}: {reason} +
+
{JSON.stringify(doc)}
+
+ ))} +
+
+ ); + } +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx index 46a79044ee5e9..7fa71193ee516 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx @@ -8,7 +8,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; -import { EuiSpacer, EuiDescriptionList, EuiCallOut, EuiAccordion } from '@elastic/eui'; +import { EuiSpacer, EuiDescriptionList, EuiCallOut } from '@elastic/eui'; +import { DocFailure, Failures } from './failures'; interface Props { index: string; @@ -20,14 +21,6 @@ interface Props { createPipeline: boolean; } -interface DocFailure { - item: number; - reason: string; - doc: { - message: string; - }; -} - export const ImportSummary: FC = ({ index, indexPattern, @@ -96,36 +89,6 @@ export const ImportSummary: FC = ({ ); }; -interface FailuresProps { - failedDocs: DocFailure[]; -} - -const Failures: FC = ({ failedDocs }) => { - return ( - - } - paddingSize="m" - > -
- {failedDocs.map(({ item, reason, doc }) => ( -
-
- {item}: {reason} -
-
{JSON.stringify(doc)}
-
- ))} -
-
- ); -}; - function createDisplayItems( index: string, indexPattern: string, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index a1882d8f65f82..cf55954110bed 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -124,8 +124,9 @@ export const Page: FC = () => { ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, restorableDefaults ); + const [currentSavedSearch, setCurrentSavedSearch] = useState(mlContext.currentSavedSearch); - const { combinedQuery, currentIndexPattern, currentSavedSearch, kibanaConfig } = mlContext; + const { combinedQuery, currentIndexPattern, kibanaConfig } = mlContext; const timefilter = useTimefilter({ timeRangeSelector: currentIndexPattern.timeFieldName !== undefined, autoRefreshSelector: true, @@ -193,6 +194,12 @@ export const Page: FC = () => { searchString: Query['query']; queryLanguage: SearchQueryLanguage; }) => { + // When the user loads saved search and then clear or modify the query + // we should remove the saved search and replace it with the index pattern id + if (currentSavedSearch !== null) { + setCurrentSavedSearch(null); + } + setDataVisualizerListState({ ...dataVisualizerListState, searchQuery: searchParams.searchQuery, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/description.tsx index cd594d25578cd..e577587b01baf 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/description.tsx @@ -25,7 +25,7 @@ export const Description: FC = memo(({ children, validation }) => { description={ } > diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index a1fac92d45b4e..4e99330610fca 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -9,6 +9,7 @@ import fs from 'fs'; import Boom from '@hapi/boom'; import numeral from '@elastic/numeral'; import { KibanaRequest, IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; + import moment from 'moment'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { merge } from 'lodash'; @@ -16,12 +17,15 @@ import { AnalysisLimits } from '../../../common/types/anomaly_detection_jobs'; import { getAuthorizationHeader } from '../../lib/request_authorization'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; import type { MlClient } from '../../lib/ml_client'; +import { ML_MODULE_SAVED_OBJECT_TYPE } from '../../../common/types/saved_objects'; import { KibanaObjects, KibanaObjectConfig, ModuleDatafeed, ModuleJob, Module, + FileBasedModule, + Logo, JobOverride, DatafeedOverride, GeneralJobsOverride, @@ -45,7 +49,10 @@ import { jobServiceProvider } from '../job_service'; import { resultsServiceProvider } from '../results_service'; import { JobExistResult, JobStat } from '../../../common/types/data_recognizer'; import { MlJobsStatsResponse } from '../../../common/types/job_service'; +import { Datafeed } from '../../../common/types/anomaly_detection_jobs'; import { JobSavedObjectService } from '../../saved_objects'; +import { isDefined } from '../../../common/types/guards'; +import { isPopulatedObject } from '../../../common/util/object_utils'; const ML_DIR = 'ml'; const KIBANA_DIR = 'kibana'; @@ -57,26 +64,18 @@ export const SAVED_OBJECT_TYPES = { VISUALIZATION: 'visualization', }; -interface RawModuleConfig { - id: string; - title: string; - description: string; - type: string; - logoFile: string; - defaultIndexPattern: string; - query: any; - jobs: Array<{ file: string; id: string }>; - datafeeds: Array<{ file: string; job_id: string; id: string }>; - kibana: { - search: Array<{ file: string; id: string }>; - visualization: Array<{ file: string; id: string }>; - dashboard: Array<{ file: string; id: string }>; - }; +function isModule(arg: unknown): arg is Module { + return isPopulatedObject(arg) && Array.isArray(arg.jobs) && arg.jobs[0]?.config !== undefined; +} + +function isFileBasedModule(arg: unknown): arg is FileBasedModule { + return isPopulatedObject(arg) && Array.isArray(arg.jobs) && arg.jobs[0]?.file !== undefined; } interface Config { - dirName: any; - json: RawModuleConfig; + dirName?: string; + module: FileBasedModule | Module; + isSavedObject: boolean; } export interface RecognizeResult { @@ -84,7 +83,7 @@ export interface RecognizeResult { title: string; query: any; description: string; - logo: { icon: string } | null; + logo: Logo; } interface ObjectExistResult { @@ -125,7 +124,7 @@ export class DataRecognizer { /** * List of the module jobs that require model memory estimation */ - jobsForModelMemoryEstimation: Array<{ job: ModuleJob; query: any }> = []; + private _jobsForModelMemoryEstimation: Array<{ job: ModuleJob; query: any }> = []; constructor( mlClusterClient: IScopedClusterClient, @@ -146,7 +145,7 @@ export class DataRecognizer { } // list all directories under the given directory - async listDirs(dirName: string): Promise { + private async _listDirs(dirName: string): Promise { const dirs: string[] = []; return new Promise((resolve, reject) => { fs.readdir(dirName, (err, fileNames) => { @@ -164,7 +163,7 @@ export class DataRecognizer { }); } - async readFile(fileName: string): Promise { + private async _readFile(fileName: string): Promise { return new Promise((resolve, reject) => { fs.readFile(fileName, 'utf-8', (err, content) => { if (err) { @@ -176,14 +175,14 @@ export class DataRecognizer { }); } - async loadManifestFiles(): Promise { + private async _loadConfigs(): Promise { const configs: Config[] = []; - const dirs = await this.listDirs(this._modulesDir); + const dirs = await this._listDirs(this._modulesDir); await Promise.all( dirs.map(async (dir) => { let file: string | undefined; try { - file = await this.readFile(`${this._modulesDir}/${dir}/manifest.json`); + file = await this._readFile(`${this._modulesDir}/${dir}/manifest.json`); } catch (error) { mlLog.warn(`Data recognizer skipping folder ${dir} as manifest.json cannot be read`); } @@ -192,7 +191,8 @@ export class DataRecognizer { try { configs.push({ dirName: dir, - json: JSON.parse(file), + module: JSON.parse(file), + isSavedObject: false, }); } catch (error) { mlLog.warn(`Data recognizer error parsing ${dir}/manifest.json. ${error}`); @@ -201,26 +201,40 @@ export class DataRecognizer { }) ); - return configs; + const savedObjectConfigs = (await this._loadSavedObjectModules()).map((module) => ({ + module, + isSavedObject: true, + })); + + return [...configs, ...savedObjectConfigs]; + } + + private async _loadSavedObjectModules() { + const jobs = await this._savedObjectsClient.find({ + type: ML_MODULE_SAVED_OBJECT_TYPE, + perPage: 10000, + }); + + return jobs.saved_objects.map((o) => o.attributes); } // get the manifest.json file for a specified id, e.g. "nginx" - async getManifestFile(id: string) { - const manifestFiles = await this.loadManifestFiles(); - return manifestFiles.find((i) => i.json.id === id); + private async _findConfig(id: string) { + const configs = await this._loadConfigs(); + return configs.find((i) => i.module.id === id); } // called externally by an endpoint - async findMatches(indexPattern: string): Promise { - const manifestFiles = await this.loadManifestFiles(); + public async findMatches(indexPattern: string): Promise { + const manifestFiles = await this._loadConfigs(); const results: RecognizeResult[] = []; await Promise.all( manifestFiles.map(async (i) => { - const moduleConfig = i.json; + const moduleConfig = i.module; let match = false; try { - match = await this.searchForFields(moduleConfig, indexPattern); + match = await this._searchForFields(moduleConfig, indexPattern); } catch (error) { mlLog.warn( `Data recognizer error running query defined for module ${moduleConfig.id}. ${error}` @@ -228,13 +242,15 @@ export class DataRecognizer { } if (match === true) { - let logo = null; - if (moduleConfig.logoFile) { + let logo: Logo = null; + if (moduleConfig.logo) { + logo = moduleConfig.logo; + } else if (moduleConfig.logoFile) { try { - logo = await this.readFile( + const logoFile = await this._readFile( `${this._modulesDir}/${i.dirName}/${moduleConfig.logoFile}` ); - logo = JSON.parse(logo); + logo = JSON.parse(logoFile); } catch (e) { logo = null; } @@ -255,7 +271,7 @@ export class DataRecognizer { return results; } - async searchForFields(moduleConfig: RawModuleConfig, indexPattern: string) { + private async _searchForFields(moduleConfig: FileBasedModule | Module, indexPattern: string) { if (moduleConfig.query === undefined) { return false; } @@ -275,29 +291,34 @@ export class DataRecognizer { return body.hits.total.value > 0; } - async listModules() { - const manifestFiles = await this.loadManifestFiles(); - const ids = manifestFiles.map(({ json }) => json.id).sort((a, b) => a.localeCompare(b)); // sort as json files are read from disk and could be in any order. + public async listModules() { + const manifestFiles = await this._loadConfigs(); + manifestFiles.sort((a, b) => a.module.id.localeCompare(b.module.id)); // sort as json files are read from disk and could be in any order. - const modules = []; - for (let i = 0; i < ids.length; i++) { - const module = await this.getModule(ids[i]); - modules.push(module); + const configs: Array = []; + for (const config of manifestFiles) { + if (config.isSavedObject) { + configs.push(config.module); + } else { + configs.push(await this.getModule(config.module.id)); + } } - return modules; + // casting return as Module[] so not to break external plugins who rely on this function + // once FileBasedModules are removed this function will only deal with Modules + return configs as Module[]; } // called externally by an endpoint // supplying an optional prefix will add the prefix // to the job and datafeed configs - async getModule(id: string, prefix = ''): Promise { - let manifestJSON: RawModuleConfig | null = null; + public async getModule(id: string, prefix = ''): Promise { + let module: FileBasedModule | Module | null = null; let dirName: string | null = null; - const manifestFile = await this.getManifestFile(id); - if (manifestFile !== undefined) { - manifestJSON = manifestFile.json; - dirName = manifestFile.dirName; + const config = await this._findConfig(id); + if (config !== undefined) { + module = config.module; + dirName = config.dirName ?? null; } else { throw Boom.notFound(`Module with the id "${id}" not found`); } @@ -306,81 +327,102 @@ export class DataRecognizer { const datafeeds: ModuleDatafeed[] = []; const kibana: KibanaObjects = {}; // load all of the job configs - await Promise.all( - manifestJSON.jobs.map(async (job) => { + if (isModule(module)) { + const tempJobs: ModuleJob[] = module.jobs.map((j) => ({ + id: `${prefix}${j.id}`, + config: j.config, + })); + jobs.push(...tempJobs); + const tempDatafeeds: ModuleDatafeed[] = module.datafeeds.map((d) => { + const jobId = `${prefix}${d.job_id}`; + return { + id: prefixDatafeedId(d.id, prefix), + job_id: jobId, + config: { + ...d.config, + job_id: jobId, + }, + }; + }); + datafeeds.push(...tempDatafeeds); + } else if (isFileBasedModule(module)) { + const tempJobs = module.jobs.map(async (job) => { try { - const jobConfig = await this.readFile( + const jobConfig = await this._readFile( `${this._modulesDir}/${dirName}/${ML_DIR}/${job.file}` ); // use the file name for the id - jobs.push({ + return { id: `${prefix}${job.id}`, config: JSON.parse(jobConfig), - }); + }; } catch (error) { mlLog.warn( `Data recognizer error loading config for job ${job.id} for module ${id}. ${error}` ); } - }) - ); + }); + jobs.push(...(await Promise.all(tempJobs)).filter(isDefined)); - // load all of the datafeed configs - await Promise.all( - manifestJSON.datafeeds.map(async (datafeed) => { + // load all of the datafeed configs + const tempDatafeed = module.datafeeds.map(async (datafeed) => { try { - const datafeedConfig = await this.readFile( + const datafeedConfigString = await this._readFile( `${this._modulesDir}/${dirName}/${ML_DIR}/${datafeed.file}` ); - const config = JSON.parse(datafeedConfig); - // use the job id from the manifestFile - config.job_id = `${prefix}${datafeed.job_id}`; + const datafeedConfig = JSON.parse(datafeedConfigString) as Datafeed; + // use the job id from the module + datafeedConfig.job_id = `${prefix}${datafeed.job_id}`; - datafeeds.push({ + return { id: prefixDatafeedId(datafeed.id, prefix), - config, - }); + job_id: datafeedConfig.job_id, + config: datafeedConfig, + }; } catch (error) { mlLog.warn( `Data recognizer error loading config for datafeed ${datafeed.id} for module ${id}. ${error}` ); } - }) - ); + }); + datafeeds.push(...(await Promise.all(tempDatafeed)).filter(isDefined)); + } // load all of the kibana saved objects - if (manifestJSON.kibana !== undefined) { - const kKeys = Object.keys(manifestJSON.kibana) as Array; + if (module.kibana !== undefined) { + const kKeys = Object.keys(module.kibana) as Array; await Promise.all( kKeys.map(async (key) => { kibana[key] = []; - await Promise.all( - manifestJSON!.kibana[key].map(async (obj) => { - try { - const kConfig = await this.readFile( - `${this._modulesDir}/${dirName}/${KIBANA_DIR}/${key}/${obj.file}` - ); - // use the file name for the id - const kId = obj.file.replace('.json', ''); - const config = JSON.parse(kConfig); - kibana[key]!.push({ - id: kId, - title: config.title, - config, - }); - } catch (error) { - mlLog.warn( - `Data recognizer error loading config for ${key} ${obj.id} for module ${id}. ${error}` - ); - } - }) - ); + if (isFileBasedModule(module)) { + await Promise.all( + module.kibana[key].map(async (obj) => { + try { + const kConfigString = await this._readFile( + `${this._modulesDir}/${dirName}/${KIBANA_DIR}/${key}/${obj.file}` + ); + // use the file name for the id + const kId = obj.file.replace('.json', ''); + const kConfig = JSON.parse(kConfigString); + kibana[key]!.push({ + id: kId, + title: kConfig.title, + config: kConfig, + }); + } catch (error) { + mlLog.warn( + `Data recognizer error loading config for ${key} ${obj.id} for module ${id}. ${error}` + ); + } + }) + ); + } }) ); } return { - ...manifestJSON, + ...module, jobs, datafeeds, kibana, @@ -391,7 +433,7 @@ export class DataRecognizer { // takes a module config id, an optional jobPrefix and the request object // creates all of the jobs, datafeeds and savedObjects listed in the module config. // if any of the savedObjects already exist, they will not be overwritten. - async setup( + public async setup( moduleId: string, jobPrefix?: string, groups?: string[], @@ -417,11 +459,11 @@ export class DataRecognizer { this._indexPatternName = indexPatternName === undefined ? moduleConfig.defaultIndexPattern : indexPatternName; - this._indexPatternId = await this.getIndexPatternId(this._indexPatternName); + this._indexPatternId = await this._getIndexPatternId(this._indexPatternName); // the module's jobs contain custom URLs which require an index patten id // but there is no corresponding index pattern, throw an error - if (this._indexPatternId === undefined && this.doJobUrlsContainIndexPatternId(moduleConfig)) { + if (this._indexPatternId === undefined && this._doJobUrlsContainIndexPatternId(moduleConfig)) { throw Boom.badRequest( `Module's jobs contain custom URLs which require a kibana index pattern (${this._indexPatternName}) which cannot be found.` ); @@ -431,7 +473,7 @@ export class DataRecognizer { // but there is no corresponding index pattern, throw an error if ( this._indexPatternId === undefined && - this.doSavedObjectsContainIndexPatternId(moduleConfig) + this._doSavedObjectsContainIndexPatternId(moduleConfig) ) { throw Boom.badRequest( `Module's saved objects contain custom URLs which require a kibana index pattern (${this._indexPatternName}) which cannot be found.` @@ -439,23 +481,23 @@ export class DataRecognizer { } // create an empty results object - const results = this.createResultsTemplate(moduleConfig); + const results = this._createResultsTemplate(moduleConfig); const saveResults: SaveResults = { jobs: [] as JobResponse[], datafeeds: [] as DatafeedResponse[], savedObjects: [] as KibanaObjectResponse[], }; - this.jobsForModelMemoryEstimation = moduleConfig.jobs.map((job) => ({ + this._jobsForModelMemoryEstimation = moduleConfig.jobs.map((job) => ({ job, query: moduleConfig.datafeeds.find((d) => d.config.job_id === job.id)?.config.query ?? null, })); this.applyJobConfigOverrides(moduleConfig, jobOverrides, jobPrefix); this.applyDatafeedConfigOverrides(moduleConfig, datafeedOverrides, jobPrefix); - this.updateDatafeedIndices(moduleConfig); - this.updateJobUrlIndexPatterns(moduleConfig); - await this.updateModelMemoryLimits(moduleConfig, estimateModelMemory, start, end); + this._updateDatafeedIndices(moduleConfig); + this._updateJobUrlIndexPatterns(moduleConfig); + await this._updateModelMemoryLimits(moduleConfig, estimateModelMemory, start, end); // create the jobs if (moduleConfig.jobs && moduleConfig.jobs.length) { @@ -468,7 +510,7 @@ export class DataRecognizer { if (useDedicatedIndex === true) { moduleConfig.jobs.forEach((job) => (job.config.results_index_name = job.id)); } - saveResults.jobs = await this.saveJobs(moduleConfig.jobs, applyToAllSpaces); + saveResults.jobs = await this._saveJobs(moduleConfig.jobs, applyToAllSpaces); } // create the datafeeds @@ -478,7 +520,7 @@ export class DataRecognizer { df.config.query = query; }); } - saveResults.datafeeds = await this.saveDatafeeds(moduleConfig.datafeeds); + saveResults.datafeeds = await this._saveDatafeeds(moduleConfig.datafeeds); if (startDatafeed) { const savedDatafeeds = moduleConfig.datafeeds.filter((df) => { @@ -486,7 +528,7 @@ export class DataRecognizer { return datafeedResult !== undefined && datafeedResult.success === true; }); - const startResults = await this.startDatafeeds(savedDatafeeds, start, end); + const startResults = await this._startDatafeeds(savedDatafeeds, start, end); saveResults.datafeeds.forEach((df) => { const startedDatafeed = startResults[df.id]; if (startedDatafeed !== undefined) { @@ -503,26 +545,26 @@ export class DataRecognizer { // create the savedObjects if (moduleConfig.kibana) { // update the saved objects with the index pattern id - this.updateSavedObjectIndexPatterns(moduleConfig); + this._updateSavedObjectIndexPatterns(moduleConfig); - const savedObjects = await this.createSavedObjectsToSave(moduleConfig); + const savedObjects = await this._createSavedObjectsToSave(moduleConfig); // update the exists flag in the results - this.updateKibanaResults(results.kibana, savedObjects); + this._updateKibanaResults(results.kibana, savedObjects); // create the savedObjects try { - saveResults.savedObjects = await this.saveKibanaObjects(savedObjects); + saveResults.savedObjects = await this._saveKibanaObjects(savedObjects); } catch (error) { // only one error is returned for the bulk create saved object request // so populate every saved object with the same error. - this.populateKibanaResultErrors(results.kibana, error.output?.payload); + this._populateKibanaResultErrors(results.kibana, error.output?.payload); } } // merge all the save results - this.updateResults(results, saveResults); + this._updateResults(results, saveResults); return results; } - async dataRecognizerJobsExist(moduleId: string): Promise { + public async dataRecognizerJobsExist(moduleId: string): Promise { const results = {} as JobExistResult; // Load the module with the specified ID and check if the jobs @@ -573,7 +615,7 @@ export class DataRecognizer { return results; } - async loadIndexPatterns() { + private async _loadIndexPatterns() { return await this._savedObjectsClient.find({ type: 'index-pattern', perPage: 1000, @@ -581,9 +623,9 @@ export class DataRecognizer { } // returns a id based on an index pattern name - async getIndexPatternId(name: string) { + private async _getIndexPatternId(name: string) { try { - const indexPatterns = await this.loadIndexPatterns(); + const indexPatterns = await this._loadIndexPatterns(); if (indexPatterns === undefined || indexPatterns.saved_objects === undefined) { return; } @@ -598,9 +640,9 @@ export class DataRecognizer { // create a list of objects which are used to save the savedObjects. // each has an exists flag and those which do not already exist // contain a savedObject object which is sent to the server to save - async createSavedObjectsToSave(moduleConfig: Module) { + private async _createSavedObjectsToSave(moduleConfig: Module) { // first check if the saved objects already exist. - const savedObjectExistResults = await this.checkIfSavedObjectsExist(moduleConfig.kibana); + const savedObjectExistResults = await this._checkIfSavedObjectsExist(moduleConfig.kibana); // loop through the kibanaSaveResults and update Object.keys(moduleConfig.kibana).forEach((type) => { // type e.g. dashboard, search ,visualization @@ -624,7 +666,7 @@ export class DataRecognizer { } // update the exists flags in the kibana results - updateKibanaResults( + private _updateKibanaResults( kibanaSaveResults: DataRecognizerConfigResponse['kibana'], objectExistResults: ObjectExistResult[] ) { @@ -640,7 +682,7 @@ export class DataRecognizer { // add an error object to every kibana saved object, // if it doesn't already exist. - populateKibanaResultErrors( + private _populateKibanaResultErrors( kibanaSaveResults: DataRecognizerConfigResponse['kibana'], error: any ) { @@ -661,11 +703,13 @@ export class DataRecognizer { // load existing savedObjects for each type and compare to find out if // items with the same id already exist. // returns a flat list of objects with exists flags set - async checkIfSavedObjectsExist(kibanaObjects: KibanaObjects): Promise { + private async _checkIfSavedObjectsExist( + kibanaObjects: KibanaObjects + ): Promise { const types = Object.keys(kibanaObjects); const results: ObjectExistResponse[][] = await Promise.all( types.map(async (type) => { - const existingObjects = await this.loadExistingSavedObjects(type); + const existingObjects = await this._loadExistingSavedObjects(type); return kibanaObjects[type]!.map((obj) => { const existingObject = existingObjects.saved_objects.find( (o) => o.attributes && o.attributes.title === obj.title @@ -683,13 +727,13 @@ export class DataRecognizer { } // find all existing savedObjects for a given type - loadExistingSavedObjects(type: string) { + private _loadExistingSavedObjects(type: string) { // TODO: define saved object type return this._savedObjectsClient.find({ type, perPage: 1000 }); } // save the savedObjects if they do not exist already - async saveKibanaObjects(objectExistResults: ObjectExistResponse[]) { + private async _saveKibanaObjects(objectExistResults: ObjectExistResponse[]) { let results = { saved_objects: [] as any[] }; const filteredSavedObjects = objectExistResults .filter((o) => o.exists === false) @@ -710,13 +754,16 @@ export class DataRecognizer { // save the jobs. // if any fail (e.g. it already exists), catch the error and mark the result // as success: false - async saveJobs(jobs: ModuleJob[], applyToAllSpaces: boolean = false): Promise { + private async _saveJobs( + jobs: ModuleJob[], + applyToAllSpaces: boolean = false + ): Promise { const resp = await Promise.all( jobs.map(async (job) => { const jobId = job.id; try { job.id = jobId; - await this.saveJob(job); + await this._saveJob(job); return { id: jobId, success: true }; } catch ({ body }) { return { id: jobId, success: false, error: body }; @@ -738,18 +785,18 @@ export class DataRecognizer { return resp; } - async saveJob(job: ModuleJob) { + private async _saveJob(job: ModuleJob) { return this._mlClient.putJob({ job_id: job.id, body: job.config }); } // save the datafeeds. // if any fail (e.g. it already exists), catch the error and mark the result // as success: false - async saveDatafeeds(datafeeds: ModuleDatafeed[]) { + private async _saveDatafeeds(datafeeds: ModuleDatafeed[]) { return await Promise.all( datafeeds.map(async (datafeed) => { try { - await this.saveDatafeed(datafeed); + await this._saveDatafeed(datafeed); return { id: datafeed.id, success: true, @@ -769,7 +816,7 @@ export class DataRecognizer { ); } - async saveDatafeed(datafeed: ModuleDatafeed) { + private async _saveDatafeed(datafeed: ModuleDatafeed) { return this._mlClient.putDatafeed( { datafeed_id: datafeed.id, @@ -779,19 +826,19 @@ export class DataRecognizer { ); } - async startDatafeeds( + private async _startDatafeeds( datafeeds: ModuleDatafeed[], start?: number, end?: number ): Promise<{ [key: string]: DatafeedResponse }> { const results = {} as { [key: string]: DatafeedResponse }; for (const datafeed of datafeeds) { - results[datafeed.id] = await this.startDatafeed(datafeed, start, end); + results[datafeed.id] = await this._startDatafeed(datafeed, start, end); } return results; } - async startDatafeed( + private async _startDatafeed( datafeed: ModuleDatafeed, start: number | undefined, end: number | undefined @@ -845,7 +892,7 @@ export class DataRecognizer { // merge all of the save results into one result object // which is returned from the endpoint - async updateResults(results: DataRecognizerConfigResponse, saveResults: SaveResults) { + private async _updateResults(results: DataRecognizerConfigResponse, saveResults: SaveResults) { // update job results results.jobs.forEach((j) => { saveResults.jobs.forEach((j2) => { @@ -894,7 +941,7 @@ export class DataRecognizer { // creates an empty results object, // listing each job/datafeed/savedObject with a save success boolean - createResultsTemplate(moduleConfig: Module): DataRecognizerConfigResponse { + private _createResultsTemplate(moduleConfig: Module): DataRecognizerConfigResponse { const results: DataRecognizerConfigResponse = {} as DataRecognizerConfigResponse; const reducedConfig = { jobs: moduleConfig.jobs, @@ -932,7 +979,7 @@ export class DataRecognizer { // if an override index pattern has been specified, // update all of the datafeeds. - updateDatafeedIndices(moduleConfig: Module) { + private _updateDatafeedIndices(moduleConfig: Module) { // if the supplied index pattern contains a comma, split into multiple indices and // add each one to the datafeed const indexPatternNames = splitIndexPatternNames(this._indexPatternName); @@ -962,7 +1009,7 @@ export class DataRecognizer { // loop through the custom urls in each job and replace the INDEX_PATTERN_ID // marker for the id of the specified index pattern - updateJobUrlIndexPatterns(moduleConfig: Module) { + private _updateJobUrlIndexPatterns(moduleConfig: Module) { if (Array.isArray(moduleConfig.jobs)) { moduleConfig.jobs.forEach((job) => { // if the job has custom_urls @@ -986,7 +1033,7 @@ export class DataRecognizer { // check the custom urls in the module's jobs to see if they contain INDEX_PATTERN_ID // which needs replacement - doJobUrlsContainIndexPatternId(moduleConfig: Module) { + private _doJobUrlsContainIndexPatternId(moduleConfig: Module) { if (Array.isArray(moduleConfig.jobs)) { for (const job of moduleConfig.jobs) { // if the job has custom_urls @@ -1004,7 +1051,7 @@ export class DataRecognizer { // loop through each kibana saved object and replace any INDEX_PATTERN_ID and // INDEX_PATTERN_NAME markers for the id or name of the specified index pattern - updateSavedObjectIndexPatterns(moduleConfig: Module) { + private _updateSavedObjectIndexPatterns(moduleConfig: Module) { if (moduleConfig.kibana) { Object.keys(moduleConfig.kibana).forEach((category) => { moduleConfig.kibana[category]!.forEach((item) => { @@ -1037,7 +1084,7 @@ export class DataRecognizer { /** * Provides a time range of the last 3 months of data */ - async getFallbackTimeRange( + private async _getFallbackTimeRange( timeField: string, query?: any ): Promise<{ start: number; end: number }> { @@ -1059,7 +1106,7 @@ export class DataRecognizer { * Ensure the model memory limit for each job is not greater than * the max model memory setting for the cluster */ - async updateModelMemoryLimits( + private async _updateModelMemoryLimits( moduleConfig: Module, estimateMML: boolean, start?: number, @@ -1069,12 +1116,12 @@ export class DataRecognizer { return; } - if (estimateMML && this.jobsForModelMemoryEstimation.length > 0) { + if (estimateMML && this._jobsForModelMemoryEstimation.length > 0) { try { // Checks if all jobs in the module have the same time field configured - const firstJobTimeField = this.jobsForModelMemoryEstimation[0].job.config.data_description + const firstJobTimeField = this._jobsForModelMemoryEstimation[0].job.config.data_description .time_field; - const isSameTimeFields = this.jobsForModelMemoryEstimation.every( + const isSameTimeFields = this._jobsForModelMemoryEstimation.every( ({ job }) => job.config.data_description.time_field === firstJobTimeField ); @@ -1085,16 +1132,16 @@ export class DataRecognizer { const { start: fallbackStart, end: fallbackEnd, - } = await this.getFallbackTimeRange(firstJobTimeField, { match_all: {} }); + } = await this._getFallbackTimeRange(firstJobTimeField, { match_all: {} }); start = fallbackStart; end = fallbackEnd; } - for (const { job, query } of this.jobsForModelMemoryEstimation) { + for (const { job, query } of this._jobsForModelMemoryEstimation) { let earliestMs = start; let latestMs = end; if (earliestMs === undefined || latestMs === undefined) { - const timeFieldRange = await this.getFallbackTimeRange( + const timeFieldRange = await this._getFallbackTimeRange( job.config.data_description.time_field, query ); @@ -1157,7 +1204,7 @@ export class DataRecognizer { // check the kibana saved searches JSON in the module to see if they contain INDEX_PATTERN_ID // which needs replacement - doSavedObjectsContainIndexPatternId(moduleConfig: Module) { + private _doSavedObjectsContainIndexPatternId(moduleConfig: Module) { if (moduleConfig.kibana) { for (const category of Object.keys(moduleConfig.kibana)) { for (const item of moduleConfig.kibana[category]!) { @@ -1171,7 +1218,7 @@ export class DataRecognizer { return false; } - applyJobConfigOverrides( + public applyJobConfigOverrides( moduleConfig: Module, jobOverrides?: JobOverride | JobOverride[], jobPrefix = '' @@ -1205,9 +1252,9 @@ export class DataRecognizer { }); if (generalOverrides.some((override) => !!override.analysis_limits?.model_memory_limit)) { - this.jobsForModelMemoryEstimation = []; + this._jobsForModelMemoryEstimation = []; } else { - this.jobsForModelMemoryEstimation = moduleConfig.jobs + this._jobsForModelMemoryEstimation = moduleConfig.jobs .filter((job) => { const override = jobSpecificOverrides.find((o) => `${jobPrefix}${o.job_id}` === job.id); return override?.analysis_limits?.model_memory_limit === undefined; @@ -1266,7 +1313,7 @@ export class DataRecognizer { }); } - applyDatafeedConfigOverrides( + public applyDatafeedConfigOverrides( moduleConfig: Module, datafeedOverrides?: DatafeedOverride | DatafeedOverride[], jobPrefix = '' diff --git a/x-pack/plugins/ml/server/saved_objects/mappings.json b/x-pack/plugins/ml/server/saved_objects/mappings.json deleted file mode 100644 index 9a23dba324dbf..0000000000000 --- a/x-pack/plugins/ml/server/saved_objects/mappings.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "job": { - "properties": { - "job_id": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword" - } - } - }, - "datafeed_id": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - } - } - } -} diff --git a/x-pack/plugins/ml/server/saved_objects/mappings.ts b/x-pack/plugins/ml/server/saved_objects/mappings.ts new file mode 100644 index 0000000000000..f452991015723 --- /dev/null +++ b/x-pack/plugins/ml/server/saved_objects/mappings.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsTypeMappingDefinition } from 'kibana/server'; + +export const mlJob: SavedObjectsTypeMappingDefinition = { + properties: { + job_id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + datafeed_id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + type: { + type: 'keyword', + }, + }, +}; + +export const mlModule: SavedObjectsTypeMappingDefinition = { + dynamic: false, + properties: { + id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + title: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + description: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + type: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + logo: { + type: 'object', + }, + defaultIndexPattern: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + query: { + type: 'object', + }, + jobs: { + type: 'object', + }, + datafeeds: { + type: 'object', + }, + }, +}; diff --git a/x-pack/plugins/ml/server/saved_objects/saved_objects.ts b/x-pack/plugins/ml/server/saved_objects/saved_objects.ts index e30ff60960e27..004b5e8e554cc 100644 --- a/x-pack/plugins/ml/server/saved_objects/saved_objects.ts +++ b/x-pack/plugins/ml/server/saved_objects/saved_objects.ts @@ -6,10 +6,13 @@ */ import { SavedObjectsServiceSetup } from 'kibana/server'; -import mappings from './mappings.json'; +import { mlJob, mlModule } from './mappings'; import { migrations } from './migrations'; -import { ML_SAVED_OBJECT_TYPE } from '../../common/types/saved_objects'; +import { + ML_SAVED_OBJECT_TYPE, + ML_MODULE_SAVED_OBJECT_TYPE, +} from '../../common/types/saved_objects'; export function setupSavedObjects(savedObjects: SavedObjectsServiceSetup) { savedObjects.registerType({ @@ -17,6 +20,13 @@ export function setupSavedObjects(savedObjects: SavedObjectsServiceSetup) { hidden: false, namespaceType: 'multiple', migrations, - mappings: mappings.job, + mappings: mlJob, + }); + savedObjects.registerType({ + name: ML_MODULE_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + migrations, + mappings: mlModule, }); } diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 18c9610656648..a6184261350b7 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -583,7 +583,7 @@ export const ALERT_ACTION_TYPE_LOG = '.server-log'; /** * To enable modifing of alerts in under actions */ -export const ALERT_REQUIRES_APP_CONTEXT = false; +export const ALERT_REQUIRES_APP_CONTEXT = true; export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo']; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.tsx similarity index 88% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.tsx index c421e4d42ded4..4e402b8b55a5b 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.tsx @@ -7,7 +7,6 @@ import React, { PureComponent } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import PropTypes from 'prop-types'; import { EuiButtonEmpty, @@ -21,15 +20,15 @@ import { EuiTitle, } from '@elastic/eui'; -import { serializeCluster } from '../../../../../common/lib'; +import { Cluster, serializeCluster } from '../../../../../common/lib'; -export class RequestFlyout extends PureComponent { - static propTypes = { - close: PropTypes.func.isRequired, - name: PropTypes.string.isRequired, - cluster: PropTypes.object.isRequired, - }; +interface Props { + close: () => void; + name: string; + cluster: Cluster; +} +export class RequestFlyout extends PureComponent { render() { const { name, close, cluster } = this.props; const endpoint = 'PUT _cluster/settings'; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_name.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap similarity index 100% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_name.test.js.snap rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_proxy.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap similarity index 100% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_proxy.test.js.snap rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_seeds.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap similarity index 100% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_seeds.test.js.snap rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts similarity index 100% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts diff --git a/x-pack/plugins/remote_clusters/public/application/services/validate_address.test.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.test.ts similarity index 100% rename from x-pack/plugins/remote_clusters/public/application/services/validate_address.test.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.test.ts diff --git a/x-pack/plugins/remote_clusters/public/application/services/validate_address.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.ts similarity index 78% rename from x-pack/plugins/remote_clusters/public/application/services/validate_address.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.ts index dde9bf31bcded..7377454780d3c 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/validate_address.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.ts @@ -5,7 +5,7 @@ * 2.0. */ -export function isAddressValid(seedNode) { +export function isAddressValid(seedNode?: string): boolean { if (!seedNode) { return false; } @@ -17,14 +17,14 @@ export function isAddressValid(seedNode) { // no need to wait for regEx if the part is empty return true; } - const [match] = part.match(/[A-Za-z0-9\-]*/); + const [match] = part.match(/[A-Za-z0-9\-]*/) ?? []; return match !== part; }); return !containsInvalidCharacters; } -export function isPortValid(seedNode) { +export function isPortValid(seedNode?: string): boolean { if (!seedNode) { return false; } @@ -42,6 +42,6 @@ export function isPortValid(seedNode) { return false; } - const isPortNumeric = port.match(/[0-9]*/)[0] === port; + const isPortNumeric = (port.match(/[0-9]*/) ?? [])[0] === port; return isPortNumeric; } diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.ts similarity index 100% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.ts diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.tsx similarity index 94% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.tsx index f7a31a748bb0a..6a3521b1a9adc 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -export function validateName(name) { +export function validateName(name?: string | null): null | JSX.Element { if (!name || !name.trim()) { return ( Boolean(seed.trim())); if (seedsHaveBeenCreated) { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.tsx similarity index 90% rename from x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.js rename to x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.tsx index 6645098d1938d..608109eee13d5 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -export function validateServerName(serverName) { +export function validateServerName(serverName?: string) { if (!serverName || !serverName.trim()) { return ( { +export function sendPost(path: string, payload: Cluster): Promise { return _httpClient.post(getFullPath(path), { body: JSON.stringify(payload), }); @@ -46,13 +40,7 @@ export function sendGet( return _httpClient.get(getFullPath(path), { asSystemRequest }); } -export function sendPut( - path: string, - payload: { - seeds: string[]; - skipUnavailable: boolean; - } -): Promise { +export function sendPut(path: string, payload: Omit): Promise { return _httpClient.put(getFullPath(path), { body: JSON.stringify(payload), }); diff --git a/x-pack/plugins/remote_clusters/public/application/services/index.js b/x-pack/plugins/remote_clusters/public/application/services/index.ts similarity index 82% rename from x-pack/plugins/remote_clusters/public/application/services/index.js rename to x-pack/plugins/remote_clusters/public/application/services/index.ts index 6f2082dcfbdf6..b64eead906921 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/index.js +++ b/x-pack/plugins/remote_clusters/public/application/services/index.ts @@ -9,9 +9,9 @@ export { loadClusters, addCluster, editCluster, removeClusterRequest } from './a export { showApiError, showApiWarning } from './api_errors'; -export { initRedirect, redirect } from './redirect'; +export { setBreadcrumbs } from './breadcrumb'; -export { isAddressValid, isPortValid } from './validate_address'; +export { redirect } from './redirect'; export { setUserHasLeftApp, getUserHasLeftApp, registerRouter, getRouter } from './routing'; diff --git a/x-pack/plugins/remote_clusters/public/application/services/routing.js b/x-pack/plugins/remote_clusters/public/application/services/routing.ts similarity index 66% rename from x-pack/plugins/remote_clusters/public/application/services/routing.js rename to x-pack/plugins/remote_clusters/public/application/services/routing.ts index 96c003563c00c..06e61914c3419 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/routing.js +++ b/x-pack/plugins/remote_clusters/public/application/services/routing.ts @@ -5,13 +5,15 @@ * 2.0. */ +import { ScopedHistory } from 'kibana/public'; + /** * This file based on guidance from https://github.com/elastic/eui/blob/master/wiki/react-router.md */ let _userHasLeftApp = false; -export function setUserHasLeftApp(userHasLeftApp) { +export function setUserHasLeftApp(userHasLeftApp: boolean) { _userHasLeftApp = userHasLeftApp; } @@ -19,8 +21,12 @@ export function getUserHasLeftApp() { return _userHasLeftApp; } -let router; -export function registerRouter(reactRouter) { +interface AppRouter { + history: ScopedHistory; + route: { location: ScopedHistory['location'] }; +} +let router: AppRouter; +export function registerRouter(reactRouter: AppRouter) { router = reactRouter; } diff --git a/x-pack/plugins/reporting/public/components/report_link.tsx b/x-pack/plugins/reporting/public/components/report_link.tsx index 889658fab467c..99053010ef4ae 100644 --- a/x-pack/plugins/reporting/public/components/report_link.tsx +++ b/x-pack/plugins/reporting/public/components/report_link.tsx @@ -21,7 +21,7 @@ export const ReportLink = ({ getUrl }: Props) => ( ), diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx index 6673aded2ecbe..6f6cf2dc9351b 100644 --- a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx +++ b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx @@ -223,7 +223,17 @@ class ReportingPanelContentUi extends Component { text: toMountPoint( + + + ), + }} /> ), 'data-test-subj': 'queueReportSuccess', diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 58a5f5197d1f7..fdeb2e5cb3831 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -83,6 +83,12 @@ export class HeadlessChromiumDriverFactory { } as puppeteer.LaunchOptions); page = await browser.newPage(); + + // Log version info for debugging / maintenance + const client = await page.target().createCDPSession(); + const versionInfo = await client.send('Browser.getVersion'); + logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`); + await page.emulateTimezone(browserTimezone ?? null); // Set the default timeout for all navigation methods to the openUrl timeout (30 seconds) diff --git a/x-pack/plugins/saved_objects_tagging/common/constants.ts b/x-pack/plugins/saved_objects_tagging/common/constants.ts index 74afc63667ddc..7a49b3da64f9a 100644 --- a/x-pack/plugins/saved_objects_tagging/common/constants.ts +++ b/x-pack/plugins/saved_objects_tagging/common/constants.ts @@ -6,7 +6,7 @@ */ /** - * The id of the tagging feature as registered to to `features` plugin + * The id of the tagging feature as registered to `features` plugin */ export const tagFeatureId = 'savedObjectsTagging'; /** diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx index d45ba91bb35fb..4e9f1a6692eb9 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -62,7 +62,7 @@ describe('', () => { }); expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( - user.full_name + `Settings for ${user.full_name}` ); expect(wrapper.find('[data-test-subj="username"]').text()).toEqual(user.username); expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); @@ -83,7 +83,9 @@ describe('', () => { wrapper.update(); }); - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( + `Settings for ${user.username}` + ); }); it(`displays a placeholder when no email address is provided`, async () => { diff --git a/x-pack/plugins/security/public/account_management/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx index c8fe80e254a46..60f48c01a6ff7 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.tsx @@ -9,6 +9,7 @@ import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { CoreStart, NotificationsStart } from 'src/core/public'; @@ -40,7 +41,13 @@ export const AccountManagementPage = ({ userAPIClient, authc, notifications }: P -

{getUserDisplayName(currentUser)}

+

+ {getUserDisplayName(currentUser)} }} + /> +

diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index bd338109a4460..f2d3fcd6ab3ca 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiHeaderSectionItemButton, EuiPopover } from '@elastic/eui'; +import { EuiContextMenuItem, EuiHeaderSectionItemButton, EuiPopover } from '@elastic/eui'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; @@ -181,4 +181,58 @@ describe('SecurityNavControl', () => { expect(findTestSubject(wrapper, 'logoutLink').text()).toBe('Log in'); }); + + it('properly renders without a custom profile link.', async () => { + const props = { + user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), + editProfileUrl: '', + logoutUrl: '', + userMenuLinks$: new BehaviorSubject([ + { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, + { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, + ]), + }; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([]); + + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([ + 'Profile', + 'link1', + 'link2', + 'Log out', + ]); + }); + + it('properly renders with a custom profile link.', async () => { + const props = { + user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), + editProfileUrl: '', + logoutUrl: '', + userMenuLinks$: new BehaviorSubject([ + { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, + { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2, setAsProfile: true }, + ]), + }; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([]); + + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([ + 'link1', + 'link2', + 'Preferences', + 'Log out', + ]); + }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index c7649494bb810..546d6cf5a6bba 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -30,6 +30,7 @@ export interface UserMenuLink { iconType: IconType; href: string; order?: number; + setAsProfile?: boolean; } interface Props { @@ -123,35 +124,39 @@ export class SecurityNavControl extends Component { const isAnonymousUser = authenticatedUser?.authentication_provider.type === 'anonymous'; const items: EuiContextMenuPanelItemDescriptor[] = []; + if (userMenuLinks.length) { + const userMenuLinkMenuItems = userMenuLinks + .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) + .map(({ label, iconType, href }: UserMenuLink) => ({ + name: {label}, + icon: , + href, + 'data-test-subj': `userMenuLink__${label}`, + })); + items.push(...userMenuLinkMenuItems); + } + if (!isAnonymousUser) { + const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true); const profileMenuItem = { name: ( ), - icon: , + icon: , href: editProfileUrl, 'data-test-subj': 'profileLink', }; - items.push(profileMenuItem); - } - if (userMenuLinks.length) { - const userMenuLinkMenuItems = userMenuLinks - .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) - .map(({ label, iconType, href }: UserMenuLink) => ({ - name: {label}, - icon: , - href, - 'data-test-subj': `userMenuLink__${label}`, - })); - - items.push(...userMenuLinkMenuItems, { - isSeparator: true, - key: 'securityNavControlComponent__userMenuLinksSeparator', - }); + // Set this as the first link if there is no user-defined profile link + if (!hasCustomProfileLinks) { + items.unshift(profileMenuItem); + } else { + items.push(profileMenuItem); + } } const logoutMenuItem = { diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 72a1a6f5817a5..035177d78c9c6 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -178,16 +178,19 @@ describe('SecurityNavControlService', () => { }); describe(`#start`, () => { - it('should return functions to register and retrieve user menu links', () => { - const license$ = new BehaviorSubject(validLicense); + let navControlService: SecurityNavControlService; + beforeEach(() => { + const license$ = new BehaviorSubject({} as ILicense); - const navControlService = new SecurityNavControlService(); + navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', }); + }); + it('should return functions to register and retrieve user menu links', () => { const coreStart = coreMock.createStart(); const navControlServiceStart = navControlService.start({ core: coreStart }); expect(navControlServiceStart).toHaveProperty('getUserMenuLinks$'); @@ -195,15 +198,6 @@ describe('SecurityNavControlService', () => { }); it('should register custom user menu links to be displayed in the nav controls', (done) => { - const license$ = new BehaviorSubject(validLicense); - - const navControlService = new SecurityNavControlService(); - navControlService.setup({ - securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, - logoutUrl: '/some/logout/url', - }); - const coreStart = coreMock.createStart(); const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); const userMenuLinks$ = getUserMenuLinks$(); @@ -231,15 +225,6 @@ describe('SecurityNavControlService', () => { }); it('should retrieve user menu links sorted by order', (done) => { - const license$ = new BehaviorSubject(validLicense); - - const navControlService = new SecurityNavControlService(); - navControlService.setup({ - securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, - logoutUrl: '/some/logout/url', - }); - const coreStart = coreMock.createStart(); const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); const userMenuLinks$ = getUserMenuLinks$(); @@ -305,5 +290,79 @@ describe('SecurityNavControlService', () => { done(); }); }); + + it('should allow adding a custom profile link', () => { + const coreStart = coreMock.createStart(); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const userMenuLinks$ = getUserMenuLinks$(); + + addUserMenuLinks([ + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3 }, + { label: 'link1', href: 'path-to-link1', iconType: 'empty', order: 1, setAsProfile: true }, + ]); + + const onUserMenuLinksHandler = jest.fn(); + userMenuLinks$.subscribe(onUserMenuLinksHandler); + + expect(onUserMenuLinksHandler).toHaveBeenCalledTimes(1); + expect(onUserMenuLinksHandler).toHaveBeenCalledWith([ + { label: 'link1', href: 'path-to-link1', iconType: 'empty', order: 1, setAsProfile: true }, + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3 }, + ]); + }); + + it('should not allow adding more than one custom profile link', () => { + const coreStart = coreMock.createStart(); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const userMenuLinks$ = getUserMenuLinks$(); + + expect(() => { + addUserMenuLinks([ + { + label: 'link3', + href: 'path-to-link3', + iconType: 'empty', + order: 3, + setAsProfile: true, + }, + { + label: 'link1', + href: 'path-to-link1', + iconType: 'empty', + order: 1, + setAsProfile: true, + }, + ]); + }).toThrowErrorMatchingInlineSnapshot( + `"Only one custom profile link can be passed at a time (found 2)"` + ); + + // Adding a single custom profile link. + addUserMenuLinks([ + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3, setAsProfile: true }, + ]); + + expect(() => { + addUserMenuLinks([ + { + label: 'link1', + href: 'path-to-link1', + iconType: 'empty', + order: 1, + setAsProfile: true, + }, + ]); + }).toThrowErrorMatchingInlineSnapshot( + `"Only one custom profile link can be set. A custom profile link named link3 (path-to-link3) already exists"` + ); + + const onUserMenuLinksHandler = jest.fn(); + userMenuLinks$.subscribe(onUserMenuLinksHandler); + + expect(onUserMenuLinksHandler).toHaveBeenCalledTimes(1); + expect(onUserMenuLinksHandler).toHaveBeenCalledWith([ + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3, setAsProfile: true }, + ]); + }); }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index fc9ba262a2026..7f3d93099704a 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -77,6 +77,23 @@ export class SecurityNavControlService { this.userMenuLinks$.pipe(map(this.sortUserMenuLinks), takeUntil(this.stop$)), addUserMenuLinks: (userMenuLinks: UserMenuLink[]) => { const currentLinks = this.userMenuLinks$.value; + const hasCustomProfileLink = currentLinks.find(({ setAsProfile }) => setAsProfile === true); + const passedCustomProfileLinkCount = userMenuLinks.filter( + ({ setAsProfile }) => setAsProfile === true + ).length; + + if (hasCustomProfileLink && passedCustomProfileLinkCount > 0) { + throw new Error( + `Only one custom profile link can be set. A custom profile link named ${hasCustomProfileLink.label} (${hasCustomProfileLink.href}) already exists` + ); + } + + if (passedCustomProfileLinkCount > 1) { + throw new Error( + `Only one custom profile link can be passed at a time (found ${passedCustomProfileLinkCount})` + ); + } + const newLinks = [...currentLinks, ...userMenuLinks]; this.userMenuLinks$.next(newLinks); }, diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 994e420260b25..143384d160471 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -180,6 +180,7 @@ export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.servicenow', '.jira', '.resilient', + '.teams', ]; if (ENABLE_CASE_CONNECTOR) { diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts index 7dfff1d5de081..8234c3a9a599d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts @@ -16,7 +16,7 @@ import { CreateExceptionListItemSchema, } from '../../../lists/common/schemas'; import { ESBoolQuery } from '../typed_json'; -import { buildExceptionFilter } from './build_exceptions_filter'; +import { buildExceptionFilter } from '../shared_imports'; import { Query, Language, Index, TimestampOverrideOrUndefined } from './schemas/common/schemas'; export const getQueryFilter = ( diff --git a/x-pack/plugins/security_solution/common/detection_engine/types.ts b/x-pack/plugins/security_solution/common/detection_engine/types.ts index 0c390353e4e07..c0e502312b2ff 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/types.ts @@ -56,21 +56,3 @@ export interface EqlSearchResponse { events?: Array>; }; } - -export interface BooleanFilter { - bool: { - must?: unknown | unknown[]; - must_not?: unknown | unknown[]; - should?: unknown[]; - filter?: unknown | unknown[]; - minimum_should_match?: number; - }; -} - -export interface NestedFilter { - nested: { - path: string; - query: unknown | unknown[]; - score_mode: string; - }; -} diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts index 4ed81eb444558..bcd2ee0358dfe 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts @@ -106,7 +106,7 @@ describe('When invoking Trusted Apps Schema', () => { expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg); }); - it('should validate `os` to to only accept known values', () => { + it('should validate `os` to only accept known values', () => { const bodyMsg = createNewTrustedApp({ os: undefined }); expect(() => body.validate(bodyMsg)).toThrow(); diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts index 0b59170dfc778..81edb51e41458 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts @@ -38,11 +38,11 @@ export interface MatrixHistogramRequestOptions extends RequestBasicOptions { stackByField: string; threshold?: | { - field: string | string[] | undefined; - value: number; + field: string[]; + value: string; cardinality?: { field: string[]; - value: number; + value: string; }; } | undefined; diff --git a/x-pack/plugins/security_solution/common/shared_exports.ts b/x-pack/plugins/security_solution/common/shared_exports.ts index f10aaf45dcac3..bf740ddce9fd6 100644 --- a/x-pack/plugins/security_solution/common/shared_exports.ts +++ b/x-pack/plugins/security_solution/common/shared_exports.ts @@ -17,6 +17,4 @@ export { exactCheck } from './exact_check'; export { getPaths, foldLeftRight, removeExternalLinkText } from './test_utils'; export { validate, validateEither } from './validate'; export { formatErrors } from './format_errors'; -export { migratePackagePolicyToV7110 } from './endpoint/policy/migrations/to_v7_11_0'; -export { migratePackagePolicyToV7120 } from './endpoint/policy/migrations/to_v7_12_0'; export { addIdToItem, removeIdFromItem } from './add_remove_id_to_item'; diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index 94afbb948bf42..aaae0d4dc25ef 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -45,6 +45,8 @@ export { Type, ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, + osType, osTypeArray, OsTypeArray, + buildExceptionFilter, } from '../../lists/common'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 60fcb65d6780c..59c3fb5876d76 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -162,7 +162,6 @@ export const closeTimeline = () => { export const createNewTimeline = () => { cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click({ force: true }); - cy.get(CREATE_NEW_TIMELINE).should('be.visible'); cy.get(CREATE_NEW_TIMELINE).click(); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index d02f7e0ee0961..9c06fc032f819 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -13,7 +13,7 @@ import { noop } from 'lodash/fp'; import { TestProviders } from '../../../common/mock'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { CommentRequest, CommentType } from '../../../../../case/common/api'; +import { CommentRequest, CommentType } from '../../../../../cases/common/api'; import { useInsertTimeline } from '../use_insert_timeline'; import { usePostComment } from '../../containers/use_post_comment'; import { AddComment, AddCommentRefObject } from '.'; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx index c94ef75523e2c..acd27e99a857f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; import React, { useCallback, forwardRef, useImperativeHandle } from 'react'; import styled from 'styled-components'; -import { CommentType } from '../../../../../case/common/api'; +import { CommentType } from '../../../../../cases/common/api'; import { usePostComment } from '../../containers/use_post_comment'; import { Case } from '../../containers/types'; import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx index e22a709325261..2cf7d3c6c555b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { CommentRequestUserType } from '../../../../../case/common/api'; +import { CommentRequestUserType } from '../../../../../cases/common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx index 046da5e833bf8..daa988641fbab 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx @@ -8,7 +8,7 @@ import { Dispatch } from 'react'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { Case, SubCase } from '../../containers/types'; import { UpdateCase } from '../../containers/use_get_cases'; import { statuses } from '../status'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx index e69f85c862962..86f854fd0a145 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx @@ -19,7 +19,7 @@ import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; -import { CaseStatuses, CaseType } from '../../../../../case/common/api'; +import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; import { getEmptyTagValue } from '../../../common/components/empty_value'; import { Case, SubCase } from '../../containers/types'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx index 1e1e925a20ada..43f0d9df49e94 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx @@ -10,7 +10,7 @@ import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; import styled from 'styled-components'; import { Case, SubCase } from '../../containers/types'; import { CasesColumns } from './columns'; -import { AssociationType } from '../../../../../case/common/api'; +import { AssociationType } from '../../../../../cases/common/api'; type ExpandedRowMap = Record | {}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts index 519be95fcdfef..8962d67319371 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts @@ -6,7 +6,7 @@ */ import { filter } from 'lodash/fp'; -import { AssociationType, CaseStatuses, CaseType } from '../../../../../case/common/api'; +import { AssociationType, CaseStatuses, CaseType } from '../../../../../cases/common/api'; import { Case, SubCase } from '../../containers/types'; import { statuses } from '../status'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index 90b1143ca750d..0fafdaf81f095 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -14,7 +14,7 @@ import { TestProviders } from '../../../common/mock'; import { casesStatus, useGetCasesMockState, collectionCase } from '../../containers/mock'; import * as i18n from './translations'; -import { CaseStatuses, CaseType } from '../../../../../case/common/api'; +import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; import { useKibana } from '../../../common/lib/kibana'; import { getEmptyTagValue } from '../../../common/components/empty_value'; import { useDeleteCases } from '../../containers/use_delete_cases'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index 7b72a2e188903..c5748a321c19b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -22,7 +22,7 @@ import styled, { css } from 'styled-components'; import classnames from 'classnames'; import * as i18n from './translations'; -import { CaseStatuses, CaseType } from '../../../../../case/common/api'; +import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; import { getCasesColumns } from './columns'; import { Case, DeleteCase, FilterOptions, SortFieldCase, SubCase } from '../../containers/types'; import { useGetCases, UpdateCase } from '../../containers/use_get_cases'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx index 9d5b36515182d..5c9f11d1e3a83 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { StatusFilter } from './status_filter'; import { StatusAll } from '../status'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx index 0bf622a54f51a..48a642aaf51a9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { TestProviders } from '../../../common/mock'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx index 84b032489f326..ff5b511ef9026 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx @@ -10,7 +10,7 @@ import { isEqual } from 'lodash/fp'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { FilterOptions } from '../../containers/types'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts index 3b27ef25eda14..ad44959ecb1dc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts @@ -9,101 +9,101 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; -export const NO_CASES = i18n.translate('xpack.securitySolution.case.caseTable.noCases.title', { +export const NO_CASES = i18n.translate('xpack.securitySolution.cases.caseTable.noCases.title', { defaultMessage: 'No Cases', }); -export const NO_CASES_BODY = i18n.translate('xpack.securitySolution.case.caseTable.noCases.body', { +export const NO_CASES_BODY = i18n.translate('xpack.securitySolution.cases.caseTable.noCases.body', { defaultMessage: 'There are no cases to display. Please create a new case or change your filter settings above.', }); -export const ADD_NEW_CASE = i18n.translate('xpack.securitySolution.case.caseTable.addNewCase', { +export const ADD_NEW_CASE = i18n.translate('xpack.securitySolution.cases.caseTable.addNewCase', { defaultMessage: 'Add New Case', }); export const SHOWING_SELECTED_CASES = (totalRules: number) => - i18n.translate('xpack.securitySolution.case.caseTable.selectedCasesTitle', { + i18n.translate('xpack.securitySolution.cases.caseTable.selectedCasesTitle', { values: { totalRules }, defaultMessage: 'Selected {totalRules} {totalRules, plural, =1 {case} other {cases}}', }); export const SHOWING_CASES = (totalRules: number) => - i18n.translate('xpack.securitySolution.case.caseTable.showingCasesTitle', { + i18n.translate('xpack.securitySolution.cases.caseTable.showingCasesTitle', { values: { totalRules }, defaultMessage: 'Showing {totalRules} {totalRules, plural, =1 {case} other {cases}}', }); export const UNIT = (totalCount: number) => - i18n.translate('xpack.securitySolution.case.caseTable.unit', { + i18n.translate('xpack.securitySolution.cases.caseTable.unit', { values: { totalCount }, defaultMessage: `{totalCount, plural, =1 {case} other {cases}}`, }); export const SEARCH_CASES = i18n.translate( - 'xpack.securitySolution.case.caseTable.searchAriaLabel', + 'xpack.securitySolution.cases.caseTable.searchAriaLabel', { defaultMessage: 'Search cases', } ); -export const BULK_ACTIONS = i18n.translate('xpack.securitySolution.case.caseTable.bulkActions', { +export const BULK_ACTIONS = i18n.translate('xpack.securitySolution.cases.caseTable.bulkActions', { defaultMessage: 'Bulk actions', }); export const EXTERNAL_INCIDENT = i18n.translate( - 'xpack.securitySolution.case.caseTable.snIncident', + 'xpack.securitySolution.cases.caseTable.snIncident', { defaultMessage: 'External Incident', } ); export const INCIDENT_MANAGEMENT_SYSTEM = i18n.translate( - 'xpack.securitySolution.case.caseTable.incidentSystem', + 'xpack.securitySolution.cases.caseTable.incidentSystem', { defaultMessage: 'Incident Management System', } ); export const SEARCH_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.case.caseTable.searchPlaceholder', + 'xpack.securitySolution.cases.caseTable.searchPlaceholder', { defaultMessage: 'e.g. case name', } ); -export const CLOSED = i18n.translate('xpack.securitySolution.case.caseTable.closed', { +export const CLOSED = i18n.translate('xpack.securitySolution.cases.caseTable.closed', { defaultMessage: 'Closed', }); -export const DELETE = i18n.translate('xpack.securitySolution.case.caseTable.delete', { +export const DELETE = i18n.translate('xpack.securitySolution.cases.caseTable.delete', { defaultMessage: 'Delete', }); export const REQUIRES_UPDATE = i18n.translate( - 'xpack.securitySolution.case.caseTable.requiresUpdate', + 'xpack.securitySolution.cases.caseTable.requiresUpdate', { defaultMessage: ' requires update', } ); -export const UP_TO_DATE = i18n.translate('xpack.securitySolution.case.caseTable.upToDate', { +export const UP_TO_DATE = i18n.translate('xpack.securitySolution.cases.caseTable.upToDate', { defaultMessage: ' is up to date', }); -export const NOT_PUSHED = i18n.translate('xpack.securitySolution.case.caseTable.notPushed', { +export const NOT_PUSHED = i18n.translate('xpack.securitySolution.cases.caseTable.notPushed', { defaultMessage: 'Not pushed', }); -export const REFRESH = i18n.translate('xpack.securitySolution.case.caseTable.refreshTitle', { +export const REFRESH = i18n.translate('xpack.securitySolution.cases.caseTable.refreshTitle', { defaultMessage: 'Refresh', }); export const SERVICENOW_LINK_ARIA = i18n.translate( - 'xpack.securitySolution.case.caseTable.serviceNowLinkAria', + 'xpack.securitySolution.cases.caseTable.serviceNowLinkAria', { defaultMessage: 'click to view the incident on servicenow', } ); -export const STATUS = i18n.translate('xpack.securitySolution.case.caseTable.status', { +export const STATUS = i18n.translate('xpack.securitySolution.cases.caseTable.status', { defaultMessage: 'Status', }); diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx index 3d2f81f5e5931..24897a14f0754 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { statuses, CaseStatusWithAllStatus } from '../status'; import * as i18n from './translations'; import { Case } from '../../containers/types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts index 4b7da5221ccda..1171495f4a202 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const BULK_ACTION_DELETE_SELECTED = i18n.translate( - 'xpack.securitySolution.case.caseTable.bulkActions.deleteSelectedTitle', + 'xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle', { defaultMessage: 'Delete selected', } diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts b/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts index 0be56791c9916..96c4d475c4308 100644 --- a/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/callout/translations.ts @@ -8,14 +8,14 @@ import { i18n } from '@kbn/i18n'; export const READ_ONLY_SAVED_OBJECT_TITLE = i18n.translate( - 'xpack.securitySolution.case.readOnlySavedObjectTitle', + 'xpack.securitySolution.cases.readOnlySavedObjectTitle', { defaultMessage: 'You cannot open new or update existing cases', } ); export const READ_ONLY_SAVED_OBJECT_MSG = i18n.translate( - 'xpack.securitySolution.case.readOnlySavedObjectDescription', + 'xpack.securitySolution.cases.readOnlySavedObjectDescription', { defaultMessage: 'You only have permissions to view cases. If you need to open and update cases, contact your Kibana administrator.', @@ -23,7 +23,7 @@ export const READ_ONLY_SAVED_OBJECT_MSG = i18n.translate( ); export const DISMISS_CALLOUT = i18n.translate( - 'xpack.securitySolution.case.dismissErrorsPushServiceCallOutTitle', + 'xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle', { defaultMessage: 'Dismiss', } diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts index b05924b5e27ff..8e26c0fd7a7ff 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { basicCase } from '../../containers/mock'; import { getStatusDate, getStatusTitle } from './helpers'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts index 10f87981094fa..68a243040145a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { Case } from '../../containers/types'; import { statuses } from '../status'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx index 95c534f7c1ede..63ce441732251 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx @@ -16,7 +16,7 @@ import { EuiFlexItem, EuiIconTip, } from '@elastic/eui'; -import { CaseStatuses, CaseType } from '../../../../../case/common/api'; +import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; import * as i18n from '../case_view/translations'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; import { Actions } from './actions'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx index 85b36d174977c..4e414706d1fd7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { StatusContextMenu } from './status_context_menu'; describe('SyncAlertsSwitch', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx index 7f9ffbd8dc01d..92dcd16a86193 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; import { memoize } from 'lodash/fp'; import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; -import { caseStatuses, CaseStatuses } from '../../../../../case/common/api'; +import { caseStatuses, CaseStatuses } from '../../../../../cases/common/api'; import { Status } from '../status'; interface Props { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx index 70e6636cc737f..18a76e2766d8d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { AssociationType, CommentType } from '../../../../../case/common/api'; +import { AssociationType, CommentType } from '../../../../../cases/common/api'; import { Comment } from '../../containers/types'; import { getManualAlertIdsWithNoRuleId, buildAlertsQuery } from './helpers'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts index 3dece29e64ac5..7211f4bca6a37 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash'; -import { CommentType } from '../../../../../case/common/api'; +import { CommentType } from '../../../../../cases/common/api'; import { Comment } from '../../containers/types'; export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index d1fa0a96a8eac..f28c7791d0110 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -28,8 +28,8 @@ import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/configure/mock'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; -import { CaseType } from '../../../../../case/common/api'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { CaseType } from '../../../../../cases/common/api'; const mockDispatch = jest.fn(); jest.mock('react-redux', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 417dc9645df30..892663c783293 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -17,7 +17,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; -import { CaseStatuses, CaseAttributes, CaseType } from '../../../../../case/common/api'; +import { CaseStatuses, CaseAttributes, CaseType } from '../../../../../cases/common/api'; import { Case, CaseConnector } from '../../containers/types'; import { getCaseDetailsUrl, getCaseUrl, useFormatUrl } from '../../../common/components/link_to'; import { gutterTimeline } from '../../../common/lib/helpers'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts index 1fbcdd455b80a..f4403a43af697 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; export const SHOWING_CASES = (actionDate: string, actionName: string, userName: string) => - i18n.translate('xpack.securitySolution.case.caseView.actionHeadline', { + i18n.translate('xpack.securitySolution.cases.caseView.actionHeadline', { values: { actionDate, actionName, @@ -20,21 +20,21 @@ export const SHOWING_CASES = (actionDate: string, actionName: string, userName: }); export const ADDED_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.addedField', + 'xpack.securitySolution.cases.caseView.actionLabel.addedField', { defaultMessage: 'added', } ); export const CHANGED_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.changededField', + 'xpack.securitySolution.cases.caseView.actionLabel.changededField', { defaultMessage: 'changed', } ); export const SELECTED_THIRD_PARTY = (thirdParty: string) => - i18n.translate('xpack.securitySolution.case.caseView.actionLabel.selectedThirdParty', { + i18n.translate('xpack.securitySolution.cases.caseView.actionLabel.selectedThirdParty', { values: { thirdParty, }, @@ -42,28 +42,28 @@ export const SELECTED_THIRD_PARTY = (thirdParty: string) => }); export const REMOVED_THIRD_PARTY = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.removedThirdParty', + 'xpack.securitySolution.cases.caseView.actionLabel.removedThirdParty', { defaultMessage: 'removed external incident management system', } ); export const EDITED_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.editedField', + 'xpack.securitySolution.cases.caseView.actionLabel.editedField', { defaultMessage: 'edited', } ); export const REMOVED_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.removedField', + 'xpack.securitySolution.cases.caseView.actionLabel.removedField', { defaultMessage: 'removed', } ); export const VIEW_INCIDENT = (incidentNumber: string) => - i18n.translate('xpack.securitySolution.case.caseView.actionLabel.viewIncident', { + i18n.translate('xpack.securitySolution.cases.caseView.actionLabel.viewIncident', { defaultMessage: 'View {incidentNumber}', values: { incidentNumber, @@ -71,87 +71,87 @@ export const VIEW_INCIDENT = (incidentNumber: string) => }); export const PUSHED_NEW_INCIDENT = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.pushedNewIncident', + 'xpack.securitySolution.cases.caseView.actionLabel.pushedNewIncident', { defaultMessage: 'pushed as new incident', } ); export const UPDATE_INCIDENT = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.updateIncident', + 'xpack.securitySolution.cases.caseView.actionLabel.updateIncident', { defaultMessage: 'updated incident', } ); export const ADDED_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.addDescription', + 'xpack.securitySolution.cases.caseView.actionLabel.addDescription', { defaultMessage: 'added description', } ); export const EDIT_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.case.caseView.edit.description', + 'xpack.securitySolution.cases.caseView.edit.description', { defaultMessage: 'Edit description', } ); -export const QUOTE = i18n.translate('xpack.securitySolution.case.caseView.edit.quote', { +export const QUOTE = i18n.translate('xpack.securitySolution.cases.caseView.edit.quote', { defaultMessage: 'Quote', }); -export const EDIT_COMMENT = i18n.translate('xpack.securitySolution.case.caseView.edit.comment', { +export const EDIT_COMMENT = i18n.translate('xpack.securitySolution.cases.caseView.edit.comment', { defaultMessage: 'Edit comment', }); -export const ON = i18n.translate('xpack.securitySolution.case.caseView.actionLabel.on', { +export const ON = i18n.translate('xpack.securitySolution.cases.caseView.actionLabel.on', { defaultMessage: 'on', }); export const ADDED_COMMENT = i18n.translate( - 'xpack.securitySolution.case.caseView.actionLabel.addComment', + 'xpack.securitySolution.cases.caseView.actionLabel.addComment', { defaultMessage: 'added comment', } ); -export const STATUS = i18n.translate('xpack.securitySolution.case.caseView.statusLabel', { +export const STATUS = i18n.translate('xpack.securitySolution.cases.caseView.statusLabel', { defaultMessage: 'Status', }); -export const CASE = i18n.translate('xpack.securitySolution.case.caseView.case', { +export const CASE = i18n.translate('xpack.securitySolution.cases.caseView.case', { defaultMessage: 'case', }); -export const COMMENT = i18n.translate('xpack.securitySolution.case.caseView.comment', { +export const COMMENT = i18n.translate('xpack.securitySolution.cases.caseView.comment', { defaultMessage: 'comment', }); -export const CASE_REFRESH = i18n.translate('xpack.securitySolution.case.caseView.caseRefresh', { +export const CASE_REFRESH = i18n.translate('xpack.securitySolution.cases.caseView.caseRefresh', { defaultMessage: 'Refresh case', }); export const EMAIL_SUBJECT = (caseTitle: string) => - i18n.translate('xpack.securitySolution.case.caseView.emailSubject', { + i18n.translate('xpack.securitySolution.cases.caseView.emailSubject', { values: { caseTitle }, defaultMessage: 'Security Case - {caseTitle}', }); export const EMAIL_BODY = (caseUrl: string) => - i18n.translate('xpack.securitySolution.case.caseView.emailBody', { + i18n.translate('xpack.securitySolution.cases.caseView.emailBody', { values: { caseUrl }, defaultMessage: 'Case reference: {caseUrl}', }); export const CHANGED_CONNECTOR_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.fieldChanged', + 'xpack.securitySolution.cases.caseView.fieldChanged', { defaultMessage: `changed connector field`, } ); -export const SYNC_ALERTS = i18n.translate('xpack.securitySolution.case.caseView.syncAlertsLabel', { +export const SYNC_ALERTS = i18n.translate('xpack.securitySolution.cases.caseView.syncAlertsLabel', { defaultMessage: `Sync alerts`, }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx index d64a52aa2a00d..ccc697a2ae84e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypes } from '../../../../../../case/common/api'; +import { ConnectorTypes } from '../../../../../../cases/common/api'; import { ActionConnector } from '../../../containers/configure/types'; import { UseConnectorsResponse } from '../../../containers/configure/use_connectors'; import { ReturnUseCaseConfigure } from '../../../containers/configure/use_configure'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx index 371ff3528f4f0..c34651c3e1dc4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx @@ -12,7 +12,7 @@ import { Connectors, Props } from './connectors'; import { TestProviders } from '../../../common/mock'; import { ConnectorsDropdown } from './connectors_dropdown'; import { connectors } from './__mock__'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; describe('Connectors', () => { let wrapper: ReactWrapper; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx index 9f75216a0425e..1e0ae95ff901c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx @@ -21,7 +21,7 @@ import * as i18n from './translations'; import { ActionConnector, CaseConnectorMapping } from '../../containers/configure/types'; import { Mapping } from './mapping'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; const EuiFormRowExtended = styled(EuiFormRow)` .euiFormRow__labelWrapper { diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx index b8eacb9dfdd91..01d975a445ab4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSuperSelect } from '@elastic/eui'; import styled from 'styled-components'; -import { ConnectorTypes } from '../../../../../case/common/api'; +import { ConnectorTypes } from '../../../../../cases/common/api'; import { ActionConnector } from '../../containers/configure/types'; import { connectorsConfiguration } from '../connectors'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx index 8e317d57dd9ac..8dbefdb731141 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx @@ -33,7 +33,7 @@ import { useConnectorsResponse, useActionTypesResponse, } from './__mock__'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; jest.mock('../../../common/lib/kibana'); jest.mock('../../containers/configure/use_connectors'); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx index 512a15db47d30..25155ff77c2d0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx @@ -10,7 +10,7 @@ import styled, { css } from 'styled-components'; import { EuiCallOut } from '@elastic/eui'; -import { SUPPORTED_CONNECTORS } from '../../../../../case/common/constants'; +import { SUPPORTED_CONNECTORS } from '../../../../../cases/common/constants'; import { useKibana } from '../../../common/lib/kibana'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useActionTypes } from '../../containers/configure/use_action_types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts index 5a08ff47f0ef6..697d5e1a7adfa 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts @@ -10,14 +10,14 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; export const INCIDENT_MANAGEMENT_SYSTEM_TITLE = i18n.translate( - 'xpack.securitySolution.case.configureCases.incidentManagementSystemTitle', + 'xpack.securitySolution.cases.configureCases.incidentManagementSystemTitle', { defaultMessage: 'Connect to external incident management system', } ); export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate( - 'xpack.securitySolution.case.configureCases.incidentManagementSystemDesc', + 'xpack.securitySolution.cases.configureCases.incidentManagementSystemDesc', { defaultMessage: 'You may optionally connect Security cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', @@ -25,28 +25,28 @@ export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate( ); export const INCIDENT_MANAGEMENT_SYSTEM_LABEL = i18n.translate( - 'xpack.securitySolution.case.configureCases.incidentManagementSystemLabel', + 'xpack.securitySolution.cases.configureCases.incidentManagementSystemLabel', { defaultMessage: 'Incident management system', } ); export const ADD_NEW_CONNECTOR = i18n.translate( - 'xpack.securitySolution.case.configureCases.addNewConnector', + 'xpack.securitySolution.cases.configureCases.addNewConnector', { defaultMessage: 'Add new connector', } ); export const CASE_CLOSURE_OPTIONS_TITLE = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsTitle', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsTitle', { defaultMessage: 'Case Closures', } ); export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsDesc', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsDesc', { defaultMessage: 'Define how you wish Security cases to be closed. Automated case closures require an established connection to an external incident management system.', @@ -54,28 +54,28 @@ export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate( ); export const CASE_COLSURE_OPTIONS_SUB_CASES = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsSubCases', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsSubCases', { defaultMessage: 'Automated closures of sub-cases is not currently supported.', } ); export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsLabel', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsLabel', { defaultMessage: 'Case closure options', } ); export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsManual', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsManual', { defaultMessage: 'Manually close Security cases', } ); export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsNewIncident', { defaultMessage: 'Automatically close Security cases when pushing new incident to external system', @@ -83,20 +83,20 @@ export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate( ); export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate( - 'xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident', + 'xpack.securitySolution.cases.configureCases.caseClosureOptionsClosedIncident', { defaultMessage: 'Automatically close Security cases when incident is closed in external system', } ); export const FIELD_MAPPING_TITLE = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.fieldMappingTitle', { + return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingTitle', { values: { thirdPartyName }, defaultMessage: '{ thirdPartyName } field mappings', }); }; export const FIELD_MAPPING_DESC = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.fieldMappingDesc', { + return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingDesc', { values: { thirdPartyName }, defaultMessage: 'Map Security Case fields to { thirdPartyName } fields when pushing data to { thirdPartyName }. Field mappings require an established connection to { thirdPartyName }.', @@ -104,85 +104,85 @@ export const FIELD_MAPPING_DESC = (thirdPartyName: string): string => { }; export const FIELD_MAPPING_DESC_ERR = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.fieldMappingDescErr', { + return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingDescErr', { values: { thirdPartyName }, defaultMessage: 'Field mappings require an established connection to { thirdPartyName }. Please check your connection credentials.', }); }; export const EDIT_FIELD_MAPPING_TITLE = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.editFieldMappingTitle', { + return i18n.translate('xpack.securitySolution.cases.configureCases.editFieldMappingTitle', { values: { thirdPartyName }, defaultMessage: 'Edit { thirdPartyName } field mappings', }); }; export const FIELD_MAPPING_FIRST_COL = i18n.translate( - 'xpack.securitySolution.case.configureCases.fieldMappingFirstCol', + 'xpack.securitySolution.cases.configureCases.fieldMappingFirstCol', { defaultMessage: 'Security case field', } ); export const FIELD_MAPPING_SECOND_COL = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.fieldMappingSecondCol', { + return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingSecondCol', { values: { thirdPartyName }, defaultMessage: '{ thirdPartyName } field', }); }; export const FIELD_MAPPING_THIRD_COL = i18n.translate( - 'xpack.securitySolution.case.configureCases.fieldMappingThirdCol', + 'xpack.securitySolution.cases.configureCases.fieldMappingThirdCol', { defaultMessage: 'On edit and update', } ); export const FIELD_MAPPING_EDIT_NOTHING = i18n.translate( - 'xpack.securitySolution.case.configureCases.fieldMappingEditNothing', + 'xpack.securitySolution.cases.configureCases.fieldMappingEditNothing', { defaultMessage: 'Nothing', } ); export const FIELD_MAPPING_EDIT_OVERWRITE = i18n.translate( - 'xpack.securitySolution.case.configureCases.fieldMappingEditOverwrite', + 'xpack.securitySolution.cases.configureCases.fieldMappingEditOverwrite', { defaultMessage: 'Overwrite', } ); export const FIELD_MAPPING_EDIT_APPEND = i18n.translate( - 'xpack.securitySolution.case.configureCases.fieldMappingEditAppend', + 'xpack.securitySolution.cases.configureCases.fieldMappingEditAppend', { defaultMessage: 'Append', } ); -export const CANCEL = i18n.translate('xpack.securitySolution.case.configureCases.cancelButton', { +export const CANCEL = i18n.translate('xpack.securitySolution.cases.configureCases.cancelButton', { defaultMessage: 'Cancel', }); -export const SAVE = i18n.translate('xpack.securitySolution.case.configureCases.saveButton', { +export const SAVE = i18n.translate('xpack.securitySolution.cases.configureCases.saveButton', { defaultMessage: 'Save', }); export const SAVE_CLOSE = i18n.translate( - 'xpack.securitySolution.case.configureCases.saveAndCloseButton', + 'xpack.securitySolution.cases.configureCases.saveAndCloseButton', { defaultMessage: 'Save & close', } ); export const WARNING_NO_CONNECTOR_TITLE = i18n.translate( - 'xpack.securitySolution.case.configureCases.warningTitle', + 'xpack.securitySolution.cases.configureCases.warningTitle', { defaultMessage: 'Warning', } ); export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate( - 'xpack.securitySolution.case.configureCases.warningMessage', + 'xpack.securitySolution.cases.configureCases.warningMessage', { defaultMessage: 'The selected connector has been deleted. Either select a different connector or create a new one.', @@ -190,18 +190,21 @@ export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate( ); export const MAPPING_FIELD_NOT_MAPPED = i18n.translate( - 'xpack.securitySolution.case.configureCases.mappingFieldNotMapped', + 'xpack.securitySolution.cases.configureCases.mappingFieldNotMapped', { defaultMessage: 'Not mapped', } ); -export const COMMENT = i18n.translate('xpack.securitySolution.case.configureCases.commentMapping', { - defaultMessage: 'Comments', -}); +export const COMMENT = i18n.translate( + 'xpack.securitySolution.cases.configureCases.commentMapping', + { + defaultMessage: 'Comments', + } +); export const NO_FIELDS_ERROR = (connectorName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.noFieldsError', { + return i18n.translate('xpack.securitySolution.cases.configureCases.noFieldsError', { values: { connectorName }, defaultMessage: 'No { connectorName } fields found. Please check your { connectorName } connector settings or your { connectorName } instance settings to resolve.', @@ -209,28 +212,28 @@ export const NO_FIELDS_ERROR = (connectorName: string): string => { }; export const BLANK_MAPPINGS = (connectorName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.blankMappings', { + return i18n.translate('xpack.securitySolution.cases.configureCases.blankMappings', { values: { connectorName }, defaultMessage: 'At least one field needs to be mapped to { connectorName }', }); }; export const REQUIRED_MAPPINGS = (connectorName: string, fields: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.requiredMappings', { + return i18n.translate('xpack.securitySolution.cases.configureCases.requiredMappings', { values: { connectorName, fields }, defaultMessage: 'At least one Case field needs to be mapped to the following required { connectorName } fields: { fields }', }); }; export const UPDATE_FIELD_MAPPINGS = i18n.translate( - 'xpack.securitySolution.case.configureCases.updateConnector', + 'xpack.securitySolution.cases.configureCases.updateConnector', { defaultMessage: 'Update field mappings', } ); export const UPDATE_SELECTED_CONNECTOR = (connectorName: string): string => { - return i18n.translate('xpack.securitySolution.case.configureCases.updateSelectedConnector', { + return i18n.translate('xpack.securitySolution.cases.configureCases.updateSelectedConnector', { values: { connectorName }, defaultMessage: 'Update { connectorName }', }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts index 6f4bc06265bac..db14371b625d8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypeFields, ConnectorTypes } from '../../../../../case/common/api'; +import { ConnectorTypeFields, ConnectorTypes } from '../../../../../cases/common/api'; import { CaseField, ActionType, diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts index 0bd37fa18281a..07bf6966e953c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts @@ -9,32 +9,32 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; export const DELETE_TITLE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.case.confirmDeleteCase.deleteTitle', { + i18n.translate('xpack.securitySolution.cases.confirmDeleteCase.deleteTitle', { values: { caseTitle }, defaultMessage: 'Delete "{caseTitle}"', }); export const DELETE_THIS_CASE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.case.confirmDeleteCase.deleteThisCase', { + i18n.translate('xpack.securitySolution.cases.confirmDeleteCase.deleteThisCase', { defaultMessage: 'Delete this case', }); export const CONFIRM_QUESTION = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.confirmQuestion', + 'xpack.securitySolution.cases.confirmDeleteCase.confirmQuestion', { defaultMessage: 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', } ); export const DELETE_SELECTED_CASES = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.selectedCases', + 'xpack.securitySolution.cases.confirmDeleteCase.selectedCases', { defaultMessage: 'Delete selected cases', } ); export const CONFIRM_QUESTION_PLURAL = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.confirmQuestionPlural', + 'xpack.securitySolution.cases.confirmDeleteCase.confirmQuestionPlural', { defaultMessage: 'By deleting these cases, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', diff --git a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx index 586a7c19cc532..63c6f265b1ab2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx @@ -11,7 +11,7 @@ import { EuiFormRow } from '@elastic/eui'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../shared_imports'; import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown'; -import { ActionConnector } from '../../../../../case/common/api'; +import { ActionConnector } from '../../../../../cases/common/api'; interface ConnectorSelectorProps { connectors: ActionConnector[]; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx index 03f909948370d..af9a86b0b711b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx @@ -10,7 +10,7 @@ import { EuiCard, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; import { connectorsConfiguration } from '.'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; interface ConnectorCardProps { connectorType: ConnectorTypes; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx index b829cefcacca6..05161456976c6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { ActionParamsProps } from '../../../../../../triggers_actions_ui/public/types'; -import { CommentType } from '../../../../../../case/common/api'; +import { CommentType } from '../../../../../../cases/common/api'; import { CaseActionParams } from './types'; import { ExistingCase } from './existing_case'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx index c1013718d5756..3c6c5f47c6d12 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useMemo, useCallback } from 'react'; -import { CaseType } from '../../../../../../case/common/api'; +import { CaseType } from '../../../../../../cases/common/api'; import { useGetCases, DEFAULT_QUERY_PARAMS, diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts index 6ce5316d0eb88..1d15a3da496a6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts @@ -10,77 +10,77 @@ import { i18n } from '@kbn/i18n'; export * from '../../../translations'; export const CASE_CONNECTOR_DESC = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.selectMessageText', + 'xpack.securitySolution.cases.components.connectors.cases.selectMessageText', { defaultMessage: 'Create or update a case.', } ); export const CASE_CONNECTOR_TITLE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.actionTypeTitle', + 'xpack.securitySolution.cases.components.connectors.cases.actionTypeTitle', { defaultMessage: 'Cases', } ); export const CASE_CONNECTOR_COMMENT_LABEL = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.commentLabel', + 'xpack.securitySolution.cases.components.connectors.cases.commentLabel', { defaultMessage: 'Comment', } ); export const CASE_CONNECTOR_COMMENT_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.commentRequired', + 'xpack.securitySolution.cases.components.connectors.cases.commentRequired', { defaultMessage: 'Comment is required.', } ); export const CASE_CONNECTOR_CASES_DROPDOWN_ROW_LABEL = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.casesDropdownRowLabel', + 'xpack.securitySolution.cases.components.connectors.cases.casesDropdownRowLabel', { defaultMessage: 'Case allowing sub-cases', } ); export const CASE_CONNECTOR_CASES_DROPDOWN_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.casesDropdownPlaceholder', + 'xpack.securitySolution.cases.components.connectors.cases.casesDropdownPlaceholder', { defaultMessage: 'Select case', } ); export const CASE_CONNECTOR_CASES_OPTION_NEW_CASE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.optionAddNewCase', + 'xpack.securitySolution.cases.components.connectors.cases.optionAddNewCase', { defaultMessage: 'Add to a new case', } ); export const CASE_CONNECTOR_CASES_OPTION_EXISTING_CASE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.optionAddToExistingCase', + 'xpack.securitySolution.cases.components.connectors.cases.optionAddToExistingCase', { defaultMessage: 'Add to existing case', } ); export const CASE_CONNECTOR_CASE_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.caseRequired', + 'xpack.securitySolution.cases.components.connectors.cases.caseRequired', { defaultMessage: 'You must select a case.', } ); export const CASE_CONNECTOR_CALL_OUT_TITLE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.callOutTitle', + 'xpack.securitySolution.cases.components.connectors.cases.callOutTitle', { defaultMessage: 'Generated alerts will be attached to sub-cases', } ); export const CASE_CONNECTOR_CALL_OUT_MSG = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.callOutMsg', + 'xpack.securitySolution.cases.components.connectors.cases.callOutMsg', { defaultMessage: 'A case can contain multiple sub-cases to allow grouping of generated alerts. Sub-cases will give more granular control over the status of these generated alerts and prevents having too many alerts attached to one case.', @@ -88,21 +88,21 @@ export const CASE_CONNECTOR_CALL_OUT_MSG = i18n.translate( ); export const CASE_CONNECTOR_ADD_NEW_CASE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.addNewCaseOption', + 'xpack.securitySolution.cases.components.connectors.cases.addNewCaseOption', { defaultMessage: 'Add new case', } ); export const CREATE_CASE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.createCaseLabel', + 'xpack.securitySolution.cases.components.connectors.cases.createCaseLabel', { defaultMessage: 'Create case', } ); export const CONNECTED_CASE = i18n.translate( - 'xpack.securitySolution.case.components.connectors.case.connectedCaseLabel', + 'xpack.securitySolution.cases.components.connectors.cases.connectedCaseLabel', { defaultMessage: 'Connected case', } diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx index 41ed99e0f6768..841c2a9e38f6d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { CaseActionConnector, ConnectorFieldsProps } from './types'; import { getCaseConnectors } from '.'; -import { ConnectorTypeFields } from '../../../../../case/common/api/connectors'; +import { ConnectorTypeFields } from '../../../../../cases/common/api/connectors'; interface Props extends Omit, 'connector'> { connector: CaseActionConnector | null; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts index 267126fc6ec8b..dad7070aad705 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts @@ -15,7 +15,7 @@ import { ServiceNowITSMFieldsType, ServiceNowSIRFieldsType, ResilientFieldsType, -} from '../../../../../case/common/api/connectors'; +} from '../../../../../cases/common/api/connectors'; export { getActionType as getCaseConnectorUI } from './case'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx index d768b552b78b4..22e80d43f34e1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx @@ -10,7 +10,7 @@ import { map } from 'lodash/fp'; import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import * as i18n from './translations'; -import { ConnectorTypes, JiraFieldsType } from '../../../../../../case/common/api/connectors'; +import { ConnectorTypes, JiraFieldsType } from '../../../../../../cases/common/api/connectors'; import { useKibana } from '../../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { useGetIssueTypes } from './use_get_issue_types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts index d05040f7406e4..40e59a081a449 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { JiraFieldsType } from '../../../../../../case/common/api/connectors'; +import { JiraFieldsType } from '../../../../../../cases/common/api/connectors'; import * as i18n from './translations'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts index 07f8f5b984cdd..a4948d61f952c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts @@ -56,21 +56,21 @@ export const SEARCH_ISSUES_LOADING = i18n.translate( ); export const PRIORITY = i18n.translate( - 'xpack.securitySolution.case.connectors.jira.prioritySelectFieldLabel', + 'xpack.securitySolution.cases.connectors.jira.prioritySelectFieldLabel', { defaultMessage: 'Priority', } ); export const ISSUE_TYPE = i18n.translate( - 'xpack.securitySolution.case.connectors.jira.issueTypesSelectFieldLabel', + 'xpack.securitySolution.cases.connectors.jira.issueTypesSelectFieldLabel', { defaultMessage: 'Issue type', } ); export const PARENT_ISSUE = i18n.translate( - 'xpack.securitySolution.case.connectors.jira.parentIssueSearchLabel', + 'xpack.securitySolution.cases.connectors.jira.parentIssueSearchLabel', { defaultMessage: 'Parent issue', } diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx index 8c62f5285c257..b1fbfb1169d08 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx @@ -21,7 +21,7 @@ import { useGetIncidentTypes } from './use_get_incident_types'; import { useGetSeverity } from './use_get_severity'; import * as i18n from './translations'; -import { ConnectorTypes, ResilientFieldsType } from '../../../../../../case/common/api/connectors'; +import { ConnectorTypes, ResilientFieldsType } from '../../../../../../cases/common/api/connectors'; import { ConnectorCard } from '../card'; const ResilientFieldsComponent: React.FunctionComponent< diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts index fd1ed428abcc4..8a2603f39e102 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { ResilientFieldsType } from '../../../../../../case/common/api/connectors'; +import { ResilientFieldsType } from '../../../../../../cases/common/api/connectors'; import * as i18n from './translations'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts index 32a72c3803708..4f8061f48aa68 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts @@ -8,35 +8,35 @@ import { i18n } from '@kbn/i18n'; export const INCIDENT_TYPES_API_ERROR = i18n.translate( - 'xpack.securitySolution.case.connectors.resilient.unableToGetIncidentTypesMessage', + 'xpack.securitySolution.cases.connectors.resilient.unableToGetIncidentTypesMessage', { defaultMessage: 'Unable to get incident types', } ); export const SEVERITY_API_ERROR = i18n.translate( - 'xpack.securitySolution.case.connectors.resilient.unableToGetSeverityMessage', + 'xpack.securitySolution.cases.connectors.resilient.unableToGetSeverityMessage', { defaultMessage: 'Unable to get severity', } ); export const INCIDENT_TYPES_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.case.connectors.resilient.incidentTypesPlaceholder', + 'xpack.securitySolution.cases.connectors.resilient.incidentTypesPlaceholder', { defaultMessage: 'Choose types', } ); export const INCIDENT_TYPES_LABEL = i18n.translate( - 'xpack.securitySolution.case.connectors.resilient.incidentTypesLabel', + 'xpack.securitySolution.cases.connectors.resilient.incidentTypesLabel', { defaultMessage: 'Incident Types', } ); export const SEVERITY_LABEL = i18n.translate( - 'xpack.securitySolution.case.connectors.resilient.severityLabel', + 'xpack.securitySolution.cases.connectors.resilient.severityLabel', { defaultMessage: 'Severity', } diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts index 81bd81124599f..b342095c39ff0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts @@ -11,7 +11,7 @@ import { CaseConnector } from '../types'; import { ServiceNowITSMFieldsType, ServiceNowSIRFieldsType, -} from '../../../../../../case/common/api/connectors'; +} from '../../../../../../cases/common/api/connectors'; import * as i18n from './translations'; export const getServiceNowITSMCaseConnector = (): CaseConnector => { diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx index 1fe592cfdebc4..accb8450802d4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx @@ -13,7 +13,7 @@ import { ConnectorFieldsProps } from '../types'; import { ConnectorTypes, ServiceNowITSMFieldsType, -} from '../../../../../../case/common/api/connectors'; +} from '../../../../../../cases/common/api/connectors'; import { useKibana } from '../../../../common/lib/kibana'; import { ConnectorCard } from '../card'; import { useGetChoices } from './use_get_choices'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx index 066b44fd5bc02..63502e3454fcf 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx @@ -11,7 +11,7 @@ import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiCheckbox } from '@ import { ConnectorTypes, ServiceNowSIRFieldsType, -} from '../../../../../../case/common/api/connectors'; +} from '../../../../../../cases/common/api/connectors'; import { useKibana } from '../../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { ConnectorCard } from '../card'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts index 46c707197fdb4..11452b966670b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts @@ -12,9 +12,9 @@ import { CaseField, ActionConnector, ConnectorTypeFields, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; -export { ThirdPartyField as AllThirdPartyFields } from '../../../../../case/common/api'; +export { ThirdPartyField as AllThirdPartyFields } from '../../../../../cases/common/api'; export type CaseActionConnector = ActionConnector; export interface ThirdPartyField { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx index bfe0d8dd78e28..7912d97528cd2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ConnectorTypes } from '../../../../../case/common/api'; +import { ConnectorTypes } from '../../../../../cases/common/api'; import { UseField, useFormData, FieldHook, useFormContext } from '../../../shared_imports'; import { useConnectors } from '../../containers/configure/use_connectors'; import { ConnectorSelector } from '../connector_selector/form'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx index 1e512ef5ffabd..99626c4cfb797 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx @@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { ConnectorTypes } from '../../../../../case/common/api'; +import { ConnectorTypes } from '../../../../../cases/common/api'; import { TestProviders } from '../../../common/mock'; import { usePostCase } from '../../containers/use_post_case'; import { useGetTags } from '../../containers/use_get_tags'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx index f56dcafdc95e4..b575dfe42f074 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx @@ -19,7 +19,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import { useConnectors } from '../../containers/configure/use_connectors'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { Case } from '../../containers/types'; -import { CaseType, ConnectorTypes } from '../../../../../case/common/api'; +import { CaseType, ConnectorTypes } from '../../../../../cases/common/api'; const initialCaseValue: FormProps = { description: '', diff --git a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts b/x-pack/plugins/security_solution/public/cases/components/create/mock.ts index 81a7fe9cd9387..6e17be8d53e5a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/components/create/mock.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { CasePostRequest, CaseType } from '../../../../../case/common/api'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { CasePostRequest, CaseType } from '../../../../../cases/common/api'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; import { choices } from '../connectors/mock'; export const sampleTags = ['coke', 'pepsi']; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx index d48c978dfebcb..b069a484d314c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { CasePostRequest, ConnectorTypeFields } from '../../../../../case/common/api'; +import { CasePostRequest, ConnectorTypeFields } from '../../../../../cases/common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx index d0f478dc17f81..f76adfd2a840f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx @@ -21,9 +21,9 @@ import styled from 'styled-components'; import { noop } from 'lodash/fp'; import { Form, UseField, useForm } from '../../../shared_imports'; -import { ConnectorTypeFields } from '../../../../../case/common/api/connectors'; +import { ConnectorTypeFields } from '../../../../../cases/common/api/connectors'; import { ConnectorSelector } from '../connector_selector/form'; -import { ActionConnector } from '../../../../../case/common/api'; +import { ActionConnector } from '../../../../../cases/common/api'; import { ConnectorFieldsForm } from '../connectors/fields_form'; import { getConnectorById } from '../configure_cases/utils'; import { CaseUserActions } from '../../containers/types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts b/x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts index b2d2c1c8420b9..12fa0d1855062 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; export const EDIT_CONNECTOR_ARIA = i18n.translate( - 'xpack.securitySolution.case.editConnector.editConnectorLinkAria', + 'xpack.securitySolution.cases.editConnector.editConnectorLinkAria', { defaultMessage: 'click to edit connector', } diff --git a/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts b/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts index e2b9b321be321..c5c11e0637d7b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const ACTIONS_ARIA = i18n.translate( - 'xpack.securitySolution.case.caseView.editActionsLinkAria', + 'xpack.securitySolution.cases.caseView.editActionsLinkAria', { defaultMessage: 'click to see all actions', } diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx index 22d72429836de..6bf4eb95bc049 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { StatusActionButton } from './button'; describe('StatusActionButton', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx b/x-pack/plugins/security_solution/public/cases/components/status/button.tsx index 4ee69766fe128..5a0d98fc8a11a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/button.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useMemo } from 'react'; import { EuiButton } from '@elastic/eui'; -import { CaseStatuses, caseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses, caseStatuses } from '../../../../../cases/common/api'; import { statuses } from './config'; interface Props { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/config.ts b/x-pack/plugins/security_solution/public/cases/components/status/config.ts index eab4bebedf064..47a74549f03cc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/config.ts +++ b/x-pack/plugins/security_solution/public/cases/components/status/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import * as i18n from './translations'; import { AllCaseStatus, Statuses, StatusAll } from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx b/x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx index 023b331b4db45..266ceb04e4335 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { Stats } from './stats'; describe('Stats', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/stats.tsx b/x-pack/plugins/security_solution/public/cases/components/status/stats.tsx index 572a169f64cf6..43001c2cf5947 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/stats.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/stats.tsx @@ -7,7 +7,7 @@ import React, { memo, useMemo } from 'react'; import { EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { statuses } from './config'; export interface Props { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx b/x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx index 388ceadd532a3..eff9d73c2adf9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { Status } from './status'; describe('Stats', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/translations.ts b/x-pack/plugins/security_solution/public/cases/components/status/translations.ts index 00dc5d3333f15..6c26513785026 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/status/translations.ts @@ -8,64 +8,64 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; -export const ALL = i18n.translate('xpack.securitySolution.case.status.all', { +export const ALL = i18n.translate('xpack.securitySolution.cases.status.all', { defaultMessage: 'All', }); -export const OPEN = i18n.translate('xpack.securitySolution.case.status.open', { +export const OPEN = i18n.translate('xpack.securitySolution.cases.status.open', { defaultMessage: 'Open', }); -export const IN_PROGRESS = i18n.translate('xpack.securitySolution.case.status.inProgress', { +export const IN_PROGRESS = i18n.translate('xpack.securitySolution.cases.status.inProgress', { defaultMessage: 'In progress', }); -export const CLOSED = i18n.translate('xpack.securitySolution.case.status.closed', { +export const CLOSED = i18n.translate('xpack.securitySolution.cases.status.closed', { defaultMessage: 'Closed', }); -export const STATUS_ICON_ARIA = i18n.translate('xpack.securitySolution.case.status.iconAria', { +export const STATUS_ICON_ARIA = i18n.translate('xpack.securitySolution.cases.status.iconAria', { defaultMessage: 'Change status', }); -export const CASE_OPENED = i18n.translate('xpack.securitySolution.case.caseView.caseOpened', { +export const CASE_OPENED = i18n.translate('xpack.securitySolution.cases.caseView.caseOpened', { defaultMessage: 'Case opened', }); export const CASE_IN_PROGRESS = i18n.translate( - 'xpack.securitySolution.case.caseView.caseInProgress', + 'xpack.securitySolution.cases.caseView.caseInProgress', { defaultMessage: 'Case in progress', } ); -export const CASE_CLOSED = i18n.translate('xpack.securitySolution.case.caseView.caseClosed', { +export const CASE_CLOSED = i18n.translate('xpack.securitySolution.cases.caseView.caseClosed', { defaultMessage: 'Case closed', }); export const BULK_ACTION_CLOSE_SELECTED = i18n.translate( - 'xpack.securitySolution.case.caseTable.bulkActions.closeSelectedTitle', + 'xpack.securitySolution.cases.caseTable.bulkActions.closeSelectedTitle', { defaultMessage: 'Close selected', } ); export const BULK_ACTION_OPEN_SELECTED = i18n.translate( - 'xpack.securitySolution.case.caseTable.bulkActions.openSelectedTitle', + 'xpack.securitySolution.cases.caseTable.bulkActions.openSelectedTitle', { defaultMessage: 'Open selected', } ); export const BULK_ACTION_DELETE_SELECTED = i18n.translate( - 'xpack.securitySolution.case.caseTable.bulkActions.deleteSelectedTitle', + 'xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle', { defaultMessage: 'Delete selected', } ); export const BULK_ACTION_MARK_IN_PROGRESS = i18n.translate( - 'xpack.securitySolution.case.caseTable.bulkActions.markInProgressTitle', + 'xpack.securitySolution.cases.caseTable.bulkActions.markInProgressTitle', { defaultMessage: 'Mark in progress', } diff --git a/x-pack/plugins/security_solution/public/cases/components/status/types.ts b/x-pack/plugins/security_solution/public/cases/components/status/types.ts index 6f642b281419b..5618e7802579d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/types.ts +++ b/x-pack/plugins/security_solution/public/cases/components/status/types.ts @@ -6,7 +6,7 @@ */ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; export const StatusAll = 'all' as const; type StatusAllType = typeof StatusAll; diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts b/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts index e01175c5fc8ac..4bddfbdbc1a85 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export * from '../../translations'; export const EDIT_TAGS_ARIA = i18n.translate( - 'xpack.securitySolution.case.caseView.editTagsLinkAria', + 'xpack.securitySolution.cases.caseView.editTagsLinkAria', { defaultMessage: 'click to edit tags', } diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 4a4420a164d0c..decd37a7646e7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -16,7 +16,7 @@ import { EuiToolTip, } from '@elastic/eui'; -import { CommentType, CaseStatuses } from '../../../../../case/common/api'; +import { CommentType, CaseStatuses } from '../../../../../cases/common/api'; import { Ecs } from '../../../../common/ecs'; import { ActionIconItem } from '../../../timelines/components/timeline/body/actions/action_icon_item'; import { usePostComment } from '../../containers/use_post_comment'; diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts index 7c1437ede9ce5..6e878d88357ab 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts @@ -8,62 +8,62 @@ import { i18n } from '@kbn/i18n'; export const ACTION_ADD_CASE = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.addCase', + 'xpack.securitySolution.cases.timeline.actions.addCase', { defaultMessage: 'Add to case', } ); export const ACTION_ADD_NEW_CASE = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.addNewCase', + 'xpack.securitySolution.cases.timeline.actions.addNewCase', { defaultMessage: 'Add to new case', } ); export const ACTION_ADD_EXISTING_CASE = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.addExistingCase', + 'xpack.securitySolution.cases.timeline.actions.addExistingCase', { defaultMessage: 'Add to existing case', } ); export const ACTION_ADD_TO_CASE_ARIA_LABEL = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.addToCaseAriaLabel', + 'xpack.securitySolution.cases.timeline.actions.addToCaseAriaLabel', { defaultMessage: 'Attach alert to case', } ); export const ACTION_ADD_TO_CASE_TOOLTIP = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.addToCaseTooltip', + 'xpack.securitySolution.cases.timeline.actions.addToCaseTooltip', { defaultMessage: 'Add to case', } ); export const CASE_CREATED_SUCCESS_TOAST = (title: string) => - i18n.translate('xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToast', { + i18n.translate('xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast', { values: { title }, defaultMessage: 'An alert has been added to "{title}"', }); export const CASE_CREATED_SUCCESS_TOAST_TEXT = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastText', + 'xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText', { defaultMessage: 'Alerts in this case have their status synched with the case status', } ); export const VIEW_CASE = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastViewCaseLink', + 'xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink', { defaultMessage: 'View Case', } ); export const PERMISSIONS_MSG = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.permissionsMessage', + 'xpack.securitySolution.cases.timeline.actions.permissionsMessage', { defaultMessage: 'You are currently missing the required permissions to attach alerts to cases. Please contact your administrator for further assistance.', @@ -71,7 +71,7 @@ export const PERMISSIONS_MSG = i18n.translate( ); export const UNSUPPORTED_EVENTS_MSG = i18n.translate( - 'xpack.securitySolution.case.timeline.actions.unsupportedEventsMessage', + 'xpack.securitySolution.cases.timeline.actions.unsupportedEventsMessage', { defaultMessage: 'This event cannot be attached to a case', } diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx index e1d6baa6e630a..10ad3d35004ba 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; import { useGetUserSavedObjectPermissions } from '../../../common/lib/kibana'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { Case, SubCase } from '../../containers/types'; import { AllCases } from '../all_cases'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx index 52b8ebe0210c0..0b30f6ac94e03 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback, useMemo } from 'react'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { Case, SubCase } from '../../containers/types'; import { AllCasesModal } from './all_cases_modal'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts index ccf089540bb2d..36db3c631100f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts @@ -6,6 +6,6 @@ */ import { i18n } from '@kbn/i18n'; -export const SELECT_CASE_TITLE = i18n.translate('xpack.securitySolution.case.caseModal.title', { +export const SELECT_CASE_TITLE = i18n.translate('xpack.securitySolution.cases.caseModal.title', { defaultMessage: 'Select case', }); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx index b1edaa56cd348..4b5eb00d95a80 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx @@ -14,7 +14,7 @@ import { CreateCaseForm } from '../create/form'; import { SubmitCaseButton } from '../create/submit_button'; import { Case } from '../../containers/types'; import * as i18n from '../../translations'; -import { CaseType } from '../../../../../case/common/api'; +import { CaseType } from '../../../../../cases/common/api'; export interface CreateCaseModalProps { isModalOpen: boolean; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx index 50887f08dee6e..5d2f54bd1f142 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback, useMemo } from 'react'; -import { CaseType } from '../../../../../case/common/api'; +import { CaseType } from '../../../../../cases/common/api'; import { Case } from '../../containers/types'; import { CreateCaseModal } from './create_case_modal'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx index 9c34afb1349e4..30d2cb720c031 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx @@ -19,7 +19,7 @@ export const getLicenseError = () => ({ description: ( @@ -42,7 +42,7 @@ export const getKibanaConfigError = () => ({ description: ( diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx index b8048afb083f1..c058473bbfe3f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx @@ -13,12 +13,12 @@ import '../../../common/mock/match_media'; import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; import { TestProviders } from '../../../common/mock'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { basicPush, actionLicenses } from '../../containers/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { connectorsMock } from '../../containers/configure/mock'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx index 21067a3e69969..d83ddb08b51d2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx @@ -17,7 +17,7 @@ import { getConfigureCasesUrl, useFormatUrl } from '../../../common/components/l import { CaseCallOut } from '../callout'; import { getLicenseError, getKibanaConfigError } from './helpers'; import * as i18n from './translations'; -import { CaseConnector, ActionConnector, CaseStatuses } from '../../../../../case/common/api'; +import { CaseConnector, ActionConnector, CaseStatuses } from '../../../../../cases/common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { LinkAnchor } from '../../../common/components/links'; import { SecurityPageName } from '../../../app/types'; @@ -90,7 +90,7 @@ export const usePushToService = ({ description: ( ), }, @@ -129,7 +129,7 @@ export const usePushToService = ({ description: ( ), errorType: 'danger', @@ -145,7 +145,7 @@ export const usePushToService = ({ description: ( ), }, diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts index bb28700121f0d..28a7312328b78 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts @@ -8,19 +8,19 @@ import { i18n } from '@kbn/i18n'; export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.errorsPushServiceCallOutTitle', + 'xpack.securitySolution.cases.caseView.errorsPushServiceCallOutTitle', { defaultMessage: 'To send cases to external systems, you need to:', } ); export const PUSH_THIRD = (thirdParty: string) => { if (thirdParty === 'none') { - return i18n.translate('xpack.securitySolution.case.caseView.pushThirdPartyIncident', { + return i18n.translate('xpack.securitySolution.cases.caseView.pushThirdPartyIncident', { defaultMessage: 'Push as external incident', }); } - return i18n.translate('xpack.securitySolution.case.caseView.pushNamedIncident', { + return i18n.translate('xpack.securitySolution.cases.caseView.pushNamedIncident', { values: { thirdParty }, defaultMessage: 'Push as { thirdParty } incident', }); @@ -28,68 +28,68 @@ export const PUSH_THIRD = (thirdParty: string) => { export const UPDATE_THIRD = (thirdParty: string) => { if (thirdParty === 'none') { - return i18n.translate('xpack.securitySolution.case.caseView.updateThirdPartyIncident', { + return i18n.translate('xpack.securitySolution.cases.caseView.updateThirdPartyIncident', { defaultMessage: 'Update external incident', }); } - return i18n.translate('xpack.securitySolution.case.caseView.updateNamedIncident', { + return i18n.translate('xpack.securitySolution.cases.caseView.updateNamedIncident', { values: { thirdParty }, defaultMessage: 'Update { thirdParty } incident', }); }; export const PUSH_DISABLE_BY_NO_CONFIG_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.pushToServiceDisableByNoConfigTitle', + 'xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConfigTitle', { defaultMessage: 'Configure external connector', } ); export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.pushToServiceDisableByNoCaseConfigTitle', + 'xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigTitle', { defaultMessage: 'Select external connector', } ); export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.pushToServiceDisableBecauseCaseClosedTitle', + 'xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle', { defaultMessage: 'Reopen the case', } ); export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.pushToServiceDisableByConfigTitle', + 'xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigTitle', { defaultMessage: 'Enable external service in Kibana configuration file', } ); export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.pushToServiceDisableByLicenseTitle', + 'xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseTitle', { defaultMessage: 'Upgrade to an appropriate license', } ); export const LINK_CLOUD_DEPLOYMENT = i18n.translate( - 'xpack.securitySolution.case.caseView.cloudDeploymentLink', + 'xpack.securitySolution.cases.caseView.cloudDeploymentLink', { defaultMessage: 'cloud deployment', } ); export const LINK_APPROPRIATE_LICENSE = i18n.translate( - 'xpack.securitySolution.case.caseView.appropiateLicense', + 'xpack.securitySolution.cases.caseView.appropiateLicense', { defaultMessage: 'appropriate license', } ); export const LINK_CONNECTOR_CONFIGURE = i18n.translate( - 'xpack.securitySolution.case.caseView.connectorConfigureLink', + 'xpack.securitySolution.cases.caseView.connectorConfigureLink', { defaultMessage: 'connector', } diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx index b861016944b8d..a62c6c0ef682d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses } from '../../../../../cases/common/api'; import { basicPush, getUserAction } from '../../containers/mock'; import { getLabelTitle, getPushedServiceLabelTitle, getConnectorLabelTitle } from './helpers'; import { connectorsMock } from '../../containers/configure/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx index be32c1c5dd036..cc8d560f91b1f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx @@ -15,7 +15,7 @@ import { ActionConnector, CaseStatuses, CommentType, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; import { CaseUserActions } from '../../containers/types'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx index bc7961b75a30a..f8d6872a4b740 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx @@ -30,7 +30,7 @@ import { AlertCommentRequestRt, CommentType, ContextTypeUserRt, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; import { OnUpdateFields } from '../case_view'; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts index 46f36615b1a4e..8218712fb359f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts @@ -10,74 +10,74 @@ import { i18n } from '@kbn/i18n'; export * from '../case_view/translations'; export const ALREADY_PUSHED_TO_SERVICE = (externalService: string) => - i18n.translate('xpack.securitySolution.case.caseView.alreadyPushedToExternalService', { + i18n.translate('xpack.securitySolution.cases.caseView.alreadyPushedToExternalService', { values: { externalService }, defaultMessage: 'Already pushed to { externalService } incident', }); export const REQUIRED_UPDATE_TO_SERVICE = (externalService: string) => - i18n.translate('xpack.securitySolution.case.caseView.requiredUpdateToExternalService', { + i18n.translate('xpack.securitySolution.cases.caseView.requiredUpdateToExternalService', { values: { externalService }, defaultMessage: 'Requires update to { externalService } incident', }); export const COPY_REFERENCE_LINK = i18n.translate( - 'xpack.securitySolution.case.caseView.copyCommentLinkAria', + 'xpack.securitySolution.cases.caseView.copyCommentLinkAria', { defaultMessage: 'Copy reference link', } ); export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate( - 'xpack.securitySolution.case.caseView.moveToCommentAria', + 'xpack.securitySolution.cases.caseView.moveToCommentAria', { defaultMessage: 'Highlight the referenced comment', } ); export const ALERT_COMMENT_LABEL_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.alertCommentLabelTitle', + 'xpack.securitySolution.cases.caseView.alertCommentLabelTitle', { defaultMessage: 'added an alert from', } ); export const GENERATED_ALERT_COMMENT_LABEL_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseView.generatedAlertCommentLabelTitle', + 'xpack.securitySolution.cases.caseView.generatedAlertCommentLabelTitle', { defaultMessage: 'were added from', } ); export const GENERATED_ALERT_COUNT_COMMENT_LABEL_TITLE = (totalCount: number) => - i18n.translate('xpack.securitySolution.case.caseView.generatedAlertCountCommentLabelTitle', { + i18n.translate('xpack.securitySolution.cases.caseView.generatedAlertCountCommentLabelTitle', { values: { totalCount }, defaultMessage: `{totalCount} {totalCount, plural, =1 {alert} other {alerts}}`, }); export const ALERT_RULE_DELETED_COMMENT_LABEL = i18n.translate( - 'xpack.securitySolution.case.caseView.alertRuleDeletedLabelTitle', + 'xpack.securitySolution.cases.caseView.alertRuleDeletedLabelTitle', { defaultMessage: 'added an alert', } ); export const SHOW_ALERT_TOOLTIP = i18n.translate( - 'xpack.securitySolution.case.caseView.showAlertTooltip', + 'xpack.securitySolution.cases.caseView.showAlertTooltip', { defaultMessage: 'Show alert details', } ); export const SEND_ALERT_TO_TIMELINE = i18n.translate( - 'xpack.securitySolution.case.caseView.sendAlertToTimelineTooltip', + 'xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip', { defaultMessage: 'Investigate in timeline', } ); export const UNKNOWN_RULE = i18n.translate( - 'xpack.securitySolution.case.caseView.unknownRule.label', + 'xpack.securitySolution.cases.caseView.unknownRule.label', { defaultMessage: 'Unknown rule', } diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx index f4f610d07e2ff..3bfdf2d2c5e62 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx @@ -11,7 +11,7 @@ import { mount } from 'enzyme'; import { TestProviders } from '../../../common/mock'; import { useKibana } from '../../../common/lib/kibana'; import { AlertCommentEvent } from './user_action_alert_comment_event'; -import { CommentType } from '../../../../../case/common/api'; +import { CommentType } from '../../../../../cases/common/api'; const props = { alertId: 'alert-id-1', diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx index 57c366d412660..a72bebbaf0999 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx @@ -15,7 +15,7 @@ import { getRuleDetailsUrl, useFormatUrl } from '../../../common/components/link import { SecurityPageName } from '../../../app/types'; import * as i18n from './translations'; -import { CommentType } from '../../../../../case/common/api'; +import { CommentType } from '../../../../../cases/common/api'; import { LinkAnchor } from '../../../common/components/links'; interface Props { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts index 8cea311e3b552..81d2c7d50e5d7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const SEND_EMAIL_ARIA = (user: string) => - i18n.translate('xpack.securitySolution.case.caseView.sendEmalLinkAria', { + i18n.translate('xpack.securitySolution.cases.caseView.sendEmalLinkAria', { values: { user }, defaultMessage: 'click to send an email to {user}', }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts b/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts index ab761309fa6ad..11ae4fd6bf178 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts @@ -33,7 +33,7 @@ import { CommentRequest, User, CaseStatuses, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; export const getCase = async ( caseId: string, diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx index 01f1ba173d5be..e6ecf45097a1a 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx @@ -7,8 +7,8 @@ import { KibanaServices } from '../../common/lib/kibana'; -import { ConnectorTypes, CommentType, CaseStatuses } from '../../../../case/common/api'; -import { CASES_URL } from '../../../../case/common/constants'; +import { ConnectorTypes, CommentType, CaseStatuses } from '../../../../cases/common/api'; +import { CASES_URL } from '../../../../cases/common/constants'; import { deleteCases, diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index a064189854879..644c7dbf716bf 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -22,7 +22,7 @@ import { SubCaseResponse, SubCasesResponse, User, -} from '../../../../case/common/api'; +} from '../../../../cases/common/api'; import { ACTION_TYPES_URL, @@ -32,7 +32,7 @@ import { CASES_URL, SUB_CASE_DETAILS_URL, SUB_CASES_PATCH_DEL_URL, -} from '../../../../case/common/constants'; +} from '../../../../cases/common/constants'; import { getCaseCommentsUrl, @@ -41,7 +41,7 @@ import { getCaseUserActionUrl, getSubCaseDetailsUrl, getSubCaseUserActionUrl, -} from '../../../../case/common/api/helpers'; +} from '../../../../cases/common/api/helpers'; import { KibanaServices } from '../../common/lib/kibana'; import { StatusAll } from '../components/status'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts index a2c38c82b7756..d9cd81f143816 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts @@ -10,7 +10,7 @@ import { CasesConfigureRequest, ActionConnector, ActionTypeConnector, -} from '../../../../../../case/common/api'; +} from '../../../../../../cases/common/api'; import { ApiProps } from '../../types'; import { CaseConfigure } from '../types'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts index 139b77119bb4f..0c7ae422be861 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts @@ -20,7 +20,7 @@ import { caseConfigurationResposeMock, caseConfigurationCamelCaseResponseMock, } from './mock'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; const abortCtrl = new AbortController(); const mockKibanaServices = KibanaServices.get as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts index cb132026c1873..943724ef08398 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts @@ -12,14 +12,14 @@ import { CasesConfigurePatch, CasesConfigureResponse, CasesConfigureRequest, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; import { KibanaServices } from '../../../common/lib/kibana'; import { CASE_CONFIGURE_CONNECTORS_URL, CASE_CONFIGURE_URL, ACTION_TYPES_URL, -} from '../../../../../case/common/constants'; +} from '../../../../../cases/common/constants'; import { ApiProps } from '../types'; import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts index c4ae60c7d1a73..4e71c9a990ece 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts @@ -11,7 +11,7 @@ import { CasesConfigureResponse, CasesConfigureRequest, ConnectorTypes, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; import { CaseConfigure, CaseConnectorMapping } from './types'; export const mappings: CaseConnectorMapping[] = [ diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts index 5650d51fd8c36..455293b217679 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export * from '../translations'; export const SUCCESS_CONFIGURE = i18n.translate( - 'xpack.securitySolution.case.configure.successSaveToast', + 'xpack.securitySolution.cases.configure.successSaveToast', { defaultMessage: 'Saved external connection settings', } diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts b/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts index df3b1b39c1bb0..aa86d1bfdb0b1 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts @@ -15,7 +15,7 @@ import { CasesConfigure, ClosureType, ThirdPartyField, -} from '../../../../../case/common/api'; +} from '../../../../../cases/common/api'; export { ActionConnector, diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx index adf7ee9e41517..44a503cd089ef 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx @@ -14,7 +14,7 @@ import { } from './use_configure'; import { mappings, caseConfigurationCamelCaseResponseMock } from './mock'; import * as api from './api'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; jest.mock('./api'); const mockErrorToToaster = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx b/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx index 21d1832796ba8..2ec2a73363bfe 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx @@ -15,7 +15,7 @@ import { } from '../../../common/components/toasters'; import * as i18n from './translations'; import { ClosureType, CaseConfigure, CaseConnector, CaseConnectorMapping } from './types'; -import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; export type ConnectorConfiguration = { connector: CaseConnector } & { closureType: CaseConfigure['closureType']; diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts index 719fe01579285..6e937fe7760cd 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -20,9 +20,9 @@ import { CommentType, AssociationType, CaseType, -} from '../../../../case/common/api'; +} from '../../../../cases/common/api'; import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; -import { ConnectorTypes } from '../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../cases/common/api/connectors'; export { connectorsMock } from './configure/mock'; export const basicCaseId = 'basic-case-id'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/translations.ts b/x-pack/plugins/security_solution/public/cases/containers/translations.ts index c79b897ba43a1..4c7afc9224445 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/translations.ts @@ -9,25 +9,25 @@ import { i18n } from '@kbn/i18n'; export * from '../translations'; -export const ERROR_TITLE = i18n.translate('xpack.securitySolution.containers.case.errorTitle', { +export const ERROR_TITLE = i18n.translate('xpack.securitySolution.containers.cases.errorTitle', { defaultMessage: 'Error fetching data', }); export const ERROR_DELETING = i18n.translate( - 'xpack.securitySolution.containers.case.errorDeletingTitle', + 'xpack.securitySolution.containers.cases.errorDeletingTitle', { defaultMessage: 'Error deleting data', } ); export const UPDATED_CASE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.containers.case.updatedCase', { + i18n.translate('xpack.securitySolution.containers.cases.updatedCase', { values: { caseTitle }, defaultMessage: 'Updated "{caseTitle}"', }); export const DELETED_CASES = (totalCases: number, caseTitle?: string) => - i18n.translate('xpack.securitySolution.containers.case.deletedCases', { + i18n.translate('xpack.securitySolution.containers.cases.deletedCases', { values: { caseTitle, totalCases }, defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); @@ -39,7 +39,7 @@ export const CLOSED_CASES = ({ totalCases: number; caseTitle?: string; }) => - i18n.translate('xpack.securitySolution.containers.case.closedCases', { + i18n.translate('xpack.securitySolution.containers.cases.closedCases', { values: { caseTitle, totalCases }, defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); @@ -51,7 +51,7 @@ export const REOPENED_CASES = ({ totalCases: number; caseTitle?: string; }) => - i18n.translate('xpack.securitySolution.containers.case.reopenedCases', { + i18n.translate('xpack.securitySolution.containers.cases.reopenedCases', { values: { caseTitle, totalCases }, defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); @@ -63,33 +63,33 @@ export const MARK_IN_PROGRESS_CASES = ({ totalCases: number; caseTitle?: string; }) => - i18n.translate('xpack.securitySolution.containers.case.markInProgressCases', { + i18n.translate('xpack.securitySolution.containers.cases.markInProgressCases', { values: { caseTitle, totalCases }, defaultMessage: 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', }); export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => - i18n.translate('xpack.securitySolution.containers.case.pushToExternalService', { + i18n.translate('xpack.securitySolution.containers.cases.pushToExternalService', { values: { serviceName }, defaultMessage: 'Successfully sent to { serviceName }', }); export const ERROR_GET_FIELDS = i18n.translate( - 'xpack.securitySolution.case.configure.errorGetFields', + 'xpack.securitySolution.cases.configure.errorGetFields', { defaultMessage: 'Error getting fields from service', } ); export const SYNC_CASE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.containers.case.syncCase', { + i18n.translate('xpack.securitySolution.containers.cases.syncCase', { values: { caseTitle }, defaultMessage: 'Alerts in "{caseTitle}" have been synced', }); export const STATUS_CHANGED_TOASTER_TEXT = i18n.translate( - 'xpack.securitySolution.case.containers.statusChangeToasterText', + 'xpack.securitySolution.cases.containers.statusChangeToasterText', { defaultMessage: 'Alerts in this case have been also had their status updated', } diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index a24a62345d67a..6feb5a1501a76 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/types.ts @@ -16,10 +16,10 @@ import { CasePatchRequest, CaseType, AssociationType, -} from '../../../../case/common/api'; +} from '../../../../cases/common/api'; import { CaseStatusWithAllStatus } from '../components/status'; -export { CaseConnector, ActionConnector, CaseStatuses } from '../../../../case/common/api'; +export { CaseConnector, ActionConnector, CaseStatuses } from '../../../../cases/common/api'; export type Comment = CommentRequest & { associationType: AssociationType; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx index 7e3ed391c946c..d5562afec1d26 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses } from '../../../../case/common/api'; +import { CaseStatuses } from '../../../../cases/common/api'; import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case'; import { basicCase } from './mock'; import * as api from './api'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx index da069ee6f1075..d39da93a06a48 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx @@ -6,7 +6,7 @@ */ import { useCallback, useReducer, useRef, useEffect } from 'react'; -import { CaseStatuses } from '../../../../case/common/api'; +import { CaseStatuses } from '../../../../cases/common/api'; import { displaySuccessToast, errorToToaster, diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx index 422eb0c92cbd8..b4fa816412c68 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseType } from '../../../../case/common/api'; +import { CaseType } from '../../../../cases/common/api'; import { useDeleteCases, UseDeleteCase } from './use_delete_cases'; import * as api from './api'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx index 60d3a8ad8215b..3b28c20d9a4df 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx @@ -10,7 +10,7 @@ import { useCallback, useEffect, useState, useRef } from 'react'; import deepEqual from 'fast-deep-equal'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { CaseFullExternalService } from '../../../../case/common/api/cases'; +import { CaseFullExternalService } from '../../../../cases/common/api/cases'; import { getCaseUserActions, getSubCaseUserActions } from './api'; import * as i18n from './translations'; import { CaseConnector, CaseExternalService, CaseUserActions, ElasticUser } from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx index 5b78cedeedf38..3a62ae70b82de 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses } from '../../../../case/common/api'; +import { CaseStatuses } from '../../../../cases/common/api'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS, diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx index f2c33ec4730fe..10c2d26d6b33d 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx @@ -8,7 +8,7 @@ import { useCallback, useEffect, useState, useRef } from 'react'; import { isEmpty } from 'lodash/fp'; -import { User } from '../../../../case/common/api'; +import { User } from '../../../../cases/common/api'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; import { getReporters } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx index 6007ed002a4a8..3731af4d73db5 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx @@ -8,7 +8,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePostCase, UsePostCase } from './use_post_case'; import * as api from './api'; -import { ConnectorTypes } from '../../../../case/common/api/connectors'; +import { ConnectorTypes } from '../../../../cases/common/api/connectors'; import { basicCasePost } from './mock'; jest.mock('./api'); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx index d890c050f5034..35c2b66156456 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CasePostRequest } from '../../../../case/common/api'; +import { CasePostRequest } from '../../../../cases/common/api'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; import { postCase } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx index 42cd0deafa048..4d4ac5d071fa5 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { CommentType } from '../../../../case/common/api'; +import { CommentType } from '../../../../cases/common/api'; import { usePostComment, UsePostComment } from './use_post_comment'; import { basicCaseId, basicSubCaseId } from './mock'; import * as api from './api'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx index 75d3047bc828e..252059514da8e 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CommentRequest } from '../../../../case/common/api'; +import { CommentRequest } from '../../../../cases/common/api'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; import { postComment } from './api'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx index 5f09ac404ca64..e008927019987 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx @@ -9,7 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePostPushToService, UsePostPushToService } from './use_post_push_to_service'; import { pushedCase } from './mock'; import * as api from './api'; -import { CaseConnector, ConnectorTypes } from '../../../../case/common/api'; +import { CaseConnector, ConnectorTypes } from '../../../../cases/common/api'; jest.mock('./api'); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx index 27a02d9300cc0..9fd0fda5c9723 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CaseConnector } from '../../../../case/common/api'; +import { CaseConnector } from '../../../../cases/common/api'; import { errorToToaster, useStateToaster, diff --git a/x-pack/plugins/security_solution/public/cases/containers/utils.ts b/x-pack/plugins/security_solution/public/cases/containers/utils.ts index 297c7e35981ac..7c33e4481b2aa 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/utils.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/utils.ts @@ -28,7 +28,7 @@ import { CaseUserActionsResponseRt, CommentType, CasePatchRequest, -} from '../../../../case/common/api'; +} from '../../../../cases/common/api'; import { AppToast, ToasterError } from '../../common/components/toasters'; import { AllCases, Case, UpdateByKey } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/pages/translations.ts b/x-pack/plugins/security_solution/public/cases/pages/translations.ts index 51e9fa8a246f0..0abf7461681cf 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/pages/translations.ts @@ -8,218 +8,221 @@ import { i18n } from '@kbn/i18n'; export const SAVED_OBJECT_NO_PERMISSIONS_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseSavedObjectNoPermissionsTitle', + 'xpack.securitySolution.cases.caseSavedObjectNoPermissionsTitle', { defaultMessage: 'Kibana feature privileges required', } ); export const SAVED_OBJECT_NO_PERMISSIONS_MSG = i18n.translate( - 'xpack.securitySolution.case.caseSavedObjectNoPermissionsMessage', + 'xpack.securitySolution.cases.caseSavedObjectNoPermissionsMessage', { defaultMessage: 'To view cases, you must have privileges for the Saved Object Management feature in the Kibana space. For more information, contact your Kibana administrator.', } ); -export const BACK_TO_ALL = i18n.translate('xpack.securitySolution.case.caseView.backLabel', { +export const BACK_TO_ALL = i18n.translate('xpack.securitySolution.cases.caseView.backLabel', { defaultMessage: 'Back to cases', }); -export const CANCEL = i18n.translate('xpack.securitySolution.case.caseView.cancel', { +export const CANCEL = i18n.translate('xpack.securitySolution.cases.caseView.cancel', { defaultMessage: 'Cancel', }); export const DELETE_CASE = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.deleteCase', + 'xpack.securitySolution.cases.confirmDeleteCase.deleteCase', { defaultMessage: 'Delete case', } ); export const DELETE_CASES = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.deleteCases', + 'xpack.securitySolution.cases.confirmDeleteCase.deleteCases', { defaultMessage: 'Delete cases', } ); -export const NAME = i18n.translate('xpack.securitySolution.case.caseView.name', { +export const NAME = i18n.translate('xpack.securitySolution.cases.caseView.name', { defaultMessage: 'Name', }); -export const OPENED_ON = i18n.translate('xpack.securitySolution.case.caseView.openedOn', { +export const OPENED_ON = i18n.translate('xpack.securitySolution.cases.caseView.openedOn', { defaultMessage: 'Opened on', }); -export const CLOSED_ON = i18n.translate('xpack.securitySolution.case.caseView.closedOn', { +export const CLOSED_ON = i18n.translate('xpack.securitySolution.cases.caseView.closedOn', { defaultMessage: 'Closed on', }); -export const REPORTER = i18n.translate('xpack.securitySolution.case.caseView.reporterLabel', { +export const REPORTER = i18n.translate('xpack.securitySolution.cases.caseView.reporterLabel', { defaultMessage: 'Reporter', }); export const PARTICIPANTS = i18n.translate( - 'xpack.securitySolution.case.caseView.particpantsLabel', + 'xpack.securitySolution.cases.caseView.particpantsLabel', { defaultMessage: 'Participants', } ); -export const CREATE_BC_TITLE = i18n.translate('xpack.securitySolution.case.caseView.breadcrumb', { +export const CREATE_BC_TITLE = i18n.translate('xpack.securitySolution.cases.caseView.breadcrumb', { defaultMessage: 'Create', }); -export const CREATE_TITLE = i18n.translate('xpack.securitySolution.case.caseView.create', { +export const CREATE_TITLE = i18n.translate('xpack.securitySolution.cases.caseView.create', { defaultMessage: 'Create new case', }); -export const DESCRIPTION = i18n.translate('xpack.securitySolution.case.caseView.description', { +export const DESCRIPTION = i18n.translate('xpack.securitySolution.cases.caseView.description', { defaultMessage: 'Description', }); export const DESCRIPTION_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.createCase.descriptionFieldRequiredError', + 'xpack.securitySolution.cases.createCase.descriptionFieldRequiredError', { defaultMessage: 'A description is required.', } ); export const COMMENT_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.caseView.commentFieldRequiredError', + 'xpack.securitySolution.cases.caseView.commentFieldRequiredError', { defaultMessage: 'A comment is required.', } ); export const REQUIRED_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.fieldRequiredError', + 'xpack.securitySolution.cases.caseView.fieldRequiredError', { defaultMessage: 'Required field', } ); -export const EDIT = i18n.translate('xpack.securitySolution.case.caseView.edit', { +export const EDIT = i18n.translate('xpack.securitySolution.cases.caseView.edit', { defaultMessage: 'Edit', }); -export const OPTIONAL = i18n.translate('xpack.securitySolution.case.caseView.optional', { +export const OPTIONAL = i18n.translate('xpack.securitySolution.cases.caseView.optional', { defaultMessage: 'Optional', }); -export const PAGE_TITLE = i18n.translate('xpack.securitySolution.case.pageTitle', { +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.cases.pageTitle', { defaultMessage: 'Cases', }); -export const CREATE_CASE = i18n.translate('xpack.securitySolution.case.caseView.createCase', { +export const CREATE_CASE = i18n.translate('xpack.securitySolution.cases.caseView.createCase', { defaultMessage: 'Create case', }); -export const CLOSE_CASE = i18n.translate('xpack.securitySolution.case.caseView.closeCase', { +export const CLOSE_CASE = i18n.translate('xpack.securitySolution.cases.caseView.closeCase', { defaultMessage: 'Close case', }); -export const REOPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.reopenCase', { +export const REOPEN_CASE = i18n.translate('xpack.securitySolution.cases.caseView.reopenCase', { defaultMessage: 'Reopen case', }); -export const CASE_NAME = i18n.translate('xpack.securitySolution.case.caseView.caseName', { +export const CASE_NAME = i18n.translate('xpack.securitySolution.cases.caseView.caseName', { defaultMessage: 'Case name', }); -export const TO = i18n.translate('xpack.securitySolution.case.caseView.to', { +export const TO = i18n.translate('xpack.securitySolution.cases.caseView.to', { defaultMessage: 'to', }); -export const TAGS = i18n.translate('xpack.securitySolution.case.caseView.tags', { +export const TAGS = i18n.translate('xpack.securitySolution.cases.caseView.tags', { defaultMessage: 'Tags', }); -export const ACTIONS = i18n.translate('xpack.securitySolution.case.allCases.actions', { +export const ACTIONS = i18n.translate('xpack.securitySolution.cases.allCases.actions', { defaultMessage: 'Actions', }); export const NO_TAGS_AVAILABLE = i18n.translate( - 'xpack.securitySolution.case.allCases.noTagsAvailable', + 'xpack.securitySolution.cases.allCases.noTagsAvailable', { defaultMessage: 'No tags available', } ); export const NO_REPORTERS_AVAILABLE = i18n.translate( - 'xpack.securitySolution.case.caseView.noReportersAvailable', + 'xpack.securitySolution.cases.caseView.noReportersAvailable', { defaultMessage: 'No reporters available.', } ); -export const COMMENTS = i18n.translate('xpack.securitySolution.case.allCases.comments', { +export const COMMENTS = i18n.translate('xpack.securitySolution.cases.allCases.comments', { defaultMessage: 'Comments', }); export const TAGS_HELP = i18n.translate( - 'xpack.securitySolution.case.createCase.fieldTagsHelpText', + 'xpack.securitySolution.cases.createCase.fieldTagsHelpText', { defaultMessage: 'Type one or more custom identifying tags for this case. Press enter after each tag to begin a new one.', } ); -export const NO_TAGS = i18n.translate('xpack.securitySolution.case.caseView.noTags', { +export const NO_TAGS = i18n.translate('xpack.securitySolution.cases.caseView.noTags', { defaultMessage: 'No tags are currently assigned to this case.', }); export const TITLE_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.createCase.titleFieldRequiredError', + 'xpack.securitySolution.cases.createCase.titleFieldRequiredError', { defaultMessage: 'A title is required.', } ); export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate( - 'xpack.securitySolution.case.configureCases.headerTitle', + 'xpack.securitySolution.cases.configureCases.headerTitle', { defaultMessage: 'Configure cases', } ); export const CONFIGURE_CASES_BUTTON = i18n.translate( - 'xpack.securitySolution.case.configureCasesButton', + 'xpack.securitySolution.cases.configureCasesButton', { defaultMessage: 'Edit external connection', } ); export const ADD_COMMENT = i18n.translate( - 'xpack.securitySolution.case.caseView.comment.addComment', + 'xpack.securitySolution.cases.caseView.comment.addComment', { defaultMessage: 'Add comment', } ); export const ADD_COMMENT_HELP_TEXT = i18n.translate( - 'xpack.securitySolution.case.caseView.comment.addCommentHelpText', + 'xpack.securitySolution.cases.caseView.comment.addCommentHelpText', { defaultMessage: 'Add a new comment...', } ); -export const SAVE = i18n.translate('xpack.securitySolution.case.caseView.description.save', { +export const SAVE = i18n.translate('xpack.securitySolution.cases.caseView.description.save', { defaultMessage: 'Save', }); export const GO_TO_DOCUMENTATION = i18n.translate( - 'xpack.securitySolution.case.caseView.goToDocumentationButton', + 'xpack.securitySolution.cases.caseView.goToDocumentationButton', { defaultMessage: 'View documentation', } ); -export const CONNECTORS = i18n.translate('xpack.securitySolution.case.caseView.connectors', { +export const CONNECTORS = i18n.translate('xpack.securitySolution.cases.caseView.connectors', { defaultMessage: 'External Incident Management System', }); -export const EDIT_CONNECTOR = i18n.translate('xpack.securitySolution.case.caseView.editConnector', { - defaultMessage: 'Change external incident management system', -}); +export const EDIT_CONNECTOR = i18n.translate( + 'xpack.securitySolution.cases.caseView.editConnector', + { + defaultMessage: 'Change external incident management system', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/translations.ts b/x-pack/plugins/security_solution/public/cases/translations.ts index b7cfe11aafda0..e8e4f207f2d23 100644 --- a/x-pack/plugins/security_solution/public/cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/translations.ts @@ -8,269 +8,272 @@ import { i18n } from '@kbn/i18n'; export const SAVED_OBJECT_NO_PERMISSIONS_TITLE = i18n.translate( - 'xpack.securitySolution.case.caseSavedObjectNoPermissionsTitle', + 'xpack.securitySolution.cases.caseSavedObjectNoPermissionsTitle', { defaultMessage: 'Kibana feature privileges required', } ); export const SAVED_OBJECT_NO_PERMISSIONS_MSG = i18n.translate( - 'xpack.securitySolution.case.caseSavedObjectNoPermissionsMessage', + 'xpack.securitySolution.cases.caseSavedObjectNoPermissionsMessage', { defaultMessage: 'To view cases, you must have privileges for the Saved Object Management feature in the Kibana space. For more information, contact your Kibana administrator.', } ); -export const BACK_TO_ALL = i18n.translate('xpack.securitySolution.case.caseView.backLabel', { +export const BACK_TO_ALL = i18n.translate('xpack.securitySolution.cases.caseView.backLabel', { defaultMessage: 'Back to cases', }); -export const CANCEL = i18n.translate('xpack.securitySolution.case.caseView.cancel', { +export const CANCEL = i18n.translate('xpack.securitySolution.cases.caseView.cancel', { defaultMessage: 'Cancel', }); export const DELETE_CASE = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.deleteCase', + 'xpack.securitySolution.cases.confirmDeleteCase.deleteCase', { defaultMessage: 'Delete case', } ); export const DELETE_CASES = i18n.translate( - 'xpack.securitySolution.case.confirmDeleteCase.deleteCases', + 'xpack.securitySolution.cases.confirmDeleteCase.deleteCases', { defaultMessage: 'Delete cases', } ); -export const NAME = i18n.translate('xpack.securitySolution.case.caseView.name', { +export const NAME = i18n.translate('xpack.securitySolution.cases.caseView.name', { defaultMessage: 'Name', }); -export const OPENED_ON = i18n.translate('xpack.securitySolution.case.caseView.openedOn', { +export const OPENED_ON = i18n.translate('xpack.securitySolution.cases.caseView.openedOn', { defaultMessage: 'Opened on', }); -export const CLOSED_ON = i18n.translate('xpack.securitySolution.case.caseView.closedOn', { +export const CLOSED_ON = i18n.translate('xpack.securitySolution.cases.caseView.closedOn', { defaultMessage: 'Closed on', }); -export const REPORTER = i18n.translate('xpack.securitySolution.case.caseView.reporterLabel', { +export const REPORTER = i18n.translate('xpack.securitySolution.cases.caseView.reporterLabel', { defaultMessage: 'Reporter', }); export const PARTICIPANTS = i18n.translate( - 'xpack.securitySolution.case.caseView.particpantsLabel', + 'xpack.securitySolution.cases.caseView.particpantsLabel', { defaultMessage: 'Participants', } ); -export const CREATE_BC_TITLE = i18n.translate('xpack.securitySolution.case.caseView.breadcrumb', { +export const CREATE_BC_TITLE = i18n.translate('xpack.securitySolution.cases.caseView.breadcrumb', { defaultMessage: 'Create', }); -export const CREATE_TITLE = i18n.translate('xpack.securitySolution.case.caseView.create', { +export const CREATE_TITLE = i18n.translate('xpack.securitySolution.cases.caseView.create', { defaultMessage: 'Create new case', }); -export const DESCRIPTION = i18n.translate('xpack.securitySolution.case.caseView.description', { +export const DESCRIPTION = i18n.translate('xpack.securitySolution.cases.caseView.description', { defaultMessage: 'Description', }); export const DESCRIPTION_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.createCase.descriptionFieldRequiredError', + 'xpack.securitySolution.cases.createCase.descriptionFieldRequiredError', { defaultMessage: 'A description is required.', } ); export const COMMENT_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.caseView.commentFieldRequiredError', + 'xpack.securitySolution.cases.caseView.commentFieldRequiredError', { defaultMessage: 'A comment is required.', } ); export const REQUIRED_FIELD = i18n.translate( - 'xpack.securitySolution.case.caseView.fieldRequiredError', + 'xpack.securitySolution.cases.caseView.fieldRequiredError', { defaultMessage: 'Required field', } ); -export const EDIT = i18n.translate('xpack.securitySolution.case.caseView.edit', { +export const EDIT = i18n.translate('xpack.securitySolution.cases.caseView.edit', { defaultMessage: 'Edit', }); -export const OPTIONAL = i18n.translate('xpack.securitySolution.case.caseView.optional', { +export const OPTIONAL = i18n.translate('xpack.securitySolution.cases.caseView.optional', { defaultMessage: 'Optional', }); -export const PAGE_TITLE = i18n.translate('xpack.securitySolution.case.pageTitle', { +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.cases.pageTitle', { defaultMessage: 'Cases', }); -export const CREATE_CASE = i18n.translate('xpack.securitySolution.case.caseView.createCase', { +export const CREATE_CASE = i18n.translate('xpack.securitySolution.cases.caseView.createCase', { defaultMessage: 'Create case', }); -export const CLOSE_CASE = i18n.translate('xpack.securitySolution.case.caseView.closeCase', { +export const CLOSE_CASE = i18n.translate('xpack.securitySolution.cases.caseView.closeCase', { defaultMessage: 'Close case', }); export const MARK_CASE_IN_PROGRESS = i18n.translate( - 'xpack.securitySolution.case.caseView.markInProgress', + 'xpack.securitySolution.cases.caseView.markInProgress', { defaultMessage: 'Mark in progress', } ); -export const REOPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.reopenCase', { +export const REOPEN_CASE = i18n.translate('xpack.securitySolution.cases.caseView.reopenCase', { defaultMessage: 'Reopen case', }); -export const OPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.openCase', { +export const OPEN_CASE = i18n.translate('xpack.securitySolution.cases.caseView.openCase', { defaultMessage: 'Open case', }); -export const CASE_NAME = i18n.translate('xpack.securitySolution.case.caseView.caseName', { +export const CASE_NAME = i18n.translate('xpack.securitySolution.cases.caseView.caseName', { defaultMessage: 'Case name', }); -export const TO = i18n.translate('xpack.securitySolution.case.caseView.to', { +export const TO = i18n.translate('xpack.securitySolution.cases.caseView.to', { defaultMessage: 'to', }); -export const TAGS = i18n.translate('xpack.securitySolution.case.caseView.tags', { +export const TAGS = i18n.translate('xpack.securitySolution.cases.caseView.tags', { defaultMessage: 'Tags', }); -export const ACTIONS = i18n.translate('xpack.securitySolution.case.allCases.actions', { +export const ACTIONS = i18n.translate('xpack.securitySolution.cases.allCases.actions', { defaultMessage: 'Actions', }); export const NO_TAGS_AVAILABLE = i18n.translate( - 'xpack.securitySolution.case.allCases.noTagsAvailable', + 'xpack.securitySolution.cases.allCases.noTagsAvailable', { defaultMessage: 'No tags available', } ); export const NO_REPORTERS_AVAILABLE = i18n.translate( - 'xpack.securitySolution.case.caseView.noReportersAvailable', + 'xpack.securitySolution.cases.caseView.noReportersAvailable', { defaultMessage: 'No reporters available.', } ); -export const COMMENTS = i18n.translate('xpack.securitySolution.case.allCases.comments', { +export const COMMENTS = i18n.translate('xpack.securitySolution.cases.allCases.comments', { defaultMessage: 'Comments', }); export const TAGS_HELP = i18n.translate( - 'xpack.securitySolution.case.createCase.fieldTagsHelpText', + 'xpack.securitySolution.cases.createCase.fieldTagsHelpText', { defaultMessage: 'Type one or more custom identifying tags for this case. Press enter after each tag to begin a new one.', } ); -export const NO_TAGS = i18n.translate('xpack.securitySolution.case.caseView.noTags', { +export const NO_TAGS = i18n.translate('xpack.securitySolution.cases.caseView.noTags', { defaultMessage: 'No tags are currently assigned to this case.', }); export const TITLE_REQUIRED = i18n.translate( - 'xpack.securitySolution.case.createCase.titleFieldRequiredError', + 'xpack.securitySolution.cases.createCase.titleFieldRequiredError', { defaultMessage: 'A title is required.', } ); export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate( - 'xpack.securitySolution.case.configureCases.headerTitle', + 'xpack.securitySolution.cases.configureCases.headerTitle', { defaultMessage: 'Configure cases', } ); export const CONFIGURE_CASES_BUTTON = i18n.translate( - 'xpack.securitySolution.case.configureCasesButton', + 'xpack.securitySolution.cases.configureCasesButton', { defaultMessage: 'Edit external connection', } ); export const ADD_COMMENT = i18n.translate( - 'xpack.securitySolution.case.caseView.comment.addComment', + 'xpack.securitySolution.cases.caseView.comment.addComment', { defaultMessage: 'Add comment', } ); export const ADD_COMMENT_HELP_TEXT = i18n.translate( - 'xpack.securitySolution.case.caseView.comment.addCommentHelpText', + 'xpack.securitySolution.cases.caseView.comment.addCommentHelpText', { defaultMessage: 'Add a new comment...', } ); -export const SAVE = i18n.translate('xpack.securitySolution.case.caseView.description.save', { +export const SAVE = i18n.translate('xpack.securitySolution.cases.caseView.description.save', { defaultMessage: 'Save', }); export const GO_TO_DOCUMENTATION = i18n.translate( - 'xpack.securitySolution.case.caseView.goToDocumentationButton', + 'xpack.securitySolution.cases.caseView.goToDocumentationButton', { defaultMessage: 'View documentation', } ); -export const CONNECTORS = i18n.translate('xpack.securitySolution.case.caseView.connectors', { +export const CONNECTORS = i18n.translate('xpack.securitySolution.cases.caseView.connectors', { defaultMessage: 'External Incident Management System', }); -export const EDIT_CONNECTOR = i18n.translate('xpack.securitySolution.case.caseView.editConnector', { - defaultMessage: 'Change external incident management system', -}); +export const EDIT_CONNECTOR = i18n.translate( + 'xpack.securitySolution.cases.caseView.editConnector', + { + defaultMessage: 'Change external incident management system', + } +); -export const NO_CONNECTOR = i18n.translate('xpack.securitySolution.case.common.noConnector', { +export const NO_CONNECTOR = i18n.translate('xpack.securitySolution.cases.common.noConnector', { defaultMessage: 'No connector selected', }); -export const UNKNOWN = i18n.translate('xpack.securitySolution.case.caseView.unknown', { +export const UNKNOWN = i18n.translate('xpack.securitySolution.cases.caseView.unknown', { defaultMessage: 'Unknown', }); -export const MARKED_CASE_AS = i18n.translate('xpack.securitySolution.case.caseView.markedCaseAs', { +export const MARKED_CASE_AS = i18n.translate('xpack.securitySolution.cases.caseView.markedCaseAs', { defaultMessage: 'marked case as', }); -export const OPEN_CASES = i18n.translate('xpack.securitySolution.case.caseTable.openCases', { +export const OPEN_CASES = i18n.translate('xpack.securitySolution.cases.caseTable.openCases', { defaultMessage: 'Open cases', }); -export const CLOSED_CASES = i18n.translate('xpack.securitySolution.case.caseTable.closedCases', { +export const CLOSED_CASES = i18n.translate('xpack.securitySolution.cases.caseTable.closedCases', { defaultMessage: 'Closed cases', }); export const IN_PROGRESS_CASES = i18n.translate( - 'xpack.securitySolution.case.caseTable.inProgressCases', + 'xpack.securitySolution.cases.caseTable.inProgressCases', { defaultMessage: 'In progress cases', } ); export const SYNC_ALERTS_SWITCH_LABEL_ON = i18n.translate( - 'xpack.securitySolution.case.settings.syncAlertsSwitchLabelOn', + 'xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOn', { defaultMessage: 'On', } ); export const SYNC_ALERTS_SWITCH_LABEL_OFF = i18n.translate( - 'xpack.securitySolution.case.settings.syncAlertsSwitchLabelOff', + 'xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOff', { defaultMessage: 'Off', } diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 3a2170d126a24..b0ffcb8c5b5b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -33,7 +33,6 @@ import { import * as i18nCommon from '../../../translations'; import * as i18n from './translations'; import * as sharedI18n from '../translations'; -import { osTypeArray, OsTypeArray } from '../../../../../common/shared_imports'; import { useAppToasts } from '../../../hooks/use_app_toasts'; import { useKibana } from '../../../lib/kibana'; import { ExceptionBuilderComponent } from '../builder'; @@ -50,6 +49,7 @@ import { defaultEndpointExceptionItems, entryHasListType, entryHasNonEcsType, + retrieveAlertOsTypes, } from '../helpers'; import { ErrorInfo, ErrorCallout } from '../error_callout'; import { AlertData, ExceptionsBuilderExceptionItem } from '../types'; @@ -291,18 +291,6 @@ export const AddExceptionModal = memo(function AddExceptionModal({ [setShouldBulkCloseAlert] ); - const retrieveAlertOsTypes = useCallback((): OsTypeArray => { - const osDefaults: OsTypeArray = ['windows', 'macos']; - if (alertData != null) { - const osTypes = alertData.host && alertData.host.os && alertData.host.os.family; - if (osTypeArray.is(osTypes) && osTypes != null && osTypes.length > 0) { - return osTypes; - } - return osDefaults; - } - return osDefaults; - }, [alertData]); - const enrichExceptionItems = useCallback((): Array< ExceptionListItemSchema | CreateExceptionListItemSchema > => { @@ -312,11 +300,11 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ? enrichNewExceptionItemsWithComments(exceptionItemsToAdd, [{ comment }]) : exceptionItemsToAdd; if (exceptionListType === 'endpoint') { - const osTypes = retrieveAlertOsTypes(); + const osTypes = retrieveAlertOsTypes(alertData); enriched = lowercaseHashValues(enrichExceptionItemsWithOS(enriched, osTypes)); } return enriched; - }, [comment, exceptionItemsToAdd, exceptionListType, retrieveAlertOsTypes]); + }, [comment, exceptionItemsToAdd, exceptionListType, alertData]); const onAddExceptionConfirm = useCallback((): void => { if (addOrUpdateExceptionItems != null) { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 8651dac8c8cfd..3463f521655cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -29,6 +29,7 @@ import { defaultEndpointExceptionItems, getFileCodeSignature, getProcessCodeSignature, + retrieveAlertOsTypes, } from './helpers'; import { AlertData, EmptyEntry } from './types'; import { @@ -533,6 +534,25 @@ describe('Exception helpers', () => { }); }); + describe('#retrieveAlertOsTypes', () => { + test('it should retrieve os type if alert data is provided', () => { + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + host: { os: { family: 'windows' } }, + }; + const result = retrieveAlertOsTypes(alertDataMock); + const expected = ['windows']; + expect(result).toEqual(expected); + }); + + test('it should return default os types if alert data is not provided', () => { + const result = retrieveAlertOsTypes(); + const expected = ['windows', 'macos']; + expect(result).toEqual(expected); + }); + }); + describe('#entryHasListType', () => { test('it should return false with an empty array', () => { const payload: ExceptionListItemSchema[] = []; @@ -723,15 +743,16 @@ describe('Exception helpers', () => { expect(prepopulatedItem.entries).toEqual([ { entries: [ - { field: 'subject_name', operator: 'included', type: 'match', value: '' }, - { field: 'trusted', operator: 'included', type: 'match', value: '' }, + { id: '123', field: 'subject_name', operator: 'included', type: 'match', value: '' }, + { id: '123', field: 'trusted', operator: 'included', type: 'match', value: '' }, ], field: 'file.Ext.code_signature', type: 'nested', + id: '123', }, - { field: 'file.path.caseless', operator: 'included', type: 'match', value: '' }, - { field: 'file.hash.sha256', operator: 'included', type: 'match', value: '' }, - { field: 'event.code', operator: 'included', type: 'match', value: '' }, + { id: '123', field: 'file.path.caseless', operator: 'included', type: 'match', value: '' }, + { id: '123', field: 'file.hash.sha256', operator: 'included', type: 'match', value: '' }, + { id: '123', field: 'event.code', operator: 'included', type: 'match', value: '' }, ]); }); @@ -748,24 +769,39 @@ describe('Exception helpers', () => { { entries: [ { + id: '123', field: 'subject_name', operator: 'included', type: 'match', value: 'someSubjectName', }, - { field: 'trusted', operator: 'included', type: 'match', value: 'false' }, + { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'false' }, ], field: 'file.Ext.code_signature', type: 'nested', + id: '123', }, { + id: '123', field: 'file.path.caseless', operator: 'included', type: 'match', value: 'some-file-path', }, - { field: 'file.hash.sha256', operator: 'included', type: 'match', value: 'some-hash' }, - { field: 'event.code', operator: 'included', type: 'match', value: 'some-event-code' }, + { + id: '123', + field: 'file.hash.sha256', + operator: 'included', + type: 'match', + value: 'some-hash', + }, + { + id: '123', + field: 'event.code', + operator: 'included', + type: 'match', + value: 'some-event-code', + }, ]); }); }); @@ -943,47 +979,77 @@ describe('Exception helpers', () => { { entries: [ { + id: '123', field: 'subject_name', operator: 'included', type: 'match', value: 'some_subject', }, - { field: 'trusted', operator: 'included', type: 'match', value: 'false' }, + { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'false' }, ], field: 'file.Ext.code_signature', type: 'nested', + id: '123', }, { + id: '123', field: 'file.path.caseless', operator: 'included', type: 'match', value: 'some file path', }, - { field: 'file.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, - { field: 'event.code', operator: 'included', type: 'match', value: 'some event code' }, + { + id: '123', + field: 'file.hash.sha256', + operator: 'included', + type: 'match', + value: 'some hash', + }, + { + id: '123', + field: 'event.code', + operator: 'included', + type: 'match', + value: 'some event code', + }, ]); expect(defaultItems[1].entries).toEqual([ { entries: [ { + id: '123', field: 'subject_name', operator: 'included', type: 'match', value: 'some_subject_2', }, - { field: 'trusted', operator: 'included', type: 'match', value: 'true' }, + { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'true' }, ], field: 'file.Ext.code_signature', type: 'nested', + id: '123', }, { + id: '123', field: 'file.path.caseless', operator: 'included', type: 'match', value: 'some file path', }, - { field: 'file.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, - { field: 'event.code', operator: 'included', type: 'match', value: 'some event code' }, + { + id: '123', + field: 'file.hash.sha256', + operator: 'included', + type: 'match', + value: 'some hash', + }, + { + id: '123', + field: 'event.code', + operator: 'included', + type: 'match', + value: 'some event code', + }, ]); }); @@ -1014,59 +1080,91 @@ describe('Exception helpers', () => { { entries: [ { + id: '123', field: 'subject_name', operator: 'included', type: 'match', value: 'some_subject', }, - { field: 'trusted', operator: 'included', type: 'match', value: 'false' }, + { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'false' }, ], field: 'process.Ext.code_signature', type: 'nested', + id: '123', }, { + id: '123', field: 'process.executable', operator: 'included', type: 'match', value: 'some file path', }, - { field: 'process.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, { + id: '123', + field: 'process.hash.sha256', + operator: 'included', + type: 'match', + value: 'some hash', + }, + { + id: '123', field: 'Ransomware.feature', operator: 'included', type: 'match', value: 'some ransomware feature', }, - { field: 'event.code', operator: 'included', type: 'match', value: 'ransomware' }, + { + id: '123', + field: 'event.code', + operator: 'included', + type: 'match', + value: 'ransomware', + }, ]); expect(defaultItems[1].entries).toEqual([ { entries: [ { + id: '123', field: 'subject_name', operator: 'included', type: 'match', value: 'some_subject_2', }, - { field: 'trusted', operator: 'included', type: 'match', value: 'true' }, + { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'true' }, ], field: 'process.Ext.code_signature', type: 'nested', + id: '123', }, { + id: '123', field: 'process.executable', operator: 'included', type: 'match', value: 'some file path', }, - { field: 'process.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, { + id: '123', + field: 'process.hash.sha256', + operator: 'included', + type: 'match', + value: 'some hash', + }, + { + id: '123', field: 'Ransomware.feature', operator: 'included', type: 'match', value: 'some ransomware feature', }, - { field: 'event.code', operator: 'included', type: 'match', value: 'ransomware' }, + { + id: '123', + field: 'event.code', + operator: 'included', + type: 'match', + value: 'ransomware', + }, ]); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index c44de4f05e7f6..43c3b6c082f1a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -13,6 +13,7 @@ import uuid from 'uuid'; import * as i18n from './translations'; import { + AlertData, BuilderEntry, CreateExceptionListItemBuilderSchema, ExceptionsBuilderExceptionItem, @@ -38,6 +39,8 @@ import { UpdateExceptionListItemSchema, EntryNested, OsTypeArray, + EntriesArray, + osType, } from '../../../shared_imports'; import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { validate } from '../../../../common/validate'; @@ -46,6 +49,19 @@ import { CodeSignature } from '../../../../common/ecs/file'; import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard'; import { addIdToItem, removeIdFromItem } from '../../../../common'; +export const addIdToEntries = (entries: EntriesArray): EntriesArray => { + return entries.map((singleEntry) => { + if (singleEntry.type === 'nested') { + return addIdToItem({ + ...singleEntry, + entries: singleEntry.entries.map((nestedEntry) => addIdToItem(nestedEntry)), + }); + } else { + return addIdToItem(singleEntry); + } + }); +}; + /** * Returns the operator type, may not need this if using io-ts types * @@ -150,14 +166,14 @@ export const getNewExceptionItem = ({ return { comments: [], description: `${ruleName} - exception list item`, - entries: [ - addIdToItem({ + entries: addIdToEntries([ + { field: '', operator: 'included', type: 'match', value: '', - }), - ], + }, + ]), item_id: undefined, list_id: listId, meta: { @@ -345,6 +361,17 @@ export const enrichExceptionItemsWithOS = ( }); }; +export const retrieveAlertOsTypes = (alertData?: AlertData): OsTypeArray => { + const osDefaults: OsTypeArray = ['windows', 'macos']; + if (alertData != null) { + const os = alertData.host && alertData.host.os && alertData.host.os.family; + if (os != null) { + return osType.is(os) ? [os] : osDefaults; + } + } + return osDefaults; +}; + /** * Returns given exceptionItems with all hash-related entries lowercased */ @@ -464,7 +491,7 @@ export const getPrepopulatedEndpointException = ({ const sha256Hash = file?.hash?.sha256 ?? ''; return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), - entries: [ + entries: addIdToEntries([ { field: 'file.Ext.code_signature', type: 'nested', @@ -501,7 +528,7 @@ export const getPrepopulatedEndpointException = ({ type: 'match', value: eventCode ?? '', }, - ], + ]), }; }; @@ -529,7 +556,7 @@ export const getPrepopulatedRansomwareException = ({ const ransomwareFeature = Ransomware?.feature ?? ''; return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), - entries: [ + entries: addIdToEntries([ { field: 'process.Ext.code_signature', type: 'nested', @@ -572,7 +599,7 @@ export const getPrepopulatedRansomwareException = ({ type: 'match', value: eventCode ?? '', }, - ], + ]), }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/external_link_icon/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/external_link_icon/index.test.tsx deleted file mode 100644 index 65da5423d19e8..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/external_link_icon/index.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mount } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../mock'; - -import { ExternalLinkIcon } from '.'; - -describe('Duration', () => { - test('it renders expected icon type when the leftMargin prop is not specified', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="external-link-icon"]').first().props().type).toEqual( - 'popout' - ); - }); - - test('it renders expected icon type when the leftMargin prop is true', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="external-link-icon"]').first().props().type).toEqual( - 'popout' - ); - }); - - test('it applies a margin-left style when the leftMargin prop is true', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="external-link-icon"]').first()).toHaveStyleRule( - 'margin-left', - '5px' - ); - }); - - test('it does NOT apply a margin-left style when the leftMargin prop is false', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="external-link-icon"]').first()).not.toHaveStyleRule( - 'margin-left' - ); - }); - - test('it renders expected icon type when the leftMargin prop is false', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="external-link-icon"]').first().props().type).toEqual( - 'popout' - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/external_link_icon/index.tsx b/x-pack/plugins/security_solution/public/common/components/external_link_icon/index.tsx deleted file mode 100644 index 825ee8a33a8a5..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/external_link_icon/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiIcon } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; - -const LinkIcon = styled(EuiIcon)` - position: relative; - top: -2px; -`; - -LinkIcon.displayName = 'LinkIcon'; - -const LinkIconWithMargin = styled(LinkIcon)` - margin-left: 5px; -`; - -LinkIconWithMargin.displayName = 'LinkIconWithMargin'; - -const color = 'subdued'; -const iconSize = 's'; -const iconType = 'popout'; - -/** - * Renders an icon that indicates following the hyperlink will navigate to - * content external to the app - */ -export const ExternalLinkIcon = React.memo<{ - leftMargin?: boolean; -}>(({ leftMargin = true }) => - leftMargin ? ( - - ) : ( - - ) -); - -ExternalLinkIcon.displayName = 'ExternalLinkIcon'; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx index 864e55b3e4a45..cd19eb5a27d7b 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mount, shallow, ShallowWrapper } from 'enzyme'; +import { mount, shallow, ReactWrapper, ShallowWrapper } from 'enzyme'; import React from 'react'; import { removeExternalLinkText } from '../../../../common/test_utils'; import { mountWithIntl } from '@kbn/test/jest'; @@ -121,11 +121,11 @@ describe('Custom Links', () => { describe('External Link', () => { const mockLink = 'https://www.virustotal.com/gui/search/'; const mockLinkName = 'Link'; - let wrapper: ShallowWrapper; + let wrapper: ReactWrapper | ShallowWrapper; describe('render', () => { beforeAll(() => { - wrapper = shallow( + wrapper = mount( {mockLinkName} @@ -137,11 +137,13 @@ describe('Custom Links', () => { }); test('it renders ExternalLinkIcon', () => { - expect(wrapper.find('[data-test-subj="externalLinkIcon"]').exists()).toBeTruthy(); + expect(wrapper.find('span [data-euiicon-type="popout"]').length).toBe(1); }); test('it renders correct url', () => { - expect(wrapper.find('[data-test-subj="externalLink"]').prop('href')).toEqual(mockLink); + expect(wrapper.find('[data-test-subj="externalLink"]').first().prop('href')).toEqual( + mockLink + ); }); test('it renders comma if id is given', () => { @@ -435,14 +437,14 @@ describe('Custom Links', () => { test('it renders correct number of external icons by default', () => { const wrapper = mountWithIntl(); - expect(wrapper.find('[data-test-subj="externalLinkIcon"]')).toHaveLength(5); + expect(wrapper.find('span [data-euiicon-type="popout"]')).toHaveLength(5); }); test('it renders correct number of external icons', () => { const wrapper = mountWithIntl( ); - expect(wrapper.find('[data-test-subj="externalLinkIcon"]')).toHaveLength(1); + expect(wrapper.find('span [data-euiicon-type="popout"]')).toHaveLength(1); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 8e2f57a1a597c..a02cc8bf76bcc 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -39,7 +39,6 @@ import { } from '../../../../common/search_strategy/security_solution/network'; import { useUiSetting$, useKibana } from '../../lib/kibana'; import { isUrlInvalid } from '../../utils/validators'; -import { ExternalLinkIcon } from '../external_link_icon'; import * as i18n from './translations'; import { SecurityPageName } from '../../../app/types'; @@ -54,6 +53,13 @@ export const LinkAnchor: React.FC = ({ children, ...props }) => ( {children} ); +export const PortContainer = styled.div` + & svg { + position: relative; + top: -1px; + } +`; + // Internal Links const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; @@ -112,11 +118,12 @@ export const ExternalLink = React.memo<{ const inAllowlist = allowedUrlSchemes.some((scheme) => url.indexOf(scheme) === 0); return url && inAllowlist && !isUrlInvalid(url) && children ? ( - - {children} - + <> + + {children} + {!isNil(idx) && idx < lastIndexToShow && } - + ) : null; } @@ -229,15 +236,17 @@ export const PortOrServiceNameLink = React.memo<{ children?: React.ReactNode; portOrServiceName: number | string; }>(({ children, portOrServiceName }) => ( - - {children ? children : portOrServiceName} - + + + {children ? children : portOrServiceName} + + )); PortOrServiceNameLink.displayName = 'PortOrServiceNameLink'; diff --git a/x-pack/plugins/security_solution/public/common/components/links/translations.ts b/x-pack/plugins/security_solution/public/common/components/links/translations.ts index a1941c2b5393f..9e2f99c17a677 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/links/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export * from '../../../network/components/details/translations'; export const CASE_DETAILS_LINK_ARIA = (detailName: string) => - i18n.translate('xpack.securitySolution.case.caseTable.caseDetailsLinkAria', { + i18n.translate('xpack.securitySolution.cases.caseTable.caseDetailsLinkAria', { values: { detailName }, defaultMessage: 'click to visit case with title {detailName}', }); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index d846d887cb681..2e0f1ac762ca8 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -15,6 +15,7 @@ import { MatrixHistogramType } from '../../../../common/search_strategy/security import { UpdateDateRange } from '../charts/common'; import { GlobalTimeArgs } from '../../containers/use_global_time'; import { DocValueFields } from '../../../../common/search_strategy'; +import { Threshold } from '../../../detections/components/rules/query_preview'; export type MatrixHistogramMappingTypes = Record< string, @@ -74,16 +75,7 @@ export interface MatrixHistogramQueryProps { stackByField: string; startDate: string; histogramType: MatrixHistogramType; - threshold?: - | { - field: string | string[] | undefined; - value: number; - cardinality?: { - field: string[]; - value: number; - }; - } - | undefined; + threshold?: Threshold; skip?: boolean; isPtrIncluded?: boolean; } diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_by_router_event_handler.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_by_router_event_handler.ts index a576bda6e4340..0b7872304c89a 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_by_router_event_handler.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_by_router_event_handler.ts @@ -13,7 +13,7 @@ type EventHandlerCallback = MouseEventHandler { yTickFormatter: (value: string | number): string => value.toLocaleString(), tickSize: 8, }, - yAxisTitle: i18n.QUERY_GRAPH_COUNT, + yAxisTitle: i18n.THRESHOLD_QUERY_GRAPH_COUNT, settings: { legendPosition: Position.Right, showLegend: true, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx index 1ca1f0710d78f..2ef114a25f32a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx @@ -328,11 +328,11 @@ describe('PreviewQuery', () => { query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} threshold={{ - field: 'agent.hostname', - value: 200, + field: ['agent.hostname'], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }} isDisabled={false} @@ -375,11 +375,11 @@ describe('PreviewQuery', () => { query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} threshold={{ - field: 'agent.hostname', - value: 200, + field: ['agent.hostname'], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }} isDisabled={false} @@ -409,7 +409,7 @@ describe('PreviewQuery', () => { expect(wrapper.find('[data-test-subj="previewQueryWarning"]').exists()).toBeTruthy(); }); - test('it renders query histogram when preview button clicked, rule type is threshold, and threshold field is not defined', () => { + test('it renders query histogram when preview button clicked, rule type is threshold, and threshold field is empty array', () => { const wrapper = mount( { query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} threshold={{ - field: undefined, - value: 200, + field: [], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }} isDisabled={false} @@ -451,11 +451,11 @@ describe('PreviewQuery', () => { query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} threshold={{ - field: ' ', - value: 200, + field: [' '], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }} isDisabled={false} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx index c0c26780e8d71..70d292660388d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx @@ -32,6 +32,7 @@ import { formatDate } from '../../../../common/components/super_date_picker'; import { State, queryPreviewReducer } from './reducer'; import { isNoisy } from './helpers'; import { PreviewCustomQueryHistogram } from './custom_histogram'; +import { FieldValueThreshold } from '../threshold_input'; const Select = styled(EuiSelect)` width: ${({ theme }) => theme.eui.euiSuperDatePickerWidth}; @@ -56,16 +57,7 @@ export const initialState: State = { showNonEqlHistogram: false, }; -export type Threshold = - | { - field: string | string[] | undefined; - value: number; - cardinality?: { - field: string[]; - value: number; - }; - } - | undefined; +export type Threshold = FieldValueThreshold | undefined; interface PreviewQueryProps { dataTestSubj: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts index b0728cd8cc827..930b7066da5cc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts @@ -335,11 +335,11 @@ describe('queryPreviewReducer', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', threshold: { - field: 'agent.hostname', - value: 200, + field: ['agent.hostname'], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }, ruleType: 'threshold', @@ -351,15 +351,15 @@ describe('queryPreviewReducer', () => { expect(update.warnings).toEqual([]); }); - test('should set thresholdFieldExists to false if threshold field is not defined', () => { + test('should set thresholdFieldExists to false if threshold field is empty array', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', threshold: { - field: undefined, - value: 200, + field: [], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }, ruleType: 'threshold', @@ -375,11 +375,11 @@ describe('queryPreviewReducer', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', threshold: { - field: ' ', - value: 200, + field: [' '], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }, ruleType: 'threshold', @@ -395,11 +395,11 @@ describe('queryPreviewReducer', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', threshold: { - field: 'agent.hostname', - value: 200, + field: ['agent.hostname'], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }, ruleType: 'eql', @@ -414,11 +414,11 @@ describe('queryPreviewReducer', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', threshold: { - field: 'agent.hostname', - value: 200, + field: ['agent.hostname'], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }, ruleType: 'query', @@ -433,11 +433,11 @@ describe('queryPreviewReducer', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', threshold: { - field: 'agent.hostname', - value: 200, + field: ['agent.hostname'], + value: '200', cardinality: { field: ['user.name'], - value: 2, + value: '2', }, }, ruleType: 'saved_query', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts index 2d301bf96122d..2dff858d61c79 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts @@ -67,7 +67,6 @@ export type Action = type: 'setToFrom'; }; -/* eslint-disable-next-line complexity */ export const queryPreviewReducer = () => (state: State, action: Action): State => { switch (action.type) { case 'setQueryInfo': { @@ -132,9 +131,8 @@ export const queryPreviewReducer = () => (state: State, action: Action): State = const thresholdField = action.threshold != null && action.threshold.field != null && - ((typeof action.threshold.field === 'string' && action.threshold.field.trim() !== '') || - (Array.isArray(action.threshold.field) && - action.threshold.field.every((field) => field.trim() !== ''))); + action.threshold.field.length > 0 && + action.threshold.field.every((field) => field.trim() !== ''); const showNonEqlHist = action.ruleType === 'query' || action.ruleType === 'saved_query' || diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/translations.ts index b3cd8769a34a3..4809a39ef2937 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/translations.ts @@ -42,6 +42,13 @@ export const QUERY_GRAPH_COUNT = i18n.translate( } ); +export const THRESHOLD_QUERY_GRAPH_COUNT = i18n.translate( + 'xpack.securitySolution.detectionEngine.queryPreview.queryThresholdGraphCountLabel', + { + defaultMessage: 'Cumulative Threshold Count', + } +); + export const QUERY_GRAPH_HITS_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.queryPreview.queryGraphHitsTitle', { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 733c2303255cc..362dbb4bb722b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -6,7 +6,7 @@ */ import { EuiButtonEmpty, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import React, { FC, memo, useCallback, useState, useEffect, useMemo } from 'react'; +import React, { FC, memo, useCallback, useState, useEffect } from 'react'; import styled from 'styled-components'; // Prefer importing entire lodash library, e.g. import { get } from "lodash" // eslint-disable-next-line no-restricted-imports @@ -54,7 +54,7 @@ import { import { EqlQueryBar } from '../eql_query_bar'; import { ThreatMatchInput } from '../threatmatch_input'; import { BrowserField, BrowserFields, useFetchIndex } from '../../../../common/containers/source'; -import { PreviewQuery, Threshold } from '../query_preview'; +import { PreviewQuery } from '../query_preview'; const CommonUseField = getUseField({ component: Field }); @@ -154,24 +154,15 @@ const StepDefineRuleComponent: FC = ({ ruleType: formRuleType, queryBar: formQuery, threatIndex: formThreatIndex, - 'threshold.field': formThresholdField, - 'threshold.value': formThresholdValue, - 'threshold.cardinality.field': formThresholdCardinalityField, - 'threshold.cardinality.value': formThresholdCardinalityValue, + threshold: formThreshold, }, - ] = useFormData< - DefineStepRule & { - 'threshold.field': string[] | undefined; - 'threshold.value': number | undefined; - 'threshold.cardinality.field': string[] | undefined; - 'threshold.cardinality.value': number | undefined; - } - >({ + ] = useFormData({ form, watch: [ 'index', 'ruleType', 'queryBar', + 'threshold', 'threshold.field', 'threshold.value', 'threshold.cardinality.field', @@ -288,24 +279,6 @@ const StepDefineRuleComponent: FC = ({ setOpenTimelineSearch(false); }, []); - const thresholdFormValue = useMemo((): Threshold | undefined => { - return formThresholdValue != null - ? { - field: formThresholdField ?? [], - value: formThresholdValue, - cardinality: { - field: formThresholdCardinalityField ?? [], - value: formThresholdCardinalityValue ?? 0, // FIXME - }, - } - : undefined; - }, [ - formThresholdField, - formThresholdValue, - formThresholdCardinalityField, - formThresholdCardinalityValue, - ]); - const ThresholdInputChildren = useCallback( ({ thresholdField, thresholdValue, thresholdCardinalityField, thresholdCardinalityValue }) => ( = ({ index={index} query={formQuery} isDisabled={!isQueryBarValid || index.length === 0} - threshold={thresholdFormValue} + threshold={formThreshold} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx index 55b2aefe21310..875bc5e647077 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx @@ -6,7 +6,7 @@ */ import { useEffect, useRef, useState } from 'react'; -import { ACTION_URL } from '../../../../../../case/common/constants'; +import { ACTION_URL } from '../../../../../../cases/common/constants'; import { KibanaServices } from '../../../../common/lib/kibana'; interface CaseAction { diff --git a/x-pack/plugins/security_solution/public/network/components/port/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/port/__snapshots__/index.test.tsx.snap index ecdaee0ab2d93..f84e858d20573 100644 --- a/x-pack/plugins/security_solution/public/network/components/port/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/components/port/__snapshots__/index.test.tsx.snap @@ -11,6 +11,5 @@ exports[`Port renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx index 47dfffefe091c..9c7a0833b24bb 100644 --- a/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/port/index.test.tsx @@ -60,13 +60,13 @@ describe('Port', () => { ); }); - test('it renders an external link', () => { + test('it renders only one external link icon', () => { const wrapper = mount( ); - expect(wrapper.find('[data-test-subj="external-link-icon"]').first().exists()).toBe(true); + expect(wrapper.find('span [data-euiicon-type="popout"]').length).toBe(1); }); }); diff --git a/x-pack/plugins/security_solution/public/network/components/port/index.tsx b/x-pack/plugins/security_solution/public/network/components/port/index.tsx index 0744ca175aa38..8ee1616d4c77b 100644 --- a/x-pack/plugins/security_solution/public/network/components/port/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/port/index.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { DefaultDraggable } from '../../../common/components/draggables'; import { getEmptyValue } from '../../../common/components/empty_value'; -import { ExternalLinkIcon } from '../../../common/components/external_link_icon'; import { PortOrServiceNameLink } from '../../../common/components/links'; export const CLIENT_PORT_FIELD_NAME = 'client.port'; @@ -40,7 +39,6 @@ export const Port = React.memo<{ value={value} > - )); diff --git a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx index d850204284bd0..29775067478a5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.tsx @@ -10,7 +10,6 @@ import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../../../common/components/draggables'; -import { ExternalLinkIcon } from '../../../common/components/external_link_icon'; import { CertificateFingerprintLink } from '../../../common/components/links'; import * as i18n from './translations'; @@ -61,7 +60,6 @@ export const CertificateFingerprint = React.memo<{ {certificateType === 'client' ? i18n.CLIENT_CERT : i18n.SERVER_CERT} - ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.tsx index df8c68df483c5..d73130417566f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.tsx @@ -9,7 +9,6 @@ import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../../../common/components/draggables'; -import { ExternalLinkIcon } from '../../../common/components/external_link_icon'; import { Ja3FingerprintLink } from '../../../common/components/links'; import * as i18n from './translations'; @@ -45,7 +44,6 @@ export const Ja3Fingerprint = React.memo<{ {i18n.JA3_FINGERPRINT_LABEL} - )); diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx index 65974a10c49c2..283a239acad24 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx @@ -7,7 +7,6 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { ExternalLinkIcon } from '../../../../common/components/external_link_icon'; import { RowRendererId } from '../../../../../common/types/timeline'; import { @@ -37,7 +36,6 @@ const Link = ({ children, url }: { children: React.ReactNode; url: string }) => data-test-subj="externalLink" > {children} -
); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx index 0bbd86479c226..96e6b916a3e11 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx @@ -9,7 +9,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { ExternalLinkIcon } from '../../../../../../common/components/external_link_icon'; import { getLinksFromSignature } from './suricata_links'; const LinkEuiFlexItem = styled(EuiFlexItem)` @@ -27,7 +26,6 @@ export const SuricataRefs = React.memo<{ signatureId: number }>(({ signatureId } {link} - ))} diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index b91ca8dcc25f9..01a85f6309c3f 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -12,7 +12,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; // eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; -import { throwErrors } from '../../../../case/common/api'; +import { throwErrors } from '../../../../cases/common/api'; import { TimelineResponse, TimelineResponseType, diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index 4658e6774b726..b2d54df80e06a 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -16,6 +16,10 @@ export const configSchema = schema.object({ maxTimelineImportExportSize: schema.number({ defaultValue: 10000 }), maxTimelineImportPayloadBytes: schema.number({ defaultValue: 10485760 }), [SIGNALS_INDEX_KEY]: schema.string({ defaultValue: DEFAULT_SIGNALS_INDEX }), + + /** Fleet server integration */ + fleetServerEnabled: schema.boolean({ defaultValue: false }), + /** * Host Endpoint Configuration */ diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index be6289dcf6a7f..0ae8a6d76c3e6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -16,6 +16,7 @@ import { createPackagePolicyServiceMock, createMockAgentPolicyService, createMockAgentService, + createArtifactsClientMock, } from '../../../fleet/server/mocks'; import { AppClientFactory } from '../client'; import { createMockConfig } from '../lib/detection_engine/routes/__mocks__'; @@ -113,6 +114,7 @@ export const createMockFleetStartContract = (indexPattern: string): FleetStartCo agentPolicyService: createMockAgentPolicyService(), registerExternalCallback: jest.fn((...args: ExternalCallback) => {}), packagePolicyService: createPackagePolicyServiceMock(), + createArtifactsClient: jest.fn().mockReturnValue(createArtifactsClientMock()), }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts index a3a9ecd2bf4d2..6ce52bdcbbd53 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts @@ -165,6 +165,12 @@ describe('test alerts route', () => { }; ingestSavedObjectClient.get.mockImplementationOnce(() => Promise.resolve(soFindResp)); + // This workaround is only temporary. The endpoint `ArtifactClient` will be removed soon + // and this entire test file refactored to start using fleet's exposed FleetArtifactClient class. + endpointAppContextService! + .getManifestManager()! + .getArtifactsClient().getArtifact = jest.fn().mockResolvedValue(soFindResp); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/artifacts/download') )!; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts index d08b6887b86eb..99a39616195dd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts @@ -5,24 +5,16 @@ * 2.0. */ -import { - IRouter, - SavedObjectsClientContract, - HttpResponseOptions, - IKibanaResponse, - SavedObject, -} from 'src/core/server'; +import { IRouter, HttpResponseOptions, IKibanaResponse } from 'src/core/server'; import LRU from 'lru-cache'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { authenticateAgentWithAccessToken } from '../../../../../fleet/server/services/agents/authenticate'; import { LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG } from '../../../../common/endpoint/constants'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; -import { ArtifactConstants } from '../../lib/artifacts'; import { DownloadArtifactRequestParamsSchema, downloadArtifactRequestParamsSchema, downloadArtifactResponseSchema, - InternalArtifactCompleteSchema, } from '../../schemas/artifacts'; import { EndpointAppContext } from '../../types'; @@ -48,12 +40,10 @@ export function registerDownloadArtifactRoute( options: { tags: [LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG] }, }, async (context, req, res) => { - let scopedSOClient: SavedObjectsClientContract; const logger = endpointContext.logFactory.get('download_artifact'); // The ApiKey must be associated with an enrolled Fleet agent try { - scopedSOClient = endpointContext.service.getScopedSavedObjectsClient(req); await authenticateAgentWithAccessToken( context.core.elasticsearch.client.asInternalUser, req @@ -91,20 +81,19 @@ export function registerDownloadArtifactRoute( return buildAndValidateResponse(req.params.identifier, cacheResp); } else { logger.debug(`Cache MISS artifact ${id}`); - return scopedSOClient - .get(ArtifactConstants.SAVED_OBJECT_TYPE, id) - .then((artifact: SavedObject) => { - const body = Buffer.from(artifact.attributes.body, 'base64'); - cache.set(id, body); - return buildAndValidateResponse(artifact.attributes.identifier, body); - }) - .catch((err) => { - if (err?.output?.statusCode === 404) { - return res.notFound({ body: `No artifact found for ${id}` }); - } else { - throw err; - } - }); + + const artifact = await endpointContext.service + .getManifestManager() + ?.getArtifactsClient() + .getArtifact(id); + + if (!artifact) { + return res.notFound({ body: `No artifact found for ${id}` }); + } + + const bodyBuffer = Buffer.from(artifact.attributes.body, 'base64'); + cache.set(id, bodyBuffer); + return buildAndValidateResponse(artifact.attributes.identifier, bodyBuffer); } } ); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts index 076f640f24559..a798de29e2ea6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts @@ -5,14 +5,31 @@ * 2.0. */ +/* eslint-disable max-classes-per-file */ + +import { inflate as _inflate } from 'zlib'; +import { promisify } from 'util'; import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; import { ArtifactConstants, getArtifactId } from '../../lib/artifacts'; import { InternalArtifactCompleteSchema, InternalArtifactCreateSchema, } from '../../schemas/artifacts'; +import { Artifact, ArtifactsClientInterface } from '../../../../../fleet/server'; + +const inflateAsync = promisify(_inflate); + +export interface EndpointArtifactClientInterface { + getArtifact(id: string): Promise | undefined>; + + createArtifact( + artifact: InternalArtifactCompleteSchema + ): Promise>; + + deleteArtifact(id: string): Promise; +} -export class ArtifactClient { +export class ArtifactClient implements EndpointArtifactClientInterface { private savedObjectsClient: SavedObjectsClientContract; constructor(savedObjectsClient: SavedObjectsClientContract) { @@ -40,6 +57,68 @@ export class ArtifactClient { } public async deleteArtifact(id: string) { - return this.savedObjectsClient.delete(ArtifactConstants.SAVED_OBJECT_TYPE, id); + await this.savedObjectsClient.delete(ArtifactConstants.SAVED_OBJECT_TYPE, id); + } +} + +/** + * Endpoint specific artifact managment client which uses FleetArtifactsClient to persist artifacts + * to the Fleet artifacts index (then used by Fleet Server) + */ +export class EndpointArtifactClient implements EndpointArtifactClientInterface { + constructor(private fleetArtifacts: ArtifactsClientInterface) {} + + private parseArtifactId( + id: string + ): Pick & { type: string } { + const idPieces = id.split('-'); + + return { + type: idPieces[1], + decodedSha256: idPieces.pop()!, + identifier: idPieces.join('-'), + }; + } + + async getArtifact(id: string) { + const { decodedSha256, identifier } = this.parseArtifactId(id); + const artifacts = await this.fleetArtifacts.listArtifacts({ + kuery: `decodedSha256: "${decodedSha256}" AND identifier: "${identifier}"`, + perPage: 1, + }); + + if (artifacts.items.length === 0) { + return; + } + + // FIXME:PT change method signature so that it returns back only the `InternalArtifactCompleteSchema` + return ({ + attributes: artifacts.items[0], + } as unknown) as SavedObject; + } + + async createArtifact( + artifact: InternalArtifactCompleteSchema + ): Promise> { + // FIXME:PT refactor to make this more efficient by passing through the uncompressed artifact content + // Artifact `.body` is compressed/encoded. We need it decoded and as a string + const artifactContent = await inflateAsync(Buffer.from(artifact.body, 'base64')); + + const createdArtifact = await this.fleetArtifacts.createArtifact({ + content: artifactContent.toString(), + identifier: artifact.identifier, + type: this.parseArtifactId(artifact.identifier).type, + }); + + return ({ + attributes: createdArtifact, + } as unknown) as SavedObject; + } + + async deleteArtifact(id: string) { + // Ignoring the `id` not being in the type until we can refactor the types in endpoint. + // @ts-ignore + const artifactId = (await this.getArtifact(id)).attributes?.id; + return this.fleetArtifacts.deleteArtifact(artifactId); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index f49f2a3e226ee..8c8ea34acfb8b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -439,4 +439,8 @@ export class ManifestManager { kuery: 'ingest-package-policies.package.name:endpoint', }); } + + public getArtifactsClient(): ArtifactClient { + return this.artifactClient; + } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts index 6733944bfd279..31e1d9c2699ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts @@ -10,16 +10,18 @@ import { requestContextMock } from './request_context'; import { serverMock } from './server'; import { requestMock } from './request'; import { responseMock } from './response_factory'; +import { ConfigType } from '../../../../config'; export { requestMock, requestContextMock, responseMock, serverMock }; -export const createMockConfig = () => ({ +export const createMockConfig = (): ConfigType => ({ enabled: true, [SIGNALS_INDEX_KEY]: DEFAULT_SIGNALS_INDEX, maxRuleImportExportSize: 10000, maxRuleImportPayloadBytes: 10485760, maxTimelineImportExportSize: 10000, maxTimelineImportPayloadBytes: 10485760, + fleetServerEnabled: true, endpointResultListDefaultFirstPageIndex: 0, endpointResultListDefaultPageSize: 10, alertResultListDefaultDateRange: { diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts index c3fea9f6d916f..6aac390a3f842 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts @@ -7,7 +7,7 @@ import { RequestParams } from '@elastic/elasticsearch'; -import { buildExceptionFilter } from '../../../common/detection_engine/build_exceptions_filter'; +import { buildExceptionFilter } from '../../../common/shared_imports'; import { ExceptionListItemSchema } from '../../../../lists/common'; import { AnomalyRecordDoc as Anomaly } from '../../../../ml/server'; import { SearchResponse } from '../types'; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 905078f676eef..04a5a525d24bf 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -59,7 +59,7 @@ import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency'; import { registerResolverRoutes } from './endpoint/routes/resolver'; import { registerPolicyRoutes } from './endpoint/routes/policy'; -import { ArtifactClient, ManifestManager } from './endpoint/services'; +import { ArtifactClient, EndpointArtifactClient, ManifestManager } from './endpoint/services'; import { EndpointAppContextService } from './endpoint/endpoint_app_context_services'; import { EndpointAppContext } from './endpoint/types'; import { registerDownloadArtifactRoute } from './endpoint/routes/artifacts'; @@ -162,19 +162,20 @@ export class Plugin implements IPlugin => Promise.resolve(config), + }; + initUsageCollectors({ core, + endpointAppContext: endpointContext, kibanaIndex: globalConfig.kibana.index, ml: plugins.ml, usageCollection: plugins.usageCollection, }); - const endpointContext: EndpointAppContext = { - logFactory: this.context.logger, - service: this.endpointAppContextService, - config: (): Promise => Promise.resolve(config), - }; - const router = core.http.createRouter(); core.http.registerRouteHandlerContext( APP_ID, @@ -343,7 +344,9 @@ export class Plugin implements IPlugin= 10', + }, + }, + cardinality_count: { cardinality: { field: 'agent.name' } }, + events: { + date_histogram: { + extended_bounds: { max: 1599667886215, min: 1599581486215 }, + field: '@timestamp', + fixed_interval: '2700000ms', + min_doc_count: 200, + }, + }, + }, + terms: { + field: 'event.action', + missing: 'All others', + order: { _count: 'desc' }, + size: 10, + }, + }, + }, + query: { + bool: { + filter: [ + { bool: { filter: [{ match_all: {} }], must: [], must_not: [], should: [] } }, + { + range: { + '@timestamp': { + format: 'strict_date_optional_time', + gte: '2020-09-08T16:11:26.215Z', + lte: '2020-09-09T16:11:26.215Z', + }, + }, + }, + ], + }, + }, + size: 0, + }, + ignoreUnavailable: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + track_total_hits: true, +}; + +export const expectedThresholdWithGroupFieldsAndCardinalityDsl = { + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + allowNoIndices: true, + ignoreUnavailable: true, + track_total_hits: true, + body: { + aggregations: { + eventActionGroup: { + terms: { + script: { + lang: 'painless', + source: "doc['host.name'].value + ':' + doc['agent.name'].value", + }, + order: { _count: 'desc' }, + size: 10, + }, + aggs: { + events: { + date_histogram: { + field: '@timestamp', + fixed_interval: '2700000ms', + min_doc_count: 200, extended_bounds: { min: 1599581486215, max: 1599667886215 }, }, }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/helpers.test.ts new file mode 100644 index 0000000000000..ed317031dab04 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/helpers.test.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildThresholdTermsQuery, buildThresholdCardinalityQuery, BaseQuery } from './helpers'; + +const BASE_QUERY: BaseQuery = { + eventActionGroup: { + terms: { + order: { + _count: 'desc', + }, + size: 10, + }, + aggs: { + events: { + date_histogram: { + field: '@timestamp', + fixed_interval: '5000ms', + min_doc_count: 0, + extended_bounds: { + min: 1599581486215, + max: 1599667886215, + }, + }, + }, + }, + }, +}; + +const STACK_BY_FIELD = 'event.action'; + +describe('buildEventsHistogramQuery - helpers', () => { + describe('buildThresholdTermsQuery', () => { + test('it builds a terms query using script if threshold field/s exist', () => { + const query = buildThresholdTermsQuery({ + query: BASE_QUERY, + fields: ['agent.name', 'host.name'], + stackByField: STACK_BY_FIELD, + missing: {}, + }); + expect(query).toEqual({ + eventActionGroup: { + aggs: { + events: { + date_histogram: { + extended_bounds: { max: 1599667886215, min: 1599581486215 }, + field: '@timestamp', + fixed_interval: '5000ms', + min_doc_count: 0, + }, + }, + }, + terms: { + order: { _count: 'desc' }, + script: { + lang: 'painless', + source: "doc['agent.name'].value + ':' + doc['host.name'].value", + }, + size: 10, + }, + }, + }); + }); + + test('it builds a terms query using default stackByField if threshold field/s do not exist', () => { + const query = buildThresholdTermsQuery({ + query: BASE_QUERY, + fields: [], + stackByField: STACK_BY_FIELD, + missing: { missing: 'All others' }, + }); + expect(query).toEqual({ + eventActionGroup: { + aggs: { + events: { + date_histogram: { + extended_bounds: { max: 1599667886215, min: 1599581486215 }, + field: '@timestamp', + fixed_interval: '5000ms', + min_doc_count: 0, + }, + }, + }, + terms: { + field: 'event.action', + missing: 'All others', + order: { _count: 'desc' }, + size: 10, + }, + }, + }); + }); + }); + + describe('buildThresholdCardinalityQuery', () => { + const TERMS_QUERY = { + eventActionGroup: { + terms: { + field: 'host.name', + order: { _count: 'desc' }, + size: 10, + min_doc_count: 200, + }, + aggs: { + events: { + date_histogram: { + field: '@timestamp', + fixed_interval: '2700000ms', + min_doc_count: 0, + extended_bounds: { min: 1599581486215, max: 1599667886215 }, + }, + }, + }, + }, + }; + + test('it builds query with cardinality', () => { + const query = buildThresholdCardinalityQuery({ + query: TERMS_QUERY, + cardinalityField: 'agent.name', + cardinalityValue: '100', + }); + expect(query).toEqual({ + eventActionGroup: { + aggs: { + cardinality_check: { + bucket_selector: { + buckets_path: { cardinalityCount: 'cardinality_count' }, + script: 'params.cardinalityCount >= 100', + }, + }, + cardinality_count: { cardinality: { field: 'agent.name' } }, + events: { + date_histogram: { + extended_bounds: { max: 1599667886215, min: 1599581486215 }, + field: '@timestamp', + fixed_interval: '2700000ms', + min_doc_count: 0, + }, + }, + }, + terms: { field: 'host.name', min_doc_count: 200, order: { _count: 'desc' }, size: 10 }, + }, + }); + }); + + test('it builds a terms query using default stackByField if threshold field/s do not exist', () => { + const query = buildThresholdCardinalityQuery({ + query: TERMS_QUERY, + cardinalityField: '', + cardinalityValue: '', + }); + expect(query).toEqual({ + eventActionGroup: { + aggs: { + events: { + date_histogram: { + extended_bounds: { max: 1599667886215, min: 1599581486215 }, + field: '@timestamp', + fixed_interval: '2700000ms', + min_doc_count: 0, + }, + }, + }, + terms: { field: 'host.name', min_doc_count: 200, order: { _count: 'desc' }, size: 10 }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/helpers.ts new file mode 100644 index 0000000000000..6aed879371a0a --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/helpers.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface BaseQuery { + eventActionGroup: { + terms: { + min_doc_count?: number; + order?: { + _count?: string; + }; + size?: number; + field?: string | string[]; + script?: { + lang: string; + source: string; + }; + missing?: string; + }; + aggs: { + events?: unknown; + cardinality_count?: { + cardinality?: { + field?: string; + }; + }; + cardinality_check?: { + bucket_selector?: { + buckets_path?: { + cardinalityCount?: string; + }; + script?: string; + }; + }; + }; + }; +} + +export const buildThresholdTermsQuery = ({ + query, + fields, + stackByField, + missing, +}: { + query: BaseQuery; + fields: string[]; + stackByField: string; + missing: { missing?: string }; +}): BaseQuery => { + if (fields.length > 1) { + return { + eventActionGroup: { + ...query.eventActionGroup, + terms: { + ...query.eventActionGroup.terms, + script: { + lang: 'painless', + source: fields.map((f) => `doc['${f}'].value`).join(` + ':' + `), + }, + }, + }, + }; + } else { + return { + eventActionGroup: { + ...query.eventActionGroup, + terms: { + ...query.eventActionGroup.terms, + field: fields[0] ?? stackByField, + ...missing, + }, + }, + }; + } +}; + +export const buildThresholdCardinalityQuery = ({ + query, + cardinalityField, + cardinalityValue, +}: { + query: BaseQuery; + cardinalityField: string | undefined; + cardinalityValue: string; +}): BaseQuery => { + if (cardinalityField != null && cardinalityField !== '' && cardinalityValue !== '') { + return { + eventActionGroup: { + ...query.eventActionGroup, + aggs: { + ...query.eventActionGroup.aggs, + cardinality_count: { + cardinality: { + field: cardinalityField, + }, + }, + cardinality_check: { + bucket_selector: { + buckets_path: { + cardinalityCount: 'cardinality_count', + }, + script: `params.cardinalityCount >= ${cardinalityValue}`, + }, + }, + }, + }, + }; + } else { + return query; + } +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.test.ts index c6e15aeefed1f..a2d8650b3380f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.test.ts @@ -11,6 +11,7 @@ import { expectedDsl, expectedThresholdDsl, expectedThresholdMissingFieldDsl, + expectedThresholdWithCardinalityDsl, } from './__mocks__/'; describe('buildEventsHistogramQuery', () => { @@ -18,15 +19,111 @@ describe('buildEventsHistogramQuery', () => { expect(buildEventsHistogramQuery(mockOptions)).toEqual(expectedDsl); }); - test('builds query with just min doc if "threshold.field" is undefined and "missing" param included', () => { + test('builds query with just min doc if "threshold.field" is empty array and "missing" param included', () => { expect( - buildEventsHistogramQuery({ ...mockOptions, threshold: { field: undefined, value: 200 } }) + buildEventsHistogramQuery({ + ...mockOptions, + threshold: { field: [], value: '200', cardinality: { field: [], value: '0' } }, + }) ).toEqual(expectedThresholdMissingFieldDsl); }); - test('builds query with specified threshold field and without "missing" param if "threshold.field" is defined', () => { + test('builds query with specified threshold fields and without "missing" param if "threshold.field" is multi field', () => { expect( - buildEventsHistogramQuery({ ...mockOptions, threshold: { field: 'host.name', value: 200 } }) + buildEventsHistogramQuery({ + ...mockOptions, + threshold: { + field: ['host.name', 'agent.name'], + value: '200', + }, + }) ).toEqual(expectedThresholdDsl); }); + + test('builds query with specified threshold cardinality if defined', () => { + expect( + buildEventsHistogramQuery({ + ...mockOptions, + threshold: { + field: [], + value: '200', + cardinality: { field: ['agent.name'], value: '10' }, + }, + }) + ).toEqual(expectedThresholdWithCardinalityDsl); + }); + + test('builds query with specified threshold group fields and cardinality if defined', () => { + expect( + buildEventsHistogramQuery({ + ...mockOptions, + threshold: { + field: ['host.name', 'agent.name'], + value: '200', + cardinality: { field: ['agent.name'], value: '10' }, + }, + }) + ).toEqual({ + allowNoIndices: true, + body: { + aggregations: { + eventActionGroup: { + aggs: { + cardinality_check: { + bucket_selector: { + buckets_path: { cardinalityCount: 'cardinality_count' }, + script: 'params.cardinalityCount >= 10', + }, + }, + cardinality_count: { cardinality: { field: 'agent.name' } }, + events: { + date_histogram: { + extended_bounds: { max: 1599667886215, min: 1599581486215 }, + field: '@timestamp', + fixed_interval: '2700000ms', + min_doc_count: 200, + }, + }, + }, + terms: { + order: { _count: 'desc' }, + script: { + lang: 'painless', + source: "doc['host.name'].value + ':' + doc['agent.name'].value", + }, + size: 10, + }, + }, + }, + query: { + bool: { + filter: [ + { bool: { filter: [{ match_all: {} }], must: [], must_not: [], should: [] } }, + { + range: { + '@timestamp': { + format: 'strict_date_optional_time', + gte: '2020-09-08T16:11:26.215Z', + lte: '2020-09-09T16:11:26.215Z', + }, + }, + }, + ], + }, + }, + size: 0, + }, + ignoreUnavailable: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + track_total_hits: true, + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts index 04b428f9de89e..15bc4694c1174 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/query.events_histogram.dsl.ts @@ -14,6 +14,7 @@ import { } from '../../../../../utils/build_query'; import { MatrixHistogramRequestOptions } from '../../../../../../common/search_strategy/security_solution/matrix_histogram'; import * as i18n from './translations'; +import { BaseQuery, buildThresholdCardinalityQuery, buildThresholdTermsQuery } from './helpers'; export const buildEventsHistogramQuery = ({ filterQuery, @@ -42,7 +43,7 @@ export const buildEventsHistogramQuery = ({ date_histogram: { field: histogramTimestampField, fixed_interval: interval, - min_doc_count: 0, + min_doc_count: threshold != null ? Number(threshold?.value) : 0, extended_bounds: { min: moment(from).valueOf(), max: moment(to).valueOf(), @@ -58,22 +59,37 @@ export const buildEventsHistogramQuery = ({ : {}; if (threshold != null) { - return { + const query: BaseQuery = { eventActionGroup: { terms: { - field: threshold.field ?? stackByField, - ...(threshold.field != null ? {} : missing), order: { _count: 'desc', }, size: 10, - min_doc_count: threshold.value, }, aggs: { events: dateHistogram, }, }, }; + const baseQuery = buildThresholdTermsQuery({ + query, + fields: threshold.field ?? [], + stackByField, + missing, + }); + + if (threshold.cardinality != null) { + const enrichedQuery = buildThresholdCardinalityQuery({ + query: baseQuery, + cardinalityField: threshold.cardinality.field[0], + cardinalityValue: threshold.cardinality.value, + }); + + return enrichedQuery; + } + + return baseQuery; } return { diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index 981101bf733c7..53fa1a1571835 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -31,6 +31,7 @@ export async function getInternalSavedObjectsClient(core: CoreSetup) { export const registerCollector: RegisterCollector = ({ core, + endpointAppContext, kibanaIndex, ml, usageCollection, @@ -138,7 +139,7 @@ export const registerCollector: RegisterCollector = ({ const [detections, detectionMetrics, endpoints] = await Promise.allSettled([ fetchDetectionsUsage(kibanaIndex, esClient, ml, savedObjectsClient), fetchDetectionsMetrics(ml, savedObjectsClient), - getEndpointTelemetryFromFleet(internalSavedObjectsClient), + getEndpointTelemetryFromFleet(savedObjectsClient, endpointAppContext, esClient), ]); return { diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts index 2c6a1bb69cf27..caa4549fbf31d 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts @@ -7,10 +7,7 @@ import { SavedObjectsFindResponse } from 'src/core/server'; import { AgentEventSOAttributes } from './../../../../fleet/common/types/models/agent'; -import { - AGENT_SAVED_OBJECT_TYPE, - AGENT_EVENT_SAVED_OBJECT_TYPE, -} from '../../../../fleet/common/constants/agent'; +import { AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../../../fleet/common/constants/agent'; import { Agent } from '../../../../fleet/common'; import { FLEET_ENDPOINT_PACKAGE_CONSTANT } from './fleet_saved_objects'; @@ -36,84 +33,68 @@ export const MockOSFullName = 'somePlatformFullName'; export const mockFleetObjectsResponse = ( hasDuplicates = true, lastCheckIn = new Date().toISOString() -): SavedObjectsFindResponse => ({ +): { agents: Agent[]; total: number; page: number; perPage: number } | undefined => ({ page: 1, - per_page: 20, + perPage: 20, total: 1, - saved_objects: [ + agents: [ { - type: AGENT_SAVED_OBJECT_TYPE, + active: true, id: testAgentId, - attributes: { - active: true, - id: testAgentId, - policy_id: 'randoAgentPolicyId', - type: 'PERMANENT', - user_provided_metadata: {}, - enrolled_at: lastCheckIn, - current_error_events: [], - local_metadata: { - elastic: { - agent: { - id: testAgentId, - }, - }, - host: { - hostname: testHostName, - name: testHostName, - id: testHostId, - }, - os: { - platform: MockOSPlatform, - version: MockOSVersion, - name: MockOSName, - full: MockOSFullName, + policy_id: 'randoAgentPolicyId', + type: 'PERMANENT', + user_provided_metadata: {}, + enrolled_at: lastCheckIn, + current_error_events: [], + local_metadata: { + elastic: { + agent: { + id: testAgentId, }, }, - packages: [FLEET_ENDPOINT_PACKAGE_CONSTANT, 'system'], - last_checkin: lastCheckIn, + host: { + hostname: testHostName, + name: testHostName, + id: testHostId, + }, + os: { + platform: MockOSPlatform, + version: MockOSVersion, + name: MockOSName, + full: MockOSFullName, + }, }, - references: [], - updated_at: lastCheckIn, - version: 'WzI4MSwxXQ==', - score: 0, + packages: [FLEET_ENDPOINT_PACKAGE_CONSTANT, 'system'], + last_checkin: lastCheckIn, }, { - type: AGENT_SAVED_OBJECT_TYPE, - id: testAgentId, - attributes: { - active: true, - id: 'oldTestAgentId', - policy_id: 'randoAgentPolicyId', - type: 'PERMANENT', - user_provided_metadata: {}, - enrolled_at: lastCheckIn, - current_error_events: [], - local_metadata: { - elastic: { - agent: { - id: 'oldTestAgentId', - }, - }, - host: { - hostname: hasDuplicates ? testHostName : 'oldRandoHostName', - name: hasDuplicates ? testHostName : 'oldRandoHostName', - id: hasDuplicates ? testHostId : 'oldRandoHostId', - }, - os: { - platform: MockOSPlatform, - version: MockOSVersion, - name: MockOSName, - full: MockOSFullName, + active: true, + id: 'oldTestAgentId', + policy_id: 'randoAgentPolicyId', + type: 'PERMANENT', + user_provided_metadata: {}, + enrolled_at: lastCheckIn, + current_error_events: [], + local_metadata: { + elastic: { + agent: { + id: 'oldTestAgentId', }, }, - packages: [FLEET_ENDPOINT_PACKAGE_CONSTANT, 'system'], - last_checkin: lastCheckIn, + host: { + hostname: hasDuplicates ? testHostName : 'oldRandoHostName', + name: hasDuplicates ? testHostName : 'oldRandoHostName', + id: hasDuplicates ? testHostId : 'oldRandoHostId', + }, + os: { + platform: MockOSPlatform, + version: MockOSVersion, + name: MockOSName, + full: MockOSFullName, + }, }, - references: [], - updated_at: lastCheckIn, - version: 'WzI4MSwxXQ==', - score: 0, + packages: [FLEET_ENDPOINT_PACKAGE_CONSTANT, 'system'], + last_checkin: lastCheckIn, }, ], }); diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts index aaf85a0201478..1541cb128f60c 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import { mockFleetObjectsResponse, mockFleetEventsObjectsResponse, @@ -13,23 +13,34 @@ import { MockOSPlatform, MockOSVersion, } from './endpoint.mocks'; -import { ISavedObjectsRepository, SavedObjectsFindResponse } from 'src/core/server'; +import { SavedObjectsClientContract, SavedObjectsFindResponse } from 'src/core/server'; import { AgentEventSOAttributes } from '../../../../fleet/common/types/models/agent'; import { Agent } from '../../../../fleet/common'; import * as endpointTelemetry from './index'; import * as fleetSavedObjects from './fleet_saved_objects'; +import { createMockEndpointAppContext } from '../../endpoint/mocks'; +import { EndpointAppContext } from '../../endpoint/types'; describe('test security solution endpoint telemetry', () => { - let mockSavedObjectsRepository: jest.Mocked; - let getFleetSavedObjectsMetadataSpy: jest.SpyInstance>>; + let mockSavedObjectsClient: jest.Mocked; + let mockEndpointAppContext: EndpointAppContext; + let mockEsClient: ReturnType; + let getEndpointIntegratedFleetMetadataSpy: jest.SpyInstance< + Promise<{ agents: Agent[]; total: number; page: number; perPage: number } | undefined> + >; let getLatestFleetEndpointEventSpy: jest.SpyInstance< Promise> >; beforeAll(() => { getLatestFleetEndpointEventSpy = jest.spyOn(fleetSavedObjects, 'getLatestFleetEndpointEvent'); - getFleetSavedObjectsMetadataSpy = jest.spyOn(fleetSavedObjects, 'getFleetSavedObjectsMetadata'); - mockSavedObjectsRepository = savedObjectsRepositoryMock.create(); + getEndpointIntegratedFleetMetadataSpy = jest.spyOn( + fleetSavedObjects, + 'getEndpointIntegratedFleetMetadata' + ); + mockSavedObjectsClient = savedObjectsClientMock.create(); + mockEndpointAppContext = createMockEndpointAppContext(); + mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); }); afterAll(() => { @@ -55,28 +66,32 @@ describe('test security solution endpoint telemetry', () => { describe('when a request for endpoint agents fails', () => { it('should return an empty object', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.reject(Error('No agents for you')) ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); - expect(getFleetSavedObjectsMetadataSpy).toHaveBeenCalled(); + expect(getEndpointIntegratedFleetMetadataSpy).toHaveBeenCalled(); expect(endpointUsage).toEqual({}); }); }); describe('when an agent has not been installed', () => { it('should return the default shape if no agents are found', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => - Promise.resolve({ saved_objects: [], total: 0, per_page: 0, page: 0 }) + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => + Promise.resolve({ agents: [], total: 0, perPage: 0, page: 0 }) ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); - expect(getFleetSavedObjectsMetadataSpy).toHaveBeenCalled(); + expect(getEndpointIntegratedFleetMetadataSpy).toHaveBeenCalled(); expect(endpointUsage).toEqual({ total_installed: 0, active_within_last_24_hours: 0, @@ -95,7 +110,7 @@ describe('test security solution endpoint telemetry', () => { describe('when agent(s) have been installed', () => { describe('when a request for events has failed', () => { it('should show only one endpoint installed but it is inactive', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse()) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -103,7 +118,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 1, @@ -129,7 +146,7 @@ describe('test security solution endpoint telemetry', () => { describe('when a request for events is successful', () => { it('should show one endpoint installed but endpoint has failed to run', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse()) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -137,7 +154,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 1, @@ -161,7 +180,7 @@ describe('test security solution endpoint telemetry', () => { }); it('should show two endpoints installed but both endpoints have failed to run', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse(false)) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -169,7 +188,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 2, @@ -197,7 +218,7 @@ describe('test security solution endpoint telemetry', () => { twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); const twoDaysAgoISOString = twoDaysAgo.toISOString(); - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse(false, twoDaysAgoISOString)) ); getLatestFleetEndpointEventSpy.mockImplementation( @@ -205,7 +226,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 2, @@ -229,7 +252,7 @@ describe('test security solution endpoint telemetry', () => { }); it('should show one endpoint installed and endpoint is running', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse()) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -237,7 +260,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 1, @@ -262,7 +287,7 @@ describe('test security solution endpoint telemetry', () => { describe('malware policy', () => { it('should have failed to enable', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse()) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -272,7 +297,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 1, @@ -296,7 +323,7 @@ describe('test security solution endpoint telemetry', () => { }); it('should be enabled successfully', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse()) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -304,7 +331,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 1, @@ -328,7 +357,7 @@ describe('test security solution endpoint telemetry', () => { }); it('should be disabled successfully', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => + getEndpointIntegratedFleetMetadataSpy.mockImplementation(() => Promise.resolve(mockFleetObjectsResponse()) ); getLatestFleetEndpointEventSpy.mockImplementation(() => @@ -338,7 +367,9 @@ describe('test security solution endpoint telemetry', () => { ); const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository + mockSavedObjectsClient, + mockEndpointAppContext, + mockEsClient ); expect(endpointUsage).toEqual({ total_installed: 1, diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts index e96ce0b2dda76..7e3620ec0ae04 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts @@ -5,38 +5,36 @@ * 2.0. */ -import { ISavedObjectsRepository } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; +import { AgentService } from '../../../../fleet/server'; import { AgentEventSOAttributes } from './../../../../fleet/common/types/models/agent'; -import { - AGENT_SAVED_OBJECT_TYPE, - AGENT_EVENT_SAVED_OBJECT_TYPE, -} from './../../../../fleet/common/constants/agent'; -import { Agent, defaultPackages as FleetDefaultPackages } from '../../../../fleet/common'; +import { AGENT_EVENT_SAVED_OBJECT_TYPE } from './../../../../fleet/common/constants/agent'; +import { defaultPackages as FleetDefaultPackages } from '../../../../fleet/common'; export const FLEET_ENDPOINT_PACKAGE_CONSTANT = FleetDefaultPackages.Endpoint; -export const getFleetSavedObjectsMetadata = async (savedObjectsClient: ISavedObjectsRepository) => - savedObjectsClient.find({ - // Get up to 10000 agents with endpoint installed - type: AGENT_SAVED_OBJECT_TYPE, - fields: [ - 'packages', - 'last_checkin', - 'local_metadata.agent.id', - 'local_metadata.host.id', - 'local_metadata.host.name', - 'local_metadata.host.hostname', - 'local_metadata.elastic.agent.id', - 'local_metadata.os', - ], - filter: `${AGENT_SAVED_OBJECT_TYPE}.attributes.packages: ${FLEET_ENDPOINT_PACKAGE_CONSTANT}`, +export const getEndpointIntegratedFleetMetadata = async ( + agentService: AgentService | undefined, + esClient: ElasticsearchClient +) => { + return agentService?.listAgents(esClient, { + kuery: `(packages : ${FLEET_ENDPOINT_PACKAGE_CONSTANT})`, perPage: 10000, + showInactive: false, sortField: 'enrolled_at', sortOrder: 'desc', }); +}; + +/* + TODO: AS OF 7.13, this access will no longer work due to the enabling of fleet server. An alternative route will have + to be discussed to retrieve the policy data we need, as well as when the endpoint was last active, which is obtained + via the last endpoint 'check in' event that was sent to fleet. Also, the only policy currently tracked is `malware`, + but the hope is to add more, so a better/more scalable solution would be desirable. +*/ export const getLatestFleetEndpointEvent = async ( - savedObjectsClient: ISavedObjectsRepository, + savedObjectsClient: SavedObjectsClientContract, agentId: string ) => savedObjectsClient.find({ diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts index 48cb50a493edf..94ff168ffffc8 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts @@ -6,11 +6,15 @@ */ import { cloneDeep } from 'lodash'; -import { ISavedObjectsRepository } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { SavedObject } from './../../../../../../src/core/types/saved_objects'; import { Agent, NewAgentEvent } from './../../../../fleet/common/types/models/agent'; import { AgentMetadata } from '../../../../fleet/common/types/models/agent'; -import { getFleetSavedObjectsMetadata, getLatestFleetEndpointEvent } from './fleet_saved_objects'; +import { + getEndpointIntegratedFleetMetadata, + getLatestFleetEndpointEvent, +} from './fleet_saved_objects'; +import { EndpointAppContext } from '../../endpoint/types'; export interface AgentOSMetadataTelemetry { full_name: string; @@ -108,7 +112,7 @@ export const updateEndpointOSTelemetry = ( * the same time span. */ export const updateEndpointDailyActiveCount = ( - latestEndpointEvent: SavedObject, + latestEndpointEvent: SavedObject, // TODO: This information will be lost in 7.13, need to find an alternative route. lastAgentCheckin: Agent['last_checkin'], currentCount: number ) => { @@ -193,19 +197,22 @@ export const updateEndpointPolicyTelemetry = ( }; /** - * @description This aggregates the telemetry details from the two fleet savedObject sources, `fleet-agents` and `fleet-agent-events` to populate + * @description This aggregates the telemetry details from the fleet agent service `listAgents` and the fleet saved object `fleet-agent-events` to populate * the telemetry details for endpoint. Since we cannot access our own indices due to `kibana_system` not having access, this is the best alternative. * Once the data is requested, we iterate over all agents with endpoints registered, and then request the events for each active agent (within last 24 hours) * to confirm whether or not the endpoint is still active */ export const getEndpointTelemetryFromFleet = async ( - soClient: ISavedObjectsRepository + soClient: SavedObjectsClientContract, + endpointAppContext: EndpointAppContext, + esClient: ElasticsearchClient ): Promise => { // Retrieve every agent (max 10000) that references the endpoint as an installed package. It will not be listed if it was never installed let endpointAgents; + const agentService = endpointAppContext.service.getAgentService(); try { - const response = await getFleetSavedObjectsMetadata(soClient); - endpointAgents = response.saved_objects; + const response = await getEndpointIntegratedFleetMetadata(agentService, esClient); + endpointAgents = response?.agents ?? []; } catch (error) { // Better to provide an empty object rather than default telemetry as this better informs us of an error return {}; @@ -225,8 +232,7 @@ export const getEndpointTelemetryFromFleet = async ( for (let i = 0; i < endpointAgentsCount; i += 1) { try { - const { attributes: metadataAttributes } = endpointAgents[i]; - const { last_checkin: lastCheckin, local_metadata: localMetadata } = metadataAttributes; + const { last_checkin: lastCheckin, local_metadata: localMetadata } = endpointAgents[i]; const { host, os, elastic } = localMetadata as AgentLocalMetadata; // Although not perfect, the goal is to dedupe hosts to get the most recent data for a host diff --git a/x-pack/plugins/security_solution/server/usage/types.ts b/x-pack/plugins/security_solution/server/usage/types.ts index 562b6a5278f64..c06c8a4722cd7 100644 --- a/x-pack/plugins/security_solution/server/usage/types.ts +++ b/x-pack/plugins/security_solution/server/usage/types.ts @@ -6,9 +6,11 @@ */ import { CoreSetup } from 'src/core/server'; +import { EndpointAppContext } from '../endpoint/types'; import { SetupPlugins } from '../plugin'; -export type CollectorDependencies = { kibanaIndex: string; core: CoreSetup } & Pick< - SetupPlugins, - 'ml' | 'usageCollection' ->; +export type CollectorDependencies = { + kibanaIndex: string; + core: CoreSetup; + endpointAppContext: EndpointAppContext; +} & Pick; diff --git a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss index 367fbf9e97925..1f272260fc9c2 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss @@ -15,3 +15,7 @@ margin: $euiSizeM; width: calc(100% - #{$euiSizeM*2}); } + +.spcMenu__item { + margin-left: $euiSizeM; +} \ No newline at end of file diff --git a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx index fda79bd93e39a..392a95c445921 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx @@ -133,14 +133,12 @@ class SpacesMenuUI extends Component { return (
{ - // @ts-ignore { const keyCode = e.keyCode; if (focusableKeyCodes.includes(keyCode)) { - // Allows the spaces list panel to recieve focus. This enables keyboard and screen reader navigation + // Allows the spaces list panel to receive focus. This enables keyboard and screen reader navigation this.setState({ allowSpacesListFocus: true, }); @@ -206,7 +204,7 @@ class SpacesMenuUI extends Component { toolTipTitle={space.description && space.name} toolTipContent={space.description} > - {space.name} + {space.name} ); }; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts index d844651d0b8aa..86e07fa64af66 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts @@ -5,10 +5,19 @@ * 2.0. */ +import uuid from 'uuid'; import type { Writable } from '@kbn/utility-types'; +import { AlertServices } from '../../../../alerting/server'; +import { + AlertServicesMock, + alertsMock, + AlertInstanceMock, +} from '../../../../alerting/server/mocks'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { getAlertType } from './alert_type'; -import { EsQueryAlertParams } from './alert_type_params'; +import { getAlertType, ConditionMetAlertInstanceId, ActionGroupId } from './alert_type'; +import { EsQueryAlertParams, EsQueryAlertState } from './alert_type_params'; +import { ActionContext } from './action_context'; +import { ESSearchResponse, ESSearchRequest } from '../../../../../typings/elasticsearch'; describe('alertType', () => { const logger = loggingSystemMock.create().get(); @@ -108,4 +117,476 @@ describe('alertType', () => { `"[threshold]: must have two elements for the \\"between\\" comparator"` ); }); + + it('alert executor handles no documentes returned by ES', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: 'between', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const searchResult: ESSearchResponse = generateResults([]); + alertServices.callCluster.mockResolvedValueOnce(searchResult); + + const result = await alertType.executor({ + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + latestTimestamp: undefined, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }); + + expect(alertServices.alertInstanceFactory).not.toHaveBeenCalled(); + + expect(result).toMatchInlineSnapshot(` + Object { + "latestTimestamp": undefined, + } + `); + }); + + it('alert executor returns the latestTimestamp of the newest detected document', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const newestDocumentTimestamp = Date.now(); + + const searchResult: ESSearchResponse = generateResults([ + { + 'time-field': newestDocumentTimestamp, + }, + { + 'time-field': newestDocumentTimestamp - 1000, + }, + { + 'time-field': newestDocumentTimestamp - 2000, + }, + ]); + alertServices.callCluster.mockResolvedValueOnce(searchResult); + + const result = await alertType.executor({ + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + latestTimestamp: undefined, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }); + + expect(alertServices.alertInstanceFactory).toHaveBeenCalledWith(ConditionMetAlertInstanceId); + const instance: AlertInstanceMock = alertServices.alertInstanceFactory.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + }); + }); + + it('alert executor correctly handles numeric time fields that were stored by legacy rules prior to v7.12.1', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const previousTimestamp = Date.now(); + const newestDocumentTimestamp = previousTimestamp + 1000; + + alertServices.callCluster.mockResolvedValueOnce( + generateResults([ + { + 'time-field': newestDocumentTimestamp, + }, + ]) + ); + + const executorOptions = { + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }; + const result = await alertType.executor({ + ...executorOptions, + state: { + // @ts-expect-error previousTimestamp is numeric, but should be string (this was a bug prior to v7.12.1) + latestTimestamp: previousTimestamp, + }, + }); + + const instance: AlertInstanceMock = alertServices.alertInstanceFactory.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + // ensure the invalid "latestTimestamp" in the state is stored as an ISO string going forward + latestTimestamp: new Date(previousTimestamp).toISOString(), + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + }); + }); + + it('alert executor ignores previous invalid latestTimestamp values stored by legacy rules prior to v7.12.1', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.callCluster.mockResolvedValueOnce( + generateResults([ + { + 'time-field': oldestDocumentTimestamp, + }, + { + 'time-field': oldestDocumentTimestamp - 1000, + }, + ]) + ); + + const result = await alertType.executor({ + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + // inaalid legacy `latestTimestamp` + latestTimestamp: 'FaslK3QBySSL_rrj9zM5', + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }); + + const instance: AlertInstanceMock = alertServices.alertInstanceFactory.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + }); + }); + + it('alert executor carries over the queried latestTimestamp in the alert state', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.callCluster.mockResolvedValueOnce( + generateResults([ + { + 'time-field': oldestDocumentTimestamp, + }, + ]) + ); + + const executorOptions = { + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + latestTimestamp: undefined, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }; + const result = await alertType.executor(executorOptions); + + const instance: AlertInstanceMock = alertServices.alertInstanceFactory.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + }); + + const newestDocumentTimestamp = oldestDocumentTimestamp + 5000; + alertServices.callCluster.mockResolvedValueOnce( + generateResults([ + { + 'time-field': newestDocumentTimestamp, + }, + { + 'time-field': newestDocumentTimestamp - 1000, + }, + ]) + ); + + const secondResult = await alertType.executor({ + ...executorOptions, + state: result as EsQueryAlertState, + }); + const existingInstance: AlertInstanceMock = + alertServices.alertInstanceFactory.mock.results[1].value; + expect(existingInstance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(secondResult).toMatchObject({ + latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + }); + }); + + it('alert executor ignores tie breaker sort values', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.callCluster.mockResolvedValueOnce( + generateResults( + [ + { + 'time-field': oldestDocumentTimestamp, + }, + { + 'time-field': oldestDocumentTimestamp - 1000, + }, + ], + true + ) + ); + + const result = await alertType.executor({ + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + latestTimestamp: undefined, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }); + + const instance: AlertInstanceMock = alertServices.alertInstanceFactory.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + }); + }); + + it('alert executor ignores results with no sort values', async () => { + const params: EsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: '>', + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.callCluster.mockResolvedValueOnce( + generateResults( + [ + { + 'time-field': oldestDocumentTimestamp, + }, + { + 'time-field': oldestDocumentTimestamp - 1000, + }, + ], + true, + true + ) + ); + + const result = await alertType.executor({ + alertId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: (alertServices as unknown) as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + latestTimestamp: undefined, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + }); + + const instance: AlertInstanceMock = alertServices.alertInstanceFactory.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp - 1000).toISOString(), + }); + }); }); + +function generateResults( + docs: Array<{ 'time-field': unknown; [key: string]: unknown }>, + includeTieBreaker: boolean = false, + skipSortOnFirst: boolean = false +): ESSearchResponse { + const hits = docs.map((doc, index) => ({ + _index: 'foo', + _type: '_doc', + _id: `${index}`, + _score: 0, + ...(skipSortOnFirst && index === 0 + ? {} + : { + sort: (includeTieBreaker + ? ['FaslK3QBySSL_rrj9zM5', doc['time-field']] + : [doc['time-field']]) as string[], + }), + _source: doc, + })); + return { + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, + }, + hits: { + total: { + value: docs.length, + relation: 'eq', + }, + max_score: 100, + hits, + }, + }; +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index bad25a0d1d09c..74af8b0038a3a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -23,8 +23,8 @@ import { ESSearchHit } from '../../../../../typings/elasticsearch'; export const ES_QUERY_ID = '.es-query'; -const ActionGroupId = 'query matched'; -const ConditionMetAlertInstanceId = 'query matched'; +export const ActionGroupId = 'query matched'; +export const ConditionMetAlertInstanceId = 'query matched'; export function getAlertType( logger: Logger @@ -173,7 +173,7 @@ export function getAlertType( // of the alert, the latestTimestamp will be used to gate the query in order to // avoid counting a document multiple times. - let timestamp: string | undefined = previousTimestamp; + let timestamp: string | undefined = tryToParseAsDate(previousTimestamp); const filter = timestamp ? { bool: { @@ -187,7 +187,7 @@ export function getAlertType( filter: [ { range: { - [params.timeField]: { lte: new Date(timestamp).toISOString() }, + [params.timeField]: { lte: timestamp }, }, }, ], @@ -251,12 +251,11 @@ export function getAlertType( .scheduleActions(ActionGroupId, actionContext); // update the timestamp based on the current search results - const firstHitWithSort = searchResult.hits.hits.find( - (hit: ESSearchHit) => hit.sort != null + const firstValidTimefieldSort = getValidTimefieldSort( + searchResult.hits.hits.find((hit: ESSearchHit) => getValidTimefieldSort(hit.sort))?.sort ); - const lastTimestamp = firstHitWithSort?.sort; - if (lastTimestamp != null && lastTimestamp.length > 0) { - timestamp = lastTimestamp[0]; + if (firstValidTimefieldSort) { + timestamp = firstValidTimefieldSort; } } } @@ -267,6 +266,21 @@ export function getAlertType( } } +function getValidTimefieldSort(sortValues: Array = []): undefined | string { + for (const sortValue of sortValues) { + const sortDate = tryToParseAsDate(sortValue); + if (sortDate) { + return sortDate; + } + } +} +function tryToParseAsDate(sortValue?: string | number): undefined | string { + const sortDate = typeof sortValue === 'string' ? Date.parse(sortValue) : sortValue; + if (sortDate && !isNaN(sortDate)) { + return new Date(sortDate).toISOString(); + } +} + function getInvalidComparatorError(comparator: string) { return i18n.translate('xpack.stackAlerts.esQuery.invalidComparatorErrorMessage', { defaultMessage: 'invalid thresholdComparator specified: {comparator}', diff --git a/x-pack/plugins/transform/common/api_schemas/type_guards.ts b/x-pack/plugins/transform/common/api_schemas/type_guards.ts index e28d55611fefa..28eaf9ce2894f 100644 --- a/x-pack/plugins/transform/common/api_schemas/type_guards.ts +++ b/x-pack/plugins/transform/common/api_schemas/type_guards.ts @@ -35,18 +35,18 @@ const isGenericResponseSchema = (arg: any): arg is T => { ); }; -export const isGetTransformsResponseSchema = (arg: any): arg is GetTransformsResponseSchema => { +export const isGetTransformsResponseSchema = (arg: unknown): arg is GetTransformsResponseSchema => { return isGenericResponseSchema(arg); }; export const isGetTransformsStatsResponseSchema = ( - arg: any + arg: unknown ): arg is GetTransformsStatsResponseSchema => { return isGenericResponseSchema(arg); }; export const isDeleteTransformsResponseSchema = ( - arg: any + arg: unknown ): arg is DeleteTransformsResponseSchema => { return ( isPopulatedObject(arg) && @@ -54,26 +54,28 @@ export const isDeleteTransformsResponseSchema = ( ); }; -export const isEsIndices = (arg: any): arg is EsIndex[] => { +export const isEsIndices = (arg: unknown): arg is EsIndex[] => { return Array.isArray(arg); }; -export const isEsSearchResponse = (arg: any): arg is SearchResponse7 => { +export const isEsSearchResponse = (arg: unknown): arg is SearchResponse7 => { return isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'hits'); }; -export const isFieldHistogramsResponseSchema = (arg: any): arg is FieldHistogramsResponseSchema => { +export const isFieldHistogramsResponseSchema = ( + arg: unknown +): arg is FieldHistogramsResponseSchema => { return Array.isArray(arg); }; export const isGetTransformsAuditMessagesResponseSchema = ( - arg: any + arg: unknown ): arg is GetTransformsAuditMessagesResponseSchema => { return Array.isArray(arg); }; export const isPostTransformsPreviewResponseSchema = ( - arg: any + arg: unknown ): arg is PostTransformsPreviewResponseSchema => { return ( isPopulatedObject(arg) && @@ -85,12 +87,12 @@ export const isPostTransformsPreviewResponseSchema = ( }; export const isPostTransformsUpdateResponseSchema = ( - arg: any + arg: unknown ): arg is PostTransformsUpdateResponseSchema => { return isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'id') && typeof arg.id === 'string'; }; -export const isPutTransformsResponseSchema = (arg: any): arg is PutTransformsResponseSchema => { +export const isPutTransformsResponseSchema = (arg: unknown): arg is PutTransformsResponseSchema => { return ( isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'transformsCreated') && @@ -100,13 +102,17 @@ export const isPutTransformsResponseSchema = (arg: any): arg is PutTransformsRes ); }; -const isGenericSuccessResponseSchema = (arg: any) => +const isGenericSuccessResponseSchema = (arg: unknown) => isPopulatedObject(arg) && Object.values(arg).every((d) => ({}.hasOwnProperty.call(d, 'success'))); -export const isStartTransformsResponseSchema = (arg: any): arg is StartTransformsResponseSchema => { +export const isStartTransformsResponseSchema = ( + arg: unknown +): arg is StartTransformsResponseSchema => { return isGenericSuccessResponseSchema(arg); }; -export const isStopTransformsResponseSchema = (arg: any): arg is StopTransformsResponseSchema => { +export const isStopTransformsResponseSchema = ( + arg: unknown +): arg is StopTransformsResponseSchema => { return isGenericSuccessResponseSchema(arg); }; diff --git a/x-pack/plugins/transform/common/types/transform.ts b/x-pack/plugins/transform/common/types/transform.ts index 6bc814e85190b..600808c475fd1 100644 --- a/x-pack/plugins/transform/common/types/transform.ts +++ b/x-pack/plugins/transform/common/types/transform.ts @@ -7,6 +7,7 @@ import { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types'; import type { LatestFunctionConfig, PutTransformsRequestSchema } from '../api_schemas/transforms'; +import { isPopulatedObject } from '../utils/object_utils'; import { PivotGroupByDict } from './pivot_group_by'; import { PivotAggDict } from './pivot_aggs'; @@ -44,14 +45,12 @@ export type TransformLatestConfig = Omit & { export type TransformConfigUnion = TransformPivotConfig | TransformLatestConfig; -export function isPivotTransform( - transform: TransformBaseConfig -): transform is TransformPivotConfig { - return transform.hasOwnProperty('pivot'); +export function isPivotTransform(transform: unknown): transform is TransformPivotConfig { + return isPopulatedObject(transform) && transform.hasOwnProperty('pivot'); } -export function isLatestTransform(transform: any): transform is TransformLatestConfig { - return transform.hasOwnProperty('latest'); +export function isLatestTransform(transform: unknown): transform is TransformLatestConfig { + return isPopulatedObject(transform) && transform.hasOwnProperty('latest'); } export interface LatestFunctionConfigUI { diff --git a/x-pack/plugins/transform/common/types/transform_stats.ts b/x-pack/plugins/transform/common/types/transform_stats.ts index f3b7000a424db..03e6b2e403b69 100644 --- a/x-pack/plugins/transform/common/types/transform_stats.ts +++ b/x-pack/plugins/transform/common/types/transform_stats.ts @@ -6,6 +6,7 @@ */ import { TransformState, TRANSFORM_STATE } from '../constants'; +import { isPopulatedObject } from '../utils/object_utils'; import { TransformId } from './transform'; export interface TransformStats { @@ -55,11 +56,12 @@ export interface TransformStats { state: TransformState; } -export function isTransformStats(arg: any): arg is TransformStats { +function isTransformState(arg: unknown): arg is TransformState { + return typeof arg === 'string' && Object.values(TRANSFORM_STATE).includes(arg as TransformState); +} + +export function isTransformStats(arg: unknown): arg is TransformStats { return ( - typeof arg === 'object' && - arg !== null && - {}.hasOwnProperty.call(arg, 'state') && - Object.values(TRANSFORM_STATE).includes(arg.state) + isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'state') && isTransformState(arg.state) ); } diff --git a/x-pack/plugins/transform/common/utils/errors.ts b/x-pack/plugins/transform/common/utils/errors.ts index c48e488a3c766..46ff3f9165c00 100644 --- a/x-pack/plugins/transform/common/utils/errors.ts +++ b/x-pack/plugins/transform/common/utils/errors.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { isPopulatedObject } from './object_utils'; + export interface ErrorResponse { body: { statusCode: number; @@ -15,16 +17,16 @@ export interface ErrorResponse { name: string; } -export function isErrorResponse(arg: any): arg is ErrorResponse { - return arg?.body?.error !== undefined && arg?.body?.message !== undefined; +export function isErrorResponse(arg: unknown): arg is ErrorResponse { + return isPopulatedObject(arg) && isPopulatedObject(arg.body) && arg?.body?.message !== undefined; } -export function getErrorMessage(error: any) { +export function getErrorMessage(error: unknown) { if (isErrorResponse(error)) { return `${error.body.error}: ${error.body.message}`; } - if (typeof error === 'object' && typeof error.message === 'string') { + if (isPopulatedObject(error) && typeof error.message === 'string') { return error.message; } diff --git a/x-pack/plugins/transform/common/utils/object_utils.ts b/x-pack/plugins/transform/common/utils/object_utils.ts index 38567741c7e13..a573535da6b4f 100644 --- a/x-pack/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/plugins/transform/common/utils/object_utils.ts @@ -52,6 +52,6 @@ export const setNestedProperty = (obj: Record, accessor: string, va return obj; }; -export const isPopulatedObject = >(arg: any): arg is T => { +export const isPopulatedObject = >(arg: unknown): arg is T => { return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0; }; diff --git a/x-pack/plugins/transform/public/app/common/aggregations.ts b/x-pack/plugins/transform/public/app/common/aggregations.ts index 314cb87e4d949..0ec756be94b08 100644 --- a/x-pack/plugins/transform/public/app/common/aggregations.ts +++ b/x-pack/plugins/transform/public/app/common/aggregations.ts @@ -9,7 +9,7 @@ import { composeValidators, patternValidator } from '../../../common/shared_impo import { AggName } from '../../../common/types/aggregations'; -export function isAggName(arg: any): arg is AggName { +export function isAggName(arg: unknown): arg is AggName { // allow all characters except `[]>` and must not start or end with a space. const validatorFn = composeValidators( patternValidator(/^[^\s]/), diff --git a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts index 05ec87f754ba1..905b40f16f7fb 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts @@ -14,12 +14,16 @@ import type { Dictionary } from '../../../common/types/common'; import type { EsFieldName } from '../../../common/types/fields'; import type { PivotAgg, PivotSupportedAggs } from '../../../common/types/pivot_aggs'; import { PIVOT_SUPPORTED_AGGS } from '../../../common/types/pivot_aggs'; +import { isPopulatedObject } from '../../../common/utils/object_utils'; import { getAggFormConfig } from '../sections/create_transform/components/step_define/common/get_agg_form_config'; import { PivotAggsConfigFilter } from '../sections/create_transform/components/step_define/common/filter_agg/types'; -export function isPivotSupportedAggs(arg: any): arg is PivotSupportedAggs { - return Object.values(PIVOT_SUPPORTED_AGGS).includes(arg); +export function isPivotSupportedAggs(arg: unknown): arg is PivotSupportedAggs { + return ( + typeof arg === 'string' && + Object.values(PIVOT_SUPPORTED_AGGS).includes(arg as PivotSupportedAggs) + ); } export const PERCENTILES_AGG_DEFAULT_PERCENTS = [1, 5, 25, 50, 75, 95, 99]; @@ -160,8 +164,9 @@ export type PivotAggsConfigWithUiSupport = | PivotAggsConfigPercentiles | PivotAggsConfigWithExtendedForm; -export function isPivotAggsConfigWithUiSupport(arg: any): arg is PivotAggsConfigWithUiSupport { +export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport { return ( + isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('aggName') && arg.hasOwnProperty('dropDownName') && @@ -175,12 +180,13 @@ export function isPivotAggsConfigWithUiSupport(arg: any): arg is PivotAggsConfig */ type PivotAggsConfigWithExtendedForm = PivotAggsConfigFilter; -export function isPivotAggsWithExtendedForm(arg: any): arg is PivotAggsConfigWithExtendedForm { - return arg.hasOwnProperty('AggFormComponent'); +export function isPivotAggsWithExtendedForm(arg: unknown): arg is PivotAggsConfigWithExtendedForm { + return isPopulatedObject(arg) && arg.hasOwnProperty('AggFormComponent'); } -export function isPivotAggsConfigPercentiles(arg: any): arg is PivotAggsConfigPercentiles { +export function isPivotAggsConfigPercentiles(arg: unknown): arg is PivotAggsConfigPercentiles { return ( + isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field') && arg.hasOwnProperty('percents') && diff --git a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts index 4f23b8aa4d86f..fac0d88a84df7 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts @@ -9,6 +9,7 @@ import { AggName } from '../../../common/types/aggregations'; import { Dictionary } from '../../../common/types/common'; import { EsFieldName } from '../../../common/types/fields'; import { GenericAgg } from '../../../common/types/pivot_group_by'; +import { isPopulatedObject } from '../../../common/utils/object_utils'; import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; import { PivotAggsConfigWithUiSupport } from './pivot_aggs'; @@ -81,8 +82,9 @@ export type PivotGroupByConfig = export type PivotGroupByConfigWithUiSupportDict = Dictionary; export type PivotGroupByConfigDict = Dictionary; -export function isGroupByDateHistogram(arg: any): arg is GroupByDateHistogram { +export function isGroupByDateHistogram(arg: unknown): arg is GroupByDateHistogram { return ( + isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field') && arg.hasOwnProperty('calendar_interval') && @@ -90,8 +92,9 @@ export function isGroupByDateHistogram(arg: any): arg is GroupByDateHistogram { ); } -export function isGroupByHistogram(arg: any): arg is GroupByHistogram { +export function isGroupByHistogram(arg: unknown): arg is GroupByHistogram { return ( + isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field') && arg.hasOwnProperty('interval') && @@ -99,15 +102,16 @@ export function isGroupByHistogram(arg: any): arg is GroupByHistogram { ); } -export function isGroupByTerms(arg: any): arg is GroupByTerms { +export function isGroupByTerms(arg: unknown): arg is GroupByTerms { return ( + isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field') && arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS ); } -export function isPivotGroupByConfigWithUiSupport(arg: any): arg is GroupByConfigWithUiSupport { +export function isPivotGroupByConfigWithUiSupport(arg: unknown): arg is GroupByConfigWithUiSupport { return isGroupByDateHistogram(arg) || isGroupByHistogram(arg) || isGroupByTerms(arg); } @@ -119,6 +123,6 @@ export function getEsAggFromGroupByConfig(groupByConfig: GroupByConfigBase): Gen }; } -export function isPivotAggConfigWithUiSupport(arg: any): arg is PivotAggsConfigWithUiSupport { - return arg.hasOwnProperty('agg') && arg.hasOwnProperty('field'); +export function isPivotAggConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport { + return isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field'); } diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index a11eb070be7da..e25548668ac5f 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -58,13 +58,19 @@ export function getPivotQuery(search: string | SavedSearchQuery): PivotQuery { return search; } -export function isSimpleQuery(arg: any): arg is SimpleQuery { - return arg.query_string !== undefined; +export function isSimpleQuery(arg: unknown): arg is SimpleQuery { + return isPopulatedObject(arg) && arg.hasOwnProperty('query_string'); } export const matchAllQuery = { match_all: {} }; -export function isMatchAllQuery(query: any): boolean { - return query.match_all !== undefined && Object.keys(query.match_all).length === 0; +export function isMatchAllQuery(query: unknown): boolean { + return ( + isPopulatedObject(query) && + query.hasOwnProperty('match_all') && + typeof query.match_all === 'object' && + query.match_all !== null && + Object.keys(query.match_all).length === 0 + ); } export const defaultQuery: PivotQuery = { query_string: { query: '*' } }; @@ -94,7 +100,7 @@ export function getCombinedRuntimeMappings( } } - if (isPopulatedObject(combinedRuntimeMappings)) { + if (isPopulatedObject(combinedRuntimeMappings)) { return combinedRuntimeMappings; } return undefined; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts index 5b1bb349ef46d..cf82478d94423 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { Privileges } from '../../../../../common/types/privileges'; +import { isPopulatedObject } from '../../../../../common/utils/object_utils'; export interface Capabilities { canGetTransform: boolean; @@ -19,10 +20,9 @@ export interface Capabilities { export type Privilege = [string, string]; -function isPrivileges(arg: any): arg is Privileges { +function isPrivileges(arg: unknown): arg is Privileges { return ( - typeof arg === 'object' && - arg !== null && + isPopulatedObject(arg) && arg.hasOwnProperty('hasAllPrivileges') && typeof arg.hasAllPrivileges === 'boolean' && arg.hasOwnProperty('missingPrivileges') && diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts index cafa887a2e903..6b4ff0090a497 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts @@ -72,7 +72,7 @@ export interface StepDefineExposedState { isRuntimeMappingsEditorEnabled: boolean; } -export function isRuntimeField(arg: any): arg is RuntimeField { +export function isRuntimeField(arg: unknown): arg is RuntimeField { return ( isPopulatedObject(arg) && ((Object.keys(arg).length === 1 && arg.hasOwnProperty('type')) || @@ -84,18 +84,18 @@ export function isRuntimeField(arg: any): arg is RuntimeField { Object.keys(arg.script).length === 1 && arg.script.hasOwnProperty('source') && typeof arg.script.source === 'string')))) && - RUNTIME_FIELD_TYPES.includes(arg.type) + RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType) ); } -export function isRuntimeMappings(arg: any): arg is RuntimeMappings { +export function isRuntimeMappings(arg: unknown): arg is RuntimeMappings { return isPopulatedObject(arg) && Object.values(arg).every((d) => isRuntimeField(d)); } -export function isPivotPartialRequest(arg: any): arg is { pivot: PivotConfigDefinition } { +export function isPivotPartialRequest(arg: unknown): arg is { pivot: PivotConfigDefinition } { return isPopulatedObject(arg) && arg.hasOwnProperty('pivot'); } -export function isLatestPartialRequest(arg: any): arg is { latest: LatestFunctionConfig } { +export function isLatestPartialRequest(arg: unknown): arg is { latest: LatestFunctionConfig } { return isPopulatedObject(arg) && arg.hasOwnProperty('latest'); } diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts index 6680495bdab91..0910b21658fa6 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts @@ -130,7 +130,7 @@ export const stringValidator: Validator = (value, isOptional = true) => { return []; }; -function parseDurationAboveZero(arg: any, errorMessage: string): ParsedDuration | string[] { +function parseDurationAboveZero(arg: unknown, errorMessage: string): ParsedDuration | string[] { if (typeof arg !== 'string' || arg === null) { return [stringNotValidErrorMessage]; } diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx index bcb07c8069ab2..fa1a42b94f0b7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx @@ -105,7 +105,7 @@ export const TransformManagement: FC = () => { diff --git a/x-pack/plugins/transform/public/app/services/navigation/doc_title.ts b/x-pack/plugins/transform/public/app/services/navigation/doc_title.ts index 05f33fb40bd22..6a30e58dc81e2 100644 --- a/x-pack/plugins/transform/public/app/services/navigation/doc_title.ts +++ b/x-pack/plugins/transform/public/app/services/navigation/doc_title.ts @@ -7,10 +7,12 @@ import { textService } from '../text'; +type ChangeDocTitle = (docTitle: string) => void; + class DocTitleService { - private changeDocTitle: any = () => {}; + private changeDocTitle: ChangeDocTitle = () => {}; - public init(changeDocTitle: any): void { + public init(changeDocTitle: ChangeDocTitle): void { this.changeDocTitle = changeDocTitle; } diff --git a/x-pack/plugins/transform/public/register_feature.ts b/x-pack/plugins/transform/public/register_feature.ts index 2702ef9f616d6..d105424052411 100644 --- a/x-pack/plugins/transform/public/register_feature.ts +++ b/x-pack/plugins/transform/public/register_feature.ts @@ -20,7 +20,7 @@ export const registerFeature = (home: HomePublicPluginSetup) => { }), description: i18n.translate('xpack.transform.transformsDescription', { defaultMessage: - 'Use transforms to pivot existing Elasticsearch indices into summarized or entity-centric indices.', + 'Use transforms to pivot existing Elasticsearch indices into summarized entity-centric indices or to create an indexed view of the latest documents for fast access.', }), icon: 'managementApp', // there is currently no Transforms icon, so using the general management app icon path: '/app/management/data/transform', diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 92bbcc230662b..31de9a1811f30 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -952,12 +952,8 @@ "data.query.queryBar.kqlOffLabel": "オフ", "data.query.queryBar.kqlOnLabel": "オン", "data.query.queryBar.luceneLanguageName": "Lucene", - "data.query.queryBar.luceneSyntaxWarningMessage": "Lucene クエリ構文を使用しているようですが、Kibana クエリ言語 (KQL) が選択されています。KQL ドキュメント {link} を確認してください。", - "data.query.queryBar.luceneSyntaxWarningOptOutText": "今後表示しない", - "data.query.queryBar.luceneSyntaxWarningTitle": "Lucene 構文警告", "data.query.queryBar.searchInputAriaLabel": "{pageType} ページの検索とフィルタリングを行うには入力を開始してください", "data.query.queryBar.searchInputPlaceholder": "検索", - "data.query.queryBar.syntaxOptionsDescription.docsLinkText": "こちら", "data.query.queryBar.syntaxOptionsTitle": "構文オプション", "data.search.aggs.aggGroups.bucketsText": "バケット", "data.search.aggs.aggGroups.metricsText": "メトリック", @@ -4909,8 +4905,8 @@ "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "アクションタイプ「{id}」は登録されていません。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "アクションタイプ「{id}」はすでに登録されています。", "xpack.actions.appName": "アクション", - "xpack.actions.builtin.case.jiraTitle": "Jira", - "xpack.actions.builtin.case.resilientTitle": "IBM Resilient", + "xpack.actions.builtin.cases.jiraTitle": "Jira", + "xpack.actions.builtin.cases.resilientTitle": "IBM Resilient", "xpack.actions.builtin.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", "xpack.actions.builtin.email.customViewInKibanaMessage": "このメッセージは Kibana によって送信されました。[{kibanaFooterLinkText}] ({link}) 。", "xpack.actions.builtin.email.errorSendingErrorMessage": "エラー送信メールアドレス", @@ -6103,9 +6099,6 @@ "xpack.canvas.functions.axisConfig.invalidMinDateStringErrorMessage": "無効なデータ文字列:「{min}」。「min」は数字、ms での日付、または ISO8601 データ文字列でなければなりません", "xpack.canvas.functions.axisConfig.invalidPositionErrorMessage": "無効なポジション:「{position}」", "xpack.canvas.functions.axisConfigHelpText": "ビジュアライゼーションの軸を構成します。{plotFn} でのみ使用されます。", - "xpack.canvas.functions.case.args.ifHelpText": "この値は、条件が満たされているかどうかを示します。両方が入力された場合、{IF_ARG}引数が{WHEN_ARG}引数を上書きします。", - "xpack.canvas.functions.case.args.thenHelpText": "条件が満たされた際に返される値です。", - "xpack.canvas.functions.case.args.whenHelpText": "等しいかを確認するために {CONTEXT} と比較される値です。{IF_ARG} 引数も指定されている場合、{WHEN_ARG} 引数は無視されます。", "xpack.canvas.functions.caseHelpText": "{switchFn} 関数に渡すため、条件と結果を含めて {case} を作成します。", "xpack.canvas.functions.clearHelpText": "{CONTEXT} を消去し、{TYPE_NULL} を返します。", "xpack.canvas.functions.columns.args.excludeHelpText": "{DATATABLE} から削除する列名のコンマ区切りのリストです。", @@ -6985,11 +6978,11 @@ "xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "説明", "xpack.canvas.workpadTemplates.table.nameColumnTitle": "テンプレート名", "xpack.canvas.workpadTemplates.table.tagsColumnTitle": "タグ", - "xpack.case.connectors.case.externalIncidentAdded": " ({date}に{user}が追加) ", - "xpack.case.connectors.case.externalIncidentCreated": " ({date}に{user}が作成) ", - "xpack.case.connectors.case.externalIncidentDefault": " ({date}に{user}が作成) ", - "xpack.case.connectors.case.externalIncidentUpdated": " ({date}に{user}が更新) ", - "xpack.case.connectors.case.title": "ケース", + "xpack.cases.connectors.cases.externalIncidentAdded": " ({date}に{user}が追加) ", + "xpack.cases.connectors.cases.externalIncidentCreated": " ({date}に{user}が作成) ", + "xpack.cases.connectors.cases.externalIncidentDefault": " ({date}に{user}が作成) ", + "xpack.cases.connectors.cases.externalIncidentUpdated": " ({date}に{user}が更新) ", + "xpack.cases.connectors.cases.title": "ケース", "xpack.cloud.deploymentLinkLabel": "このデプロイの管理", "xpack.cloud.userMenuLinks.accountLinkText": "会計・請求", "xpack.cloud.userMenuLinks.profileLinkText": "クラウドプロファイル", @@ -10049,7 +10042,6 @@ "xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableTitle": "コールドティアに割り当てられているノードがありません", "xpack.indexLifecycleMgmt.coldPhase.dataTier.description": "頻度が低い読み取り専用アクセス用に最適化されたノードにデータを移動します。安価なハードウェアのコールドフェーズにデータを格納します。", "xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel": "インデックスを凍結", - "xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel": "レプリカの数", "xpack.indexLifecycleMgmt.common.dataTier.title": "データ割り当て", "xpack.indexLifecycleMgmt.confirmDelete.cancelButton": "キャンセル", "xpack.indexLifecycleMgmt.confirmDelete.deleteButton": "削除", @@ -10064,16 +10056,10 @@ "xpack.indexLifecycleMgmt.editPolicy.coldPhase.activateColdPhaseSwitchLabel": "コールドフェーズを有効にする", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseDescription": "データをコールドティアに移動します。これは、検索パフォーマンスよりもコスト削減を優先するように最適化されています。通常、コールドフェーズではデータが読み取り専用です。", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseTitle": "コールドフェーズ", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeIndexExplanationText": "インデックスを読み取り専用にし、メモリー消費量を最小化します。", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeText": "凍結", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText": "ノード属性に基づいてデータを移動します。", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input": "カスタム", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.helpText": "コールドティアのノードにデータを移動します。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.input": "コールドノードを使用 (推奨) ", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.helpText": "コールドフェーズにデータを移動しないでください。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.input": "オフ", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.helpText": "ノード属性に基づいてデータを移動します。", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.input": "カスタム", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.helpText": "ウォームティアのノードにデータを移動します。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.input": "ウォームノードを使用 (推奨) ", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.noneOption.helpText": "ウォームフェーズにデータを移動しないでください。", @@ -10186,7 +10172,6 @@ "xpack.indexLifecycleMgmt.editPolicy.saveErrorMessage": "ライフサイクルポリシー {lifecycleName} の保存中にエラーが発生しました", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotCalloutBody": "検索可能なスナップショットがホットフェーズで有効な場合には、強制、マージ、縮小、凍結、コールドフェーズの検索可能なスナップショットは許可されません。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldDescription": "選択したリポジトリで管理されたインデックスのスナップショットを作成し、検索可能なスナップショットとしてマウントします。{learnMoreLink}", - "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldLabel": "検索可能なスナップショットリポジドリ", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldTitle": "検索可能スナップショット", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutBody": "検索可能なスナップショットを作成するには、エンタープライズライセンスが必要です。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutTitle": "エンタープライズライセンスが必要です", @@ -10352,7 +10337,6 @@ "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm": "このポリシーはウォームフェーズのデータを{tier}ティアノードに移動します。", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title": "ウォームティアに割り当てられているノードがありません", "xpack.indexLifecycleMgmt.warmPhase.dataTier.description": "頻度が低い読み取り専用アクセス用に最適化されたノードにデータを移動します。", - "xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel": "レプリカの数", "xpack.infra.alerting.alertDropdownTitle": "アラート", "xpack.infra.alerting.alertFlyout.groupBy.placeholder": "なし (グループなし) ", "xpack.infra.alerting.alertFlyout.groupByLabel": "グループ分けの条件", @@ -11862,7 +11846,6 @@ "xpack.lens.configure.invalidConfigTooltipClick": "詳細はクリックしてください。", "xpack.lens.customBucketContainer.dragToReorder": "ドラッグして並べ替え", "xpack.lens.dataPanelWrapper.switchDatasource": "データソースに切り替える", - "xpack.lens.datatable.breakdown": "内訳の基準", "xpack.lens.datatable.conjunctionSign": " & ", "xpack.lens.datatable.expressionHelpLabel": "データベースレンダー", "xpack.lens.datatable.label": "データテーブル", @@ -17080,7 +17063,6 @@ "xpack.reporting.panelContent.notification.cantReachServerDescription": "サーバーと通信できません。再試行してください。", "xpack.reporting.panelContent.notification.reportingErrorTitle": "レポートエラー", "xpack.reporting.panelContent.saveWorkDescription": "レポートの生成前に変更内容を保存してください。", - "xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription": "管理で進捗を確認できます", "xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle": "{objectType} のレポートキュー", "xpack.reporting.panelContent.whatCanBeExportedWarningDescription": "初めに変更内容を保存してください", "xpack.reporting.panelContent.whatCanBeExportedWarningTitle": "保存された {objectType} のみエクスポートできます", @@ -17099,7 +17081,6 @@ "xpack.reporting.publicNotifier.maxSizeReached.partialReportTitle": "{reportObjectType}「{reportObjectTitle}」の部分レポートが作成されました", "xpack.reporting.publicNotifier.pollingErrorMessage": "レポート通知エラー", "xpack.reporting.publicNotifier.reportLink.pickItUpFromPathDescription": "{path}から開始します。", - "xpack.reporting.publicNotifier.reportLink.reportingSectionUrlLinkLabel": "管理 > Kibana > レポート", "xpack.reporting.publicNotifier.successfullyCreatedReportNotificationTitle": "{reportObjectType}「{reportObjectTitle}」のレポートが作成されました", "xpack.reporting.registerFeature.reportingDescription": "Discover、可視化、ダッシュボードから生成されたレポートを管理します。", "xpack.reporting.registerFeature.reportingTitle": "レポート", @@ -17959,7 +17940,6 @@ "xpack.security.management.users.usersTitle": "ユーザー", "xpack.security.management.usersTitle": "ユーザー", "xpack.security.navControlComponent.accountMenuAriaLabel": "アカウントメニュー", - "xpack.security.navControlComponent.editProfileLinkText": "プロフィール", "xpack.security.navControlComponent.loginLinkText": "ログイン", "xpack.security.navControlComponent.logoutLinkText": "ログアウト", "xpack.security.overwrittenSession.continueAsUserText": "{username} として続行", @@ -18167,212 +18147,212 @@ "xpack.securitySolution.beatFields.errorSearchDescription": "Beatフィールドの取得でエラーが発生しました", "xpack.securitySolution.beatFields.failSearchDescription": "Beat フィールドで検索を実行できませんでした", "xpack.securitySolution.callouts.dismissButton": "閉じる", - "xpack.securitySolution.case.allCases.actions": "アクション", - "xpack.securitySolution.case.allCases.comments": "コメント", - "xpack.securitySolution.case.allCases.noTagsAvailable": "利用可能なタグがありません", - "xpack.securitySolution.case.caseModal.title": "ケースを選択", - "xpack.securitySolution.case.caseSavedObjectNoPermissionsMessage": "ケースを表示するには、Kibana スペースで保存されたオブジェクト管理機能の権限が必要です。詳細については、Kibana管理者に連絡してください。", - "xpack.securitySolution.case.caseSavedObjectNoPermissionsTitle": "Kibana機能権限が必要です", - "xpack.securitySolution.case.caseTable.addNewCase": "新規ケースの追加", - "xpack.securitySolution.case.caseTable.bulkActions": "一斉アクション", - "xpack.securitySolution.case.caseTable.bulkActions.closeSelectedTitle": "選択した項目を閉じる", - "xpack.securitySolution.case.caseTable.bulkActions.deleteSelectedTitle": "選択した項目を削除", - "xpack.securitySolution.case.caseTable.bulkActions.openSelectedTitle": "選択した項目を再開", - "xpack.securitySolution.case.caseTable.caseDetailsLinkAria": "クリックすると、タイトル{detailName}のケースを表示します", - "xpack.securitySolution.case.caseTable.closed": "終了", - "xpack.securitySolution.case.caseTable.closedCases": "終了したケース", - "xpack.securitySolution.case.caseTable.delete": "削除", - "xpack.securitySolution.case.caseTable.incidentSystem": "インシデント管理システム", - "xpack.securitySolution.case.caseTable.inProgressCases": "進行中のケース", - "xpack.securitySolution.case.caseTable.noCases.body": "表示するケースがありません。新しいケースを作成するか、または上記のフィルター設定を変更してください。", - "xpack.securitySolution.case.caseTable.noCases.title": "ケースなし", - "xpack.securitySolution.case.caseTable.notPushed": "プッシュされません", - "xpack.securitySolution.case.caseTable.openCases": "ケースを開く", - "xpack.securitySolution.case.caseTable.refreshTitle": "更新", - "xpack.securitySolution.case.caseTable.requiresUpdate": " 更新が必要", - "xpack.securitySolution.case.caseTable.searchAriaLabel": "ケースの検索", - "xpack.securitySolution.case.caseTable.searchPlaceholder": "例:ケース名", - "xpack.securitySolution.case.caseTable.serviceNowLinkAria": "クリックすると、servicenowでインシデントを表示します", - "xpack.securitySolution.case.caseTable.snIncident": "外部インシデント", - "xpack.securitySolution.case.caseTable.status": "ステータス", - "xpack.securitySolution.case.caseTable.upToDate": " は最新です", - "xpack.securitySolution.case.caseView.actionHeadline": "{actionDate} の {userName} {actionName}", - "xpack.securitySolution.case.caseView.actionLabel.addComment": "コメントを追加しました", - "xpack.securitySolution.case.caseView.actionLabel.addDescription": "説明を追加しました", - "xpack.securitySolution.case.caseView.actionLabel.addedField": "追加しました", - "xpack.securitySolution.case.caseView.actionLabel.changededField": "変更しました", - "xpack.securitySolution.case.caseView.actionLabel.editedField": "編集しました", - "xpack.securitySolution.case.caseView.actionLabel.on": "日付", - "xpack.securitySolution.case.caseView.actionLabel.pushedNewIncident": "新しいインシデントとしてプッシュしました", - "xpack.securitySolution.case.caseView.actionLabel.removedField": "削除しました", - "xpack.securitySolution.case.caseView.actionLabel.removedThirdParty": "外部のインシデント管理システムを削除しました", - "xpack.securitySolution.case.caseView.actionLabel.selectedThirdParty": "インシデント管理システムとして{ thirdParty }を選択しました", - "xpack.securitySolution.case.caseView.actionLabel.updateIncident": "インシデントを更新しました", - "xpack.securitySolution.case.caseView.actionLabel.viewIncident": "{incidentNumber}を表示", - "xpack.securitySolution.case.caseView.alertCommentLabelTitle": "アラートを追加しました", - "xpack.securitySolution.case.caseView.alertRuleDeletedLabelTitle": "アラートを追加しました", - "xpack.securitySolution.case.caseView.alreadyPushedToExternalService": "すでに{ externalService }インシデントにプッシュしました", - "xpack.securitySolution.case.caseView.appropiateLicense": "適切なライセンス", - "xpack.securitySolution.case.caseView.backLabel": "ケースに戻る", - "xpack.securitySolution.case.caseView.breadcrumb": "作成", - "xpack.securitySolution.case.caseView.cancel": "キャンセル", - "xpack.securitySolution.case.caseView.case": "ケース", - "xpack.securitySolution.case.caseView.caseClosed": "ケースを閉じました", - "xpack.securitySolution.case.caseView.caseInProgress": "進行中のケース", - "xpack.securitySolution.case.caseView.caseName": "ケース名", - "xpack.securitySolution.case.caseView.caseOpened": "ケースを開きました", - "xpack.securitySolution.case.caseView.caseRefresh": "ケースを更新", - "xpack.securitySolution.case.caseView.closeCase": "ケースを閉じる", - "xpack.securitySolution.case.caseView.closedOn": "終了日", - "xpack.securitySolution.case.caseView.cloudDeploymentLink": "クラウド展開", - "xpack.securitySolution.case.caseView.comment": "コメント", - "xpack.securitySolution.case.caseView.comment.addComment": "コメントを追加", - "xpack.securitySolution.case.caseView.comment.addCommentHelpText": "新しいコメントを追加...", - "xpack.securitySolution.case.caseView.commentFieldRequiredError": "コメントが必要です。", - "xpack.securitySolution.case.caseView.connectorConfigureLink": "コネクター", - "xpack.securitySolution.case.caseView.connectors": "外部インシデント管理システム", - "xpack.securitySolution.case.caseView.copyCommentLinkAria": "参照リンクをコピー", - "xpack.securitySolution.case.caseView.create": "新規ケースを作成", - "xpack.securitySolution.case.caseView.createCase": "ケースを作成", - "xpack.securitySolution.case.caseView.description": "説明", - "xpack.securitySolution.case.caseView.description.save": "保存", - "xpack.securitySolution.case.caseView.edit": "編集", - "xpack.securitySolution.case.caseView.edit.comment": "コメントを編集", - "xpack.securitySolution.case.caseView.edit.description": "説明を編集", - "xpack.securitySolution.case.caseView.edit.quote": "お客様の声", - "xpack.securitySolution.case.caseView.editActionsLinkAria": "クリックすると、すべてのアクションを表示します", - "xpack.securitySolution.case.caseView.editConnector": "外部インシデント管理システムを変更", - "xpack.securitySolution.case.caseView.editTagsLinkAria": "クリックすると、タグを編集します", - "xpack.securitySolution.case.caseView.emailBody": "ケースリファレンス:{caseUrl}", - "xpack.securitySolution.case.caseView.emailSubject": "セキュリティケース - {caseTitle}", - "xpack.securitySolution.case.caseView.errorsPushServiceCallOutTitle": "ケースを外部システムにプッシュするには、以下が必要です。", - "xpack.securitySolution.case.caseView.fieldChanged": "変更されたコネクターフィールド", - "xpack.securitySolution.case.caseView.fieldRequiredError": "必須フィールド", - "xpack.securitySolution.case.caseView.generatedAlertCommentLabelTitle": "から追加されました", - "xpack.securitySolution.case.caseView.goToDocumentationButton": "ドキュメンテーションを表示", - "xpack.securitySolution.case.caseView.markedCaseAs": "ケースを設定", - "xpack.securitySolution.case.caseView.markInProgress": "実行中に設定", - "xpack.securitySolution.case.caseView.moveToCommentAria": "参照されたコメントをハイライト", - "xpack.securitySolution.case.caseView.name": "名前", - "xpack.securitySolution.case.caseView.noReportersAvailable": "利用可能なレポートがありません。", - "xpack.securitySolution.case.caseView.noTags": "現在、このケースにタグは割り当てられていません。", - "xpack.securitySolution.case.caseView.openedOn": "開始日", - "xpack.securitySolution.case.caseView.optional": "オプション", - "xpack.securitySolution.case.caseView.particpantsLabel": "参加者", - "xpack.securitySolution.case.caseView.pushNamedIncident": "{ thirdParty }インシデントとしてプッシュ", - "xpack.securitySolution.case.caseView.pushThirdPartyIncident": "外部インシデントとしてプッシュ", - "xpack.securitySolution.case.caseView.pushToServiceDisableBecauseCaseClosedDescription": "終了したケースは外部システムに送信できません。外部システムでケースを開始または更新したい場合にはケースを再開します。", - "xpack.securitySolution.case.caseView.pushToServiceDisableBecauseCaseClosedTitle": "ケースを再開する", - "xpack.securitySolution.case.caseView.pushToServiceDisableByConfigDescription": "kibana.ymlファイルは、特定のコネクターのみを許可するように構成されています。外部システムでケースを開けるようにするには、xpack.actions.enabledActiontypes設定に.[actionTypeId] (例:.servicenow | .jira) を追加します。詳細は{link}をご覧ください。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByConfigTitle": "Kibanaの構成ファイルで外部サービスを有効にする", - "xpack.securitySolution.case.caseView.pushToServiceDisableByInvalidConnector": "外部サービスに更新を送信するために使用されるコネクターが削除されました。外部システムでケースを更新するには、別のコネクターを選択するか、新しいコネクターを作成してください。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByLicenseDescription": "{appropriateLicense}があるか、{cloud}を使用しているか、無償試用版をテストしているときには、外部システムでケースを開くことができます。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByLicenseTitle": "適切なライセンスにアップグレード", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoCaseConfigDescription": "外部システムでケースを開いて更新するには、このケースの外部インシデント管理システムを選択する必要があります。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoCaseConfigTitle": "外部コネクターを選択", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoConfigTitle": "外部コネクターを構成", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoConnectors": "外部システムでケースを開いて更新するには、{link}を設定する必要があります。", - "xpack.securitySolution.case.caseView.reopenCase": "ケースを再開", - "xpack.securitySolution.case.caseView.reporterLabel": "報告者", - "xpack.securitySolution.case.caseView.requiredUpdateToExternalService": "{ externalService }インシデントの更新が必要です", - "xpack.securitySolution.case.caseView.sendAlertToTimelineTooltip": "タイムラインで調査", - "xpack.securitySolution.case.caseView.sendEmalLinkAria": "クリックすると、{user}に電子メールを送信します", - "xpack.securitySolution.case.caseView.showAlertTooltip": "アラートの詳細を表示", - "xpack.securitySolution.case.caseView.statusLabel": "ステータス", - "xpack.securitySolution.case.caseView.syncAlertsLabel": "アラートの同期", - "xpack.securitySolution.case.caseView.tags": "タグ", - "xpack.securitySolution.case.caseView.to": "に", - "xpack.securitySolution.case.caseView.unknown": "不明", - "xpack.securitySolution.case.caseView.unknownRule.label": "不明なルール", - "xpack.securitySolution.case.caseView.updateNamedIncident": "{ thirdParty }インシデントを更新", - "xpack.securitySolution.case.caseView.updateThirdPartyIncident": "外部インシデントを更新", - "xpack.securitySolution.case.common.noConnector": "コネクターを選択していません", - "xpack.securitySolution.case.components.connectors.case.actionTypeTitle": "ケース", - "xpack.securitySolution.case.components.connectors.case.addNewCaseOption": "新規ケースの追加", - "xpack.securitySolution.case.components.connectors.case.caseRequired": "ケースの選択が必要です。", - "xpack.securitySolution.case.components.connectors.case.casesDropdownPlaceholder": "ケースを選択", - "xpack.securitySolution.case.components.connectors.case.casesDropdownRowLabel": "ケース", - "xpack.securitySolution.case.components.connectors.case.commentLabel": "コメント", - "xpack.securitySolution.case.components.connectors.case.commentRequired": "コメントが必要です。", - "xpack.securitySolution.case.components.connectors.case.connectedCaseLabel": "接続されたケース", - "xpack.securitySolution.case.components.connectors.case.createCaseLabel": "ケースを作成", - "xpack.securitySolution.case.components.connectors.case.optionAddNewCase": "新しいケースに追加", - "xpack.securitySolution.case.components.connectors.case.optionAddToExistingCase": "既存のケースに追加", - "xpack.securitySolution.case.components.connectors.case.selectMessageText": "ケースを作成または更新します。", - "xpack.securitySolution.case.configure.errorGetFields": "サービスからのフィールドの取得中にエラーが発生しました", - "xpack.securitySolution.case.configure.successSaveToast": "保存された外部接続設定", - "xpack.securitySolution.case.configureCases.addNewConnector": "新しいコネクターを追加", - "xpack.securitySolution.case.configureCases.blankMappings": "1 つ以上のフィールドを { connectorName } にマッピングする必要があります", - "xpack.securitySolution.case.configureCases.cancelButton": "キャンセル", - "xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident": "新しいインシデントが外部システムで閉じたときにセキュリティケースを自動的に閉じる", - "xpack.securitySolution.case.configureCases.caseClosureOptionsDesc": "セキュリティケースの終了のしかたを定義します。自動ケース終了のためには、外部のインシデント管理システムへの接続を確立する必要がいります。", - "xpack.securitySolution.case.configureCases.caseClosureOptionsLabel": "ケース終了オプション", - "xpack.securitySolution.case.configureCases.caseClosureOptionsManual": "セキュリティケースを手動で閉じる", - "xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident": "新しいインシデントを外部システムにプッシュするときにセキュリティケースを自動的に閉じる", - "xpack.securitySolution.case.configureCases.caseClosureOptionsTitle": "ケースのクローズ", - "xpack.securitySolution.case.configureCases.commentMapping": "コメント", - "xpack.securitySolution.case.configureCases.editFieldMappingTitle": "{ thirdPartyName } フィールドマッピングを編集", - "xpack.securitySolution.case.configureCases.fieldMappingDesc": "データを { thirdPartyName } にプッシュするときに、セキュリティケースフィールドを { thirdPartyName } フィールドにマッピングします。フィールドマッピングでは、{ thirdPartyName } への接続を確立する必要があります。", - "xpack.securitySolution.case.configureCases.fieldMappingDescErr": "フィールドマッピングでは、{ thirdPartyName } への接続を確立する必要があります。接続資格情報を確認してください。", - "xpack.securitySolution.case.configureCases.fieldMappingEditAppend": "末尾に追加", - "xpack.securitySolution.case.configureCases.fieldMappingEditNothing": "何もしない", - "xpack.securitySolution.case.configureCases.fieldMappingEditOverwrite": "上書き", - "xpack.securitySolution.case.configureCases.fieldMappingFirstCol": "セキュリティケースフィールド", - "xpack.securitySolution.case.configureCases.fieldMappingSecondCol": "{ thirdPartyName } フィールド", - "xpack.securitySolution.case.configureCases.fieldMappingThirdCol": "編集時と更新時", - "xpack.securitySolution.case.configureCases.fieldMappingTitle": "{ thirdPartyName } フィールドマッピング", - "xpack.securitySolution.case.configureCases.headerTitle": "ケースを構成", - "xpack.securitySolution.case.configureCases.incidentManagementSystemDesc": "オプションとして、セキュリティケースを選択した外部のインシデント管理システムに接続できます。そうすると、選択したサードパーティシステム内でケースデータをインシデントとしてプッシュできます。", - "xpack.securitySolution.case.configureCases.incidentManagementSystemLabel": "インシデント管理システム", - "xpack.securitySolution.case.configureCases.incidentManagementSystemTitle": "外部のインシデント管理システムに接続", - "xpack.securitySolution.case.configureCases.mappingFieldNotMapped": "マップされません", - "xpack.securitySolution.case.configureCases.noFieldsError": "{ connectorName } フィールドが見つかりません。解決する { connectorName } コネクター設定または { connectorName } インスタンス設定を確認してください。", - "xpack.securitySolution.case.configureCases.requiredMappings": "1 つ以上のケースフィールドを次の { connectorName } フィールドにマッピングする必要があります:{ fields }", - "xpack.securitySolution.case.configureCases.saveAndCloseButton": "保存して閉じる", - "xpack.securitySolution.case.configureCases.saveButton": "保存", - "xpack.securitySolution.case.configureCases.updateConnector": "フィールドマッピングを更新", - "xpack.securitySolution.case.configureCases.updateSelectedConnector": "{ connectorName }を更新", - "xpack.securitySolution.case.configureCases.warningMessage": "選択したコネクターが削除されました。別のコネクターを選択するか、新しいコネクターを作成してください。", - "xpack.securitySolution.case.configureCases.warningTitle": "警告", - "xpack.securitySolution.case.configureCasesButton": "外部接続を編集", - "xpack.securitySolution.case.confirmDeleteCase.confirmQuestion": "このケースを削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", - "xpack.securitySolution.case.confirmDeleteCase.confirmQuestionPlural": "これらのケースを削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", - "xpack.securitySolution.case.confirmDeleteCase.deleteCase": "ケースを削除", - "xpack.securitySolution.case.confirmDeleteCase.deleteCases": "ケースを削除", - "xpack.securitySolution.case.confirmDeleteCase.deleteThisCase": "このケースを削除", - "xpack.securitySolution.case.confirmDeleteCase.deleteTitle": "「{caseTitle}」を削除", - "xpack.securitySolution.case.confirmDeleteCase.selectedCases": "選択したケースを削除", - "xpack.securitySolution.case.connectors.jira.issueTypesSelectFieldLabel": "問題タイプ", - "xpack.securitySolution.case.connectors.jira.parentIssueSearchLabel": "親問題", - "xpack.securitySolution.case.connectors.jira.prioritySelectFieldLabel": "優先度", - "xpack.securitySolution.case.connectors.resilient.incidentTypesLabel": "インシデントタイプ", - "xpack.securitySolution.case.connectors.resilient.incidentTypesPlaceholder": "タイプを選択", - "xpack.securitySolution.case.connectors.resilient.severityLabel": "深刻度", - "xpack.securitySolution.case.connectors.resilient.unableToGetIncidentTypesMessage": "インシデントタイプを取得できません", - "xpack.securitySolution.case.connectors.resilient.unableToGetSeverityMessage": "深刻度を取得できません", - "xpack.securitySolution.case.containers.statusChangeToasterText": "このケースのアラートはステータスが更新されました", - "xpack.securitySolution.case.createCase.descriptionFieldRequiredError": "説明が必要です。", - "xpack.securitySolution.case.createCase.fieldTagsHelpText": "このケースの1つ以上のカスタム識別タグを入力します。新しいタグを開始するには、各タグの後でEnterを押します。", - "xpack.securitySolution.case.createCase.titleFieldRequiredError": "タイトルが必要です。", - "xpack.securitySolution.case.dismissErrorsPushServiceCallOutTitle": "閉じる", - "xpack.securitySolution.case.editConnector.editConnectorLinkAria": "クリックしてコネクターを編集", - "xpack.securitySolution.case.pageTitle": "ケース", - "xpack.securitySolution.case.readOnlySavedObjectDescription": "ケースを表示する権限のみが付与されています。ケースを開いて更新する必要がある場合は、Kibana管理者に連絡してください。", - "xpack.securitySolution.case.readOnlySavedObjectTitle": "新しいケースを開いたり、既存のケースを更新したりすることはできません", - "xpack.securitySolution.case.settings.syncAlertsSwitchLabelOff": "オフ", - "xpack.securitySolution.case.settings.syncAlertsSwitchLabelOn": "オン", - "xpack.securitySolution.case.status.closed": "終了", - "xpack.securitySolution.case.status.iconAria": "ステータスの変更", - "xpack.securitySolution.case.status.inProgress": "進行中", - "xpack.securitySolution.case.status.open": "開く", - "xpack.securitySolution.case.timeline.actions.addCase": "ケースに追加", - "xpack.securitySolution.case.timeline.actions.addExistingCase": "既存のケースに追加", - "xpack.securitySolution.case.timeline.actions.addNewCase": "新しいケースに追加", - "xpack.securitySolution.case.timeline.actions.addToCaseAriaLabel": "アラートをケースに関連付ける", - "xpack.securitySolution.case.timeline.actions.addToCaseTooltip": "ケースに追加", - "xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToast": "アラートが「{title}」に追加されました", - "xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastText": "このケースのアラートはステータスがケースステータスと同期されました", - "xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastViewCaseLink": "ケースの表示", + "xpack.securitySolution.cases.allCases.actions": "アクション", + "xpack.securitySolution.cases.allCases.comments": "コメント", + "xpack.securitySolution.cases.allCases.noTagsAvailable": "利用可能なタグがありません", + "xpack.securitySolution.cases.caseModal.title": "ケースを選択", + "xpack.securitySolution.cases.caseSavedObjectNoPermissionsMessage": "ケースを表示するには、Kibana スペースで保存されたオブジェクト管理機能の権限が必要です。詳細については、Kibana管理者に連絡してください。", + "xpack.securitySolution.cases.caseSavedObjectNoPermissionsTitle": "Kibana機能権限が必要です", + "xpack.securitySolution.cases.caseTable.addNewCase": "新規ケースの追加", + "xpack.securitySolution.cases.caseTable.bulkActions": "一斉アクション", + "xpack.securitySolution.cases.caseTable.bulkActions.closeSelectedTitle": "選択した項目を閉じる", + "xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle": "選択した項目を削除", + "xpack.securitySolution.cases.caseTable.bulkActions.openSelectedTitle": "選択した項目を再開", + "xpack.securitySolution.cases.caseTable.caseDetailsLinkAria": "クリックすると、タイトル{detailName}のケースを表示します", + "xpack.securitySolution.cases.caseTable.closed": "終了", + "xpack.securitySolution.cases.caseTable.closedCases": "終了したケース", + "xpack.securitySolution.cases.caseTable.delete": "削除", + "xpack.securitySolution.cases.caseTable.incidentSystem": "インシデント管理システム", + "xpack.securitySolution.cases.caseTable.inProgressCases": "進行中のケース", + "xpack.securitySolution.cases.caseTable.noCases.body": "表示するケースがありません。新しいケースを作成するか、または上記のフィルター設定を変更してください。", + "xpack.securitySolution.cases.caseTable.noCases.title": "ケースなし", + "xpack.securitySolution.cases.caseTable.notPushed": "プッシュされません", + "xpack.securitySolution.cases.caseTable.openCases": "ケースを開く", + "xpack.securitySolution.cases.caseTable.refreshTitle": "更新", + "xpack.securitySolution.cases.caseTable.requiresUpdate": " 更新が必要", + "xpack.securitySolution.cases.caseTable.searchAriaLabel": "ケースの検索", + "xpack.securitySolution.cases.caseTable.searchPlaceholder": "例:ケース名", + "xpack.securitySolution.cases.caseTable.serviceNowLinkAria": "クリックすると、servicenowでインシデントを表示します", + "xpack.securitySolution.cases.caseTable.snIncident": "外部インシデント", + "xpack.securitySolution.cases.caseTable.status": "ステータス", + "xpack.securitySolution.cases.caseTable.upToDate": " は最新です", + "xpack.securitySolution.cases.caseView.actionHeadline": "{actionDate} の {userName} {actionName}", + "xpack.securitySolution.cases.caseView.actionLabel.addComment": "コメントを追加しました", + "xpack.securitySolution.cases.caseView.actionLabel.addDescription": "説明を追加しました", + "xpack.securitySolution.cases.caseView.actionLabel.addedField": "追加しました", + "xpack.securitySolution.cases.caseView.actionLabel.changededField": "変更しました", + "xpack.securitySolution.cases.caseView.actionLabel.editedField": "編集しました", + "xpack.securitySolution.cases.caseView.actionLabel.on": "日付", + "xpack.securitySolution.cases.caseView.actionLabel.pushedNewIncident": "新しいインシデントとしてプッシュしました", + "xpack.securitySolution.cases.caseView.actionLabel.removedField": "削除しました", + "xpack.securitySolution.cases.caseView.actionLabel.removedThirdParty": "外部のインシデント管理システムを削除しました", + "xpack.securitySolution.cases.caseView.actionLabel.selectedThirdParty": "インシデント管理システムとして{ thirdParty }を選択しました", + "xpack.securitySolution.cases.caseView.actionLabel.updateIncident": "インシデントを更新しました", + "xpack.securitySolution.cases.caseView.actionLabel.viewIncident": "{incidentNumber}を表示", + "xpack.securitySolution.cases.caseView.alertCommentLabelTitle": "アラートを追加しました", + "xpack.securitySolution.cases.caseView.alertRuleDeletedLabelTitle": "アラートを追加しました", + "xpack.securitySolution.cases.caseView.alreadyPushedToExternalService": "すでに{ externalService }インシデントにプッシュしました", + "xpack.securitySolution.cases.caseView.appropiateLicense": "適切なライセンス", + "xpack.securitySolution.cases.caseView.backLabel": "ケースに戻る", + "xpack.securitySolution.cases.caseView.breadcrumb": "作成", + "xpack.securitySolution.cases.caseView.cancel": "キャンセル", + "xpack.securitySolution.cases.caseView.case": "ケース", + "xpack.securitySolution.cases.caseView.caseClosed": "ケースを閉じました", + "xpack.securitySolution.cases.caseView.caseInProgress": "進行中のケース", + "xpack.securitySolution.cases.caseView.caseName": "ケース名", + "xpack.securitySolution.cases.caseView.caseOpened": "ケースを開きました", + "xpack.securitySolution.cases.caseView.caseRefresh": "ケースを更新", + "xpack.securitySolution.cases.caseView.closeCase": "ケースを閉じる", + "xpack.securitySolution.cases.caseView.closedOn": "終了日", + "xpack.securitySolution.cases.caseView.cloudDeploymentLink": "クラウド展開", + "xpack.securitySolution.cases.caseView.comment": "コメント", + "xpack.securitySolution.cases.caseView.comment.addComment": "コメントを追加", + "xpack.securitySolution.cases.caseView.comment.addCommentHelpText": "新しいコメントを追加...", + "xpack.securitySolution.cases.caseView.commentFieldRequiredError": "コメントが必要です。", + "xpack.securitySolution.cases.caseView.connectorConfigureLink": "コネクター", + "xpack.securitySolution.cases.caseView.connectors": "外部インシデント管理システム", + "xpack.securitySolution.cases.caseView.copyCommentLinkAria": "参照リンクをコピー", + "xpack.securitySolution.cases.caseView.create": "新規ケースを作成", + "xpack.securitySolution.cases.caseView.createCase": "ケースを作成", + "xpack.securitySolution.cases.caseView.description": "説明", + "xpack.securitySolution.cases.caseView.description.save": "保存", + "xpack.securitySolution.cases.caseView.edit": "編集", + "xpack.securitySolution.cases.caseView.edit.comment": "コメントを編集", + "xpack.securitySolution.cases.caseView.edit.description": "説明を編集", + "xpack.securitySolution.cases.caseView.edit.quote": "お客様の声", + "xpack.securitySolution.cases.caseView.editActionsLinkAria": "クリックすると、すべてのアクションを表示します", + "xpack.securitySolution.cases.caseView.editConnector": "外部インシデント管理システムを変更", + "xpack.securitySolution.cases.caseView.editTagsLinkAria": "クリックすると、タグを編集します", + "xpack.securitySolution.cases.caseView.emailBody": "ケースリファレンス:{caseUrl}", + "xpack.securitySolution.cases.caseView.emailSubject": "セキュリティケース - {caseTitle}", + "xpack.securitySolution.cases.caseView.errorsPushServiceCallOutTitle": "ケースを外部システムにプッシュするには、以下が必要です。", + "xpack.securitySolution.cases.caseView.fieldChanged": "変更されたコネクターフィールド", + "xpack.securitySolution.cases.caseView.fieldRequiredError": "必須フィールド", + "xpack.securitySolution.cases.caseView.generatedAlertCommentLabelTitle": "から追加されました", + "xpack.securitySolution.cases.caseView.goToDocumentationButton": "ドキュメンテーションを表示", + "xpack.securitySolution.cases.caseView.markedCaseAs": "ケースを設定", + "xpack.securitySolution.cases.caseView.markInProgress": "実行中に設定", + "xpack.securitySolution.cases.caseView.moveToCommentAria": "参照されたコメントをハイライト", + "xpack.securitySolution.cases.caseView.name": "名前", + "xpack.securitySolution.cases.caseView.noReportersAvailable": "利用可能なレポートがありません。", + "xpack.securitySolution.cases.caseView.noTags": "現在、このケースにタグは割り当てられていません。", + "xpack.securitySolution.cases.caseView.openedOn": "開始日", + "xpack.securitySolution.cases.caseView.optional": "オプション", + "xpack.securitySolution.cases.caseView.particpantsLabel": "参加者", + "xpack.securitySolution.cases.caseView.pushNamedIncident": "{ thirdParty }インシデントとしてプッシュ", + "xpack.securitySolution.cases.caseView.pushThirdPartyIncident": "外部インシデントとしてプッシュ", + "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedDescription": "終了したケースは外部システムに送信できません。外部システムでケースを開始または更新したい場合にはケースを再開します。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle": "ケースを再開する", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigDescription": "kibana.ymlファイルは、特定のコネクターのみを許可するように構成されています。外部システムでケースを開けるようにするには、xpack.actions.enabledActiontypes設定に.[actionTypeId] (例:.servicenow | .jira) を追加します。詳細は{link}をご覧ください。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigTitle": "Kibanaの構成ファイルで外部サービスを有効にする", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByInvalidConnector": "外部サービスに更新を送信するために使用されるコネクターが削除されました。外部システムでケースを更新するには、別のコネクターを選択するか、新しいコネクターを作成してください。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseDescription": "{appropriateLicense}があるか、{cloud}を使用しているか、無償試用版をテストしているときには、外部システムでケースを開くことができます。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseTitle": "適切なライセンスにアップグレード", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigDescription": "外部システムでケースを開いて更新するには、このケースの外部インシデント管理システムを選択する必要があります。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigTitle": "外部コネクターを選択", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConfigTitle": "外部コネクターを構成", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConnectors": "外部システムでケースを開いて更新するには、{link}を設定する必要があります。", + "xpack.securitySolution.cases.caseView.reopenCase": "ケースを再開", + "xpack.securitySolution.cases.caseView.reporterLabel": "報告者", + "xpack.securitySolution.cases.caseView.requiredUpdateToExternalService": "{ externalService }インシデントの更新が必要です", + "xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip": "タイムラインで調査", + "xpack.securitySolution.cases.caseView.sendEmalLinkAria": "クリックすると、{user}に電子メールを送信します", + "xpack.securitySolution.cases.caseView.showAlertTooltip": "アラートの詳細を表示", + "xpack.securitySolution.cases.caseView.statusLabel": "ステータス", + "xpack.securitySolution.cases.caseView.syncAlertsLabel": "アラートの同期", + "xpack.securitySolution.cases.caseView.tags": "タグ", + "xpack.securitySolution.cases.caseView.to": "に", + "xpack.securitySolution.cases.caseView.unknown": "不明", + "xpack.securitySolution.cases.caseView.unknownRule.label": "不明なルール", + "xpack.securitySolution.cases.caseView.updateNamedIncident": "{ thirdParty }インシデントを更新", + "xpack.securitySolution.cases.caseView.updateThirdPartyIncident": "外部インシデントを更新", + "xpack.securitySolution.cases.common.noConnector": "コネクターを選択していません", + "xpack.securitySolution.cases.components.connectors.cases.actionTypeTitle": "ケース", + "xpack.securitySolution.cases.components.connectors.cases.addNewCaseOption": "新規ケースの追加", + "xpack.securitySolution.cases.components.connectors.cases.caseRequired": "ケースの選択が必要です。", + "xpack.securitySolution.cases.components.connectors.cases.casesDropdownPlaceholder": "ケースを選択", + "xpack.securitySolution.cases.components.connectors.cases.casesDropdownRowLabel": "ケース", + "xpack.securitySolution.cases.components.connectors.cases.commentLabel": "コメント", + "xpack.securitySolution.cases.components.connectors.cases.commentRequired": "コメントが必要です。", + "xpack.securitySolution.cases.components.connectors.cases.connectedCaseLabel": "接続されたケース", + "xpack.securitySolution.cases.components.connectors.cases.createCaseLabel": "ケースを作成", + "xpack.securitySolution.cases.components.connectors.cases.optionAddNewCase": "新しいケースに追加", + "xpack.securitySolution.cases.components.connectors.cases.optionAddToExistingCase": "既存のケースに追加", + "xpack.securitySolution.cases.components.connectors.cases.selectMessageText": "ケースを作成または更新します。", + "xpack.securitySolution.cases.configure.errorGetFields": "サービスからのフィールドの取得中にエラーが発生しました", + "xpack.securitySolution.cases.configure.successSaveToast": "保存された外部接続設定", + "xpack.securitySolution.cases.configureCases.addNewConnector": "新しいコネクターを追加", + "xpack.securitySolution.cases.configureCases.blankMappings": "1 つ以上のフィールドを { connectorName } にマッピングする必要があります", + "xpack.securitySolution.cases.configureCases.cancelButton": "キャンセル", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsClosedIncident": "新しいインシデントが外部システムで閉じたときにセキュリティケースを自動的に閉じる", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsDesc": "セキュリティケースの終了のしかたを定義します。自動ケース終了のためには、外部のインシデント管理システムへの接続を確立する必要がいります。", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsLabel": "ケース終了オプション", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsManual": "セキュリティケースを手動で閉じる", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsNewIncident": "新しいインシデントを外部システムにプッシュするときにセキュリティケースを自動的に閉じる", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsTitle": "ケースのクローズ", + "xpack.securitySolution.cases.configureCases.commentMapping": "コメント", + "xpack.securitySolution.cases.configureCases.editFieldMappingTitle": "{ thirdPartyName } フィールドマッピングを編集", + "xpack.securitySolution.cases.configureCases.fieldMappingDesc": "データを { thirdPartyName } にプッシュするときに、セキュリティケースフィールドを { thirdPartyName } フィールドにマッピングします。フィールドマッピングでは、{ thirdPartyName } への接続を確立する必要があります。", + "xpack.securitySolution.cases.configureCases.fieldMappingDescErr": "フィールドマッピングでは、{ thirdPartyName } への接続を確立する必要があります。接続資格情報を確認してください。", + "xpack.securitySolution.cases.configureCases.fieldMappingEditAppend": "末尾に追加", + "xpack.securitySolution.cases.configureCases.fieldMappingEditNothing": "何もしない", + "xpack.securitySolution.cases.configureCases.fieldMappingEditOverwrite": "上書き", + "xpack.securitySolution.cases.configureCases.fieldMappingFirstCol": "セキュリティケースフィールド", + "xpack.securitySolution.cases.configureCases.fieldMappingSecondCol": "{ thirdPartyName } フィールド", + "xpack.securitySolution.cases.configureCases.fieldMappingThirdCol": "編集時と更新時", + "xpack.securitySolution.cases.configureCases.fieldMappingTitle": "{ thirdPartyName } フィールドマッピング", + "xpack.securitySolution.cases.configureCases.headerTitle": "ケースを構成", + "xpack.securitySolution.cases.configureCases.incidentManagementSystemDesc": "オプションとして、セキュリティケースを選択した外部のインシデント管理システムに接続できます。そうすると、選択したサードパーティシステム内でケースデータをインシデントとしてプッシュできます。", + "xpack.securitySolution.cases.configureCases.incidentManagementSystemLabel": "インシデント管理システム", + "xpack.securitySolution.cases.configureCases.incidentManagementSystemTitle": "外部のインシデント管理システムに接続", + "xpack.securitySolution.cases.configureCases.mappingFieldNotMapped": "マップされません", + "xpack.securitySolution.cases.configureCases.noFieldsError": "{ connectorName } フィールドが見つかりません。解決する { connectorName } コネクター設定または { connectorName } インスタンス設定を確認してください。", + "xpack.securitySolution.cases.configureCases.requiredMappings": "1 つ以上のケースフィールドを次の { connectorName } フィールドにマッピングする必要があります:{ fields }", + "xpack.securitySolution.cases.configureCases.saveAndCloseButton": "保存して閉じる", + "xpack.securitySolution.cases.configureCases.saveButton": "保存", + "xpack.securitySolution.cases.configureCases.updateConnector": "フィールドマッピングを更新", + "xpack.securitySolution.cases.configureCases.updateSelectedConnector": "{ connectorName }を更新", + "xpack.securitySolution.cases.configureCases.warningMessage": "選択したコネクターが削除されました。別のコネクターを選択するか、新しいコネクターを作成してください。", + "xpack.securitySolution.cases.configureCases.warningTitle": "警告", + "xpack.securitySolution.cases.configureCasesButton": "外部接続を編集", + "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestion": "このケースを削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", + "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestionPlural": "これらのケースを削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", + "xpack.securitySolution.cases.confirmDeleteCase.deleteCase": "ケースを削除", + "xpack.securitySolution.cases.confirmDeleteCase.deleteCases": "ケースを削除", + "xpack.securitySolution.cases.confirmDeleteCase.deleteThisCase": "このケースを削除", + "xpack.securitySolution.cases.confirmDeleteCase.deleteTitle": "「{caseTitle}」を削除", + "xpack.securitySolution.cases.confirmDeleteCase.selectedCases": "選択したケースを削除", + "xpack.securitySolution.cases.connectors.jira.issueTypesSelectFieldLabel": "問題タイプ", + "xpack.securitySolution.cases.connectors.jira.parentIssueSearchLabel": "親問題", + "xpack.securitySolution.cases.connectors.jira.prioritySelectFieldLabel": "優先度", + "xpack.securitySolution.cases.connectors.resilient.incidentTypesLabel": "インシデントタイプ", + "xpack.securitySolution.cases.connectors.resilient.incidentTypesPlaceholder": "タイプを選択", + "xpack.securitySolution.cases.connectors.resilient.severityLabel": "深刻度", + "xpack.securitySolution.cases.connectors.resilient.unableToGetIncidentTypesMessage": "インシデントタイプを取得できません", + "xpack.securitySolution.cases.connectors.resilient.unableToGetSeverityMessage": "深刻度を取得できません", + "xpack.securitySolution.cases.containers.statusChangeToasterText": "このケースのアラートはステータスが更新されました", + "xpack.securitySolution.cases.createCase.descriptionFieldRequiredError": "説明が必要です。", + "xpack.securitySolution.cases.createCase.fieldTagsHelpText": "このケースの1つ以上のカスタム識別タグを入力します。新しいタグを開始するには、各タグの後でEnterを押します。", + "xpack.securitySolution.cases.createCase.titleFieldRequiredError": "タイトルが必要です。", + "xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle": "閉じる", + "xpack.securitySolution.cases.editConnector.editConnectorLinkAria": "クリックしてコネクターを編集", + "xpack.securitySolution.cases.pageTitle": "ケース", + "xpack.securitySolution.cases.readOnlySavedObjectDescription": "ケースを表示する権限のみが付与されています。ケースを開いて更新する必要がある場合は、Kibana管理者に連絡してください。", + "xpack.securitySolution.cases.readOnlySavedObjectTitle": "新しいケースを開いたり、既存のケースを更新したりすることはできません", + "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOff": "オフ", + "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOn": "オン", + "xpack.securitySolution.cases.status.closed": "終了", + "xpack.securitySolution.cases.status.iconAria": "ステータスの変更", + "xpack.securitySolution.cases.status.inProgress": "進行中", + "xpack.securitySolution.cases.status.open": "開く", + "xpack.securitySolution.cases.timeline.actions.addCase": "ケースに追加", + "xpack.securitySolution.cases.timeline.actions.addExistingCase": "既存のケースに追加", + "xpack.securitySolution.cases.timeline.actions.addNewCase": "新しいケースに追加", + "xpack.securitySolution.cases.timeline.actions.addToCaseAriaLabel": "アラートをケースに関連付ける", + "xpack.securitySolution.cases.timeline.actions.addToCaseTooltip": "ケースに追加", + "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast": "アラートが「{title}」に追加されました", + "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText": "このケースのアラートはステータスがケースステータスと同期されました", + "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "ケースの表示", "xpack.securitySolution.caseConnectorsRegistry.get.missingCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」は登録されていません。", "xpack.securitySolution.caseConnectorsRegistry.register.duplicateCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」はすでに登録されています。", "xpack.securitySolution.certificate.fingerprint.clientCertLabel": "クライアント証明書", @@ -18485,14 +18465,14 @@ "xpack.securitySolution.containers.anomalies.errorFetchingAnomaliesData": "異常データをクエリできませんでした", "xpack.securitySolution.containers.anomalies.stackByJobId": "ジョブ", "xpack.securitySolution.containers.anomalies.title": "異常", - "xpack.securitySolution.containers.case.closedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}をクローズしました", - "xpack.securitySolution.containers.case.deletedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を削除しました", - "xpack.securitySolution.containers.case.errorDeletingTitle": "データの削除エラー", - "xpack.securitySolution.containers.case.errorTitle": "データの取得中にエラーが発生", - "xpack.securitySolution.containers.case.pushToExternalService": "{ serviceName }への送信が正常に完了しました", - "xpack.securitySolution.containers.case.reopenedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を再オープンしました", - "xpack.securitySolution.containers.case.syncCase": "\"{caseTitle}\"のアラートが同期されました", - "xpack.securitySolution.containers.case.updatedCase": "\"{caseTitle}\"を更新しました", + "xpack.securitySolution.containers.cases.closedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}をクローズしました", + "xpack.securitySolution.containers.cases.deletedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を削除しました", + "xpack.securitySolution.containers.cases.errorDeletingTitle": "データの削除エラー", + "xpack.securitySolution.containers.cases.errorTitle": "データの取得中にエラーが発生", + "xpack.securitySolution.containers.cases.pushToExternalService": "{ serviceName }への送信が正常に完了しました", + "xpack.securitySolution.containers.cases.reopenedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を再オープンしました", + "xpack.securitySolution.containers.cases.syncCase": "\"{caseTitle}\"のアラートが同期されました", + "xpack.securitySolution.containers.cases.updatedCase": "\"{caseTitle}\"を更新しました", "xpack.securitySolution.containers.detectionEngine.addRuleFailDescription": "ルールを追加できませんでした", "xpack.securitySolution.containers.detectionEngine.alerts.createListsIndex.errorDescription": "リストインデックスを作成できませんでした", "xpack.securitySolution.containers.detectionEngine.alerts.errorFetchingAlertsDescription": "アラートをクエリできませんでした", @@ -22209,7 +22189,7 @@ "xpack.triggersActionsUI.actionVariables.dateLabel": "アラートがアクションをスケジュールした日付。", "xpack.triggersActionsUI.alerts.breadcrumbTitle": "アラート", "xpack.triggersActionsUI.appName": "アラートとアクション", - "xpack.triggersActionsUI.case.configureCases.mappingFieldSummary": "まとめ", + "xpack.triggersActionsUI.cases.configureCases.mappingFieldSummary": "まとめ", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByConfigMessage": "このコネクターは Kibana の構成で無効になっています。", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage": "このコネクターには {minimumLicenseRequired} ライセンスが必要です。", "xpack.triggersActionsUI.checkAlertTypeEnabled.alertTypeDisabledByLicenseMessage": "このアラートタイプには {minimumLicenseRequired} ライセンスが必要です。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e189c53adf8c3..f94de1cb3599a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -955,12 +955,8 @@ "data.query.queryBar.kqlOffLabel": "关闭", "data.query.queryBar.kqlOnLabel": "开启", "data.query.queryBar.luceneLanguageName": "Lucene", - "data.query.queryBar.luceneSyntaxWarningMessage": "尽管选择了 Kibana 查询语言 (KQL),但似乎您正在尝试使用 Lucene 查询语法。请查看 KQL 文档 {link}。", - "data.query.queryBar.luceneSyntaxWarningOptOutText": "不再显示", - "data.query.queryBar.luceneSyntaxWarningTitle": "Lucene 语法警告", "data.query.queryBar.searchInputAriaLabel": "开始键入内容,以搜索并筛选 {pageType} 页面", "data.query.queryBar.searchInputPlaceholder": "搜索", - "data.query.queryBar.syntaxOptionsDescription.docsLinkText": "此处", "data.query.queryBar.syntaxOptionsTitle": "语法选项", "data.search.aggs.aggGroups.bucketsText": "存储桶", "data.search.aggs.aggGroups.metricsText": "指标", @@ -4936,8 +4932,8 @@ "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "操作类型“{id}”未注册。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "操作类型“{id}”已注册。", "xpack.actions.appName": "操作", - "xpack.actions.builtin.case.jiraTitle": "Jira", - "xpack.actions.builtin.case.resilientTitle": "IBM Resilient", + "xpack.actions.builtin.cases.jiraTitle": "Jira", + "xpack.actions.builtin.cases.resilientTitle": "IBM Resilient", "xpack.actions.builtin.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", "xpack.actions.builtin.email.customViewInKibanaMessage": "此消息由 Kibana 发送。[{kibanaFooterLinkText}]({link})。", "xpack.actions.builtin.email.errorSendingErrorMessage": "发送电子邮件时出错", @@ -6144,9 +6140,6 @@ "xpack.canvas.functions.axisConfig.invalidMinDateStringErrorMessage": "日期字符串无效:“{min}”。“min”必须是数值、以毫秒为单位的日期或 ISO8601 日期字符串", "xpack.canvas.functions.axisConfig.invalidPositionErrorMessage": "无效的位置:“{position}”", "xpack.canvas.functions.axisConfigHelpText": "配置可视化的轴。仅用于 {plotFn}。", - "xpack.canvas.functions.case.args.ifHelpText": "此值指示是否符合条件。当 {IF_ARG} 和 {WHEN_ARG} 参数都提供时,前者将覆盖后者。", - "xpack.canvas.functions.case.args.thenHelpText": "条件得到满足时返回的值。", - "xpack.canvas.functions.case.args.whenHelpText": "与 {CONTEXT} 比较以确定与其是否相等的值。同时指定 {WHEN_ARG} 时,将忽略 {IF_ARG}。", "xpack.canvas.functions.caseHelpText": "构建要传递给 {switchFn} 函数的 {case},包括条件/结果。", "xpack.canvas.functions.clearHelpText": "清除 {CONTEXT},然后返回 {TYPE_NULL}。", "xpack.canvas.functions.columns.args.excludeHelpText": "要从 {DATATABLE} 中移除的列名称逗号分隔列表。", @@ -7035,11 +7028,11 @@ "xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "描述", "xpack.canvas.workpadTemplates.table.nameColumnTitle": "模板名称", "xpack.canvas.workpadTemplates.table.tagsColumnTitle": "标签", - "xpack.case.connectors.case.externalIncidentAdded": " (由 {user} 于 {date}添加) ", - "xpack.case.connectors.case.externalIncidentCreated": " (由 {user} 于 {date}创建) ", - "xpack.case.connectors.case.externalIncidentDefault": " (由 {user} 于 {date}创建) ", - "xpack.case.connectors.case.externalIncidentUpdated": " (由 {user} 于 {date}更新) ", - "xpack.case.connectors.case.title": "案例", + "xpack.cases.connectors.cases.externalIncidentAdded": " (由 {user} 于 {date}添加) ", + "xpack.cases.connectors.cases.externalIncidentCreated": " (由 {user} 于 {date}创建) ", + "xpack.cases.connectors.cases.externalIncidentDefault": " (由 {user} 于 {date}创建) ", + "xpack.cases.connectors.cases.externalIncidentUpdated": " (由 {user} 于 {date}更新) ", + "xpack.cases.connectors.cases.title": "案例", "xpack.cloud.deploymentLinkLabel": "管理此部署", "xpack.cloud.userMenuLinks.accountLinkText": "帐户和帐单", "xpack.cloud.userMenuLinks.profileLinkText": "云配置文件", @@ -10176,7 +10169,6 @@ "xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableTitle": "没有分配到冷层的节点", "xpack.indexLifecycleMgmt.coldPhase.dataTier.description": "将数据移到针对不太频繁的只读访问优化的节点。将处于冷阶段的数据存储在成本较低的硬件上。", "xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel": "冻结索引", - "xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel": "副本分片数目", "xpack.indexLifecycleMgmt.common.dataTier.title": "数据分配", "xpack.indexLifecycleMgmt.confirmDelete.cancelButton": "取消", "xpack.indexLifecycleMgmt.confirmDelete.deleteButton": "删除", @@ -10191,16 +10183,10 @@ "xpack.indexLifecycleMgmt.editPolicy.coldPhase.activateColdPhaseSwitchLabel": "激活冷阶段", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseDescription": "将数据移到经过优化后节省了成本但牺牲了搜索性能的冷层。数据在冷阶段通常为只读。", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseTitle": "冷阶段", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeIndexExplanationText": "使索引只读,并最大限度减小其内存占用。", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeText": "冻结", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText": "根据节点属性移动数据。", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input": "定制", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.helpText": "将数据移到冷层中的节点。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.input": "使用冷节点 (建议) ", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.helpText": "不要移动冷阶段的数据。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.input": "关闭", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.helpText": "根据节点属性移动数据。", - "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.input": "定制", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.helpText": "将数据移到温层中的节点。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.input": "使用温节点 (建议) ", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.noneOption.helpText": "不要移动温阶段的数据。", @@ -10313,7 +10299,6 @@ "xpack.indexLifecycleMgmt.editPolicy.saveErrorMessage": "保存生命周期策略 {lifecycleName} 时出错", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotCalloutBody": "在热阶段启用可搜索快照时,不允许强制合并、缩小、冻结可搜索快照以及将其置入冷阶段。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldDescription": "在所选存储库中拍取受管索引的快照,并将其安装为可搜索快照。{learnMoreLink}", - "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldLabel": "可搜索快照存储库", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldTitle": "可搜索快照", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutBody": "要创建可搜索快照,需要企业许可证。", "xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotLicenseCalloutTitle": "需要企业许可证", @@ -10483,7 +10468,6 @@ "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm": "此策略会改为将温阶段的数据移到{tier}层节点。", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title": "没有分配到温层的节点", "xpack.indexLifecycleMgmt.warmPhase.dataTier.description": "将数据移到针对不太频繁的只读访问优化的节点。", - "xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel": "副本分片数目", "xpack.infra.alerting.alertDropdownTitle": "告警", "xpack.infra.alerting.alertFlyout.groupBy.placeholder": "无内容 (未分组) ", "xpack.infra.alerting.alertFlyout.groupByLabel": "分组依据", @@ -12019,7 +12003,6 @@ "xpack.lens.configure.invalidConfigTooltipClick": "单击了解更多详情。", "xpack.lens.customBucketContainer.dragToReorder": "拖动以重新排序", "xpack.lens.dataPanelWrapper.switchDatasource": "切换到数据源", - "xpack.lens.datatable.breakdown": "细分方式", "xpack.lens.datatable.conjunctionSign": " & ", "xpack.lens.datatable.expressionHelpLabel": "数据表呈现器", "xpack.lens.datatable.label": "数据表", @@ -17306,7 +17289,6 @@ "xpack.reporting.panelContent.notification.cantReachServerDescription": "无法访问服务器。请重试。", "xpack.reporting.panelContent.notification.reportingErrorTitle": "报告错误", "xpack.reporting.panelContent.saveWorkDescription": "请在生成报告之前保存您的工作。", - "xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription": "在“管理”中跟踪其进度", "xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle": "已为 {objectType} 排队报告", "xpack.reporting.panelContent.whatCanBeExportedWarningDescription": "请先保存您的工作", "xpack.reporting.panelContent.whatCanBeExportedWarningTitle": "只会导出保存的 {objectType}", @@ -17325,7 +17307,6 @@ "xpack.reporting.publicNotifier.maxSizeReached.partialReportTitle": "已为 {reportObjectType} '{reportObjectTitle}' 创建部分报告", "xpack.reporting.publicNotifier.pollingErrorMessage": "报告通知器错误!", "xpack.reporting.publicNotifier.reportLink.pickItUpFromPathDescription": "从 {path} 中提取。", - "xpack.reporting.publicNotifier.reportLink.reportingSectionUrlLinkLabel": "管理 > Kibana > Reporting", "xpack.reporting.publicNotifier.successfullyCreatedReportNotificationTitle": "已为 {reportObjectType}“{reportObjectTitle}”创建报告", "xpack.reporting.registerFeature.reportingDescription": "管理您从 Discover、Visualize 和 Dashboard 生成的报告。", "xpack.reporting.registerFeature.reportingTitle": "Reporting", @@ -18209,7 +18190,6 @@ "xpack.security.management.users.usersTitle": "用户", "xpack.security.management.usersTitle": "用户", "xpack.security.navControlComponent.accountMenuAriaLabel": "帐户菜单", - "xpack.security.navControlComponent.editProfileLinkText": "配置文件", "xpack.security.navControlComponent.loginLinkText": "登录", "xpack.security.navControlComponent.logoutLinkText": "注销", "xpack.security.overwrittenSession.continueAsUserText": "作为 {username} 继续", @@ -18421,216 +18401,216 @@ "xpack.securitySolution.beatFields.errorSearchDescription": "获取 Beat 字段时发生错误", "xpack.securitySolution.beatFields.failSearchDescription": "无法对 Beat 字段执行搜索", "xpack.securitySolution.callouts.dismissButton": "关闭", - "xpack.securitySolution.case.allCases.actions": "操作", - "xpack.securitySolution.case.allCases.comments": "注释", - "xpack.securitySolution.case.allCases.noTagsAvailable": "没有可用标签", - "xpack.securitySolution.case.caseModal.title": "选择案例", - "xpack.securitySolution.case.caseSavedObjectNoPermissionsMessage": "要查看案例,必须对 Kibana 工作区中的已保存对象管理功能有权限。有关详细信息,请联系您的 Kibana 管理员。", - "xpack.securitySolution.case.caseSavedObjectNoPermissionsTitle": "需要 Kibana 功能权限", - "xpack.securitySolution.case.caseTable.addNewCase": "添加新案例", - "xpack.securitySolution.case.caseTable.bulkActions": "批处理操作", - "xpack.securitySolution.case.caseTable.bulkActions.closeSelectedTitle": "关闭所选", - "xpack.securitySolution.case.caseTable.bulkActions.deleteSelectedTitle": "删除所选", - "xpack.securitySolution.case.caseTable.bulkActions.openSelectedTitle": "重新打开所选", - "xpack.securitySolution.case.caseTable.caseDetailsLinkAria": "单击以访问标题为 {detailName} 的案例", - "xpack.securitySolution.case.caseTable.closed": "已关闭", - "xpack.securitySolution.case.caseTable.closedCases": "已关闭案例", - "xpack.securitySolution.case.caseTable.delete": "删除", - "xpack.securitySolution.case.caseTable.incidentSystem": "事件管理系统", - "xpack.securitySolution.case.caseTable.inProgressCases": "进行中的案例", - "xpack.securitySolution.case.caseTable.noCases.body": "没有可显示的案例。请创建新案例或在上面更改您的筛选设置。", - "xpack.securitySolution.case.caseTable.noCases.title": "无案例", - "xpack.securitySolution.case.caseTable.notPushed": "未推送", - "xpack.securitySolution.case.caseTable.openCases": "未结案例", - "xpack.securitySolution.case.caseTable.refreshTitle": "刷新", - "xpack.securitySolution.case.caseTable.requiresUpdate": " 需要更新", - "xpack.securitySolution.case.caseTable.searchAriaLabel": "搜索案例", - "xpack.securitySolution.case.caseTable.searchPlaceholder": "例如案例名", - "xpack.securitySolution.case.caseTable.selectedCasesTitle": "已选择 {totalRules} 个{totalRules, plural, other {案例}}", - "xpack.securitySolution.case.caseTable.serviceNowLinkAria": "单击可在 servicenow 上查看该事件", - "xpack.securitySolution.case.caseTable.showingCasesTitle": "正在显示 {totalRules} 个{totalRules, plural, other {案例}}", - "xpack.securitySolution.case.caseTable.snIncident": "外部事件", - "xpack.securitySolution.case.caseTable.status": "状态", - "xpack.securitySolution.case.caseTable.unit": "{totalCount, plural, other {案例}}", - "xpack.securitySolution.case.caseTable.upToDate": " 是最新的", - "xpack.securitySolution.case.caseView.actionHeadline": "{userName} 在 {actionDate}{actionName}", - "xpack.securitySolution.case.caseView.actionLabel.addComment": "添加了注释", - "xpack.securitySolution.case.caseView.actionLabel.addDescription": "添加了描述", - "xpack.securitySolution.case.caseView.actionLabel.addedField": "添加了", - "xpack.securitySolution.case.caseView.actionLabel.changededField": "更改了", - "xpack.securitySolution.case.caseView.actionLabel.editedField": "编辑了", - "xpack.securitySolution.case.caseView.actionLabel.on": "在", - "xpack.securitySolution.case.caseView.actionLabel.pushedNewIncident": "已推送为新事件", - "xpack.securitySolution.case.caseView.actionLabel.removedField": "移除了", - "xpack.securitySolution.case.caseView.actionLabel.removedThirdParty": "已移除外部事件管理系统", - "xpack.securitySolution.case.caseView.actionLabel.selectedThirdParty": "已选择 { thirdParty } 作为事件管理系统", - "xpack.securitySolution.case.caseView.actionLabel.updateIncident": "更新了事件", - "xpack.securitySolution.case.caseView.actionLabel.viewIncident": "查看 {incidentNumber}", - "xpack.securitySolution.case.caseView.alertCommentLabelTitle": "添加了告警,从", - "xpack.securitySolution.case.caseView.alertRuleDeletedLabelTitle": "添加了告警", - "xpack.securitySolution.case.caseView.alreadyPushedToExternalService": "已推送到 { externalService } 事件", - "xpack.securitySolution.case.caseView.appropiateLicense": "适当的许可证", - "xpack.securitySolution.case.caseView.backLabel": "返回到案例", - "xpack.securitySolution.case.caseView.breadcrumb": "创建", - "xpack.securitySolution.case.caseView.cancel": "取消", - "xpack.securitySolution.case.caseView.case": "案例", - "xpack.securitySolution.case.caseView.caseClosed": "案例已关闭", - "xpack.securitySolution.case.caseView.caseInProgress": "案例进行中", - "xpack.securitySolution.case.caseView.caseName": "案例名称", - "xpack.securitySolution.case.caseView.caseOpened": "案例已打开", - "xpack.securitySolution.case.caseView.caseRefresh": "刷新案例", - "xpack.securitySolution.case.caseView.closeCase": "关闭案例", - "xpack.securitySolution.case.caseView.closedOn": "关闭日期", - "xpack.securitySolution.case.caseView.cloudDeploymentLink": "云部署", - "xpack.securitySolution.case.caseView.comment": "注释", - "xpack.securitySolution.case.caseView.comment.addComment": "添加注释", - "xpack.securitySolution.case.caseView.comment.addCommentHelpText": "添加新注释......", - "xpack.securitySolution.case.caseView.commentFieldRequiredError": "注释必填。", - "xpack.securitySolution.case.caseView.connectorConfigureLink": "连接器", - "xpack.securitySolution.case.caseView.connectors": "外部事件管理系统", - "xpack.securitySolution.case.caseView.copyCommentLinkAria": "复制引用链接", - "xpack.securitySolution.case.caseView.create": "创建新案例", - "xpack.securitySolution.case.caseView.createCase": "创建案例", - "xpack.securitySolution.case.caseView.description": "描述", - "xpack.securitySolution.case.caseView.description.save": "保存", - "xpack.securitySolution.case.caseView.edit": "编辑", - "xpack.securitySolution.case.caseView.edit.comment": "编辑注释", - "xpack.securitySolution.case.caseView.edit.description": "编辑描述", - "xpack.securitySolution.case.caseView.edit.quote": "引述", - "xpack.securitySolution.case.caseView.editActionsLinkAria": "单击可查看所有操作", - "xpack.securitySolution.case.caseView.editConnector": "更改外部事件管理系统", - "xpack.securitySolution.case.caseView.editTagsLinkAria": "单击可编辑标签", - "xpack.securitySolution.case.caseView.emailBody": "案例参考:{caseUrl}", - "xpack.securitySolution.case.caseView.emailSubject": "Security 案例 - {caseTitle}", - "xpack.securitySolution.case.caseView.errorsPushServiceCallOutTitle": "要将案例发送到外部系统,您需要:", - "xpack.securitySolution.case.caseView.fieldChanged": "已更改连接器字段", - "xpack.securitySolution.case.caseView.fieldRequiredError": "必填字段", - "xpack.securitySolution.case.caseView.generatedAlertCommentLabelTitle": "添加自", - "xpack.securitySolution.case.caseView.generatedAlertCountCommentLabelTitle": "{totalCount} 个{totalCount, plural, other {告警}}", - "xpack.securitySolution.case.caseView.goToDocumentationButton": "查看文档", - "xpack.securitySolution.case.caseView.markedCaseAs": "将案例标记为", - "xpack.securitySolution.case.caseView.markInProgress": "标记为进行中", - "xpack.securitySolution.case.caseView.moveToCommentAria": "高亮显示引用的注释", - "xpack.securitySolution.case.caseView.name": "名称", - "xpack.securitySolution.case.caseView.noReportersAvailable": "没有报告者。", - "xpack.securitySolution.case.caseView.noTags": "当前没有为此案例分配标签。", - "xpack.securitySolution.case.caseView.openedOn": "打开时间", - "xpack.securitySolution.case.caseView.optional": "可选", - "xpack.securitySolution.case.caseView.particpantsLabel": "参与者", - "xpack.securitySolution.case.caseView.pushNamedIncident": "推送为 { thirdParty } 事件", - "xpack.securitySolution.case.caseView.pushThirdPartyIncident": "推送为外部事件", - "xpack.securitySolution.case.caseView.pushToServiceDisableBecauseCaseClosedDescription": "关闭的案例无法发送到外部系统。如果希望在外部系统中打开或更新案例,请重新打开案例。", - "xpack.securitySolution.case.caseView.pushToServiceDisableBecauseCaseClosedTitle": "重新打开案例", - "xpack.securitySolution.case.caseView.pushToServiceDisableByConfigDescription": "kibana.yml 文件已配置为仅允许特定连接器。要在外部系统中打开案例,请将 .[actionTypeId] (例如:.servicenow | .jira) 添加到 xpack.actions.enabledActiontypes 设置。有关更多信息,请参阅{link}。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByConfigTitle": "在 Kibana 配置文件中启用外部服务", - "xpack.securitySolution.case.caseView.pushToServiceDisableByInvalidConnector": "用于将更新发送到外部服务的连接器已删除。要在外部系统中更新案例,请选择不同的连接器或创建新的连接器。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByLicenseDescription": "有{appropriateLicense}、正使用{cloud}或正在免费试用时,可在外部系统中创建案例。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByLicenseTitle": "升级适当的许可", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoCaseConfigDescription": "要在外部系统中打开和更新案例,必须为此案例选择外部事件管理系统。", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoCaseConfigTitle": "选择外部连接器", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoConfigTitle": "配置外部连接器", - "xpack.securitySolution.case.caseView.pushToServiceDisableByNoConnectors": "要在外部系统上打开和更新案例,必须配置{link}。", - "xpack.securitySolution.case.caseView.reopenCase": "重新打开案例", - "xpack.securitySolution.case.caseView.reporterLabel": "报告者", - "xpack.securitySolution.case.caseView.requiredUpdateToExternalService": "需要更新 { externalService } 事件", - "xpack.securitySolution.case.caseView.sendAlertToTimelineTooltip": "在时间线中调查", - "xpack.securitySolution.case.caseView.sendEmalLinkAria": "单击可向 {user} 发送电子邮件", - "xpack.securitySolution.case.caseView.showAlertTooltip": "显示告警详情", - "xpack.securitySolution.case.caseView.statusLabel": "状态", - "xpack.securitySolution.case.caseView.syncAlertsLabel": "同步告警", - "xpack.securitySolution.case.caseView.tags": "标签", - "xpack.securitySolution.case.caseView.to": "到", - "xpack.securitySolution.case.caseView.unknown": "未知", - "xpack.securitySolution.case.caseView.unknownRule.label": "未知规则", - "xpack.securitySolution.case.caseView.updateNamedIncident": "更新 { thirdParty } 事件", - "xpack.securitySolution.case.caseView.updateThirdPartyIncident": "更新外部事件", - "xpack.securitySolution.case.common.noConnector": "未选择任何连接器", - "xpack.securitySolution.case.components.connectors.case.actionTypeTitle": "案例", - "xpack.securitySolution.case.components.connectors.case.addNewCaseOption": "添加新案例", - "xpack.securitySolution.case.components.connectors.case.caseRequired": "必须选择策略。", - "xpack.securitySolution.case.components.connectors.case.casesDropdownPlaceholder": "选择案例", - "xpack.securitySolution.case.components.connectors.case.casesDropdownRowLabel": "案例", - "xpack.securitySolution.case.components.connectors.case.commentLabel": "注释", - "xpack.securitySolution.case.components.connectors.case.commentRequired": "“注释”必填。", - "xpack.securitySolution.case.components.connectors.case.connectedCaseLabel": "已连接案例", - "xpack.securitySolution.case.components.connectors.case.createCaseLabel": "创建案例", - "xpack.securitySolution.case.components.connectors.case.optionAddNewCase": "添加到新案例", - "xpack.securitySolution.case.components.connectors.case.optionAddToExistingCase": "添加到现有案例", - "xpack.securitySolution.case.components.connectors.case.selectMessageText": "创建或更新案例。", - "xpack.securitySolution.case.configure.errorGetFields": "从服务中获取字段时出错", - "xpack.securitySolution.case.configure.successSaveToast": "已保存外部连接设置", - "xpack.securitySolution.case.configureCases.addNewConnector": "添加新连接器", - "xpack.securitySolution.case.configureCases.blankMappings": "至少一个字段需映射到 { connectorName }", - "xpack.securitySolution.case.configureCases.cancelButton": "取消", - "xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident": "在外部系统中关闭事件时自动关闭 Security 案例", - "xpack.securitySolution.case.configureCases.caseClosureOptionsDesc": "定义关闭 Security 案例的方式。要自动关闭案例,需要与外部事件管理系统建立连接。", - "xpack.securitySolution.case.configureCases.caseClosureOptionsLabel": "案例关闭选项", - "xpack.securitySolution.case.configureCases.caseClosureOptionsManual": "手动关闭 Security 案例", - "xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident": "将新事件推送到外部系统时自动关闭 Security 案例", - "xpack.securitySolution.case.configureCases.caseClosureOptionsTitle": "案例关闭", - "xpack.securitySolution.case.configureCases.commentMapping": "注释", - "xpack.securitySolution.case.configureCases.editFieldMappingTitle": "编辑 { thirdPartyName } 字段映射", - "xpack.securitySolution.case.configureCases.fieldMappingDesc": "将数据推送到 { thirdPartyName } 时,将 Security 案例字段映射到 { thirdPartyName } 字段。字段映射需要与 { thirdPartyName } 建立连接。", - "xpack.securitySolution.case.configureCases.fieldMappingDescErr": "字段映射需要与 { thirdPartyName } 建立连接。请检查您的连接凭据。", - "xpack.securitySolution.case.configureCases.fieldMappingEditAppend": "追加", - "xpack.securitySolution.case.configureCases.fieldMappingEditNothing": "无内容", - "xpack.securitySolution.case.configureCases.fieldMappingEditOverwrite": "覆盖", - "xpack.securitySolution.case.configureCases.fieldMappingFirstCol": "Security 案例字段", - "xpack.securitySolution.case.configureCases.fieldMappingSecondCol": "{ thirdPartyName } 字段", - "xpack.securitySolution.case.configureCases.fieldMappingThirdCol": "编辑和更新时", - "xpack.securitySolution.case.configureCases.fieldMappingTitle": "{ thirdPartyName } 字段映射", - "xpack.securitySolution.case.configureCases.headerTitle": "配置案例", - "xpack.securitySolution.case.configureCases.incidentManagementSystemDesc": "您可能会根据需要将 Security 案例连接到选择的外部事件管理系统。这将允许您将案例数据作为事件推送到所选第三方系统。", - "xpack.securitySolution.case.configureCases.incidentManagementSystemLabel": "事件管理系统", - "xpack.securitySolution.case.configureCases.incidentManagementSystemTitle": "连接到外部事件管理系统", - "xpack.securitySolution.case.configureCases.mappingFieldNotMapped": "未映射", - "xpack.securitySolution.case.configureCases.noFieldsError": "未找到任何 { connectorName } 字段。请检查您的 { connectorName } 连接器设置或 { connectorName } 实例设置以解决问题。", - "xpack.securitySolution.case.configureCases.requiredMappings": "至少有一个案例字段需要映射到以下所需的 { connectorName } 字段:{ fields }", - "xpack.securitySolution.case.configureCases.saveAndCloseButton": "保存并关闭", - "xpack.securitySolution.case.configureCases.saveButton": "保存", - "xpack.securitySolution.case.configureCases.updateConnector": "更新字段映射", - "xpack.securitySolution.case.configureCases.updateSelectedConnector": "更新 { connectorName }", - "xpack.securitySolution.case.configureCases.warningMessage": "选定的连接器已删除。选择不同的连接器或创建新的连接器。", - "xpack.securitySolution.case.configureCases.warningTitle": "警告", - "xpack.securitySolution.case.configureCasesButton": "编辑外部连接", - "xpack.securitySolution.case.confirmDeleteCase.confirmQuestion": "删除此案例即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", - "xpack.securitySolution.case.confirmDeleteCase.confirmQuestionPlural": "删除这些案例即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", - "xpack.securitySolution.case.confirmDeleteCase.deleteCase": "删除案例", - "xpack.securitySolution.case.confirmDeleteCase.deleteCases": "删除案例", - "xpack.securitySolution.case.confirmDeleteCase.deleteThisCase": "删除此案例", - "xpack.securitySolution.case.confirmDeleteCase.deleteTitle": "删除“{caseTitle}”", - "xpack.securitySolution.case.confirmDeleteCase.selectedCases": "删除选定案例", - "xpack.securitySolution.case.connectors.jira.issueTypesSelectFieldLabel": "问题类型", - "xpack.securitySolution.case.connectors.jira.parentIssueSearchLabel": "父问题", - "xpack.securitySolution.case.connectors.jira.prioritySelectFieldLabel": "优先级", - "xpack.securitySolution.case.connectors.resilient.incidentTypesLabel": "事件类型", - "xpack.securitySolution.case.connectors.resilient.incidentTypesPlaceholder": "选择类型", - "xpack.securitySolution.case.connectors.resilient.severityLabel": "严重性", - "xpack.securitySolution.case.connectors.resilient.unableToGetIncidentTypesMessage": "无法获取事件类型", - "xpack.securitySolution.case.connectors.resilient.unableToGetSeverityMessage": "无法获取严重性", - "xpack.securitySolution.case.containers.statusChangeToasterText": "此案例中的告警也更新了状态", - "xpack.securitySolution.case.createCase.descriptionFieldRequiredError": "描述必填。", - "xpack.securitySolution.case.createCase.fieldTagsHelpText": "为此案例键入一个或多个定制识别标签。在每个标签后按 Enter 键可开始新的标签。", - "xpack.securitySolution.case.createCase.titleFieldRequiredError": "标题必填。", - "xpack.securitySolution.case.dismissErrorsPushServiceCallOutTitle": "关闭", - "xpack.securitySolution.case.editConnector.editConnectorLinkAria": "单击以编辑连接器", - "xpack.securitySolution.case.pageTitle": "案例", - "xpack.securitySolution.case.readOnlySavedObjectDescription": "您仅有权查看案例。如果需要创建和更新案例,请联系您的 Kibana 管理员。", - "xpack.securitySolution.case.readOnlySavedObjectTitle": "您无法创建新案例或更新现有案例", - "xpack.securitySolution.case.settings.syncAlertsSwitchLabelOff": "关闭", - "xpack.securitySolution.case.settings.syncAlertsSwitchLabelOn": "开启", - "xpack.securitySolution.case.status.closed": "已关闭", - "xpack.securitySolution.case.status.iconAria": "更改状态", - "xpack.securitySolution.case.status.inProgress": "进行中", - "xpack.securitySolution.case.status.open": "未结", - "xpack.securitySolution.case.timeline.actions.addCase": "添加到案例", - "xpack.securitySolution.case.timeline.actions.addExistingCase": "添加到现有案例", - "xpack.securitySolution.case.timeline.actions.addNewCase": "添加到新案例", - "xpack.securitySolution.case.timeline.actions.addToCaseAriaLabel": "将告警附加到案例", - "xpack.securitySolution.case.timeline.actions.addToCaseTooltip": "添加到案例", - "xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToast": "告警已添加到“{title}”", - "xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastText": "此案例中的告警的状态已经与案例状态同步", - "xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastViewCaseLink": "查看案例", + "xpack.securitySolution.cases.allCases.actions": "操作", + "xpack.securitySolution.cases.allCases.comments": "注释", + "xpack.securitySolution.cases.allCases.noTagsAvailable": "没有可用标签", + "xpack.securitySolution.cases.caseModal.title": "选择案例", + "xpack.securitySolution.cases.caseSavedObjectNoPermissionsMessage": "要查看案例,必须对 Kibana 工作区中的已保存对象管理功能有权限。有关详细信息,请联系您的 Kibana 管理员。", + "xpack.securitySolution.cases.caseSavedObjectNoPermissionsTitle": "需要 Kibana 功能权限", + "xpack.securitySolution.cases.caseTable.addNewCase": "添加新案例", + "xpack.securitySolution.cases.caseTable.bulkActions": "批处理操作", + "xpack.securitySolution.cases.caseTable.bulkActions.closeSelectedTitle": "关闭所选", + "xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle": "删除所选", + "xpack.securitySolution.cases.caseTable.bulkActions.openSelectedTitle": "重新打开所选", + "xpack.securitySolution.cases.caseTable.caseDetailsLinkAria": "单击以访问标题为 {detailName} 的案例", + "xpack.securitySolution.cases.caseTable.closed": "已关闭", + "xpack.securitySolution.cases.caseTable.closedCases": "已关闭案例", + "xpack.securitySolution.cases.caseTable.delete": "删除", + "xpack.securitySolution.cases.caseTable.incidentSystem": "事件管理系统", + "xpack.securitySolution.cases.caseTable.inProgressCases": "进行中的案例", + "xpack.securitySolution.cases.caseTable.noCases.body": "没有可显示的案例。请创建新案例或在上面更改您的筛选设置。", + "xpack.securitySolution.cases.caseTable.noCases.title": "无案例", + "xpack.securitySolution.cases.caseTable.notPushed": "未推送", + "xpack.securitySolution.cases.caseTable.openCases": "未结案例", + "xpack.securitySolution.cases.caseTable.refreshTitle": "刷新", + "xpack.securitySolution.cases.caseTable.requiresUpdate": " 需要更新", + "xpack.securitySolution.cases.caseTable.searchAriaLabel": "搜索案例", + "xpack.securitySolution.cases.caseTable.searchPlaceholder": "例如案例名", + "xpack.securitySolution.cases.caseTable.selectedCasesTitle": "已选择 {totalRules} 个{totalRules, plural, other {案例}}", + "xpack.securitySolution.cases.caseTable.serviceNowLinkAria": "单击可在 servicenow 上查看该事件", + "xpack.securitySolution.cases.caseTable.showingCasesTitle": "正在显示 {totalRules} 个{totalRules, plural, other {案例}}", + "xpack.securitySolution.cases.caseTable.snIncident": "外部事件", + "xpack.securitySolution.cases.caseTable.status": "状态", + "xpack.securitySolution.cases.caseTable.unit": "{totalCount, plural, other {案例}}", + "xpack.securitySolution.cases.caseTable.upToDate": " 是最新的", + "xpack.securitySolution.cases.caseView.actionHeadline": "{userName} 在 {actionDate}{actionName}", + "xpack.securitySolution.cases.caseView.actionLabel.addComment": "添加了注释", + "xpack.securitySolution.cases.caseView.actionLabel.addDescription": "添加了描述", + "xpack.securitySolution.cases.caseView.actionLabel.addedField": "添加了", + "xpack.securitySolution.cases.caseView.actionLabel.changededField": "更改了", + "xpack.securitySolution.cases.caseView.actionLabel.editedField": "编辑了", + "xpack.securitySolution.cases.caseView.actionLabel.on": "在", + "xpack.securitySolution.cases.caseView.actionLabel.pushedNewIncident": "已推送为新事件", + "xpack.securitySolution.cases.caseView.actionLabel.removedField": "移除了", + "xpack.securitySolution.cases.caseView.actionLabel.removedThirdParty": "已移除外部事件管理系统", + "xpack.securitySolution.cases.caseView.actionLabel.selectedThirdParty": "已选择 { thirdParty } 作为事件管理系统", + "xpack.securitySolution.cases.caseView.actionLabel.updateIncident": "更新了事件", + "xpack.securitySolution.cases.caseView.actionLabel.viewIncident": "查看 {incidentNumber}", + "xpack.securitySolution.cases.caseView.alertCommentLabelTitle": "添加了告警,从", + "xpack.securitySolution.cases.caseView.alertRuleDeletedLabelTitle": "添加了告警", + "xpack.securitySolution.cases.caseView.alreadyPushedToExternalService": "已推送到 { externalService } 事件", + "xpack.securitySolution.cases.caseView.appropiateLicense": "适当的许可证", + "xpack.securitySolution.cases.caseView.backLabel": "返回到案例", + "xpack.securitySolution.cases.caseView.breadcrumb": "创建", + "xpack.securitySolution.cases.caseView.cancel": "取消", + "xpack.securitySolution.cases.caseView.case": "案例", + "xpack.securitySolution.cases.caseView.caseClosed": "案例已关闭", + "xpack.securitySolution.cases.caseView.caseInProgress": "案例进行中", + "xpack.securitySolution.cases.caseView.caseName": "案例名称", + "xpack.securitySolution.cases.caseView.caseOpened": "案例已打开", + "xpack.securitySolution.cases.caseView.caseRefresh": "刷新案例", + "xpack.securitySolution.cases.caseView.closeCase": "关闭案例", + "xpack.securitySolution.cases.caseView.closedOn": "关闭日期", + "xpack.securitySolution.cases.caseView.cloudDeploymentLink": "云部署", + "xpack.securitySolution.cases.caseView.comment": "注释", + "xpack.securitySolution.cases.caseView.comment.addComment": "添加注释", + "xpack.securitySolution.cases.caseView.comment.addCommentHelpText": "添加新注释......", + "xpack.securitySolution.cases.caseView.commentFieldRequiredError": "注释必填。", + "xpack.securitySolution.cases.caseView.connectorConfigureLink": "连接器", + "xpack.securitySolution.cases.caseView.connectors": "外部事件管理系统", + "xpack.securitySolution.cases.caseView.copyCommentLinkAria": "复制引用链接", + "xpack.securitySolution.cases.caseView.create": "创建新案例", + "xpack.securitySolution.cases.caseView.createCase": "创建案例", + "xpack.securitySolution.cases.caseView.description": "描述", + "xpack.securitySolution.cases.caseView.description.save": "保存", + "xpack.securitySolution.cases.caseView.edit": "编辑", + "xpack.securitySolution.cases.caseView.edit.comment": "编辑注释", + "xpack.securitySolution.cases.caseView.edit.description": "编辑描述", + "xpack.securitySolution.cases.caseView.edit.quote": "引述", + "xpack.securitySolution.cases.caseView.editActionsLinkAria": "单击可查看所有操作", + "xpack.securitySolution.cases.caseView.editConnector": "更改外部事件管理系统", + "xpack.securitySolution.cases.caseView.editTagsLinkAria": "单击可编辑标签", + "xpack.securitySolution.cases.caseView.emailBody": "案例参考:{caseUrl}", + "xpack.securitySolution.cases.caseView.emailSubject": "Security 案例 - {caseTitle}", + "xpack.securitySolution.cases.caseView.errorsPushServiceCallOutTitle": "要将案例发送到外部系统,您需要:", + "xpack.securitySolution.cases.caseView.fieldChanged": "已更改连接器字段", + "xpack.securitySolution.cases.caseView.fieldRequiredError": "必填字段", + "xpack.securitySolution.cases.caseView.generatedAlertCommentLabelTitle": "添加自", + "xpack.securitySolution.cases.caseView.generatedAlertCountCommentLabelTitle": "{totalCount} 个{totalCount, plural, other {告警}}", + "xpack.securitySolution.cases.caseView.goToDocumentationButton": "查看文档", + "xpack.securitySolution.cases.caseView.markedCaseAs": "将案例标记为", + "xpack.securitySolution.cases.caseView.markInProgress": "标记为进行中", + "xpack.securitySolution.cases.caseView.moveToCommentAria": "高亮显示引用的注释", + "xpack.securitySolution.cases.caseView.name": "名称", + "xpack.securitySolution.cases.caseView.noReportersAvailable": "没有报告者。", + "xpack.securitySolution.cases.caseView.noTags": "当前没有为此案例分配标签。", + "xpack.securitySolution.cases.caseView.openedOn": "打开时间", + "xpack.securitySolution.cases.caseView.optional": "可选", + "xpack.securitySolution.cases.caseView.particpantsLabel": "参与者", + "xpack.securitySolution.cases.caseView.pushNamedIncident": "推送为 { thirdParty } 事件", + "xpack.securitySolution.cases.caseView.pushThirdPartyIncident": "推送为外部事件", + "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedDescription": "关闭的案例无法发送到外部系统。如果希望在外部系统中打开或更新案例,请重新打开案例。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle": "重新打开案例", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigDescription": "kibana.yml 文件已配置为仅允许特定连接器。要在外部系统中打开案例,请将 .[actionTypeId] (例如:.servicenow | .jira) 添加到 xpack.actions.enabledActiontypes 设置。有关更多信息,请参阅{link}。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigTitle": "在 Kibana 配置文件中启用外部服务", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByInvalidConnector": "用于将更新发送到外部服务的连接器已删除。要在外部系统中更新案例,请选择不同的连接器或创建新的连接器。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseDescription": "有{appropriateLicense}、正使用{cloud}或正在免费试用时,可在外部系统中创建案例。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseTitle": "升级适当的许可", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigDescription": "要在外部系统中打开和更新案例,必须为此案例选择外部事件管理系统。", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigTitle": "选择外部连接器", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConfigTitle": "配置外部连接器", + "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConnectors": "要在外部系统上打开和更新案例,必须配置{link}。", + "xpack.securitySolution.cases.caseView.reopenCase": "重新打开案例", + "xpack.securitySolution.cases.caseView.reporterLabel": "报告者", + "xpack.securitySolution.cases.caseView.requiredUpdateToExternalService": "需要更新 { externalService } 事件", + "xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip": "在时间线中调查", + "xpack.securitySolution.cases.caseView.sendEmalLinkAria": "单击可向 {user} 发送电子邮件", + "xpack.securitySolution.cases.caseView.showAlertTooltip": "显示告警详情", + "xpack.securitySolution.cases.caseView.statusLabel": "状态", + "xpack.securitySolution.cases.caseView.syncAlertsLabel": "同步告警", + "xpack.securitySolution.cases.caseView.tags": "标签", + "xpack.securitySolution.cases.caseView.to": "到", + "xpack.securitySolution.cases.caseView.unknown": "未知", + "xpack.securitySolution.cases.caseView.unknownRule.label": "未知规则", + "xpack.securitySolution.cases.caseView.updateNamedIncident": "更新 { thirdParty } 事件", + "xpack.securitySolution.cases.caseView.updateThirdPartyIncident": "更新外部事件", + "xpack.securitySolution.cases.common.noConnector": "未选择任何连接器", + "xpack.securitySolution.cases.components.connectors.cases.actionTypeTitle": "案例", + "xpack.securitySolution.cases.components.connectors.cases.addNewCaseOption": "添加新案例", + "xpack.securitySolution.cases.components.connectors.cases.caseRequired": "必须选择策略。", + "xpack.securitySolution.cases.components.connectors.cases.casesDropdownPlaceholder": "选择案例", + "xpack.securitySolution.cases.components.connectors.cases.casesDropdownRowLabel": "案例", + "xpack.securitySolution.cases.components.connectors.cases.commentLabel": "注释", + "xpack.securitySolution.cases.components.connectors.cases.commentRequired": "“注释”必填。", + "xpack.securitySolution.cases.components.connectors.cases.connectedCaseLabel": "已连接案例", + "xpack.securitySolution.cases.components.connectors.cases.createCaseLabel": "创建案例", + "xpack.securitySolution.cases.components.connectors.cases.optionAddNewCase": "添加到新案例", + "xpack.securitySolution.cases.components.connectors.cases.optionAddToExistingCase": "添加到现有案例", + "xpack.securitySolution.cases.components.connectors.cases.selectMessageText": "创建或更新案例。", + "xpack.securitySolution.cases.configure.errorGetFields": "从服务中获取字段时出错", + "xpack.securitySolution.cases.configure.successSaveToast": "已保存外部连接设置", + "xpack.securitySolution.cases.configureCases.addNewConnector": "添加新连接器", + "xpack.securitySolution.cases.configureCases.blankMappings": "至少一个字段需映射到 { connectorName }", + "xpack.securitySolution.cases.configureCases.cancelButton": "取消", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsClosedIncident": "在外部系统中关闭事件时自动关闭 Security 案例", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsDesc": "定义关闭 Security 案例的方式。要自动关闭案例,需要与外部事件管理系统建立连接。", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsLabel": "案例关闭选项", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsManual": "手动关闭 Security 案例", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsNewIncident": "将新事件推送到外部系统时自动关闭 Security 案例", + "xpack.securitySolution.cases.configureCases.caseClosureOptionsTitle": "案例关闭", + "xpack.securitySolution.cases.configureCases.commentMapping": "注释", + "xpack.securitySolution.cases.configureCases.editFieldMappingTitle": "编辑 { thirdPartyName } 字段映射", + "xpack.securitySolution.cases.configureCases.fieldMappingDesc": "将数据推送到 { thirdPartyName } 时,将 Security 案例字段映射到 { thirdPartyName } 字段。字段映射需要与 { thirdPartyName } 建立连接。", + "xpack.securitySolution.cases.configureCases.fieldMappingDescErr": "字段映射需要与 { thirdPartyName } 建立连接。请检查您的连接凭据。", + "xpack.securitySolution.cases.configureCases.fieldMappingEditAppend": "追加", + "xpack.securitySolution.cases.configureCases.fieldMappingEditNothing": "无内容", + "xpack.securitySolution.cases.configureCases.fieldMappingEditOverwrite": "覆盖", + "xpack.securitySolution.cases.configureCases.fieldMappingFirstCol": "Security 案例字段", + "xpack.securitySolution.cases.configureCases.fieldMappingSecondCol": "{ thirdPartyName } 字段", + "xpack.securitySolution.cases.configureCases.fieldMappingThirdCol": "编辑和更新时", + "xpack.securitySolution.cases.configureCases.fieldMappingTitle": "{ thirdPartyName } 字段映射", + "xpack.securitySolution.cases.configureCases.headerTitle": "配置案例", + "xpack.securitySolution.cases.configureCases.incidentManagementSystemDesc": "您可能会根据需要将 Security 案例连接到选择的外部事件管理系统。这将允许您将案例数据作为事件推送到所选第三方系统。", + "xpack.securitySolution.cases.configureCases.incidentManagementSystemLabel": "事件管理系统", + "xpack.securitySolution.cases.configureCases.incidentManagementSystemTitle": "连接到外部事件管理系统", + "xpack.securitySolution.cases.configureCases.mappingFieldNotMapped": "未映射", + "xpack.securitySolution.cases.configureCases.noFieldsError": "未找到任何 { connectorName } 字段。请检查您的 { connectorName } 连接器设置或 { connectorName } 实例设置以解决问题。", + "xpack.securitySolution.cases.configureCases.requiredMappings": "至少有一个案例字段需要映射到以下所需的 { connectorName } 字段:{ fields }", + "xpack.securitySolution.cases.configureCases.saveAndCloseButton": "保存并关闭", + "xpack.securitySolution.cases.configureCases.saveButton": "保存", + "xpack.securitySolution.cases.configureCases.updateConnector": "更新字段映射", + "xpack.securitySolution.cases.configureCases.updateSelectedConnector": "更新 { connectorName }", + "xpack.securitySolution.cases.configureCases.warningMessage": "选定的连接器已删除。选择不同的连接器或创建新的连接器。", + "xpack.securitySolution.cases.configureCases.warningTitle": "警告", + "xpack.securitySolution.cases.configureCasesButton": "编辑外部连接", + "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestion": "删除此案例即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", + "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestionPlural": "删除这些案例即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", + "xpack.securitySolution.cases.confirmDeleteCase.deleteCase": "删除案例", + "xpack.securitySolution.cases.confirmDeleteCase.deleteCases": "删除案例", + "xpack.securitySolution.cases.confirmDeleteCase.deleteThisCase": "删除此案例", + "xpack.securitySolution.cases.confirmDeleteCase.deleteTitle": "删除“{caseTitle}”", + "xpack.securitySolution.cases.confirmDeleteCase.selectedCases": "删除选定案例", + "xpack.securitySolution.cases.connectors.jira.issueTypesSelectFieldLabel": "问题类型", + "xpack.securitySolution.cases.connectors.jira.parentIssueSearchLabel": "父问题", + "xpack.securitySolution.cases.connectors.jira.prioritySelectFieldLabel": "优先级", + "xpack.securitySolution.cases.connectors.resilient.incidentTypesLabel": "事件类型", + "xpack.securitySolution.cases.connectors.resilient.incidentTypesPlaceholder": "选择类型", + "xpack.securitySolution.cases.connectors.resilient.severityLabel": "严重性", + "xpack.securitySolution.cases.connectors.resilient.unableToGetIncidentTypesMessage": "无法获取事件类型", + "xpack.securitySolution.cases.connectors.resilient.unableToGetSeverityMessage": "无法获取严重性", + "xpack.securitySolution.cases.containers.statusChangeToasterText": "此案例中的告警也更新了状态", + "xpack.securitySolution.cases.createCase.descriptionFieldRequiredError": "描述必填。", + "xpack.securitySolution.cases.createCase.fieldTagsHelpText": "为此案例键入一个或多个定制识别标签。在每个标签后按 Enter 键可开始新的标签。", + "xpack.securitySolution.cases.createCase.titleFieldRequiredError": "标题必填。", + "xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle": "关闭", + "xpack.securitySolution.cases.editConnector.editConnectorLinkAria": "单击以编辑连接器", + "xpack.securitySolution.cases.pageTitle": "案例", + "xpack.securitySolution.cases.readOnlySavedObjectDescription": "您仅有权查看案例。如果需要创建和更新案例,请联系您的 Kibana 管理员。", + "xpack.securitySolution.cases.readOnlySavedObjectTitle": "您无法创建新案例或更新现有案例", + "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOff": "关闭", + "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOn": "开启", + "xpack.securitySolution.cases.status.closed": "已关闭", + "xpack.securitySolution.cases.status.iconAria": "更改状态", + "xpack.securitySolution.cases.status.inProgress": "进行中", + "xpack.securitySolution.cases.status.open": "未结", + "xpack.securitySolution.cases.timeline.actions.addCase": "添加到案例", + "xpack.securitySolution.cases.timeline.actions.addExistingCase": "添加到现有案例", + "xpack.securitySolution.cases.timeline.actions.addNewCase": "添加到新案例", + "xpack.securitySolution.cases.timeline.actions.addToCaseAriaLabel": "将告警附加到案例", + "xpack.securitySolution.cases.timeline.actions.addToCaseTooltip": "添加到案例", + "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast": "告警已添加到“{title}”", + "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText": "此案例中的告警的状态已经与案例状态同步", + "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "查看案例", "xpack.securitySolution.caseConnectorsRegistry.get.missingCaseConnectorErrorMessage": "对象类型“{id}”未注册。", "xpack.securitySolution.caseConnectorsRegistry.register.duplicateCaseConnectorErrorMessage": "已注册对象类型“{id}”。", "xpack.securitySolution.certificate.fingerprint.clientCertLabel": "客户端证书", @@ -18746,14 +18726,14 @@ "xpack.securitySolution.containers.anomalies.errorFetchingAnomaliesData": "无法查询异常数据", "xpack.securitySolution.containers.anomalies.stackByJobId": "作业", "xpack.securitySolution.containers.anomalies.title": "异常", - "xpack.securitySolution.containers.case.closedCases": "已关闭{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.securitySolution.containers.case.deletedCases": "已删除{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.securitySolution.containers.case.errorDeletingTitle": "删除数据时出错", - "xpack.securitySolution.containers.case.errorTitle": "提取数据时出错", - "xpack.securitySolution.containers.case.pushToExternalService": "已成功发送到 { serviceName }", - "xpack.securitySolution.containers.case.reopenedCases": "已重新打开{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.securitySolution.containers.case.syncCase": "“{caseTitle}”中的告警已同步", - "xpack.securitySolution.containers.case.updatedCase": "已更新“{caseTitle}”", + "xpack.securitySolution.containers.cases.closedCases": "已关闭{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", + "xpack.securitySolution.containers.cases.deletedCases": "已删除{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", + "xpack.securitySolution.containers.cases.errorDeletingTitle": "删除数据时出错", + "xpack.securitySolution.containers.cases.errorTitle": "提取数据时出错", + "xpack.securitySolution.containers.cases.pushToExternalService": "已成功发送到 { serviceName }", + "xpack.securitySolution.containers.cases.reopenedCases": "已重新打开{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", + "xpack.securitySolution.containers.cases.syncCase": "“{caseTitle}”中的告警已同步", + "xpack.securitySolution.containers.cases.updatedCase": "已更新“{caseTitle}”", "xpack.securitySolution.containers.detectionEngine.addRuleFailDescription": "无法添加规则", "xpack.securitySolution.containers.detectionEngine.alerts.createListsIndex.errorDescription": "无法创建列表索引", "xpack.securitySolution.containers.detectionEngine.alerts.errorFetchingAlertsDescription": "无法查询告警", @@ -22561,7 +22541,7 @@ "xpack.triggersActionsUI.actionVariables.dateLabel": "告警计划操作的日期。", "xpack.triggersActionsUI.alerts.breadcrumbTitle": "告警", "xpack.triggersActionsUI.appName": "告警和操作", - "xpack.triggersActionsUI.case.configureCases.mappingFieldSummary": "摘要", + "xpack.triggersActionsUI.cases.configureCases.mappingFieldSummary": "摘要", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByConfigMessage": "连接器已由 Kibana 配置禁用。", "xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage": "此连接器需要{minimumLicenseRequired}许可证。", "xpack.triggersActionsUI.checkAlertTypeEnabled.alertTypeDisabledByLicenseMessage": "此告警类型需要{minimumLicenseRequired}许可证。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts index fe7ea61e68193..76dbe96f44f71 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts @@ -115,7 +115,7 @@ export const JIRA_API_TOKEN_REQUIRED = i18n.translate( ); export const MAPPING_FIELD_SUMMARY = i18n.translate( - 'xpack.triggersActionsUI.case.configureCases.mappingFieldSummary', + 'xpack.triggersActionsUI.cases.configureCases.mappingFieldSummary', { defaultMessage: 'Summary', } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts index b4258e42aaab6..2a80280ef2aab 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts @@ -64,7 +64,7 @@ function getIndexRecordActionType() { secrets: secretsSchema, }, async executor({ config, secrets, params, services, actionId }) { - await services.callCluster('index', { + await services.scopedClusterClient.index({ index: params.index, refresh: 'wait_for', body: { @@ -95,7 +95,7 @@ function getFailingActionType() { params: paramsSchema, }, async executor({ config, secrets, params, services }) { - await services.callCluster('index', { + await services.scopedClusterClient.index({ index: params.index, refresh: 'wait_for', body: { @@ -128,7 +128,7 @@ function getRateLimitedActionType() { params: paramsSchema, }, async executor({ config, params, services }) { - await services.callCluster('index', { + await services.scopedClusterClient.index({ index: params.index, refresh: 'wait_for', body: { @@ -149,7 +149,6 @@ function getRateLimitedActionType() { } function getAuthorizationActionType(core: CoreSetup) { - const clusterClient = core.elasticsearch.legacy.client; const paramsSchema = schema.object({ callClusterAuthorizationIndex: schema.string(), savedObjectsClientType: schema.string(), @@ -170,7 +169,7 @@ function getAuthorizationActionType(core: CoreSetup) { let callClusterSuccess = false; let callClusterError; try { - await services.callCluster('index', { + await services.scopedClusterClient.index({ index: params.callClusterAuthorizationIndex, refresh: 'wait_for', body: { @@ -182,11 +181,11 @@ function getAuthorizationActionType(core: CoreSetup) { callClusterError = e; } // Call scoped cluster - const scopedClusterClient = services.getLegacyScopedClusterClient(clusterClient); + const scopedClusterClient = services.scopedClusterClient; let callScopedClusterSuccess = false; let callScopedClusterError; try { - await scopedClusterClient.callAsCurrentUser('index', { + await scopedClusterClient.index({ index: params.callClusterAuthorizationIndex, refresh: 'wait_for', body: { @@ -210,7 +209,7 @@ function getAuthorizationActionType(core: CoreSetup) { savedObjectsClientError = e; } // Save the result - await services.callCluster('index', { + await services.scopedClusterClient.index({ index: params.index, refresh: 'wait_for', body: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts index f3c3ee74551c0..03ae0e6daf933 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts @@ -460,11 +460,9 @@ export default function ({ getService }: FtrProviderContext) { savedObjectsClientSuccess: false, callClusterError: { ...indexedRecord._source.state.callClusterError, - statusCode: 403, }, callScopedClusterError: { ...indexedRecord._source.state.callScopedClusterError, - statusCode: 403, }, savedObjectsClientError: { ...indexedRecord._source.state.savedObjectsClientError, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 8622b9c4c587c..05060a2fcf7a9 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -652,11 +652,9 @@ instanceStateValue: true savedObjectsClientSuccess: false, callClusterError: { ...searchResult.hits.hits[0]._source.state.callClusterError, - statusCode: 403, }, callScopedClusterError: { ...searchResult.hits.hits[0]._source.state.callScopedClusterError, - statusCode: 403, }, savedObjectsClientError: { ...searchResult.hits.hits[0]._source.state.savedObjectsClientError, diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/fixtures.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/fixtures.js index ec3f5fe6fe349..e61470cc2cc84 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/fixtures.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/fixtures.js @@ -75,8 +75,16 @@ export const getPolicyPayload = (name) => ({ }, }, }, + frozen: { + min_age: '20d', + actions: { + searchable_snapshot: { + snapshot_repository: 'backing_repo', + }, + }, + }, delete: { - min_age: '10d', + min_age: '30d', actions: { wait_for_snapshot: { policy: 'policy', diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap index 11c557fd02e38..468776bf692f4 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -1,31 +1,182 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environment is selected should return the correct anomaly boundaries 1`] = ` +exports[`APM API tests basic apm_8.0.0 Latency with a basic license when data is loaded time comparison returns some data 1`] = ` Array [ Object { - "x": 1607436000000, - "y": 0, - "y0": 0, + "x": 1607436780000, + "y": 51029, }, Object { - "x": 1607436900000, - "y": 0, - "y0": 0, + "x": 1607436820000, + "y": 38124, + }, + Object { + "x": 1607436860000, + "y": 16327, + }, + Object { + "x": 1607436980000, + "y": 35617.5, + }, + Object { + "x": 1607436990000, + "y": 34599.75, + }, + Object { + "x": 1607437100000, + "y": 26980, + }, + Object { + "x": 1607437110000, + "y": 42808, + }, + Object { + "x": 1607437130000, + "y": 22230.5, + }, + Object { + "x": 1607437220000, + "y": 34973, + }, + Object { + "x": 1607437230000, + "y": 19284.2, + }, + Object { + "x": 1607437240000, + "y": 9280, + }, + Object { + "x": 1607437250000, + "y": 42777, + }, + Object { + "x": 1607437260000, + "y": 10702, + }, + Object { + "x": 1607437340000, + "y": 22452, + }, + Object { + "x": 1607437470000, + "y": 14495.5, + }, + Object { + "x": 1607437480000, + "y": 11644.5714285714, + }, + Object { + "x": 1607437570000, + "y": 17359.6666666667, + }, + Object { + "x": 1607437590000, + "y": 11394.2, + }, +] +`; + +exports[`APM API tests basic apm_8.0.0 Latency with a basic license when data is loaded time comparison returns some data 2`] = ` +Array [ + Object { + "x": 1607436800000, + "y": 23448.25, + }, + Object { + "x": 1607436820000, + "y": 25181, + }, + Object { + "x": 1607436840000, + "y": 16834, + }, + Object { + "x": 1607436910000, + "y": 21582, + }, + Object { + "x": 1607437040000, + "y": 31800, + }, + Object { + "x": 1607437050000, + "y": 21341, + }, + Object { + "x": 1607437060000, + "y": 21108.5, + }, + Object { + "x": 1607437150000, + "y": 12147.3333333333, + }, + Object { + "x": 1607437160000, + "y": 23941.5, + }, + Object { + "x": 1607437180000, + "y": 18244, + }, + Object { + "x": 1607437240000, + "y": 24359.5, + }, + Object { + "x": 1607437280000, + "y": 27767, + }, + Object { + "x": 1607437290000, + "y": 21909.6666666667, + }, + Object { + "x": 1607437390000, + "y": 31521, + }, + Object { + "x": 1607437410000, + "y": 20227.5, + }, + Object { + "x": 1607437420000, + "y": 18664, + }, + Object { + "x": 1607437510000, + "y": 14197.5, + }, + Object { + "x": 1607437520000, + "y": 19199.8571428571, + }, + Object { + "x": 1607437540000, + "y": 63745.75, + }, + Object { + "x": 1607437640000, + "y": 63220, + }, + Object { + "x": 1607437660000, + "y": 20040, }, ] `; -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments is seleted should return the correct anomaly boundaries 1`] = ` Array [ Object { "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, + "y": 0, + "y0": 0, }, Object { "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, + "y": 0, + "y0": 0, }, ] `; @@ -34,13 +185,13 @@ exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license Array [ Object { "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, + "y": 136610.507897203, + "y0": 22581.5157631454, }, Object { "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, + "y": 136610.507897203, + "y0": 22581.5157631454, }, ] `; diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index 4b9409ce6f16b..cb72f7f9844bc 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -6,21 +6,22 @@ */ import expect from '@kbn/expect'; +import url from 'url'; +import moment from 'moment'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; +type LatencyChartReturnType = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; + export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); + const { start, end } = archives_metadata[archiveName]; registry.when( 'Latency with a basic license when data is not loaded ', @@ -28,7 +29,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns 400 when latencyAggregationType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + transactionType: 'request', + environment: 'testing', + }, + }) ); expect(response.status).to.be(400); @@ -36,7 +45,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 400 when transactionType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + environment: 'testing', + }, + }) ); expect(response.status).to.be(400); @@ -44,13 +61,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&latencyAggregationType=avg&transactionType=request` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType: 'request', + environment: 'testing', + }, + }) ); expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).to.be(null); - expect(response.body.latencyTimeseries.length).to.be(0); + const latencyChartReturn = response.body as LatencyChartReturnType; + + expect(latencyChartReturn.currentPeriod.overallAvgDuration).to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be(0); + expect(latencyChartReturn.previousPeriod.latencyTimeseries.length).to.be(0); }); } ); @@ -64,42 +93,113 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('average latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType: 'request', + environment: 'testing', + }, + }) ); }); it('returns average duration and timeseries', async () => { expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); }); }); describe('95th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request&latencyAggregationType=p95` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'p95', + transactionType: 'request', + environment: 'testing', + }, + }) ); }); it('returns average duration and timeseries', async () => { expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); }); }); describe('99th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=request&latencyAggregationType=p99` + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'p99', + transactionType: 'request', + environment: 'testing', + }, + }) ); }); it('returns average duration and timeseries', async () => { expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.overallAvgDuration).not.to.be(null); + expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('time comparison', () => { + before(async () => { + response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + query: { + latencyAggregationType: 'avg', + transactionType: 'request', + start: moment(end).subtract(15, 'minutes').toISOString(), + end, + comparisonStart: start, + comparisonEnd: moment(start).add(15, 'minutes').toISOString(), + }, + }) + ); + }); + + it('returns some data', async () => { + expect(response.status).to.be(200); + const latencyChartReturn = response.body as LatencyChartReturnType; + const currentPeriodNonNullDataPoints = latencyChartReturn.currentPeriod.latencyTimeseries.filter( + ({ y }) => y !== null + ); + expect(currentPeriodNonNullDataPoints.length).to.be.greaterThan(0); + const previousPeriodNonNullDataPoints = latencyChartReturn.previousPeriod.latencyTimeseries.filter( + ({ y }) => y !== null + ); + expect(previousPeriodNonNullDataPoints.length).to.be.greaterThan(0); + + expectSnapshot(currentPeriodNonNullDataPoints).toMatch(); + expectSnapshot(previousPeriodNonNullDataPoints).toMatch(); + }); + + it('matches x-axis on current period and previous period', () => { + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn.currentPeriod.latencyTimeseries.map(({ x }) => x)).to.be.eql( + latencyChartReturn.previousPeriod.latencyTimeseries.map(({ x }) => x) + ); }); }); } @@ -116,7 +216,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('without an environment', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + }, + }) ); }); @@ -128,7 +236,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('with environment selected', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-python/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + environment: 'production', + }, + }) ); }); @@ -137,24 +254,37 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expect(latencyChartReturn.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.jobId).toMatchInline( `"apm-production-1369-high_mean_transaction_duration"` ); }); it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expect(latencyChartReturn.anomalyTimeseries?.anomalyBoundaries?.length).to.be.greaterThan( + 0 + ); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.anomalyBoundaries).toMatch(); }); }); - describe('when not defined environment is selected', () => { + describe('when not defined environments is seleted', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?environment=ENVIRONMENT_NOT_DEFINED&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-python/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + environment: 'ENVIRONMENT_NOT_DEFINED', + }, + }) ); }); @@ -163,23 +293,34 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('should return the ML job id for anomalies with no defined environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expect(latencyChartReturn.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.jobId).toMatchInline( `"apm-environment_not_defined-5626-high_mean_transaction_duration"` ); }); it('should return the correct anomaly boundaries', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.have.property('anomalyTimeseries'); + expectSnapshot(latencyChartReturn.anomalyTimeseries?.anomalyBoundaries).toMatch(); }); }); describe('with all environments selected', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + url.format({ + pathname: `/api/apm/services/opbeans-java/transactions/charts/latency`, + query: { + start, + end, + latencyAggregationType: 'avg', + transactionType, + environment: 'ENVIRONMENT_ALL', + }, + }) ); }); @@ -188,33 +329,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('should not return anomaly timeseries data', () => { - expect(response.body).to.not.have.property('anomalyTimeseries'); - }); - }); - - describe('with environment selected and empty kuery filter', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + const latencyChartReturn = response.body as LatencyChartReturnType; + expect(latencyChartReturn).to.not.have.property('anomalyTimeseries'); }); }); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts index 1065bc9405838..72fb0e832412d 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_comparison_statistics.ts @@ -77,8 +77,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(Object.keys(currentPeriod).sort()).to.be.eql(transactionNames.sort()); - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); expect(previousPeriodItems.length).to.be.eql(0); @@ -210,8 +210,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct latency data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; @@ -227,8 +227,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct throughput data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; @@ -244,8 +244,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct error rate data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; @@ -256,13 +256,26 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect( removeEmptyCoordinates(previousPeriodFirstItem.errorRate).length ).to.be.greaterThan(0); + expectSnapshot(currentPeriodFirstItem.errorRate).toMatch(); expectSnapshot(previousPeriodFirstItem.errorRate).toMatch(); }); + it('matches x-axis on current period and previous period', () => { + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); + + const currentPeriodFirstItem = currentPeriodItems[0]; + const previousPeriodFirstItem = previousPeriodItems[0]; + + expect(currentPeriodFirstItem.errorRate.map(({ x }) => x)).to.be.eql( + previousPeriodFirstItem.errorRate.map(({ x }) => x) + ); + }); + it('returns correct impact data', () => { - const currentPeriodItems = Object.values(currentPeriod).map((data) => data); - const previousPeriodItems = Object.values(previousPeriod).map((data) => data); + const currentPeriodItems = Object.values(currentPeriod); + const previousPeriodItems = Object.values(previousPeriod); const currentPeriodFirstItem = currentPeriodItems[0]; const previousPeriodFirstItem = previousPeriodItems[0]; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts index c58ca0242a5b5..7cb66b6815b98 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/delete_comment.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; import { postCaseReq, postCommentUserReq } from '../../../../common/lib/mock'; import { createCaseAction, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts index 2d8e4c44e023e..7bbc8e344ee23 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/find_comments.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; -import { CommentsResponse, CommentType } from '../../../../../../plugins/case/common/api'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; +import { CommentsResponse, CommentType } from '../../../../../../plugins/cases/common/api'; import { postCaseReq, postCommentUserReq } from '../../../../common/lib/mock'; import { createCaseAction, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts index 264103a2052e5..723c9eba33beb 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_all_comments.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; import { postCaseReq, postCommentUserReq } from '../../../../common/lib/mock'; import { createCaseAction, @@ -16,7 +16,7 @@ import { deleteAllCaseItems, deleteCaseAction, } from '../../../../common/lib/utils'; -import { CommentType } from '../../../../../../plugins/case/common/api'; +import { CommentType } from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts index bf63c55938dfe..1a1bb727bd429 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/get_comment.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; import { postCaseReq, postCommentUserReq } from '../../../../common/lib/mock'; import { createCaseAction, @@ -16,7 +16,7 @@ import { deleteAllCaseItems, deleteCaseAction, } from '../../../../common/lib/utils'; -import { CommentResponse, CommentType } from '../../../../../../plugins/case/common/api'; +import { CommentResponse, CommentType } from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/migrations.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/migrations.ts index ba677602ac570..264ac2a0898e0 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/migrations.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/migrations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts index 6d9962e938249..bddc620535dda 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts @@ -9,8 +9,8 @@ import { omit } from 'lodash/fp'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; -import { CaseResponse, CommentType } from '../../../../../../plugins/case/common/api'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; +import { CaseResponse, CommentType } from '../../../../../../plugins/cases/common/api'; import { defaultUser, postCaseReq, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts index 9447f7ad3613c..5e48e39164e6b 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts @@ -9,9 +9,9 @@ import { omit } from 'lodash/fp'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../../plugins/security_solution/common/constants'; -import { CommentsResponse, CommentType } from '../../../../../../plugins/case/common/api'; +import { CommentsResponse, CommentType } from '../../../../../../plugins/cases/common/api'; import { defaultUser, postCaseReq, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts index 5e761e4d7e33a..b5187931a9f01 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../plugins/cases/common/constants'; import { postCaseReq, postCommentUserReq } from '../../../common/lib/mock'; import { createCaseAction, @@ -19,8 +19,8 @@ import { deleteCasesUserActions, deleteComments, } from '../../../common/lib/utils'; -import { getSubCaseDetailsUrl } from '../../../../../plugins/case/common/api/helpers'; -import { CaseResponse } from '../../../../../plugins/case/common/api'; +import { getSubCaseDetailsUrl } from '../../../../../plugins/cases/common/api/helpers'; +import { CaseResponse } from '../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts index 6791c9d1c9e71..b808ff4ccdf35 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/find_cases.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL, SUB_CASES_PATCH_DEL_URL } from '../../../../../plugins/case/common/constants'; +import { CASES_URL, SUB_CASES_PATCH_DEL_URL } from '../../../../../plugins/cases/common/constants'; import { postCaseReq, postCommentUserReq, findCasesResp } from '../../../common/lib/mock'; import { deleteAllCaseItems, @@ -18,7 +18,7 @@ import { createCaseAction, deleteCaseAction, } from '../../../common/lib/utils'; -import { CasesFindResponse, CaseStatuses, CaseType } from '../../../../../plugins/case/common/api'; +import { CasesFindResponse, CaseStatuses, CaseType } from '../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts index c0d937f53d6c3..fb4ab2c86469a 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/get_case.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../plugins/cases/common/constants'; import { postCaseReq, postCaseResp, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts b/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts index e66b623138e60..abbb749a2aaca 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../plugins/cases/common/constants'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts index b41f77fc42e05..950fde37e3078 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts @@ -8,14 +8,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../plugins/cases/common/constants'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../plugins/security_solution/common/constants'; import { CasesResponse, CaseStatuses, CaseType, CommentType, -} from '../../../../../plugins/case/common/api'; +} from '../../../../../plugins/cases/common/api'; import { defaultUser, postCaseReq, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/post_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/post_case.ts index 20574ffbc2f4a..5de5644ccf68a 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/post_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/post_case.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../plugins/cases/common/constants'; import { postCaseReq, postCaseResp, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts index ea5cd9c7c5020..2db15eb603f7c 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { ObjectRemover as ActionsRemover } from '../../../../alerting_api_integration/common/lib'; -import { CASE_CONFIGURE_URL, CASES_URL } from '../../../../../plugins/case/common/constants'; +import { CASE_CONFIGURE_URL, CASES_URL } from '../../../../../plugins/cases/common/constants'; import { postCaseReq, defaultUser, @@ -28,7 +28,7 @@ import { ExternalServiceSimulator, getExternalServiceSimulatorPath, } from '../../../../alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin'; -import { CaseStatuses } from '../../../../../plugins/case/common/api'; +import { CaseStatuses } from '../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/reporters/get_reporters.ts b/x-pack/test/case_api_integration/basic/tests/cases/reporters/get_reporters.ts index c9981115f5dd9..c51bfda5bd8b0 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/reporters/get_reporters.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/reporters/get_reporters.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL, CASE_REPORTERS_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL, CASE_REPORTERS_URL } from '../../../../../../plugins/cases/common/constants'; import { defaultUser, postCaseReq } from '../../../../common/lib/mock'; import { deleteCases } from '../../../../common/lib/utils'; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/status/get_status.ts b/x-pack/test/case_api_integration/basic/tests/cases/status/get_status.ts index 5312649860275..1657293953246 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/status/get_status.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/status/get_status.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL, CASE_STATUS_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL, CASE_STATUS_URL } from '../../../../../../plugins/cases/common/constants'; import { postCaseReq } from '../../../../common/lib/mock'; import { deleteCases } from '../../../../common/lib/utils'; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts index 1d8216ded8b7c..d179120cd3d85 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/delete_sub_cases.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { CASES_URL, SUB_CASES_PATCH_DEL_URL, -} from '../../../../../../plugins/case/common/constants'; +} from '../../../../../../plugins/cases/common/constants'; import { postCommentUserReq } from '../../../../common/lib/mock'; import { createCaseAction, @@ -18,8 +18,8 @@ import { deleteAllCaseItems, deleteCaseAction, } from '../../../../common/lib/utils'; -import { getSubCaseDetailsUrl } from '../../../../../../plugins/case/common/api/helpers'; -import { CaseResponse } from '../../../../../../plugins/case/common/api'; +import { getSubCaseDetailsUrl } from '../../../../../../plugins/cases/common/api/helpers'; +import { CaseResponse } from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts index 4fd4cd6ec7542..2c1bd9c7bd883 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/find_sub_cases.ts @@ -16,13 +16,13 @@ import { deleteCaseAction, setStatus, } from '../../../../common/lib/utils'; -import { getSubCasesUrl } from '../../../../../../plugins/case/common/api/helpers'; +import { getSubCasesUrl } from '../../../../../../plugins/cases/common/api/helpers'; import { CaseResponse, CaseStatuses, SubCasesFindResponse, -} from '../../../../../../plugins/case/common/api'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +} from '../../../../../../plugins/cases/common/api'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts index dff462d78ba82..440731cd07fe7 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/get_sub_case.ts @@ -25,12 +25,12 @@ import { import { getCaseCommentsUrl, getSubCaseDetailsUrl, -} from '../../../../../../plugins/case/common/api/helpers'; +} from '../../../../../../plugins/cases/common/api/helpers'; import { AssociationType, CaseResponse, SubCaseResponse, -} from '../../../../../../plugins/case/common/api'; +} from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts index 746d8a601bed6..d647bb09f804a 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/sub_cases/patch_sub_cases.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { CASES_URL, SUB_CASES_PATCH_DEL_URL, -} from '../../../../../../plugins/case/common/constants'; +} from '../../../../../../plugins/cases/common/constants'; import { createCaseAction, createSubCase, @@ -19,13 +19,13 @@ import { getSignalsWithES, setStatus, } from '../../../../common/lib/utils'; -import { getSubCaseDetailsUrl } from '../../../../../../plugins/case/common/api/helpers'; +import { getSubCaseDetailsUrl } from '../../../../../../plugins/cases/common/api/helpers'; import { CaseStatuses, CommentType, SubCaseResponse, -} from '../../../../../../plugins/case/common/api'; -import { createAlertsString } from '../../../../../../plugins/case/server/connectors'; +} from '../../../../../../plugins/cases/common/api'; +import { createAlertsString } from '../../../../../../plugins/cases/server/connectors'; import { postCaseReq, postCollectionReq } from '../../../../common/lib/mock'; const defaultSignalsIndex = '.siem-signals-default-000001'; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/tags/get_tags.ts b/x-pack/test/case_api_integration/basic/tests/cases/tags/get_tags.ts index d0f62dbd73da0..f5cbb7c7f0eb0 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/tags/get_tags.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/tags/get_tags.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL, CASE_TAGS_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL, CASE_TAGS_URL } from '../../../../../../plugins/cases/common/constants'; import { postCaseReq } from '../../../../common/lib/mock'; import { deleteCases } from '../../../../common/lib/utils'; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts index b771da84d4360..a3bc2a4399db2 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASE_CONFIGURE_URL, CASES_URL } from '../../../../../../plugins/case/common/constants'; -import { CommentType } from '../../../../../../plugins/case/common/api'; +import { CASE_CONFIGURE_URL, CASES_URL } from '../../../../../../plugins/cases/common/constants'; +import { CommentType } from '../../../../../../plugins/cases/common/api'; import { userActionPostResp, defaultUser, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/migrations.ts b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/migrations.ts index a3fb1003f3dc7..d0f852b3f57e7 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/migrations.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/migrations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../../plugins/case/common/constants'; +import { CASES_URL } from '../../../../../../plugins/cases/common/constants'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/case_api_integration/basic/tests/configure/get_configure.ts b/x-pack/test/case_api_integration/basic/tests/configure/get_configure.ts index bdeb2f30b4481..c892edff2d458 100644 --- a/x-pack/test/case_api_integration/basic/tests/configure/get_configure.ts +++ b/x-pack/test/case_api_integration/basic/tests/configure/get_configure.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASE_CONFIGURE_URL } from '../../../../../plugins/case/common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../plugins/cases/common/constants'; import { getConfiguration, removeServerGeneratedPropertiesFromConfigure, diff --git a/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts b/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts index 7e2cfc6c1781b..1789fa719ec9f 100644 --- a/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts +++ b/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../plugins/case/common/constants'; +import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../plugins/cases/common/constants'; import { ObjectRemover as ActionsRemover } from '../../../../alerting_api_integration/common/lib'; import { getServiceNowConnector, diff --git a/x-pack/test/case_api_integration/basic/tests/configure/migrations.ts b/x-pack/test/case_api_integration/basic/tests/configure/migrations.ts index 6214ed77557da..4ee2021399fae 100644 --- a/x-pack/test/case_api_integration/basic/tests/configure/migrations.ts +++ b/x-pack/test/case_api_integration/basic/tests/configure/migrations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASE_CONFIGURE_URL } from '../../../../../plugins/case/common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../plugins/cases/common/constants'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/case_api_integration/basic/tests/configure/patch_configure.ts b/x-pack/test/case_api_integration/basic/tests/configure/patch_configure.ts index 070170fad8537..ea4982a8f04ad 100644 --- a/x-pack/test/case_api_integration/basic/tests/configure/patch_configure.ts +++ b/x-pack/test/case_api_integration/basic/tests/configure/patch_configure.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASE_CONFIGURE_URL } from '../../../../../plugins/case/common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../plugins/cases/common/constants'; import { getConfiguration, removeServerGeneratedPropertiesFromConfigure, diff --git a/x-pack/test/case_api_integration/basic/tests/configure/post_configure.ts b/x-pack/test/case_api_integration/basic/tests/configure/post_configure.ts index c3341521fc023..7ab98a07cf046 100644 --- a/x-pack/test/case_api_integration/basic/tests/configure/post_configure.ts +++ b/x-pack/test/case_api_integration/basic/tests/configure/post_configure.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASE_CONFIGURE_URL } from '../../../../../plugins/case/common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../plugins/cases/common/constants'; import { getConfiguration, removeServerGeneratedPropertiesFromConfigure, diff --git a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts index 62a5df7643337..ee4d671f7880f 100644 --- a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts +++ b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts @@ -9,8 +9,8 @@ import { omit } from 'lodash/fp'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { CASES_URL } from '../../../../../plugins/case/common/constants'; -import { CommentType } from '../../../../../plugins/case/common/api'; +import { CASES_URL } from '../../../../../plugins/cases/common/constants'; +import { CommentType } from '../../../../../plugins/cases/common/api'; import { postCaseReq, postCaseResp, diff --git a/x-pack/test/case_api_integration/common/lib/mock.ts b/x-pack/test/case_api_integration/common/lib/mock.ts index c3c37bd20f140..53dd6440a47df 100644 --- a/x-pack/test/case_api_integration/common/lib/mock.ts +++ b/x-pack/test/case_api_integration/common/lib/mock.ts @@ -11,7 +11,7 @@ import { createAlertsString, isCommentGeneratedAlert, transformConnectorComment, -} from '../../../../plugins/case/server/connectors'; +} from '../../../../plugins/cases/server/connectors'; import { CasePostRequest, CaseResponse, @@ -23,12 +23,12 @@ import { CommentType, CaseStatuses, CaseType, - CaseClientPostRequest, + CasesClientPostRequest, SubCaseResponse, AssociationType, SubCasesFindResponse, CommentRequest, -} from '../../../../plugins/case/common/api'; +} from '../../../../plugins/cases/common/api'; export const defaultUser = { email: null, full_name: null, username: 'elastic' }; export const postCaseReq: CasePostRequest = { @@ -57,7 +57,7 @@ export const postCollectionReq: CasePostRequest = { /** * This is needed because the post api does not allow specifying the case type. But the response will include the type. */ -export const userActionPostResp: CaseClientPostRequest = { +export const userActionPostResp: CasesClientPostRequest = { ...postCaseReq, type: CaseType.individual, }; diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts index 169f85080f4eb..96806af37e2c1 100644 --- a/x-pack/test/case_api_integration/common/lib/utils.ts +++ b/x-pack/test/case_api_integration/common/lib/utils.ts @@ -10,7 +10,7 @@ import { Client } from '@elastic/elasticsearch'; import * as st from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; import { Explanation, SearchResponse } from 'elasticsearch'; -import { CASES_URL, SUB_CASES_PATCH_DEL_URL } from '../../../../plugins/case/common/constants'; +import { CASES_URL, SUB_CASES_PATCH_DEL_URL } from '../../../../plugins/cases/common/constants'; import { CasesConfigureRequest, CasesConfigureResponse, @@ -22,10 +22,10 @@ import { CaseStatuses, SubCasesResponse, CasesResponse, -} from '../../../../plugins/case/common/api'; +} from '../../../../plugins/cases/common/api'; import { postCollectionReq, postCommentGenAlertReq } from './mock'; -import { getSubCasesUrl } from '../../../../plugins/case/common/api/helpers'; -import { ContextTypeGeneratedAlertType } from '../../../../plugins/case/server/connectors'; +import { getSubCasesUrl } from '../../../../plugins/cases/common/api/helpers'; +import { ContextTypeGeneratedAlertType } from '../../../../plugins/cases/server/connectors'; import { SignalHit } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; interface Hit { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts index a319c30fa20de..919be0fcf311c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts @@ -21,7 +21,8 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('create_index', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/94367 + describe.skip('create_index', () => { afterEach(async () => { await deleteSignalsIndex(supertest); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 59526fd5abb8f..a7925fa756693 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isEqual } from 'lodash'; import expect from '@kbn/expect'; import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; @@ -27,6 +28,18 @@ import { import { getCreateThreatMatchRulesSchemaMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock'; import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks'; +const format = (value: unknown): string => JSON.stringify(value, null, 2); + +// Asserts that each expected value is included in the subject, independent of +// ordering. Uses _.isEqual for value comparison. +const assertContains = (subject: unknown[], expected: unknown[]) => + expected.forEach((expectedValue) => + expect(subject.some((value) => isEqual(value, expectedValue))).to.eql( + true, + `expected ${format(subject)} to contain ${format(expectedValue)}` + ) + ); + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); @@ -35,8 +48,7 @@ export default ({ getService }: FtrProviderContext) => { /** * Specific api integration tests for threat matching rule type */ - // FLAKY: https://github.com/elastic/kibana/issues/93152 - describe.skip('create_threat_matching', () => { + describe('create_threat_matching', () => { describe('validation errors', () => { it('should give an error that the index must exist first if it does not exist before creating a rule', async () => { const { body } = await supertest @@ -383,40 +395,37 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).equal(1); const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source.threat); - expect(threats).to.eql([ + const [threat] = hits.map((hit) => hit._source.threat) as Array<{ indicator: unknown[] }>; + + assertContains(threat.indicator, [ { - indicator: [ - { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: 'url', - }, - port: 57324, - provider: 'geenensp', - type: 'url', - }, - { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: 'ip', - }, - provider: 'other_provider', - type: 'ip', - }, - ], + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: 'url', + }, + port: 57324, + provider: 'geenensp', + type: 'url', + }, + { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: 'ip', + }, + provider: 'other_provider', + type: 'ip', }, ]); }); @@ -466,61 +475,57 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).equal(1); const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source.threat); + const [threat] = hits.map((hit) => hit._source.threat) as Array<{ indicator: unknown[] }>; - expect(threats).to.eql([ + assertContains(threat.indicator, [ { - indicator: [ - { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: 'url', - }, - port: 57324, - provider: 'geenensp', - type: 'url', - }, - // We do not merge matched indicators during enrichment, so in - // certain circumstances a given indicator document could appear - // multiple times in an enriched alert (albeit with different - // threat.indicator.matched data). That's the case with the - // first and third indicators matched, here. - { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: 'url', - }, - port: 57324, - provider: 'geenensp', - type: 'url', - }, - { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: 'ip', - }, - provider: 'other_provider', - type: 'ip', - }, - ], + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: 'url', + }, + port: 57324, + provider: 'geenensp', + type: 'url', + }, + // We do not merge matched indicators during enrichment, so in + // certain circumstances a given indicator document could appear + // multiple times in an enriched alert (albeit with different + // threat.indicator.matched data). That's the case with the + // first and third indicators matched, here. + { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: 'url', + }, + port: 57324, + provider: 'geenensp', + type: 'url', + }, + { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: 'ip', + }, + provider: 'other_provider', + type: 'ip', }, ]); }); @@ -575,81 +580,77 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).equal(2); const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source.threat); - expect(threats).to.eql([ + const threats = hits.map((hit) => hit._source.threat) as Array<{ indicator: unknown[] }>; + + assertContains(threats[0].indicator, [ { - indicator: [ - { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: 'url', - }, - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - ], + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: 'url', + }, + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, }, + ]); + + assertContains(threats[1].indicator, [ { - indicator: [ - { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: 'url', - }, - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: 'url', - }, - port: 57324, - provider: 'geenensp', - type: 'url', - }, - { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: 'url', - }, - port: 57324, - provider: 'geenensp', - type: 'url', - }, - ], + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: 'url', + }, + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: 'url', + }, + port: 57324, + provider: 'geenensp', + type: 'url', + }, + { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: 'url', + }, + port: 57324, + provider: 'geenensp', + type: 'url', }, ]); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts index d54b4525459e8..cdb5035c06155 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_signals_migrations.ts @@ -35,7 +35,8 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('deleting signals migrations', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/94367 + describe.skip('deleting signals migrations', () => { let outdatedSignalsIndexName: string; let createdMigration: CreateResponse; let finalizedMigration: FinalizeResponse; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts index 0aac596cc3adb..89228fa4f239d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts @@ -47,7 +47,8 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('Finalizing signals migrations', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/94367 + describe.skip('Finalizing signals migrations', () => { let legacySignalsIndexName: string; let outdatedSignalsIndexName: string; let createdMigrations: CreateResponse[]; diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index 5fcf43f274b31..7fa88be708077 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -102,6 +102,14 @@ export default function ({ getService }: FtrProviderContext) { it('should return a 400 when given an invalid "kuery" value', async () => { await supertest.get(`/api/fleet/agents?kuery=.test%3A`).expect(400); }); + + it('should return a 200 and an empty list when given a "kuery" value with a missing saved object type', async () => { + const { body: apiResponse } = await supertest + .get(`/api/fleet/agents?kuery=m`) // missing saved object type + .expect(200); + expect(apiResponse.total).to.eql(0); + }); + it('should accept a valid "kuery" value', async () => { const filter = encodeURIComponent('fleet-agents.access_api_key_id : "api-key-2"'); const { body: apiResponse } = await supertest diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap index 39404909d5190..7584dfcc8a6c0 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap +++ b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap @@ -276,7 +276,6 @@ Object { "type": "image/svg+xml", }, ], - "latestVersion": "0.3.3", "license": "basic", "name": "apache", "owner": Object { @@ -664,7 +663,6 @@ Object { "removable": true, "version": "0.1.4", }, - "coreMigrationVersion": "8.0.0", "id": "apache", "namespaces": Array [], "references": Array [], diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index 56eb48149e87e..71cf7ed79fa2b 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -88,9 +88,11 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(200); + delete packageInfoRes.body.response.latestVersion; delete packageInfoRes.body.response.savedObject.attributes.install_started_at; delete packageInfoRes.body.response.savedObject.version; delete packageInfoRes.body.response.savedObject.updated_at; + delete packageInfoRes.body.response.savedObject.coreMigrationVersion; expectSnapshot(packageInfoRes.body.response).toMatch(); }); diff --git a/x-pack/test/functional/apps/lens/drag_and_drop.ts b/x-pack/test/functional/apps/lens/drag_and_drop.ts index 5e4557057212f..519d33a947888 100644 --- a/x-pack/test/functional/apps/lens/drag_and_drop.ts +++ b/x-pack/test/functional/apps/lens/drag_and_drop.ts @@ -30,32 +30,32 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.lens.dragFieldToDimensionTrigger( 'clientip', - 'lnsDatatable_column > lns-dimensionTrigger' + 'lnsDatatable_rows > lns-dimensionTrigger' ); - expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_column')).to.eql( + expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_rows')).to.eql( 'Top values of clientip' ); await PageObjects.lens.dragFieldToDimensionTrigger( 'bytes', - 'lnsDatatable_column > lns-empty-dimension' + 'lnsDatatable_rows > lns-empty-dimension' ); - expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_column', 1)).to.eql( + expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_rows', 1)).to.eql( 'bytes' ); await PageObjects.lens.dragFieldToDimensionTrigger( '@message.raw', - 'lnsDatatable_column > lns-empty-dimension' + 'lnsDatatable_rows > lns-empty-dimension' ); - expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_column', 2)).to.eql( + expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_rows', 2)).to.eql( 'Top values of @message.raw' ); }); it('should reorder the elements for the table', async () => { - await PageObjects.lens.reorderDimensions('lnsDatatable_column', 3, 1); + await PageObjects.lens.reorderDimensions('lnsDatatable_rows', 3, 1); await PageObjects.lens.waitForVisualization(); - expect(await PageObjects.lens.getDimensionTriggersTexts('lnsDatatable_column')).to.eql([ + expect(await PageObjects.lens.getDimensionTriggersTexts('lnsDatatable_rows')).to.eql([ 'Top values of @message.raw', 'Top values of clientip', 'bytes', diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 946f3a1dcba95..62e9b39d208b5 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.lens.switchToVisualization('lnsDatatable'); - await PageObjects.lens.removeDimension('lnsDatatable_column'); + await PageObjects.lens.removeDimension('lnsDatatable_rows'); await PageObjects.lens.switchToVisualization('bar_stacked'); await PageObjects.lens.configureDimension({ @@ -446,7 +446,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.switchToVisualization('lnsDatatable'); await PageObjects.lens.configureDimension({ - dimension: 'lnsDatatable_column > lns-empty-dimension', + dimension: 'lnsDatatable_rows > lns-empty-dimension', operation: 'date_histogram', field: '@timestamp', }); diff --git a/x-pack/test/functional/apps/lens/table.ts b/x-pack/test/functional/apps/lens/table.ts index 2d96458523cb6..f0f3ce27f4c31 100644 --- a/x-pack/test/functional/apps/lens/table.ts +++ b/x-pack/test/functional/apps/lens/table.ts @@ -59,16 +59,39 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('@timestamp per 3 hours'); expect(await PageObjects.lens.getDatatableHeaderText(2)).to.equal('Average of bytes'); - await PageObjects.lens.toggleColumnVisibility('lnsDatatable_column > lns-dimensionTrigger'); + await PageObjects.lens.toggleColumnVisibility('lnsDatatable_rows > lns-dimensionTrigger'); expect(await PageObjects.lens.getDatatableHeaderText(0)).to.equal('@timestamp per 3 hours'); expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('Average of bytes'); - await PageObjects.lens.toggleColumnVisibility('lnsDatatable_column > lns-dimensionTrigger'); + await PageObjects.lens.toggleColumnVisibility('lnsDatatable_rows > lns-dimensionTrigger'); expect(await PageObjects.lens.getDatatableHeaderText(0)).to.equal('Top values of ip'); expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('@timestamp per 3 hours'); expect(await PageObjects.lens.getDatatableHeaderText(2)).to.equal('Average of bytes'); }); + + it('should allow to transpose columns', async () => { + await PageObjects.lens.dragDimensionToDimension( + 'lnsDatatable_rows > lns-dimensionTrigger', + 'lnsDatatable_columns > lns-empty-dimension' + ); + expect(await PageObjects.lens.getDatatableHeaderText(0)).to.equal('@timestamp per 3 hours'); + expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal( + '169.228.188.120 › Average of bytes' + ); + expect(await PageObjects.lens.getDatatableHeaderText(2)).to.equal( + '78.83.247.30 › Average of bytes' + ); + expect(await PageObjects.lens.getDatatableHeaderText(3)).to.equal( + '226.82.228.233 › Average of bytes' + ); + }); + + it('should allow to sort by transposed columns', async () => { + await PageObjects.lens.changeTableSortingBy(2, 'ascending'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.lens.getDatatableCellText(0, 2)).to.eql('17,246'); + }); }); } diff --git a/x-pack/test/functional/apps/maps/auto_fit_to_bounds.js b/x-pack/test/functional/apps/maps/auto_fit_to_bounds.js index 9847923c1bf5b..8af2e45b59838 100644 --- a/x-pack/test/functional/apps/maps/auto_fit_to_bounds.js +++ b/x-pack/test/functional/apps/maps/auto_fit_to_bounds.js @@ -11,8 +11,7 @@ export default function ({ getPageObjects, getService }) { const PageObjects = getPageObjects(['maps']); const security = getService('security'); - // FLAKY: https://github.com/elastic/kibana/issues/93737 - describe.skip('auto fit map to bounds', () => { + describe('auto fit map to bounds', () => { describe('initial location', () => { before(async () => { await security.testUser.setRoles(['global_maps_all', 'test_logstash_reader']); diff --git a/x-pack/test/functional/apps/maps/documents_source/search_hits.js b/x-pack/test/functional/apps/maps/documents_source/search_hits.js index 2663242406a75..4da36a44cff08 100644 --- a/x-pack/test/functional/apps/maps/documents_source/search_hits.js +++ b/x-pack/test/functional/apps/maps/documents_source/search_hits.js @@ -84,8 +84,7 @@ export default function ({ getPageObjects, getService }) { expect(beforeQueryRefreshTimestamp).not.to.equal(afterQueryRefreshTimestamp); }); - // https://github.com/elastic/kibana/issues/93718 - it.skip('should apply query to fit to bounds', async () => { + it('should apply query to fit to bounds', async () => { // Set view to other side of world so no matching results await PageObjects.maps.setView(-15, -100, 6); await PageObjects.maps.clickFitToBounds('logstash'); diff --git a/x-pack/test/functional/apps/maps/visualize_create_menu.js b/x-pack/test/functional/apps/maps/visualize_create_menu.js index bac879dd9c81d..c9044353fbde8 100644 --- a/x-pack/test/functional/apps/maps/visualize_create_menu.js +++ b/x-pack/test/functional/apps/maps/visualize_create_menu.js @@ -29,9 +29,8 @@ export default function ({ getService, getPageObjects }) { it('should take users to Maps application when Maps is clicked', async () => { await PageObjects.visualize.clickMapsApp(); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.maps.waitForLayersToLoad(); - const doesLayerExist = await PageObjects.maps.doesLayerExist('Road map'); - expect(doesLayerExist).to.equal(true); + const onMapPage = await PageObjects.maps.onMapPage(); + expect(onMapPage).to.equal(true); }); }); diff --git a/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json new file mode 100644 index 0000000000000..c7d6ba9918eb2 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/data.json @@ -0,0 +1,109 @@ +{ + "type": "doc", + "value": { + "id": "0dce5b12-b88e-4141-ac0f-e93eefd7bb9f", + "index": ".fleet-artifacts_1", + "source": { + "body": "eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==", + "created": "2021-03-10T21:51:33.155Z", + "compressionAlgorithm": "zlib", + "encryptionAlgorithm": "none", + "identifier": "endpoint-exceptionlist-macos-v1", + "encodedSha256": "f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda", + "encodedSize": 14, + "decodedSha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "decodedSize": 22, + "packageName": "endpoint", + "relative_url": "/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "type": "exceptionlist" + } + } +} + +{ + "type": "doc", + "value": { + "id": "3c656388-4b51-4903-abc9-ee726657a164", + "index": ".fleet-artifacts_1", + "source": { + "body": "eJzFkL0KwjAUhV+lZA55gG4OXcXJRYqE9LZeiElJbotSsvsIbr6ij2AaakVwUqTr+fkOnIGBIYfgWb4bGJ1bYDnzeGw1MP7m1Qi6iqZUhKbZOKvAe1GjBuGxMeBi3rbgJFkXY2iU7iqoojpR4RSreyV9Enupu1EttPSEimdrsRUs8OHj6C8L99v1ksBPGLnOU4p8QYtlYKHkM21+QFLn4FU3kEZCOU4vcOzKWDqAyybGP54tetSLPluGB+Nu8h4=", + "created": "2021-03-10T21:51:33.155Z", + "compressionAlgorithm": "zlib", + "encryptionAlgorithm": "none", + "identifier": "endpoint-exceptionlist-windows-v1", + "encodedSha256": "73015ee5131dabd1b48aa4776d3e766d836f8dd8c9fa8999c9b931f60027f07f", + "encodedSize": 191, + "decodedSha256": "8d2bcc37e82fad5d06e2c9e4bd96793ea8905ace1d528a57d0d0579ecc8c647e", + "decodedSize": 704, + "packageName": "endpoint", + "relative_url": "/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "type": "exceptionlist" + } + } +} + +{ + "type": "doc", + "value": { + "id": "9596fbb6-64d0-432f-99e3-8b7eb559244c", + "index": ".fleet-artifacts_1", + "source": { + "body": "eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==", + "compressionAlgorithm": "zlib", + "created": "2021-03-10T21:51:31.136Z", + "decodedSha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "decodedSize": 14, + "encodedSha256": "f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda", + "encodedSize": 22, + "encryptionAlgorithm": "none", + "identifier": "endpoint-trustlist-macos-v1", + "packageName": "endpoint", + "relative_url": "/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "type": "trustlist" + } + } +} + +{ + "type": "doc", + "value": { + "id": "74536df6-6bdb-499d-bb08-46789dadb026", + "index": ".fleet-artifacts_1", + "source": { + "body": "eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==", + "compressionAlgorithm": "zlib", + "created": "2021-03-10T21:51:32.146Z", + "decodedSha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "decodedSize": 14, + "encodedSha256": "f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda", + "encodedSize": 22, + "encryptionAlgorithm": "none", + "identifier": "endpoint-trustlist-windows-v1", + "packageName": "endpoint", + "relative_url": "/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "type": "trustlist" + } + } +} + +{ + "type": "doc", + "value": { + "id": "6ebf4306-2113-4316-8295-6bc00ebea385", + "index": ".fleet-artifacts_1", + "source": { + "body": "eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==", + "compressionAlgorithm": "zlib", + "created": "2021-03-10T21:51:33.155Z", + "decodedSha256": "d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "decodedSize": 14, + "encodedSha256": "f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda", + "encodedSize": 22, + "encryptionAlgorithm": "none", + "identifier": "endpoint-trustlist-linux-v1", + "packageName": "endpoint", + "relative_url": "/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658", + "type": "trustlist" + } + } +} diff --git a/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json new file mode 100644 index 0000000000000..b35b0acc6bdd2 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/artifacts/fleet_artifacts/mappings.json @@ -0,0 +1,64 @@ +{ + "type": "index", + "value": { + "aliases": { + ".fleet-artifacts": { + } + }, + "index": ".fleet-artifacts_1", + "mappings": { + "_meta": { + "migrationHash": "766a1f59e685a9c07b04480e9a5dc2727843bd1f" + }, + "dynamic": "false", + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "decodedSha256": { + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + }, + "packageName": { + "type": "keyword" + }, + "relative_url": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file 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 5d6a355939d30..79f869040f74a 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -150,7 +150,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\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[]},\"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\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[]},\"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\"}]", "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "document example", "uiStateJSON": "{\"isDarkMode\":false}" @@ -181,7 +181,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\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"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\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"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\"}]", "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"machine.os.raw : \\\"ios\\\"\",\"language\":\"kuery\"}}", "title": "document example with query", "uiStateJSON": "{\"isDarkMode\":false}" @@ -212,7 +212,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\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"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\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"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\"}]", "description": "", "mapStateJSON": "{\"zoom\":4.09,\"center\":{\"lon\":-100.58836,\"lat\":33.21778},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"index\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"machine.os.raw\",\"value\":\"ios\",\"params\":{\"query\":\"ios\"}},\"query\":{\"match\":{\"machine.os.raw\":{\"query\":\"ios\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}]}", "title": "document example with filter", @@ -288,7 +288,7 @@ "title" : "document example top hits split with scripted field", "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\"},\"filters\":[]}", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_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\":[],\"scalingType\":\"TOP_HITS\",\"topHitsSplitField\":\"hour_of_day\",\"topHitsSize\":1,\"sortField\":\"@timestamp\",\"sortOrder\":\"desc\",\"applyGlobalQuery\":true,\"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}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}},\"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\":[],\"scalingType\":\"TOP_HITS\",\"topHitsSplitField\":\"hour_of_day\",\"topHitsSize\":1,\"sortField\":\"@timestamp\",\"sortOrder\":\"desc\",\"applyGlobalQuery\":true,\"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}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", "bounds" : { "type" : "Polygon", @@ -344,7 +344,7 @@ "title" : "document example with data driven styles", "description" : "", "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_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\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"hour_of_day\",\"name\":\"hour_of_day\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"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\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"hour_of_day\",\"name\":\"hour_of_day\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\"}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", "bounds" : { "type" : "Polygon", @@ -400,7 +400,7 @@ "title" : "document example with data driven styles on date field", "description" : "", "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}", - "layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_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\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"@timestamp\",\"name\":\"@timestamp\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"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\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"@timestamp\",\"name\":\"@timestamp\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\"}]", "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", "bounds" : { "type" : "Polygon", @@ -456,7 +456,7 @@ "title" : "document example hidden", "description" : "", "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_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\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":false,\"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}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"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\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":false,\"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}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" }, "type" : "map", @@ -681,7 +681,7 @@ "type": "polygon" }, "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\":\"frk92\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"7d807c75-088a-44b7-920a-e7e47f4fc038\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"1035e930-1811-11e9-b78a-23d706cd2507\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"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\":\"frk92\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"7d807c75-088a-44b7-920a-e7e47f4fc038\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"1035e930-1811-11e9-b78a-23d706cd2507\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"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\"}]", "mapStateJSON": "{\"zoom\":8.8,\"center\":{\"lon\":-179.98743,\"lat\":-0.09561},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}", "title": "antimeridian points example", "uiStateJSON": "{\"isDarkMode\":false}" @@ -726,7 +726,7 @@ "type": "polygon" }, "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\":\"ad9fj\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"4e4b5628-dbdc-40bb-93f0-8a7a48be1141\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"502886a0-18f8-11e9-97c8-5da5e037299c\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":true,\"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}}}},\"type\":\"VECTOR\"}]", + "layerListJSON": "[{\"id\":\"ad9fj\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"4e4b5628-dbdc-40bb-93f0-8a7a48be1141\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"502886a0-18f8-11e9-97c8-5da5e037299c\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":true,\"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}}}},\"type\":\"VECTOR\"}]", "mapStateJSON": "{\"zoom\":5.65,\"center\":{\"lon\":179.03193,\"lat\":0.09593},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"}}", "title": "antimeridian shapes example", "uiStateJSON": "{\"isDarkMode\":false}" @@ -964,7 +964,7 @@ "title" : "blended document example", "description" : "", "mapStateJSON" : "{\"zoom\":10.27,\"center\":{\"lon\":-83.70716,\"lat\":32.73679},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-23T00:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}", - "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"43a70a86-00fd-43af-9e84-4d9fe2d7513d\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{},\"type\":\"VECTOR_TILE\"},{\"id\":\"307c8495-89f7-431b-83d8-78724d9a8f72\",\"label\":\"logstash-*\",\"sourceDescriptor\":{\"geoField\":\"geo.coordinates\",\"id\":\"20fc58c3-3c0a-4c7b-9cdc-37552cafdc21\",\"tooltipProperties\":[],\"type\":\"ES_SEARCH\",\"scalingType\":\"CLUSTERS\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"type\":\"BLENDED_VECTOR\",\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}},\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#54B399\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true}}]", + "layerListJSON" : "[{\"id\":\"307c8495-89f7-431b-83d8-78724d9a8f72\",\"label\":\"logstash-*\",\"sourceDescriptor\":{\"geoField\":\"geo.coordinates\",\"id\":\"20fc58c3-3c0a-4c7b-9cdc-37552cafdc21\",\"tooltipProperties\":[],\"type\":\"ES_SEARCH\",\"scalingType\":\"CLUSTERS\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"type\":\"BLENDED_VECTOR\",\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}},\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#54B399\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true}}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", "bounds" : { "type" : "Polygon", @@ -1020,7 +1020,7 @@ "title" : "document example - auto fit to bounds for initial location", "description" : "", "mapStateJSON" : "{\"zoom\":5.2,\"center\":{\"lon\":-67.80052,\"lat\":-55.25331},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"AUTO_FIT_TO_BOUNDS\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_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\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"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}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"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\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"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}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" }, "type" : "map", @@ -1047,7 +1047,7 @@ "source": { "map" : { "description":"shapes with mvt scaling", - "layerListJSON":"[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"76b9fc1d-1e8a-4d2f-9f9e-6ba2b19f24bb\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"type\":\"VECTOR_TILE\"},{\"sourceDescriptor\":{\"geoField\":\"geometry\",\"filterByMapBounds\":true,\"scalingType\":\"MVT\",\"topHitsSize\":1,\"id\":\"97f8555e-8db0-4bd8-8b18-22e32f468667\",\"type\":\"ES_SEARCH\",\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"caffa63a-ebfb-466d-8ff6-d797975b88ab\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"prop1\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":1},\"type\":\"ORDINAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\",\"joins\":[]}]", + "layerListJSON":"[{\"sourceDescriptor\":{\"geoField\":\"geometry\",\"filterByMapBounds\":true,\"scalingType\":\"MVT\",\"topHitsSize\":1,\"id\":\"97f8555e-8db0-4bd8-8b18-22e32f468667\",\"type\":\"ES_SEARCH\",\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"caffa63a-ebfb-466d-8ff6-d797975b88ab\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"prop1\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":1},\"type\":\"ORDINAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\",\"joins\":[]}]", "mapStateJSON":"{\"zoom\":3.75,\"center\":{\"lon\":80.01106,\"lat\":3.65009},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", "title":"geo_shape_mvt", "uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 6694a494cf853..3f6b5691314bb 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -196,6 +196,13 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte return await listingTable.onListingPage('map'); } + async onMapPage() { + log.debug(`onMapPage`); + return await testSubjects.exists('mapLayerTOC', { + timeout: 5000, + }); + } + async searchForMapWithName(name: string) { log.debug(`searchForMapWithName: ${name}`); diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index 727f6493910ff..70e3d7c1b9b15 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -167,10 +167,10 @@ export function MachineLearningCommonUIProvider({ getService }: FtrProviderConte async setSliderValue(testDataSubj: string, value: number) { const slider = await testSubjects.find(testDataSubj); - let currentValue = await slider.getAttribute('value'); - let currentDiff = +currentValue - +value; - await retry.tryForTime(60 * 1000, async () => { + const currentValue = await slider.getAttribute('value'); + const currentDiff = +currentValue - +value; + if (currentDiff === 0) { return true; } else { @@ -189,20 +189,13 @@ export function MachineLearningCommonUIProvider({ getService }: FtrProviderConte } await retry.tryForTime(1000, async () => { const newValue = await slider.getAttribute('value'); - if (newValue !== currentValue) { - currentValue = newValue; - currentDiff = +currentValue - +value; - return true; - } else { + if (newValue === currentValue) { throw new Error(`slider value should have changed, but is still ${currentValue}`); } }); - - throw new Error(`slider value should be '${value}' (got '${currentValue}')`); + await this.assertSliderValue(testDataSubj, value); } }); - - await this.assertSliderValue(testDataSubj, value); }, async assertSliderValue(testDataSubj: string, expectedValue: number) { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts index 6d0a3255685f2..57f03a197b389 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts @@ -12,7 +12,9 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const telemetryTestResources = getService('telemetryTestResources'); - describe('security solution endpoint telemetry', () => { + // The source of the data for these tests have changed and need to be updated + // There are currently tests in the security_solution application being maintained + describe.skip('security solution endpoint telemetry', () => { after(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 4cf931a042221..f28545f83a890 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -32,7 +32,6 @@ export default function (providerContext: FtrProviderContext) { }); loadTestFile(require.resolve('./endpoint_list')); loadTestFile(require.resolve('./policy_details')); - loadTestFile(require.resolve('./resolver')); loadTestFile(require.resolve('./endpoint_telemetry')); loadTestFile(require.resolve('./trusted_apps_list')); loadTestFile(require.resolve('./fleet_integrations')); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 710099dfca8e9..75f266e29a91e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -21,7 +21,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const policyTestResources = getService('policyTestResources'); - // Failing: See https://github.com/elastic/kibana/issues/92567 describe('When on the Endpoint Policy Details Page', function () { this.tags(['ciGroup7']); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts deleted file mode 100644 index 6ab2a3e584eb8..0000000000000 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['common', 'timePicker', 'hosts', 'settings']); - const testSubjects = getService('testSubjects'); - const esArchiver = getService('esArchiver'); - const browser = getService('browser'); - - /** - * Navigating to the hosts page must be done after data is loaded into ES otherwise - * the hosts page will display the empty default page and if we load data after that - * we'd have to set the source filter on the page. - */ - const navigateToHostsAndSetDate = async () => { - await pageObjects.hosts.navigateToSecurityHostsPage(); - await pageObjects.common.dismissBanner(); - const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; - const toTime = 'now'; - await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - }; - - describe.skip('Endpoint Event Resolver', function () { - before(async () => { - await browser.setWindowSize(1800, 1200); - }); - after(async () => { - await pageObjects.hosts.deleteDataStreams(); - }); - - describe('Endpoint Resolver Tree', function () { - before(async () => { - await esArchiver.load('empty_kibana'); - await esArchiver.load('endpoint/resolver_tree/functions', { useCreate: true }); - await navigateToHostsAndSetDate(); - await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.events.file'); - }); - after(async () => { - await pageObjects.hosts.deleteDataStreams(); - }); - - it('check that Resolver and Data table is loaded', async () => { - await testSubjects.existOrFail('resolver:graph'); - await testSubjects.existOrFail('tableHeaderCell_name_0'); - await testSubjects.existOrFail('tableHeaderCell_timestamp_1'); - }); - - it('compare resolver Nodes Table data and Data length', async () => { - const nodeData: string[] = []; - const TableData: string[] = []; - - const Table = await testSubjects.findAll('resolver:node-list:node-link:title'); - for (const value of Table) { - const text = await value._webElement.getText(); - TableData.push(text.split('\n')[0]); - } - await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); - const Nodes = await testSubjects.findAll('resolver:node:primary-button'); - for (const value of Nodes) { - nodeData.push(await value._webElement.getText()); - } - for (let i = 0; i < nodeData.length; i++) { - expect(TableData[i]).to.eql(nodeData[i]); - } - expect(nodeData.length).to.eql(TableData.length); - await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); - }); - - it('resolver Nodes navigation Up', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await (await testSubjects.find('resolver:graph-controls:north-button')).click(); - - const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); - for (let i = 0; i < OriginalNodeDataStyle.length; i++) { - expect(parseFloat(OriginalNodeDataStyle[i].top)).to.lessThan( - parseFloat(NewNodeDataStyle[i].top) - ); - expect(parseFloat(OriginalNodeDataStyle[i].left)).to.equal( - parseFloat(NewNodeDataStyle[i].left) - ); - } - await (await testSubjects.find('resolver:graph-controls:center-button')).click(); - }); - - it('resolver Nodes navigation Down', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await (await testSubjects.find('resolver:graph-controls:south-button')).click(); - - const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); - for (let i = 0; i < NewNodeDataStyle.length; i++) { - expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].top) - ); - expect(parseFloat(OriginalNodeDataStyle[i].left)).to.equal( - parseFloat(NewNodeDataStyle[i].left) - ); - } - await (await testSubjects.find('resolver:graph-controls:center-button')).click(); - }); - - it('resolver Nodes navigation Left', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await (await testSubjects.find('resolver:graph-controls:east-button')).click(); - - const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); - for (let i = 0; i < OriginalNodeDataStyle.length; i++) { - expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].left) - ); - expect(parseFloat(NewNodeDataStyle[i].top)).to.equal( - parseFloat(OriginalNodeDataStyle[i].top) - ); - } - await (await testSubjects.find('resolver:graph-controls:center-button')).click(); - }); - - it('resolver Nodes navigation Right', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await testSubjects.click('resolver:graph-controls:west-button'); - const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); - for (let i = 0; i < NewNodeDataStyle.length; i++) { - expect(parseFloat(OriginalNodeDataStyle[i].left)).to.lessThan( - parseFloat(NewNodeDataStyle[i].left) - ); - expect(parseFloat(NewNodeDataStyle[i].top)).to.equal( - parseFloat(OriginalNodeDataStyle[i].top) - ); - } - await (await testSubjects.find('resolver:graph-controls:center-button')).click(); - }); - - it('resolver Nodes navigation Center', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await (await testSubjects.find('resolver:graph-controls:east-button')).click(); - await (await testSubjects.find('resolver:graph-controls:south-button')).click(); - - const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); - for (let i = 0; i < NewNodeDataStyle.length; i++) { - expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].left) - ); - expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].top) - ); - } - await (await testSubjects.find('resolver:graph-controls:center-button')).click(); - const CenterNodeDataStyle = await pageObjects.hosts.parseStyles(); - - for (let i = 0; i < CenterNodeDataStyle.length; i++) { - expect(parseFloat(CenterNodeDataStyle[i].left)).to.equal( - parseFloat(OriginalNodeDataStyle[i].left) - ); - expect(parseFloat(CenterNodeDataStyle[i].top)).to.equal( - parseFloat(OriginalNodeDataStyle[i].top) - ); - } - }); - - it('resolver Nodes navigation zoom in', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); - - const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); - for (let i = 1; i < NewNodeDataStyle.length; i++) { - expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].left) - ); - expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].top) - ); - expect(parseFloat(OriginalNodeDataStyle[i].width)).to.lessThan( - parseFloat(NewNodeDataStyle[i].width) - ); - expect(parseFloat(OriginalNodeDataStyle[i].height)).to.lessThan( - parseFloat(NewNodeDataStyle[i].height) - ); - await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); - } - }); - - it('resolver Nodes navigation zoom out', async () => { - const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); - await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); - const NewNodeDataStyle1 = await pageObjects.hosts.parseStyles(); - for (let i = 1; i < OriginalNodeDataStyle.length; i++) { - expect(parseFloat(OriginalNodeDataStyle[i].left)).to.lessThan( - parseFloat(NewNodeDataStyle1[i].left) - ); - expect(parseFloat(OriginalNodeDataStyle[i].top)).to.lessThan( - parseFloat(NewNodeDataStyle1[i].top) - ); - expect(parseFloat(NewNodeDataStyle1[i].width)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].width) - ); - expect(parseFloat(NewNodeDataStyle1[i].height)).to.lessThan( - parseFloat(OriginalNodeDataStyle[i].height) - ); - } - await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); - }); - }); - - describe('node related event pills', function () { - /** - * Verifies that the pills of a node have the correct text. - * - * @param id the node ID to verify the pills for. - * @param expectedPills a map of expected pills for all nodes - */ - const verifyPills = async (id: string, expectedPills: Set) => { - const relatedEventPills = await pageObjects.hosts.findNodePills(id); - expect(relatedEventPills.length).to.equal(expectedPills.size); - for (const pill of relatedEventPills) { - const pillText = await pill._webElement.getText(); - // check that we have the pill text in our expected map - expect(expectedPills.has(pillText)).to.equal(true); - } - }; - - before(async () => { - await esArchiver.load('empty_kibana'); - await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true }); - await navigateToHostsAndSetDate(); - }); - after(async () => { - await pageObjects.hosts.deleteDataStreams(); - }); - - describe('endpoint.alerts filter', () => { - before(async () => { - await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.alerts'); - await pageObjects.hosts.clickZoomOut(); - await browser.setWindowSize(2100, 1500); - }); - - it('has the correct pill text', async () => { - const expectedData: Map> = new Map([ - [ - 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTc2MzYtMTMyNDc2MTQ0NDIuOTU5MTE2NjAw', - new Set(['1 library']), - ], - [ - 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTMxMTYtMTMyNDcyNDk0MjQuOTg4ODI4NjAw', - new Set(['157 file', '520 registry']), - ], - [ - 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTUwODQtMTMyNDc2MTQ0NDIuOTcyODQ3MjAw', - new Set(), - ], - [ - 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTg2OTYtMTMyNDc2MTQ0MjEuNjc1MzY0OTAw', - new Set(['3 file']), - ], - [ - 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTcyNjAtMTMyNDc2MTQ0MjIuMjQwNDI2MTAw', - new Set(), - ], - [ - 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTczMDAtMTMyNDc2MTQ0MjEuNjg2NzI4NTAw', - new Set(), - ], - ]); - - for (const [id, expectedPills] of expectedData.entries()) { - // center the node in the view - await pageObjects.hosts.clickNodeLinkInPanel(id); - await verifyPills(id, expectedPills); - } - }); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts b/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts deleted file mode 100644 index e7553e68d670b..0000000000000 --- a/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; -import { nudgeAnimationDuration } from '../../../plugins/security_solution/public/resolver/store/camera/scaling_constants'; -import { FtrProviderContext } from '../ftr_provider_context'; -import { - deleteEventsStream, - deleteAlertsStream, - deleteMetadataStream, - deletePolicyStream, - deleteTelemetryStream, -} from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; - -export interface DataStyle { - left: string; - top: string; - width: string; - height: string; -} - -export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const pageObjects = getPageObjects(['common', 'header']); - const testSubjects = getService('testSubjects'); - const queryBar = getService('queryBar'); - const find = getService('find'); - - /** - * Returns the node IDs for the visible nodes in the resolver graph. - */ - const findVisibleNodeIDs = async (): Promise => { - const visibleNodes = await testSubjects.findAll('resolver:node'); - return Promise.all( - visibleNodes.map(async (node: WebElementWrapper) => { - return node.getAttribute('data-test-resolver-node-id'); - }) - ); - }; - - /** - * This assumes you are on the process list in the panel and will find and click the node - * with the given ID to bring it into view in the graph. - * - * @param id the ID of the node to find and click. - */ - const clickNodeLinkInPanel = async (id: string): Promise => { - await navigateToProcessListInPanel(); - const panelNodeButton = await find.byCssSelector( - `[data-test-subj='resolver:node-list:node-link'][data-test-node-id='${id}']` - ); - - await panelNodeButton?.click(); - // ensure that we wait longer than the animation time - await pageObjects.common.sleep(nudgeAnimationDuration * 2); - }; - - /** - * Finds all the pills for a particular node. - * - * @param id the ID of the node - */ - const findNodePills = async (id: string): Promise => { - return testSubjects.findAllDescendant( - 'resolver:map:node-submenu-item', - await find.byCssSelector( - `[data-test-subj='resolver:node'][data-test-resolver-node-id='${id}']` - ) - ); - }; - - /** - * Navigate back to the process list view in the panel. - */ - const navigateToProcessListInPanel = async () => { - const [ - isOnNodeListPage, - isOnCategoryPage, - isOnNodeDetailsPage, - isOnRelatedEventDetailsPage, - ] = await Promise.all([ - testSubjects.exists('resolver:node-list', { timeout: 1 }), - testSubjects.exists('resolver:node-events-in-category:breadcrumbs:node-list-link', { - timeout: 1, - }), - testSubjects.exists('resolver:node-detail:breadcrumbs:node-list-link', { timeout: 1 }), - testSubjects.exists('resolver:event-detail:breadcrumbs:node-list-link', { timeout: 1 }), - ]); - - if (isOnNodeListPage) { - return; - } else if (isOnCategoryPage) { - await ( - await testSubjects.find('resolver:node-events-in-category:breadcrumbs:node-list-link') - ).click(); - } else if (isOnNodeDetailsPage) { - await (await testSubjects.find('resolver:node-detail:breadcrumbs:node-list-link')).click(); - } else if (isOnRelatedEventDetailsPage) { - await (await testSubjects.find('resolver:event-detail:breadcrumbs:node-list-link')).click(); - } else { - // unknown page - return; - } - - await pageObjects.common.sleep(100); - }; - - /** - * Click the zoom out control. - */ - const clickZoomOut = async () => { - await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); - }; - - /** - * Navigate to Events Panel - */ - const navigateToEventsPanel = async () => { - const isFullScreen = await testSubjects.exists('exit-full-screen', { timeout: 400 }); - if (isFullScreen) { - await (await testSubjects.find('exit-full-screen')).click(); - } - - if (!(await testSubjects.exists('investigate-in-resolver-button', { timeout: 400 }))) { - await (await testSubjects.find('navigation-hosts')).click(); - await testSubjects.click('navigation-events'); - await testSubjects.existOrFail('event'); - } - }; - - /** - * @function parseStyles - * Parses a string of inline styles into a typescript object with casing for react - * @param {string} styles - * @returns {Object} - */ - const parseStyle = ( - styles: string - ): { - left?: string; - top?: string; - width?: string; - height?: string; - } => - styles - .split(';') - .filter((style: string) => style.split(':')[0] && style.split(':')[1]) - .map((style: string) => [ - style - .split(':')[0] - .trim() - .replace(/-./g, (c: string) => c.substr(1).toUpperCase()), - style.split(':').slice(1).join(':').trim(), - ]) - .reduce( - (styleObj: {}, style: string[]) => ({ - ...styleObj, - [style[0]]: style[1], - }), - {} - ); - - /** - * Navigate to the Security Hosts page - */ - const navigateToSecurityHostsPage = async () => { - await pageObjects.common.navigateToUrlWithBrowserHistory('security', '/hosts/AllHosts'); - await pageObjects.header.waitUntilLoadingHasFinished(); - }; - - /** - * Finds a table and returns the data in a nested array with row 0 is the headers if they exist. - * It uses euiTableCellContent to avoid polluting the array data with the euiTableRowCell__mobileHeader data. - * @param dataTestSubj - * @param element - * @returns Promise - */ - const getEndpointEventResolverNodeData = async (dataTestSubj: string, element: string) => { - await testSubjects.exists(dataTestSubj); - const Elements = await testSubjects.findAll(dataTestSubj); - const $ = []; - for (const value of Elements) { - $.push(await value.getAttribute(element)); - } - return $; - }; - - /** - * Gets a array of not parsed styles and returns the Array of parsed styles. - * @returns Promise - */ - const parseStyles = async () => { - const tableData = await getEndpointEventResolverNodeData('resolver:node', 'style'); - const styles: DataStyle[] = []; - for (let i = 1; i < tableData.length; i++) { - const eachStyle = parseStyle(tableData[i]); - styles.push({ - top: eachStyle.top ?? '', - height: eachStyle.height ?? '', - left: eachStyle.left ?? '', - width: eachStyle.width ?? '', - }); - } - return styles; - }; - /** - * Deletes DataStreams from Index Management. - */ - const deleteDataStreams = async () => { - await deleteEventsStream(getService); - await deleteAlertsStream(getService); - await deletePolicyStream(getService); - await deleteMetadataStream(getService); - await deleteTelemetryStream(getService); - }; - - /** - * execute Query And Open Resolver - */ - const executeQueryAndOpenResolver = async (query: string) => { - await navigateToEventsPanel(); - await queryBar.setQuery(query); - await queryBar.submitQuery(); - await testSubjects.click('full-screen'); - await testSubjects.click('investigate-in-resolver-button'); - }; - - return { - navigateToProcessListInPanel, - findNodePills, - clickNodeLinkInPanel, - findVisibleNodeIDs, - clickZoomOut, - navigateToEventsPanel, - navigateToSecurityHostsPage, - parseStyles, - deleteDataStreams, - executeQueryAndOpenResolver, - }; -} diff --git a/x-pack/test/security_solution_endpoint/page_objects/index.ts b/x-pack/test/security_solution_endpoint/page_objects/index.ts index 44c4bb21787a4..961e5ae44716d 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/index.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/index.ts @@ -11,7 +11,6 @@ import { EndpointPolicyPageProvider } from './policy_page'; import { TrustedAppsPageProvider } from './trusted_apps_page'; import { EndpointPageUtils } from './page_utils'; import { IngestManagerCreatePackagePolicy } from './ingest_manager_create_package_policy_page'; -import { SecurityHostsPageProvider } from './hosts_page'; import { FleetIntegrations } from './fleet_integrations_page'; export const pageObjects = { @@ -21,6 +20,5 @@ export const pageObjects = { trustedApps: TrustedAppsPageProvider, endpointPageUtils: EndpointPageUtils, ingestManagerCreatePackagePolicy: IngestManagerCreatePackagePolicy, - hosts: SecurityHostsPageProvider, fleetIntegrations: FleetIntegrations, }; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts index 1a643e1f07f7a..e1edeb7808697 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts @@ -20,8 +20,15 @@ export default function (providerContext: FtrProviderContext) { let agentAccessAPIKey: string; describe('artifact download', () => { + const esArchiverSnapshots = [ + 'endpoint/artifacts/fleet_artifacts', + 'endpoint/artifacts/api_feature', + ]; + before(async () => { - await esArchiver.load('endpoint/artifacts/api_feature', { useCreate: true }); + await Promise.all( + esArchiverSnapshots.map((archivePath) => esArchiver.load(archivePath, { useCreate: true })) + ); const { body: enrollmentApiKeysResponse } = await supertest .get(`/api/fleet/enrollment-api-keys`) @@ -56,7 +63,9 @@ export default function (providerContext: FtrProviderContext) { agentAccessAPIKey = enrollmentResponse.item.access_api_key; }); - after(() => esArchiver.unload('endpoint/artifacts/api_feature')); + after(() => + Promise.all(esArchiverSnapshots.map((archivePath) => esArchiver.unload(archivePath))) + ); it('should fail to find artifact with invalid hash', async () => { await supertestWithoutAuth diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 2f137a305b230..8ac8bdad6dfd0 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -43,7 +43,6 @@ { "path": "../plugins/banners/tsconfig.json" }, { "path": "../plugins/beats_management/tsconfig.json" }, { "path": "../plugins/cloud/tsconfig.json" }, - { "path": "../plugins/code/tsconfig.json" }, { "path": "../plugins/console_extensions/tsconfig.json" }, { "path": "../plugins/dashboard_mode/tsconfig.json" }, { "path": "../plugins/enterprise_search/tsconfig.json" }, diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index b8014572d1bfb..aaf014ea6165c 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -4,9 +4,9 @@ "mocks.ts", "typings/**/*", "tasks/**/*", - "plugins/case/**/*", + "plugins/cases/**/*", "plugins/lists/**/*", - "plugins/security_solution/**/*", + "plugins/security_solution/**/*" ], "exclude": [ "test/**/*", @@ -61,7 +61,6 @@ { "path": "./plugins/beats_management/tsconfig.json" }, { "path": "./plugins/canvas/tsconfig.json" }, { "path": "./plugins/cloud/tsconfig.json" }, - { "path": "./plugins/code/tsconfig.json" }, { "path": "./plugins/console_extensions/tsconfig.json" }, { "path": "./plugins/data_enhanced/tsconfig.json" }, { "path": "./plugins/dashboard_mode/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 753b79d8a9122..d50e94210707e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2016,16 +2016,6 @@ dependencies: "@hapi/hoek" "9.x.x" -"@hapi/vision@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@hapi/vision/-/vision-6.0.1.tgz#976c3575be56d3cb5b472ddcfe0b7403778706fd" - integrity sha512-xv4PwmhbXCLzDfojZ7l4+P/YynBhMInV8GtLPH4gB74prhwOl8lGcJxxK8V9rf1aMH/vonM5yVGd9FuoA9sT0A== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bounce" "2.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - "@hapi/wreck@17.x.x", "@hapi/wreck@^17.0.0", "@hapi/wreck@^17.1.0": version "17.1.0" resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" @@ -14213,7 +14203,7 @@ gaze@^1.0.0: dependencies: globule "^1.0.0" -geckodriver@^1.21.2: +geckodriver@^1.22.2: version "1.22.2" resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-1.22.2.tgz#e0904bed50a1d2abaa24597d4ae43eb6662f9d72" integrity sha512-xcf1OLfHqNX4+wQhj4weu2gtiwtPnV8yEEKvLkC8GuFtUc5WjOGodV/2pHiYJjCSJRQfsmIgY5Xs1zaJf/OGFA== @@ -14436,9 +14426,9 @@ glob-parent@^3.1.0: path-dirname "^1.0.0" glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -18212,10 +18202,10 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== -kea@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/kea/-/kea-2.2.0.tgz#1ba4a174a53880cca8002a67cf62b19b30d09702" - integrity sha512-IzgTC6SC89wTLfiBMPlduG4r4YanxONYK4werz8RMZxPvcYw4XEEK8xQJguwVYtLCEGm4x5YiLCubGqGfRcbEw== +kea@^2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/kea/-/kea-2.3.3.tgz#8fbd6d0c4ba5079c5abe46486bbc7dc1fd071a62" + integrity sha512-NZQHisfEvlg+e6BsHckW03IYaIBY+fuK4xiov7ShZ0GudUmNLhqgHSxUsykU/wdrCPEI6ANX1gyDIRTnUd3HyA== kew@~0.1.7: version "0.1.7" @@ -22941,9 +22931,9 @@ react-datetime@^2.14.0: react-onclickoutside "^6.5.0" react-dev-utils@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.3.tgz#b61ed499c7d74f447d4faddcc547e5e671e97c08" - integrity sha512-4lEA5gF4OHrcJLMUV1t+4XbNDiJbsAWCH5Z2uqlTqW6dD7Cf5nEASkeXrCI/Mz83sI2o527oBIFKVMXtRf1Vtg== + version "11.0.4" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a" + integrity sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A== dependencies: "@babel/code-frame" "7.10.4" address "1.1.2"