diff --git a/package.json b/package.json index 7d4d96e8407529..6f5994a8679d68 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "@hapi/boom": "^9.1.1", "@hapi/cookie": "^11.0.2", "@hapi/good-squeeze": "6.0.0", - "@hapi/h2o2": "^9.0.2", + "@hapi/h2o2": "^9.1.0", "@hapi/hapi": "^20.0.3", "@hapi/hoek": "^9.1.1", "@hapi/inert": "^6.0.3", @@ -198,7 +198,7 @@ "broadcast-channel": "^3.0.3", "chalk": "^4.1.0", "check-disk-space": "^2.1.0", - "cheerio": "0.22.0", + "cheerio": "^1.0.0-rc.9", "chokidar": "^3.4.3", "chroma-js": "^1.4.1", "classnames": "2.2.6", @@ -501,7 +501,6 @@ "@types/base64-js": "^1.2.5", "@types/bluebird": "^3.1.1", "@types/chance": "^1.0.0", - "@types/cheerio": "^0.22.28", "@types/chroma-js": "^1.4.2", "@types/chromedriver": "^81.0.0", "@types/classnames": "^2.2.9", diff --git a/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts index a0afbe3a9b8c90..34c6be02847a7a 100644 --- a/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts +++ b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts @@ -185,6 +185,45 @@ describe('BasePathProxyServer', () => { }); }); + test('forwards request cancellation', async () => { + let propagated = false; + + let notifyRequestReceived: () => void; + const requestReceived = new Promise((resolve) => { + notifyRequestReceived = resolve; + }); + + let notifyRequestAborted: () => void; + const requestAborted = new Promise((resolve) => { + notifyRequestAborted = resolve; + }); + + server.route({ + method: 'GET', + path: `${basePath}/foo/{test}`, + handler: async (request, h) => { + notifyRequestReceived(); + + request.raw.req.once('aborted', () => { + notifyRequestAborted(); + propagated = true; + }); + return await new Promise((resolve) => undefined); + }, + }); + await server.start(); + + const request = proxySupertest.get(`${basePath}/foo/some-string`).end(); + + await requestReceived; + + request.abort(); + + await requestAborted; + + expect(propagated).toEqual(true); + }); + test('handles putting', async () => { server.route({ method: 'PUT', diff --git a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json index 7d8a90c3aad77a..0c3f65bc1b205d 100644 --- a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json +++ b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json @@ -598,7 +598,7 @@ "section": "def-public.ImAType", "text": "ImAType" }, - ", e?: string | undefined) => ", + ", e: string | undefined) => ", { "pluginId": "pluginA", "scope": "public", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 5b064e492beb52..e455f487d13843 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -650,6 +650,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__read", function() { return __read; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return __spread; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spreadArrays", function() { return __spreadArrays; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spreadArray", function() { return __spreadArray; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__await", function() { return __await; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncGenerator", function() { return __asyncGenerator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncDelegator", function() { return __asyncDelegator; }); @@ -683,6 +684,8 @@ var extendStatics = function(d, b) { }; function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); @@ -805,19 +808,27 @@ function __read(o, n) { return ar; } +/** @deprecated */ function __spread() { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; } +/** @deprecated */ function __spreadArrays() { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; -}; +} + +function __spreadArray(to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +} function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); @@ -872,19 +883,17 @@ function __importDefault(mod) { return (mod && mod.__esModule) ? mod : { default: mod }; } -function __classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver); +function __classPrivateFieldGet(receiver, state, kind, f) { + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); + return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); } -function __classPrivateFieldSet(receiver, privateMap, value) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to set private field on non-instance"); - } - privateMap.set(receiver, value); - return value; +function __classPrivateFieldSet(receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; } diff --git a/src/core/public/styles/_base.scss b/src/core/public/styles/_base.scss index 46f46b469783b8..3386fa73f328aa 100644 --- a/src/core/public/styles/_base.scss +++ b/src/core/public/styles/_base.scss @@ -28,3 +28,9 @@ .euiBody--collapsibleNavIsDocked .euiBottomBar { margin-left: $euiCollapsibleNavWidth; } + +// Temporary fix for EuiPageHeader with a bottom border but no tabs or padding +// Will fix in EUI -- @cchaos +.euiPageHeader--bottomBorder:not(.euiPageHeader--tabsAtBottom):not([class*='euiPageHeader--padding']) { + padding-bottom: $euiSizeL; +} diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index c7a8c0a6135c7a..1391312df5231a 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -269,6 +269,7 @@ export class AdvancedSettings extends Component
- - - - + + - +

General

-
- - - - - - - , - "settingsCount": -1, - } + + + + + + + + + , + "settingsCount": -1, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

Dashboard

-
-
-
- + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

X-pack

-
- - - - - - - , - "settingsCount": 9, - } + + + + + + + + + , + "settingsCount": 9, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + @@ -269,52 +272,53 @@ exports[`Form should not render no settings message when instructed not to 1`] = exports[`Form should render no settings message when there are no settings 1`] = `
- - - - + + - +

General

-
- - - - - - - , - "settingsCount": -1, - } + + + + + + + + + , + "settingsCount": -1, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

Dashboard

-
-
-
- + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

X-pack

-
- - - - - - - , - "settingsCount": 9, - } + + + + + + + + + , + "settingsCount": 9, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + @@ -535,52 +541,53 @@ exports[`Form should render no settings message when there are no settings 1`] = exports[`Form should render normally 1`] = `
- - - - + + - +

General

-
- - - - - - - , - "settingsCount": -1, - } + + + + + + + + + , + "settingsCount": -1, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

Dashboard

-
-
-
- + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

X-pack

-
- - - - - - - , - "settingsCount": 9, - } + + + + + + + + + , + "settingsCount": 9, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + @@ -801,52 +810,53 @@ exports[`Form should render normally 1`] = ` exports[`Form should render read-only when saving is disabled 1`] = `
- - - - + + - +

General

-
- - - - - - - , - "settingsCount": -1, - } + + + + + + + + + , + "settingsCount": -1, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

Dashboard

-
-
-
- + + + + + <_EuiSplitPanelInner> -
-
+ + - - - - + + - +

X-pack

-
- - - - - - - , - "settingsCount": 9, - } + + + + + + + + + , + "settingsCount": 9, } - /> - - -
-
- + } + /> + + + + + <_EuiSplitPanelInner> -
-
+ + diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx index d953bb8f6a6f61..0b08a317e87c9f 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx @@ -11,16 +11,16 @@ import React, { PureComponent, Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiForm, + EuiSplitPanel, EuiLink, - EuiPanel, + EuiCallOut, EuiSpacer, - EuiText, EuiTextColor, EuiBottomBar, EuiButton, EuiToolTip, EuiButtonEmpty, + EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { isEmpty } from 'lodash'; @@ -47,6 +47,7 @@ interface FormProps { dockLinks: DocLinksStart['links']; toasts: ToastsStart; trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; + queryText?: string; } interface FormState { @@ -241,17 +242,18 @@ export class Form extends PureComponent { renderCategory(category: Category, settings: FieldSetting[], totalSettings: number) { return ( - - - - - + + + + +

{getCategoryName(category)}

-
- {this.renderClearQueryLink(totalSettings, settings.length)} -
-
- + + + {this.renderClearQueryLink(totalSettings, settings.length)} + + + {settings.map((setting) => { return ( { /> ); })} -
-
+ +
); @@ -276,22 +278,28 @@ export class Form extends PureComponent { maybeRenderNoSettings(clearQuery: FormProps['clearQuery']) { if (this.props.showNoResultsMessage) { return ( - - - - - ), - }} - /> - + + + + + ), + queryText: {this.props.queryText}, + }} + /> + + } + /> ); } return null; diff --git a/src/plugins/management/public/components/landing/landing.tsx b/src/plugins/management/public/components/landing/landing.tsx index 5e361cfaff87d2..4683185a5a7831 100644 --- a/src/plugins/management/public/components/landing/landing.tsx +++ b/src/plugins/management/public/components/landing/landing.tsx @@ -8,15 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; - -import { - EuiHorizontalRule, - EuiIcon, - EuiPageContent, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; +import { EuiEmptyPrompt, EuiHorizontalRule, EuiPageContent } from '@elastic/eui'; interface ManagementLandingPageProps { version: string; @@ -27,39 +19,37 @@ export const ManagementLandingPage = ({ version, setBreadcrumbs }: ManagementLan setBreadcrumbs(); return ( - -
-
- - - -

- -

-
- + + - -
- - - - -

- -

-
-
+ + } + body={ + <> +

+ +

+ +

+ +

+ + } + />
); }; diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx index 350e2f3de0cdf6..23d0a290837475 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -5,20 +5,23 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import './management_app.scss'; import React, { useState, useEffect, useCallback } from 'react'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from 'kibana/public'; import { I18nProvider } from '@kbn/i18n/react'; -import { EuiPage } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ManagementSection, MANAGEMENT_BREADCRUMB } from '../../utils'; import { ManagementRouter } from './management_router'; -import { ManagementSidebarNav } from '../management_sidebar_nav'; -import { reactRouterNavigate } from '../../../../kibana_react/public'; +import { managementSidebarNav } from '../management_sidebar_nav/management_sidebar_nav'; +import { + KibanaPageTemplate, + KibanaPageTemplateProps, + reactRouterNavigate, +} from '../../../../kibana_react/public'; import { SectionsServiceStart } from '../../types'; -import './management_app.scss'; - interface ManagementAppProps { appBasePath: string; history: AppMountParameters['history']; @@ -64,10 +67,30 @@ export const ManagementApp = ({ dependencies, history }: ManagementAppProps) => return null; } + const solution: KibanaPageTemplateProps['solutionNav'] = { + name: i18n.translate('management.nav.label', { + defaultMessage: 'Management', + }), + icon: 'managementApp', + 'data-test-subj': 'mgtSideBarNav', + items: managementSidebarNav({ + selectedId, + sections, + history, + }), + }; + return ( - - + sections={sections} dependencies={dependencies} /> - + ); }; diff --git a/src/plugins/management/public/components/management_app/management_router.tsx b/src/plugins/management/public/components/management_app/management_router.tsx index b3f978d5c6cd2e..5ec1253ba77efd 100644 --- a/src/plugins/management/public/components/management_app/management_router.tsx +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -8,7 +8,6 @@ import React, { memo } from 'react'; import { Route, Router, Switch } from 'react-router-dom'; -import { EuiPageBody } from '@elastic/eui'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from 'kibana/public'; import { ManagementAppWrapper } from '../management_app_wrapper'; import { ManagementLandingPage } from '../landing'; @@ -26,36 +25,34 @@ interface ManagementRouterProps { export const ManagementRouter = memo( ({ dependencies, history, setBreadcrumbs, onAppMounted, sections }: ManagementRouterProps) => ( - - - {sections.map((section) => - section - .getAppsEnabled() - .map((app) => ( - ( - - )} - /> - )) - )} - ( - + {sections.map((section) => + section + .getAppsEnabled() + .map((app) => ( + ( + + )} /> - )} - /> - - + )) + )} + ( + + )} + /> + ) ); diff --git a/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx index 8e0043937303f2..72bfe609c141a5 100644 --- a/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx +++ b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx @@ -9,8 +9,10 @@ import React, { createRef, Component } from 'react'; import { ChromeBreadcrumb, AppMountParameters, ScopedHistory } from 'kibana/public'; +import classNames from 'classnames'; import { ManagementApp } from '../../utils'; import { Unmount } from '../../types'; +import { APP_WRAPPER_CLASS } from '../../../../../../src/core/public'; interface ManagementSectionWrapperProps { app: ManagementApp; @@ -53,6 +55,12 @@ export class ManagementAppWrapper extends Component; + return ( +
+ ); } } diff --git a/src/plugins/management/public/components/management_sidebar_nav/index.ts b/src/plugins/management/public/components/management_sidebar_nav/index.ts index 5bb80adf3e8bac..9cee992a179480 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/index.ts +++ b/src/plugins/management/public/components/management_sidebar_nav/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { ManagementSidebarNav } from './management_sidebar_nav'; +export { managementSidebarNav } from './management_sidebar_nav'; diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx index 1b40bfcca3c63f..bf1b81061f6a98 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -6,24 +6,13 @@ * Side Public License, v 1. */ -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; +import React from 'react'; import { sortBy } from 'lodash'; -import { - EuiIcon, - EuiSideNav, - EuiScreenReaderOnly, - EuiSideNavItemType, - EuiFlexGroup, - EuiFlexItem, - EuiToolTip, -} from '@elastic/eui'; +import { EuiIcon, EuiSideNavItemType, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { AppMountParameters } from 'kibana/public'; import { ManagementApp, ManagementSection } from '../../utils'; -import './management_sidebar_nav.scss'; - import { ManagementItem } from '../../utils/management_item'; import { reactRouterNavigate } from '../../../../kibana_react/public'; @@ -33,24 +22,12 @@ interface ManagementSidebarNavProps { selectedId: string; } -const headerLabel = i18n.translate('management.nav.label', { - defaultMessage: 'Management', -}); - -const navMenuLabel = i18n.translate('management.nav.menu', { - defaultMessage: 'Management menu', -}); - /** @internal **/ -export const ManagementSidebarNav = ({ +export const managementSidebarNav = ({ selectedId, sections, history, }: ManagementSidebarNavProps) => { - const HEADER_ID = 'stack-management-nav-header'; - const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false); - const toggleOpenOnMobile = () => setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile); - const sectionsToNavItems = (managementSections: ManagementSection[]) => { const sortedManagementSections = sortBy(managementSections, 'order'); @@ -83,11 +60,11 @@ export const ManagementSidebarNav = ({ const TooltipWrapper = ({ text, tip }: TooltipWrapperProps) => ( - + {text} - + @@ -109,19 +86,5 @@ export const ManagementSidebarNav = ({ }; }; - return ( - <> - -

{headerLabel}

-
- - - ); + return sectionsToNavItems(sections); }; diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index 3b043b8aab895e..710e209537b8e3 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -8,7 +8,14 @@ "declarationMap": true, "isolatedModules": true }, - "include": ["public/**/**/*", "server/**/**/*", "common/**/*", "../../../typings/**/*"], + "include": [ + "public/**/**/*", + "server/**/**/*", + "common/**/*", + "../../../typings/**/*", + "schema/oss_plugins.json", + "schema/oss_root.json", + ], "references": [ { "path": "../../core/tsconfig.json" }, { "path": "../../plugins/kibana_react/tsconfig.json" }, diff --git a/test/api_integration/fixtures/es_archiver/management/saved_objects/scroll_count/mappings.json b/test/api_integration/fixtures/es_archiver/management/saved_objects/scroll_count/mappings.json index 8270c573e4c1e7..d2177481130a2c 100644 --- a/test/api_integration/fixtures/es_archiver/management/saved_objects/scroll_count/mappings.json +++ b/test/api_integration/fixtures/es_archiver/management/saved_objects/scroll_count/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/api_integration/fixtures/es_archiver/management/saved_objects/search/mappings.json b/test/api_integration/fixtures/es_archiver/management/saved_objects/search/mappings.json index c670508247b1a2..7a6d752eafb08e 100644 --- a/test/api_integration/fixtures/es_archiver/management/saved_objects/search/mappings.json +++ b/test/api_integration/fixtures/es_archiver/management/saved_objects/search/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/ui_counters/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/ui_counters/mappings.json index 39902f8a9211a6..99f2f999db9881 100644 --- a/test/api_integration/fixtures/es_archiver/saved_objects/ui_counters/mappings.json +++ b/test/api_integration/fixtures/es_archiver/saved_objects/ui_counters/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/usage_counters/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/usage_counters/mappings.json index 14ed147b2da8e7..c2ec5c88810876 100644 --- a/test/api_integration/fixtures/es_archiver/saved_objects/usage_counters/mappings.json +++ b/test/api_integration/fixtures/es_archiver/saved_objects/usage_counters/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/api_integration/fixtures/es_archiver/search/count/mappings.json b/test/api_integration/fixtures/es_archiver/search/count/mappings.json index b62c5da05c2e6b..41d5c07e932394 100644 --- a/test/api_integration/fixtures/es_archiver/search/count/mappings.json +++ b/test/api_integration/fixtures/es_archiver/search/count/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/common/services/kibana_server/extend_es_archiver.js b/test/common/services/kibana_server/extend_es_archiver.ts similarity index 70% rename from test/common/services/kibana_server/extend_es_archiver.js rename to test/common/services/kibana_server/extend_es_archiver.ts index 9a06dd7b749699..98c28960bf5230 100644 --- a/test/common/services/kibana_server/extend_es_archiver.js +++ b/test/common/services/kibana_server/extend_es_archiver.ts @@ -6,10 +6,23 @@ * Side Public License, v 1. */ -const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload', 'emptyKibanaIndex']; +import type { ProvidedType } from '@kbn/test'; + +import type { EsArchiverProvider } from '../es_archiver'; +import type { RetryService } from '../retry'; +import type { KibanaServerProvider } from './kibana_server'; + +const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload', 'emptyKibanaIndex'] as const; const KIBANA_INDEX = '.kibana'; -export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) { +interface Options { + esArchiver: ProvidedType; + kibanaServer: ProvidedType; + retry: RetryService; + defaults: Record; +} + +export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }: Options) { // only extend the esArchiver if there are default uiSettings to restore if (!defaults) { return; @@ -18,9 +31,9 @@ export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) ES_ARCHIVER_LOAD_METHODS.forEach((method) => { const originalMethod = esArchiver[method]; - esArchiver[method] = async (...args) => { + esArchiver[method] = async (...args: unknown[]) => { // esArchiver methods return a stats object, with information about the indexes created - const stats = await originalMethod.apply(esArchiver, args); + const stats = await originalMethod.apply(esArchiver, args as any); const statsKeys = Object.keys(stats); const kibanaKeys = statsKeys.filter( diff --git a/test/common/services/kibana_server/index.ts b/test/common/services/kibana_server/index.ts index 7f727179032f2c..2a749de2b4ce86 100644 --- a/test/common/services/kibana_server/index.ts +++ b/test/common/services/kibana_server/index.ts @@ -7,5 +7,4 @@ */ export { KibanaServerProvider } from './kibana_server'; -// @ts-ignore export { extendEsArchiver } from './extend_es_archiver'; diff --git a/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json b/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json index a89fe1dfacfc83..45b2508d380330 100644 --- a/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json index 5f7c7e0e7b7dca..41cddecef0c419 100644 --- a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json +++ b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/discover/mappings.json b/test/functional/fixtures/es_archiver/discover/mappings.json index 53bbe8a5baa5bd..519af2dd75b9e1 100644 --- a/test/functional/fixtures/es_archiver/discover/mappings.json +++ b/test/functional/fixtures/es_archiver/discover/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/empty_kibana/mappings.json b/test/functional/fixtures/es_archiver/empty_kibana/mappings.json index 403a891ba11757..264096beb11ee5 100644 --- a/test/functional/fixtures/es_archiver/empty_kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/empty_kibana/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json b/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json index 0024c6943ed1c0..63cc283f96d328 100644 --- a/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json +++ b/test/functional/fixtures/es_archiver/invalid_scripted_field/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/test/functional/fixtures/es_archiver/management/mappings.json b/test/functional/fixtures/es_archiver/management/mappings.json index a89fe1dfacfc83..45b2508d380330 100644 --- a/test/functional/fixtures/es_archiver/management/mappings.json +++ b/test/functional/fixtures/es_archiver/management/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/mgmt/mappings.json b/test/functional/fixtures/es_archiver/mgmt/mappings.json index 28198102d3d686..aefbd9d0ccc8a8 100644 --- a/test/functional/fixtures/es_archiver/mgmt/mappings.json +++ b/test/functional/fixtures/es_archiver/mgmt/mappings.json @@ -2,8 +2,9 @@ "type": "index", "value": { "aliases": { + ".kibana": {} }, - "index": ".kibana", + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json index a89fe1dfacfc83..45b2508d380330 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json index 6a416126d7f263..05ca4d8e8307e5 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json index 43b851e817fa81..653e6399548135 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json index 1de768d290d35d..a158deb527cc83 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json index a9abae009cb59b..61763f55c1b6a6 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_types/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json index 43b851e817fa81..653e6399548135 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json index a5478a5805d501..aba581867bb8a9 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/show_relationships/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/test/functional/fixtures/es_archiver/timelion/mappings.json b/test/functional/fixtures/es_archiver/timelion/mappings.json index a89fe1dfacfc83..45b2508d380330 100644 --- a/test/functional/fixtures/es_archiver/timelion/mappings.json +++ b/test/functional/fixtures/es_archiver/timelion/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/visualize/mappings.json b/test/functional/fixtures/es_archiver/visualize/mappings.json index 464f6751eac5ca..59ec24853e2270 100644 --- a/test/functional/fixtures/es_archiver/visualize/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json b/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json index a89fe1dfacfc83..45b2508d380330 100644 --- a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json index a89fe1dfacfc83..45b2508d380330 100644 --- a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json index 0f17621dbf5291..5ac113e7e4b743 100644 --- a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index b1561b29342dad..148c21ffac191b 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -9,7 +9,6 @@ import { delay } from 'bluebird'; import { WebElement, WebDriver, By, Key } from 'selenium-webdriver'; import { PNG } from 'pngjs'; -// @ts-ignore not supported yet import cheerio from 'cheerio'; import testSubjSelector from '@kbn/test-subj-selector'; import { ToolingLog } from '@kbn/dev-utils'; diff --git a/test/functional/services/management/management_menu.ts b/test/functional/services/management/management_menu.ts index 738a8d55439ece..2b93fce4daa51e 100644 --- a/test/functional/services/management/management_menu.ts +++ b/test/functional/services/management/management_menu.ts @@ -13,7 +13,7 @@ export class ManagementMenuService extends FtrService { public async getSections() { const sectionsElements = await this.find.allByCssSelector( - '.mgtSideBarNav > .euiSideNav__content > .euiSideNavItem' + '.kbnPageTemplateSolutionNav .euiSideNavItem--root' ); const sections = []; diff --git a/test/tsconfig.json b/test/tsconfig.json index 2524755d3f291c..3e022839460803 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -11,7 +11,11 @@ "include": [ "**/*", "../typings/**/*", - "../packages/kbn-test/types/ftr_globals/**/*" + "../packages/kbn-test/types/ftr_globals/**/*", + "api_integration/apis/logstash/pipeline/fixtures/*.json", + "api_integration/apis/logstash/pipelines/fixtures/*.json", + "api_integration/apis/telemetry/fixtures/*.json", + "api_integration/apis/telemetry/fixtures/*.json", ], "exclude": ["target/**/*", "plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], "references": [ diff --git a/tsconfig.refs.json b/tsconfig.refs.json index a2c1ee43a92c46..3baf5c323ef81e 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -119,5 +119,6 @@ { "path": "./x-pack/plugins/index_lifecycle_management/tsconfig.json" }, { "path": "./x-pack/plugins/uptime/tsconfig.json" }, { "path": "./x-pack/plugins/xpack_legacy/tsconfig.json" }, + { "path": "./x-pack/test/tsconfig.json" }, ] } diff --git a/x-pack/examples/alerting_example/public/components/documentation.tsx b/x-pack/examples/alerting_example/public/components/documentation.tsx index 2cef1746755332..7dd5af38288cc3 100644 --- a/x-pack/examples/alerting_example/public/components/documentation.tsx +++ b/x-pack/examples/alerting_example/public/components/documentation.tsx @@ -17,6 +17,7 @@ import { EuiPageHeader, EuiPageHeaderSection, EuiTitle, + EuiCallOut, EuiSpacer, } from '@elastic/eui'; import { CreateAlert } from './create_alert'; @@ -49,6 +50,13 @@ export const DocumentationPage = ( registration of example the RuleTypes, while the `public` handles creation of, and navigation for, these rule types.

+ + If you see a message about needing to enable the Transport Layer Security, start ES with{' '} + yarn es snapshot --ssl --license trial and Kibana with{' '} + yarn start --run-examples --ssl. If you running chrome on a mac, you may + need to type in thisisunsafe if you see the Certificate invalid screen with + no way to ‘proceed anyway’. + diff --git a/x-pack/plugins/cases/common/api/cases/configure.ts b/x-pack/plugins/cases/common/api/cases/configure.ts index 2814dd44f513ff..6c92702c523b4b 100644 --- a/x-pack/plugins/cases/common/api/cases/configure.ts +++ b/x-pack/plugins/cases/common/api/cases/configure.ts @@ -9,13 +9,11 @@ import * as rt from 'io-ts'; import { UserRT } from '../user'; import { CaseConnectorRt, ConnectorMappingsRt, ESCaseConnector } from '../connectors'; -import { OmitProp } from '../runtime_types'; -import { OWNER_FIELD } from './constants'; // TODO: we will need to add this type rt.literal('close-by-third-party') const ClosureTypeRT = rt.union([rt.literal('close-by-user'), rt.literal('close-by-pushing')]); -const CasesConfigureBasicRt = rt.type({ +const CasesConfigureBasicWithoutOwnerRt = rt.type({ /** * The external connector */ @@ -24,15 +22,17 @@ const CasesConfigureBasicRt = rt.type({ * Whether to close the case after it has been synced with the external system */ closure_type: ClosureTypeRT, - /** - * The plugin owner that manages this configuration - */ - owner: rt.string, }); -const CasesConfigureBasicWithoutOwnerRt = rt.type( - OmitProp(CasesConfigureBasicRt.props, OWNER_FIELD) -); +const CasesConfigureBasicRt = rt.intersection([ + CasesConfigureBasicWithoutOwnerRt, + rt.type({ + /** + * The plugin owner that manages this configuration + */ + owner: rt.string, + }), +]); export const CasesConfigureRequestRt = CasesConfigureBasicRt; export const CasesConfigurePatchRt = rt.intersection([ diff --git a/x-pack/plugins/cases/common/api/runtime_types.ts b/x-pack/plugins/cases/common/api/runtime_types.ts index 361786985c6de3..8817764e261d92 100644 --- a/x-pack/plugins/cases/common/api/runtime_types.ts +++ b/x-pack/plugins/cases/common/api/runtime_types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { omit } from 'lodash'; import { either, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -14,9 +13,6 @@ import { isObject } from 'lodash/fp'; type ErrorFactory = (message: string) => Error; -export const OmitProp = (o: O, k: K): Omit => - omit(o, k); - /** * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts * Bug fix for the TODO is in the format_errors package diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index 72c21aa12dcf2b..f0d3e8ccbcdea2 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -92,3 +92,6 @@ export const ENABLE_CASE_CONNECTOR = false; if (ENABLE_CASE_CONNECTOR) { SAVED_OBJECT_TYPES.push(SUB_CASE_SAVED_OBJECT); } + +export const MAX_DOCS_PER_PAGE = 10000; +export const MAX_CONCURRENT_SEARCHES = 10; diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index d935a0c8f09db0..89e12d7f7ea398 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -6,9 +6,15 @@ */ import Boom from '@hapi/boom'; -import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../common/constants'; - -import { AssociationType } from '../../../common/api'; +import pMap from 'p-map'; + +import { SavedObject } from 'kibana/public'; +import { + CASE_SAVED_OBJECT, + MAX_CONCURRENT_SEARCHES, + SUB_CASE_SAVED_OBJECT, +} from '../../../common/constants'; +import { AssociationType, CommentAttributes } from '../../../common/api'; import { CasesClientArgs } from '../types'; import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; import { createCaseError } from '../../common/error'; @@ -88,14 +94,16 @@ export async function deleteAll( })), }); - await Promise.all( - comments.saved_objects.map((comment) => - attachmentService.delete({ - unsecuredSavedObjectsClient, - attachmentId: comment.id, - }) - ) - ); + const mapper = async (comment: SavedObject) => + attachmentService.delete({ + unsecuredSavedObjectsClient, + attachmentId: comment.id, + }); + + // Ensuring we don't too many concurrent deletions running. + await pMap(comments.saved_objects, mapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); const deleteDate = new Date().toISOString(); diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index b66abc6cc7be4b..8e99e39ec473be 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -5,15 +5,16 @@ * 2.0. */ +import pMap from 'p-map'; import { Boom } from '@hapi/boom'; -import { SavedObjectsClientContract } from 'kibana/server'; -import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; +import { SavedObject, SavedObjectsClientContract, SavedObjectsFindResponse } from 'kibana/server'; +import { ENABLE_CASE_CONNECTOR, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { CasesClientArgs } from '..'; import { createCaseError } from '../../common/error'; import { AttachmentService, CasesService } from '../../services'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { Operations, OwnerEntity } from '../../authorization'; -import { OWNER_FIELD } from '../../../common/api'; +import { OWNER_FIELD, SubCaseAttributes, CommentAttributes } from '../../../common/api'; async function deleteSubCases({ attachmentService, @@ -37,19 +38,24 @@ async function deleteSubCases({ id: subCaseIDs, }); - // This shouldn't actually delete anything because all the comments should be deleted when comments are deleted - // per case ID - await Promise.all( - commentsForSubCases.saved_objects.map((commentSO) => - attachmentService.delete({ unsecuredSavedObjectsClient, attachmentId: commentSO.id }) - ) - ); - - await Promise.all( - subCasesForCaseIds.saved_objects.map((subCaseSO) => - caseService.deleteSubCase(unsecuredSavedObjectsClient, subCaseSO.id) - ) - ); + const commentMapper = (commentSO: SavedObject) => + attachmentService.delete({ unsecuredSavedObjectsClient, attachmentId: commentSO.id }); + + const subCasesMapper = (subCaseSO: SavedObject) => + caseService.deleteSubCase(unsecuredSavedObjectsClient, subCaseSO.id); + + /** + * This shouldn't actually delete anything because + * all the comments should be deleted when comments are deleted + * per case ID. We also ensure that we don't too many concurrent deletions running. + */ + await pMap(commentsForSubCases.saved_objects, commentMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); + + await pMap(subCasesForCaseIds.saved_objects, subCasesMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); } /** @@ -88,38 +94,46 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P entities: Array.from(entities.values()), }); - await Promise.all( - ids.map((id) => - caseService.deleteCase({ - unsecuredSavedObjectsClient, - id, - }) - ) - ); + const deleteCasesMapper = async (id: string) => + caseService.deleteCase({ + unsecuredSavedObjectsClient, + id, + }); - const comments = await Promise.all( - ids.map((id) => - caseService.getAllCaseComments({ + // Ensuring we don't too many concurrent deletions running. + await pMap(ids, deleteCasesMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); + + const getCommentsMapper = async (id: string) => + caseService.getAllCaseComments({ + unsecuredSavedObjectsClient, + id, + }); + + // Ensuring we don't too many concurrent get running. + const comments = await pMap(ids, getCommentsMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); + + /** + * This is a nested pMap.Mapper. + * Each element of the comments array contains all comments of a particular case. + * For that reason we need first to create a map that iterate over all cases + * and return a pMap that deletes the comments for that case + */ + const deleteCommentsMapper = async (commentRes: SavedObjectsFindResponse) => + pMap(commentRes.saved_objects, (comment) => + attachmentService.delete({ unsecuredSavedObjectsClient, - id, + attachmentId: comment.id, }) - ) - ); - - if (comments.some((c) => c.saved_objects.length > 0)) { - await Promise.all( - comments.map((c) => - Promise.all( - c.saved_objects.map(({ id }) => - attachmentService.delete({ - unsecuredSavedObjectsClient, - attachmentId: id, - }) - ) - ) - ) ); - } + + // Ensuring we don't too many concurrent deletions running. + await pMap(comments, deleteCommentsMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); if (ENABLE_CASE_CONNECTOR) { await deleteSubCases({ diff --git a/x-pack/plugins/cases/server/client/cases/find.ts b/x-pack/plugins/cases/server/client/cases/find.ts index 3b4efe78f642bb..73eca5e7abb934 100644 --- a/x-pack/plugins/cases/server/client/cases/find.ts +++ b/x-pack/plugins/cases/server/client/cases/find.ts @@ -77,6 +77,7 @@ export const find = async ( ensureSavedObjectsAreAuthorized([...cases.casesMap.values()]); + // casesStatuses are bounded by us. No need to limit concurrent calls. const [openCases, inProgressCases, closedCases] = await Promise.all([ ...caseStatuses.map((status) => { const statusQuery = constructQueryOptions({ ...queryArgs, status, authorizationFilter }); diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index db20ba83184470..608c726f185313 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -5,6 +5,7 @@ * 2.0. */ +import pMap from 'p-map'; import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; @@ -42,6 +43,7 @@ import { CasesService } from '../../services'; import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, + MAX_CONCURRENT_SEARCHES, SUB_CASE_SAVED_OBJECT, } from '../../../common/constants'; import { @@ -162,9 +164,11 @@ async function throwIfInvalidUpdateOfTypeWithAlerts({ }; const requestsUpdatingTypeField = requests.filter((req) => req.type === CaseType.collection); - const casesAlertTotals = await Promise.all( - requestsUpdatingTypeField.map((caseToUpdate) => getAlertsForID(caseToUpdate)) - ); + const getAlertsMapper = async (caseToUpdate: ESCasePatchRequest) => getAlertsForID(caseToUpdate); + // Ensuring we don't too many concurrent get running. + const casesAlertTotals = await pMap(requestsUpdatingTypeField, getAlertsMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); // grab the cases that have at least one alert comment attached to them const typeUpdateWithAlerts = casesAlertTotals.filter((caseInfo) => caseInfo.alerts.total > 0); diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index 14348e03f99cc1..d95667d5eee047 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -4,13 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import pMap from 'p-map'; import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { SavedObjectsFindResponse, SavedObjectsUtils } from '../../../../../../src/core/server'; -import { SUPPORTED_CONNECTORS } from '../../../common/constants'; +import { + SavedObject, + SavedObjectsFindResponse, + SavedObjectsUtils, +} from '../../../../../../src/core/server'; +import { MAX_CONCURRENT_SEARCHES, SUPPORTED_CONNECTORS } from '../../../common/constants'; import { CaseConfigureResponseRt, CasesConfigurePatch, @@ -26,6 +32,7 @@ import { CaseConfigurationsResponseRt, CasesConfigurePatchRt, ConnectorMappings, + ESCasesConfigureAttributes, } from '../../../common/api'; import { createCaseError } from '../../common/error'; import { @@ -175,8 +182,9 @@ async function get( })) ); - const configurations = await Promise.all( - myCaseConfigure.saved_objects.map(async (configuration) => { + const configurations = await pMap( + myCaseConfigure.saved_objects, + async (configuration: SavedObject) => { const { connector, ...caseConfigureWithoutConnector } = configuration?.attributes ?? { connector: null, }; @@ -204,7 +212,7 @@ async function get( error, id: configuration.id, }; - }) + } ); return CaseConfigurationsResponseRt.encode(configurations); @@ -400,11 +408,13 @@ async function create( ); if (myCaseConfigure.saved_objects.length > 0) { - await Promise.all( - myCaseConfigure.saved_objects.map((cc) => - caseConfigureService.delete({ unsecuredSavedObjectsClient, configurationId: cc.id }) - ) - ); + const deleteConfigurationMapper = async (c: SavedObject) => + caseConfigureService.delete({ unsecuredSavedObjectsClient, configurationId: c.id }); + + // Ensuring we don't too many concurrent deletions running. + await pMap(myCaseConfigure.saved_objects, deleteConfigurationMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); } const savedObjectID = SavedObjectsUtils.generateId(); diff --git a/x-pack/plugins/cases/server/client/stats/client.ts b/x-pack/plugins/cases/server/client/stats/client.ts index 0e222d54ab218b..4fc8be4ccbfb22 100644 --- a/x-pack/plugins/cases/server/client/stats/client.ts +++ b/x-pack/plugins/cases/server/client/stats/client.ts @@ -63,6 +63,7 @@ async function getStatusTotalsByType( ensureSavedObjectsAreAuthorized, } = await authorization.getAuthorizationFilter(Operations.getCaseStatuses); + // casesStatuses are bounded by us. No need to limit concurrent calls. const [openCases, inProgressCases, closedCases] = await Promise.all([ ...caseStatuses.map((status) => { const statusQuery = constructQueryOptions({ diff --git a/x-pack/plugins/cases/server/client/sub_cases/client.ts b/x-pack/plugins/cases/server/client/sub_cases/client.ts index b35d58ce060108..9a7dad77909e97 100644 --- a/x-pack/plugins/cases/server/client/sub_cases/client.ts +++ b/x-pack/plugins/cases/server/client/sub_cases/client.ts @@ -5,10 +5,13 @@ * 2.0. */ +import pMap from 'p-map'; import Boom from '@hapi/boom'; +import { SavedObject } from 'kibana/server'; import { caseStatuses, + CommentAttributes, SubCaseResponse, SubCaseResponseRt, SubCasesFindRequest, @@ -19,7 +22,7 @@ import { import { CasesClientArgs, CasesClientInternal } from '..'; import { countAlertsForID, flattenSubCaseSavedObject, transformSubCases } from '../../common'; import { createCaseError } from '../../common/error'; -import { CASE_SAVED_OBJECT } from '../../../common/constants'; +import { CASE_SAVED_OBJECT, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { constructQueryOptions } from '../utils'; import { defaultPage, defaultPerPage } from '../../routes/api'; @@ -121,13 +124,20 @@ async function deleteSubCase(ids: string[], clientArgs: CasesClientArgs): Promis return acc; }, new Map()); - await Promise.all( - comments.saved_objects.map((comment) => - attachmentService.delete({ unsecuredSavedObjectsClient, attachmentId: comment.id }) - ) - ); + const deleteCommentMapper = async (comment: SavedObject) => + attachmentService.delete({ unsecuredSavedObjectsClient, attachmentId: comment.id }); + + const deleteSubCasesMapper = async (id: string) => + caseService.deleteSubCase(unsecuredSavedObjectsClient, id); - await Promise.all(ids.map((id) => caseService.deleteSubCase(unsecuredSavedObjectsClient, id))); + // Ensuring we don't too many concurrent deletions running. + await pMap(comments.saved_objects, deleteCommentMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); + + await pMap(ids, deleteSubCasesMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); const deleteDate = new Date().toISOString(); @@ -181,6 +191,7 @@ async function find( }, }); + // casesStatuses are bounded by us. No need to limit concurrent calls. const [open, inProgress, closed] = await Promise.all([ ...caseStatuses.map((status) => { const { subCase: statusQueryOptions } = constructQueryOptions({ diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index 2d1e1e18b50987..241278c77ab48f 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -34,7 +34,11 @@ import { flattenSubCaseSavedObject, transformNewComment, } from '..'; -import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../common/constants'; +import { + CASE_SAVED_OBJECT, + MAX_DOCS_PER_PAGE, + SUB_CASE_SAVED_OBJECT, +} from '../../../common/constants'; import { AttachmentService, CasesService } from '../../services'; import { createCaseError } from '../error'; import { countAlertsForID } from '../index'; @@ -309,23 +313,13 @@ export class CommentableCase { public async encode(): Promise { try { - const collectionCommentStats = await this.caseService.getAllCaseComments({ - unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, - id: this.collection.id, - options: { - fields: [], - page: 1, - perPage: 1, - }, - }); - const collectionComments = await this.caseService.getAllCaseComments({ unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, id: this.collection.id, options: { fields: [], page: 1, - perPage: collectionCommentStats.total, + perPage: MAX_DOCS_PER_PAGE, }, }); @@ -335,7 +329,7 @@ export class CommentableCase { const caseResponse = { comments: flattenCommentSavedObjects(collectionComments.saved_objects), totalAlerts: collectionTotalAlerts, - ...this.formatCollectionForEncoding(collectionCommentStats.total), + ...this.formatCollectionForEncoding(collectionComments.total), }; if (this.subCase) { diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index c9b9d11a896899..077f00a571fa79 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -57,10 +57,10 @@ export class AttachmentService { public async delete({ unsecuredSavedObjectsClient, attachmentId }: GetAttachmentArgs) { try { - this.log.debug(`Attempting to GET attachment ${attachmentId}`); + this.log.debug(`Attempting to DELETE attachment ${attachmentId}`); return await unsecuredSavedObjectsClient.delete(CASE_COMMENT_SAVED_OBJECT, attachmentId); } catch (error) { - this.log.error(`Error on GET attachment ${attachmentId}: ${error}`); + this.log.error(`Error on DELETE attachment ${attachmentId}: ${error}`); throw error; } } diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 5618f6c83ff060..fd0a5e878bfb92 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { cloneDeep } from 'lodash'; +import pMap from 'p-map'; import { KibanaRequest, Logger, @@ -43,7 +43,11 @@ import { groupTotalAlertsByID, SavedObjectFindOptionsKueryNode, } from '../../common'; -import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; +import { + ENABLE_CASE_CONNECTOR, + MAX_CONCURRENT_SEARCHES, + MAX_DOCS_PER_PAGE, +} from '../../../common/constants'; import { defaultPage, defaultPerPage } from '../../routes/api'; import { CASE_SAVED_OBJECT, @@ -250,7 +254,7 @@ export class CasesService { filter, ]); - let response = await unsecuredSavedObjectsClient.find< + const response = await unsecuredSavedObjectsClient.find< CommentAttributes, GetCaseIdsByAlertIdAggs >({ @@ -259,23 +263,9 @@ export class CasesService { page: 1, perPage: 1, sortField: defaultSortField, - aggs: this.buildCaseIdsAggs(), + aggs: this.buildCaseIdsAggs(MAX_DOCS_PER_PAGE), filter: combinedFilter, }); - if (response.total > 100) { - response = await unsecuredSavedObjectsClient.find< - CommentAttributes, - GetCaseIdsByAlertIdAggs - >({ - type: CASE_COMMENT_SAVED_OBJECT, - fields: includeFieldsRequiredForAuthentication(), - page: 1, - perPage: 1, - sortField: defaultSortField, - aggs: this.buildCaseIdsAggs(response.total), - filter: combinedFilter, - }); - } return response; } catch (error) { this.log.error(`Error on GET all cases for alert id ${alertId}: ${error}`); @@ -393,16 +383,6 @@ export class CasesService { ensureSavedObjectsAreAuthorized: EnsureSOAuthCallback; subCaseOptions?: SavedObjectFindOptionsKueryNode; }): Promise { - const casesStats = await this.findCases({ - unsecuredSavedObjectsClient, - options: { - ...caseOptions, - fields: [], - page: 1, - perPage: 1, - }, - }); - /** * This could be made more performant. What we're doing here is retrieving all cases * that match the API request's filters instead of just counts. This is because we need to grab @@ -429,7 +409,7 @@ export class CasesService { ...caseOptions, fields: includeFieldsRequiredForAuthentication([caseTypeField]), page: 1, - perPage: casesStats.total, + perPage: MAX_DOCS_PER_PAGE, }, }); @@ -447,7 +427,7 @@ export class CasesService { if (ENABLE_CASE_CONNECTOR && subCaseOptions) { subCasesTotal = await this.findSubCaseStatusStats({ unsecuredSavedObjectsClient, - options: cloneDeep(subCaseOptions), + options: subCaseOptions, ids: caseIds, }); } @@ -505,16 +485,18 @@ export class CasesService { const refType = associationType === AssociationType.case ? CASE_SAVED_OBJECT : SUB_CASE_SAVED_OBJECT; - const allComments = await Promise.all( - ids.map((id) => - this.getCommentsByAssociation({ - unsecuredSavedObjectsClient, - associationType, - id, - options: { page: 1, perPage: 1 }, - }) - ) - ); + const getCommentsMapper = async (id: string) => + this.getCommentsByAssociation({ + unsecuredSavedObjectsClient, + associationType, + id, + options: { page: 1, perPage: 1 }, + }); + + // Ensuring we don't too many concurrent get running. + const allComments = await pMap(ids, getCommentsMapper, { + concurrency: MAX_CONCURRENT_SEARCHES, + }); const alerts = await this.getCommentsByAssociation({ unsecuredSavedObjectsClient, @@ -795,7 +777,7 @@ export class CasesService { this.log.debug(`Attempting to find cases`); return await unsecuredSavedObjectsClient.find({ sortField: defaultSortField, - ...cloneDeep(options), + ...options, type: CASE_SAVED_OBJECT, }); } catch (error) { @@ -815,24 +797,16 @@ export class CasesService { if (options?.page !== undefined || options?.perPage !== undefined) { return unsecuredSavedObjectsClient.find({ sortField: defaultSortField, - ...cloneDeep(options), + ...options, type: SUB_CASE_SAVED_OBJECT, }); } - const stats = await unsecuredSavedObjectsClient.find({ - fields: [], - page: 1, - perPage: 1, - sortField: defaultSortField, - ...cloneDeep(options), - type: SUB_CASE_SAVED_OBJECT, - }); return unsecuredSavedObjectsClient.find({ page: 1, - perPage: stats.total, + perPage: MAX_DOCS_PER_PAGE, sortField: defaultSortField, - ...cloneDeep(options), + ...options, type: SUB_CASE_SAVED_OBJECT, }); } catch (error) { @@ -902,26 +876,16 @@ export class CasesService { return unsecuredSavedObjectsClient.find({ type: CASE_COMMENT_SAVED_OBJECT, sortField: defaultSortField, - ...cloneDeep(options), + ...options, }); } - // get the total number of comments that are in ES then we'll grab them all in one go - const stats = await unsecuredSavedObjectsClient.find({ - type: CASE_COMMENT_SAVED_OBJECT, - fields: [], - page: 1, - perPage: 1, - sortField: defaultSortField, - // spread the options after so the caller can override the default behavior if they want - ...cloneDeep(options), - }); return unsecuredSavedObjectsClient.find({ type: CASE_COMMENT_SAVED_OBJECT, page: 1, - perPage: stats.total, + perPage: MAX_DOCS_PER_PAGE, sortField: defaultSortField, - ...cloneDeep(options), + ...options, }); } catch (error) { this.log.error(`Error on GET all comments internal for ${JSON.stringify(id)}: ${error}`); @@ -1022,20 +986,13 @@ export class CasesService { }: GetReportersArgs): Promise> { try { this.log.debug(`Attempting to GET all reporters`); - const firstReporters = await unsecuredSavedObjectsClient.find({ - type: CASE_SAVED_OBJECT, - fields: ['created_by', OWNER_FIELD], - page: 1, - perPage: 1, - filter: cloneDeep(filter), - }); return await unsecuredSavedObjectsClient.find({ type: CASE_SAVED_OBJECT, fields: ['created_by', OWNER_FIELD], page: 1, - perPage: firstReporters.total, - filter: cloneDeep(filter), + perPage: MAX_DOCS_PER_PAGE, + filter, }); } catch (error) { this.log.error(`Error on GET all reporters: ${error}`); @@ -1049,20 +1006,13 @@ export class CasesService { }: GetTagsArgs): Promise> { try { this.log.debug(`Attempting to GET all cases`); - const firstTags = await unsecuredSavedObjectsClient.find({ - type: CASE_SAVED_OBJECT, - fields: ['tags', OWNER_FIELD], - page: 1, - perPage: 1, - filter: cloneDeep(filter), - }); return await unsecuredSavedObjectsClient.find({ type: CASE_SAVED_OBJECT, fields: ['tags', OWNER_FIELD], page: 1, - perPage: firstTags.total, - filter: cloneDeep(filter), + perPage: MAX_DOCS_PER_PAGE, + filter, }); } catch (error) { this.log.error(`Error on GET tags: ${error}`); diff --git a/x-pack/plugins/cases/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts index 8ea1c903622b72..745e64d9016f92 100644 --- a/x-pack/plugins/cases/server/services/configure/index.ts +++ b/x-pack/plugins/cases/server/services/configure/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { cloneDeep } from 'lodash'; import { Logger, SavedObjectsClientContract } from 'kibana/server'; import { SavedObjectFindOptionsKueryNode } from '../../common'; @@ -63,7 +62,7 @@ export class CaseConfigureService { try { this.log.debug(`Attempting to find all case configuration`); return await unsecuredSavedObjectsClient.find({ - ...cloneDeep(options), + ...options, // Get the latest configuration sortField: 'created_at', sortOrder: 'desc', diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts index e691b9305fb37f..2c20bf808ad4e0 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.ts @@ -12,6 +12,7 @@ import { CASE_USER_ACTION_SAVED_OBJECT, CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT, + MAX_DOCS_PER_PAGE, } from '../../../common/constants'; import { ClientArgs } from '..'; @@ -36,19 +37,12 @@ export class CaseUserActionService { try { const id = subCaseId ?? caseId; const type = subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT; - const caseUserActionInfo = await unsecuredSavedObjectsClient.find({ - type: CASE_USER_ACTION_SAVED_OBJECT, - fields: [], - hasReference: { type, id }, - page: 1, - perPage: 1, - }); return await unsecuredSavedObjectsClient.find({ type: CASE_USER_ACTION_SAVED_OBJECT, hasReference: { type, id }, page: 1, - perPage: caseUserActionInfo.total, + perPage: MAX_DOCS_PER_PAGE, sortField: 'action_at', sortOrder: 'asc', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts index d16391089120a2..23d638d5f25f3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts @@ -12,6 +12,7 @@ import { generateEncodedPath } from '../utils/encode_path_params'; export const mockEngineValues = { engineName: 'some-engine', engine: {} as EngineDetails, + searchKey: 'search-abc123', }; export const mockEngineActions = { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index 5435450f1bfdbb..836265a037e169 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -9,6 +9,8 @@ import { LogicMounter, mockHttpValues } from '../../../__mocks__'; import { nextTick } from '@kbn/test/jest'; +import { ApiTokenTypes } from '../credentials/constants'; + import { EngineTypes } from './types'; import { EngineLogic } from './'; @@ -47,6 +49,7 @@ describe('EngineLogic', () => { hasSchemaConflicts: false, hasUnconfirmedSchemaFields: false, engineNotFound: false, + searchKey: '', }; beforeEach(() => { @@ -263,5 +266,57 @@ describe('EngineLogic', () => { }); }); }); + + describe('searchKey', () => { + it('should select the first available search key for this engine', () => { + const engine = { + ...mockEngineData, + apiTokens: [ + { + key: 'private-123xyz', + name: 'privateKey', + type: ApiTokenTypes.Private, + }, + { + key: 'search-123xyz', + name: 'searchKey', + type: ApiTokenTypes.Search, + }, + { + key: 'search-8910abc', + name: 'searchKey2', + type: ApiTokenTypes.Search, + }, + ], + }; + mount({ engine }); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine, + searchKey: 'search-123xyz', + }); + }); + + it('should return an empty string if none are available', () => { + const engine = { + ...mockEngineData, + apiTokens: [ + { + key: 'private-123xyz', + name: 'privateKey', + type: ApiTokenTypes.Private, + }, + ], + }; + mount({ engine }); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine, + searchKey: '', + }); + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts index 8fbf45bc85e52c..5cbe89b364859e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts @@ -8,6 +8,8 @@ import { kea, MakeLogicType } from 'kea'; import { HttpLogic } from '../../../shared/http'; +import { ApiTokenTypes } from '../credentials/constants'; +import { ApiToken } from '../credentials/types'; import { EngineDetails, EngineTypes } from './types'; @@ -21,6 +23,7 @@ interface EngineValues { hasSchemaConflicts: boolean; hasUnconfirmedSchemaFields: boolean; engineNotFound: boolean; + searchKey: string; } interface EngineActions { @@ -87,6 +90,14 @@ export const EngineLogic = kea>({ () => [selectors.engine], (engine) => engine?.unconfirmedFields?.length > 0, ], + searchKey: [ + () => [selectors.engine], + (engine: Partial) => { + const isSearchKey = (token: ApiToken) => token.type === ApiTokenTypes.Search; + const searchKey = (engine.apiTokens || []).find(isSearchKey); + return searchKey?.key || ''; + }, + ], }), listeners: ({ actions, values }) => ({ initializeEngine: async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx index b015c2dec6c0a0..82b925f57f2dff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx @@ -13,7 +13,9 @@ import { setMockValues, setMockActions } from '../../../../__mocks__'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiForm } from '@elastic/eui'; import { ActiveField } from '../types'; import { generatePreviewUrl } from '../utils'; @@ -29,6 +31,7 @@ describe('SearchUIForm', () => { urlField: 'url', facetFields: ['category'], sortFields: ['size'], + dataLoading: false, }; const actions = { onActiveFieldChange: jest.fn(), @@ -43,10 +46,6 @@ describe('SearchUIForm', () => { setMockActions(actions); }); - beforeEach(() => { - jest.clearAllMocks(); - }); - it('renders', () => { const wrapper = shallow(); expect(wrapper.find('[data-test-subj="selectTitle"]').exists()).toBe(true); @@ -56,6 +55,7 @@ describe('SearchUIForm', () => { }); describe('title field', () => { + beforeEach(() => jest.clearAllMocks()); const subject = () => shallow().find('[data-test-subj="selectTitle"]'); it('renders with its value set from state', () => { @@ -84,6 +84,7 @@ describe('SearchUIForm', () => { }); describe('url field', () => { + beforeEach(() => jest.clearAllMocks()); const subject = () => shallow().find('[data-test-subj="selectUrl"]'); it('renders with its value set from state', () => { @@ -112,6 +113,7 @@ describe('SearchUIForm', () => { }); describe('filters field', () => { + beforeEach(() => jest.clearAllMocks()); const subject = () => shallow().find('[data-test-subj="selectFilters"]'); it('renders with its value set from state', () => { @@ -145,6 +147,7 @@ describe('SearchUIForm', () => { }); describe('sorts field', () => { + beforeEach(() => jest.clearAllMocks()); const subject = () => shallow().find('[data-test-subj="selectSort"]'); it('renders with its value set from state', () => { @@ -177,26 +180,61 @@ describe('SearchUIForm', () => { }); }); - it('includes a link to generate the preview', () => { - (generatePreviewUrl as jest.Mock).mockReturnValue('http://www.example.com?foo=bar'); + describe('generate preview button', () => { + let wrapper: ShallowWrapper; - setMockValues({ - ...values, - urlField: 'foo', - titleField: 'bar', - facetFields: ['baz'], - sortFields: ['qux'], + beforeAll(() => { + jest.clearAllMocks(); + (generatePreviewUrl as jest.Mock).mockReturnValue('http://www.example.com?foo=bar'); + setMockValues({ + ...values, + urlField: 'foo', + titleField: 'bar', + facetFields: ['baz'], + sortFields: ['qux'], + searchKey: 'search-123abc', + }); + wrapper = shallow(); + }); + + it('should be a submit button', () => { + expect(wrapper.find('[data-test-subj="generateSearchUiPreview"]').prop('type')).toBe( + 'submit' + ); + }); + + it('should be wrapped in a form configured to POST to the preview screen in a new tab', () => { + const form = wrapper.find(EuiForm); + expect(generatePreviewUrl).toHaveBeenCalledWith({ + urlField: 'foo', + titleField: 'bar', + facets: ['baz'], + sortFields: ['qux'], + }); + expect(form.prop('action')).toBe('http://www.example.com?foo=bar'); + expect(form.prop('target')).toBe('_blank'); + expect(form.prop('method')).toBe('POST'); + expect(form.prop('component')).toBe('form'); }); - const subject = () => - shallow().find('[data-test-subj="generateSearchUiPreview"]'); + it('should include a searchKey in that form POST', () => { + const form = wrapper.find(EuiForm); + const hiddenInput = form.find('input[type="hidden"]'); + expect(hiddenInput.prop('id')).toBe('searchKey'); + expect(hiddenInput.prop('value')).toBe('search-123abc'); + }); + }); - expect(subject().prop('href')).toBe('http://www.example.com?foo=bar'); - expect(generatePreviewUrl).toHaveBeenCalledWith({ - urlField: 'foo', - titleField: 'bar', - facets: ['baz'], - sortFields: ['qux'], + it('should disable everything while data is loading', () => { + setMockValues({ + ...values, + dataLoading: true, }); + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="selectTitle"]').prop('disabled')).toBe(true); + expect(wrapper.find('[data-test-subj="selectFilters"]').prop('isDisabled')).toBe(true); + expect(wrapper.find('[data-test-subj="selectSort"]').prop('isDisabled')).toBe(true); + expect(wrapper.find('[data-test-subj="selectUrl"]').prop('disabled')).toBe(true); + expect(wrapper.find('[data-test-subj="generateSearchUiPreview"]').prop('disabled')).toBe(true); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx index 15bd699be721cd..b795a46268237e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx @@ -11,6 +11,7 @@ import { useValues, useActions } from 'kea'; import { EuiForm, EuiFormRow, EuiSelect, EuiComboBox, EuiButton } from '@elastic/eui'; +import { EngineLogic } from '../../engine'; import { TITLE_FIELD_LABEL, TITLE_FIELD_HELP_TEXT, @@ -27,7 +28,9 @@ import { ActiveField } from '../types'; import { generatePreviewUrl } from '../utils'; export const SearchUIForm: React.FC = () => { + const { searchKey } = useValues(EngineLogic); const { + dataLoading, validFields, validSortFields, validFacetFields, @@ -70,9 +73,11 @@ export const SearchUIForm: React.FC = () => { const selectedFacetOptions = formatMultiOptions(facetFields); return ( - + + onTitleFieldChange(e.target.value)} @@ -85,6 +90,7 @@ export const SearchUIForm: React.FC = () => { onFacetFieldsChange(newValues.map((field) => field.value!))} @@ -96,6 +102,7 @@ export const SearchUIForm: React.FC = () => { onSortFieldsChange(newValues.map((field) => field.value!))} @@ -108,6 +115,7 @@ export const SearchUIForm: React.FC = () => { onUrlFieldChange(e.target.value)} @@ -119,8 +127,8 @@ export const SearchUIForm: React.FC = () => { /> + i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.noSearchKeyErrorMessage', { + defaultMessage: + "It looks like you don't have any Public Search Keys with access to the '{engineName}' engine. Please visit the {credentialsTitle} page to set one up.", + values: { engineName, credentialsTitle: CREDENTIALS_TITLE }, + }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts index 2191c633131bb2..2e29ac0d398a66 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts @@ -18,7 +18,7 @@ import { SearchUILogic } from './'; describe('SearchUILogic', () => { const { mount } = new LogicMounter(SearchUILogic); const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; + const { flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers; const DEFAULT_VALUES = { dataLoading: true, @@ -35,6 +35,7 @@ describe('SearchUILogic', () => { beforeEach(() => { jest.clearAllMocks(); mockEngineValues.engineName = 'engine1'; + mockEngineValues.searchKey = 'search-abc123'; }); it('has expected default values', () => { @@ -155,6 +156,17 @@ describe('SearchUILogic', () => { }); }); + it('will short circuit the call if there is no searchKey available for this engine', async () => { + mockEngineValues.searchKey = ''; + mount(); + + SearchUILogic.actions.loadFieldData(); + + expect(setErrorMessage).toHaveBeenCalledWith( + "It looks like you don't have any Public Search Keys with access to the 'engine1' engine. Please visit the Credentials page to set one up." + ); + }); + it('handles errors', async () => { http.get.mockReturnValueOnce(Promise.reject('error')); mount(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts index c9e2e5623d9fd1..096365f57ea36d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts @@ -7,10 +7,12 @@ import { kea, MakeLogicType } from 'kea'; -import { flashAPIErrors } from '../../../shared/flash_messages'; +import { flashAPIErrors, setErrorMessage } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; import { EngineLogic } from '../engine'; +import { NO_SEARCH_KEY_ERROR } from './i18n'; + import { ActiveField } from './types'; interface InitialFieldValues { @@ -84,7 +86,12 @@ export const SearchUILogic = kea> listeners: ({ actions }) => ({ loadFieldData: async () => { const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; + const { searchKey, engineName } = EngineLogic.values; + + if (!searchKey) { + setErrorMessage(NO_SEARCH_KEY_ERROR(engineName)); + return; + } const url = `/api/app_search/engines/${engineName}/search_ui/field_config`; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts index 8d99ffc514b3fb..9a005649e7dc95 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts @@ -33,7 +33,7 @@ describe('generatePreviewUrl', () => { empty2: [''], // Empty fields should be stripped }) ).toEqual( - 'http://localhost:3002/as/engines/national-parks-demo/reference_application/preview?facets[]=baz&facets[]=qux&sortFields[]=quux&sortFields[]=quuz&titleField=foo&urlField=bar' + 'http://localhost:3002/as/engines/national-parks-demo/search_experience/preview?facets[]=baz&facets[]=qux&sortFields[]=quux&sortFields[]=quuz&titleField=foo&urlField=bar' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts index 72d90514ea0a08..6c57df0d66a2a9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts @@ -15,7 +15,7 @@ export const generatePreviewUrl = (query: ParsedQuery) => { return queryString.stringifyUrl( { query, - url: getAppSearchUrl(`/engines/${engineName}/reference_application/preview`), + url: getAppSearchUrl(`/engines/${engineName}/search_experience/preview`), }, { arrayFormat: 'bracket', skipEmptyString: true } ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 12ccf089b85fb1..003aa045f9591a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -8,17 +8,7 @@ import React, { useEffect } from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiPageBody, - EuiPageContent, - EuiSpacer, - EuiTab, - EuiTabs, - EuiTitle, -} from '@elastic/eui'; +import { EuiButtonEmpty, EuiPageHeader, EuiSpacer } from '@elastic/eui'; import { documentationService } from '../../services/documentation'; import { DataStreamList } from './data_stream_list'; import { IndexList } from './index_list'; @@ -93,73 +83,59 @@ export const IndexManagementHome: React.FunctionComponent - - - - -

- -

-
- - - - - -
-
+ <> + + + + } + bottomBorder + rightSideItems={[ + + + , + ]} + tabs={tabs.map((tab) => ({ + onClick: () => onSectionChange(tab.id), + isSelected: tab.id === section, + key: tab.id, + 'data-test-subj': `${tab.id}Tab`, + label: tab.name, + }))} + /> - + - - {tabs.map((tab) => ( - onSectionChange(tab.id)} - isSelected={tab.id === section} - key={tab.id} - data-test-subj={`${tab.id}Tab`} - > - {tab.name} - - ))} - - - - - - - - - - -
- + + + + + + + ); }; diff --git a/x-pack/plugins/infra/common/typed_json.ts b/x-pack/plugins/infra/common/typed_json.ts index b14de38c47eac6..5b7bbdcfbc07bd 100644 --- a/x-pack/plugins/infra/common/typed_json.ts +++ b/x-pack/plugins/infra/common/typed_json.ts @@ -11,6 +11,7 @@ import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana export { JsonArray, JsonObject, JsonValue }; export const jsonScalarRT = rt.union([rt.null, rt.boolean, rt.number, rt.string]); +export type JsonScalar = rt.TypeOf; export const jsonValueRT: rt.Type = rt.recursion('JsonValue', () => rt.union([jsonScalarRT, jsonArrayRT, jsonObjectRT]) diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx index 3b19a3eb0902c8..d061866cf24a87 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx @@ -7,21 +7,37 @@ import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import * as rt from 'io-ts'; import React, { useMemo } from 'react'; +import { Query } from '../../../../../../../src/plugins/data/public'; import { LogEntryField } from '../../../../common/log_entry'; import { LogEntry } from '../../../../common/search_strategies/log_entries/log_entry'; import { TimeKey } from '../../../../common/time'; +import { JsonScalar, jsonScalarRT } from '../../../../common/typed_json'; import { FieldValue } from '../log_text_stream/field_value'; export const LogEntryFieldsTable: React.FC<{ logEntry: LogEntry; - onSetFieldFilter?: (filter: string, logEntryId: string, timeKey?: TimeKey) => void; + onSetFieldFilter?: (filter: Query, logEntryId: string, timeKey?: TimeKey) => void; }> = ({ logEntry, onSetFieldFilter }) => { const createSetFilterHandler = useMemo( () => onSetFieldFilter ? (field: LogEntryField) => () => { - onSetFieldFilter?.(`${field.field}:"${field.value}"`, logEntry.id, logEntry.cursor); + if (!rt.array(jsonScalarRT).is(field.value)) { + return; + } + + onSetFieldFilter?.( + { + language: 'kuery', + query: `${escapeKueryLiteral(field.field)}:${field.value + .map(escapeKueryLiteral) + .join(' OR ')}`, + }, + logEntry.id, + logEntry.cursor + ); } : undefined, [logEntry, onSetFieldFilter] @@ -98,3 +114,8 @@ const setFilterButtonLabel = i18n.translate('xpack.infra.logFlyout.filterAriaLab const setFilterButtonDescription = i18n.translate('xpack.infra.logFlyout.setFilterTooltip', { defaultMessage: 'View event with filter', }); + +const escapeKueryLiteral = (unquotedLiteral: JsonScalar): JsonScalar => + typeof unquotedLiteral === 'string' + ? `"${unquotedLiteral.replace(/"/g, '\\"')}"` + : unquotedLiteral; diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx index dcd36d73dd0bb6..a296b7063282a6 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx @@ -18,6 +18,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useEffect } from 'react'; +import type { Query } from '../../../../../../../src/plugins/data/public'; import { TimeKey } from '../../../../common/time'; import { useLogEntry } from '../../../containers/logs/log_entry'; import { CenteredEuiFlyoutBody } from '../../centered_flyout_body'; @@ -29,7 +30,7 @@ import { LogEntryFieldsTable } from './log_entry_fields_table'; export interface LogEntryFlyoutProps { logEntryId: string | null | undefined; onCloseFlyout: () => void; - onSetFieldFilter?: (filter: string, logEntryId: string, timeKey?: TimeKey) => void; + onSetFieldFilter?: (filter: Query, logEntryId: string, timeKey?: TimeKey) => void; sourceId: string | null | undefined; } diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 54617d025652b1..0741423fd38862 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -10,9 +10,11 @@ import moment from 'moment'; import { stringify } from 'query-string'; import React, { useCallback, useMemo } from 'react'; import { encode, RisonValue } from 'rison-node'; +import type { Query } from '../../../../../../../src/plugins/data/public'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { useTrackPageview } from '../../../../../observability/public'; +import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { TimeKey } from '../../../../common/time'; import { CategoryJobNoticesSection, @@ -27,10 +29,9 @@ import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analy import { useLogEntryFlyoutContext } from '../../../containers/logs/log_flyout'; import { useLogSourceContext } from '../../../containers/logs/log_source'; import { AnomaliesResults } from './sections/anomalies'; -import { useLogEntryAnomaliesResults } from './use_log_entry_anomalies_results'; import { useDatasetFiltering } from './use_dataset_filtering'; +import { useLogEntryAnomaliesResults } from './use_log_entry_anomalies_results'; import { useLogAnalysisResultsUrlState } from './use_log_entry_rate_results_url_state'; -import { isJobStatusWithResults } from '../../../../common/log_analysis'; export const SORT_DEFAULTS = { direction: 'desc' as const, @@ -102,7 +103,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { } = useLogEntryFlyoutContext(); const linkToLogStream = useCallback( - (filter: string, id: string, timeKey?: TimeKey) => { + (filterQuery: Query, id: string, timeKey?: TimeKey) => { const params = { logPosition: encode({ end: moment(timeRange.value.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), @@ -113,10 +114,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { flyoutOptions: encode({ surroundingLogsId: id, }), - logFilter: encode({ - expression: filter, - kind: 'kuery', - }), + logFilter: encode(filterQuery), }; navigateToApp?.('logs', { path: `/stream?${stringify(params)}` }); diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index 664cccb5522028..165a5a03bcc6ea 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -5,10 +5,12 @@ * 2.0. */ -import React, { useContext, useCallback, useMemo, useEffect } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; -import { LogEntry } from '../../../../common/log_entry'; +import type { Query } from '../../../../../../../src/plugins/data/public'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; +import { LogEntry } from '../../../../common/log_entry'; +import { TimeKey } from '../../../../common/time'; import { AutoSizer } from '../../../components/auto_sizer'; import { LogEntryFlyout } from '../../../components/logging/log_entry_flyout'; import { LogMinimap } from '../../../components/logging/log_minimap'; @@ -23,14 +25,14 @@ import { import { LogHighlightsState } from '../../../containers/logs/log_highlights'; import { LogPositionState } from '../../../containers/logs/log_position'; import { useLogSourceContext } from '../../../containers/logs/log_source'; +import { useLogStreamContext } from '../../../containers/logs/log_stream'; import { WithSummary } from '../../../containers/logs/log_summary'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview'; +import { datemathToEpochMillis, isValidDatemath } from '../../../utils/datemath'; import { LogsToolbar } from './page_toolbar'; import { PageViewLogInContext } from './page_view_log_in_context'; -import { useLogStreamContext } from '../../../containers/logs/log_stream'; -import { datemathToEpochMillis, isValidDatemath } from '../../../utils/datemath'; const PAGE_THRESHOLD = 2; @@ -190,7 +192,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { ); const setFilter = useCallback( - (filter, flyoutItemId, timeKey) => { + (filter: Query, flyoutItemId: string, timeKey: TimeKey | undefined | null) => { applyLogFilterQuery(filter); if (timeKey) { jumpToTargetPosition(timeKey); diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 6b57da132e895e..a2a811a8a42239 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -123,7 +123,7 @@ function getDataRequestContext( dispatch(endDataLoad(layerId, dataId, requestToken, data, meta)), onLoadError: (dataId: string, requestToken: symbol, errorMessage: string) => dispatch(onDataLoadError(layerId, dataId, requestToken, errorMessage)), - updateSourceData: (newData: unknown) => { + updateSourceData: (newData: object) => { dispatch(updateSourceDataRequest(layerId, newData)); }, isRequestStillActive: (dataId: string, requestToken: symbol) => { @@ -316,7 +316,7 @@ function onDataLoadError( }; } -export function updateSourceDataRequest(layerId: string, newData: unknown) { +export function updateSourceDataRequest(layerId: string, newData: object) { return (dispatch: ThunkDispatch) => { dispatch({ type: UPDATE_SOURCE_DATA_REQUEST, diff --git a/x-pack/plugins/maps/public/reducers/map/data_request_utils.ts b/x-pack/plugins/maps/public/reducers/map/data_request_utils.ts index 637f06f1d2e81b..18a3fd3d6c150d 100644 --- a/x-pack/plugins/maps/public/reducers/map/data_request_utils.ts +++ b/x-pack/plugins/maps/public/reducers/map/data_request_utils.ts @@ -38,7 +38,7 @@ export function startDataRequest( export function updateSourceDataRequest( state: MapState, layerId: string, - newData?: object + newData: object ): MapState { const dataRequest = getDataRequest(state, layerId, SOURCE_DATA_REQUEST_ID); return dataRequest ? setDataRequest(state, layerId, { ...dataRequest, data: newData }) : state; diff --git a/x-pack/plugins/maps/public/reducers/map/map.ts b/x-pack/plugins/maps/public/reducers/map/map.ts index ec622e01dab38f..e259e97bf260f4 100644 --- a/x-pack/plugins/maps/public/reducers/map/map.ts +++ b/x-pack/plugins/maps/public/reducers/map/map.ts @@ -84,7 +84,7 @@ export const DEFAULT_MAP_STATE: MapState = { __rollbackSettings: null, }; -export function map(state: MapState = DEFAULT_MAP_STATE, action: any) { +export function map(state: MapState = DEFAULT_MAP_STATE, action: Record) { switch (action.type) { case UPDATE_DRAW_STATE: return { @@ -183,7 +183,7 @@ export function map(state: MapState = DEFAULT_MAP_STATE, action: any) { ], }; case UPDATE_SOURCE_DATA_REQUEST: - return updateSourceDataRequest(state, action); + return updateSourceDataRequest(state, action.layerId, action.newData); case LAYER_DATA_LOAD_STARTED: return startDataRequest( state, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index c2b806abcf2860..b6b63d7713b852 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -33,6 +33,7 @@ import { EuiBadge, } from '@elastic/eui'; import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; +import { TimeSeriesExplorerHelpPopover } from './timeseriesexplorer_help_popover'; import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; import { @@ -1083,58 +1084,67 @@ export class TimeSeriesExplorer extends React.Component { hasResults === true && (
- -

- - {i18n.translate('xpack.ml.timeSeriesExplorer.singleTimeSeriesAnalysisTitle', { - defaultMessage: 'Single time series analysis of {functionLabel}', - values: { functionLabel: chartDetails.functionLabel }, - })} - -   - {chartDetails.entityData.count === 1 && ( - - {chartDetails.entityData.entities.length > 0 && '('} - {chartDetails.entityData.entities - .map((entity) => { - return `${entity.fieldName}: ${entity.fieldValue}`; - }) - .join(', ')} - {chartDetails.entityData.entities.length > 0 && ')'} - - )} - {chartDetails.entityData.count !== 1 && ( - - {chartDetails.entityData.entities.map((countData, i) => { - return ( - - {i18n.translate( - 'xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription', - { - defaultMessage: - '{openBrace}{cardinalityValue} distinct {fieldName} {cardinality, plural, one {} other { values}}{closeBrace}', - values: { - openBrace: i === 0 ? '(' : '', - closeBrace: - i === chartDetails.entityData.entities.length - 1 ? ')' : '', - cardinalityValue: - countData.cardinality === 0 - ? allValuesLabel - : countData.cardinality, - cardinality: countData.cardinality, - fieldName: countData.fieldName, - }, - } - )} - {i !== chartDetails.entityData.entities.length - 1 ? ', ' : ''} - - ); - })} + + +

+ + {i18n.translate( + 'xpack.ml.timeSeriesExplorer.singleTimeSeriesAnalysisTitle', + { + defaultMessage: 'Single time series analysis of {functionLabel}', + values: { functionLabel: chartDetails.functionLabel }, + } + )} - )} -

-
- +   + {chartDetails.entityData.count === 1 && ( + + {chartDetails.entityData.entities.length > 0 && '('} + {chartDetails.entityData.entities + .map((entity) => { + return `${entity.fieldName}: ${entity.fieldValue}`; + }) + .join(', ')} + {chartDetails.entityData.entities.length > 0 && ')'} + + )} + {chartDetails.entityData.count !== 1 && ( + + {chartDetails.entityData.entities.map((countData, i) => { + return ( + + {i18n.translate( + 'xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription', + { + defaultMessage: + '{openBrace}{cardinalityValue} distinct {fieldName} {cardinality, plural, one {} other { values}}{closeBrace}', + values: { + openBrace: i === 0 ? '(' : '', + closeBrace: + i === chartDetails.entityData.entities.length - 1 + ? ')' + : '', + cardinalityValue: + countData.cardinality === 0 + ? allValuesLabel + : countData.cardinality, + cardinality: countData.cardinality, + fieldName: countData.fieldName, + }, + } + )} + {i !== chartDetails.entityData.entities.length - 1 ? ', ' : ''} + + ); + })} + + )} +

+
+ + + + {showModelBoundsCheckbox && ( diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx new file mode 100644 index 00000000000000..59249791f00784 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { HelpPopover, HelpPopoverButton } from '../components/help_popover/help_popover'; + +export const TimeSeriesExplorerHelpPopover = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + { + setIsPopoverOpen(!isPopoverOpen); + }} + /> + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.ml.timeSeriesExplorer.popoverTitle', { + defaultMessage: 'Single time series analysis', + })} + > +

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+
+ ); +}; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 442c1d910f8142..452b119e83921d 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -34,6 +34,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { NotificationsStart } from 'src/core/public'; +import { APP_WRAPPER_CLASS } from '../../../../../../../src/core/public'; import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public'; import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import type { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; @@ -88,7 +89,7 @@ export class APIKeysGridPage extends Component { public render() { return ( -
+
{ if (!areApiKeysEnabled) { return ( - + ); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx index cd74c0de4dfcfd..70199189ca7bf0 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/not_enabled/not_enabled.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -15,30 +15,35 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ export const NotEnabled: React.FunctionComponent = () => { const docLinks = useKibana().services.docLinks!; return ( - +

+ +

} color="danger" iconType="alert" - > - - - - ), - }} - /> -
+ body={ +

+ + + + ), + }} + /> +

+ } + /> ); }; diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx index f5f6d2daf306df..0e8b1c18fdc1cc 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { EuiButton, EuiButtonIcon, @@ -14,15 +13,11 @@ import { EuiInMemoryTable, EuiLink, EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, + EuiPageHeader, EuiSpacer, - EuiText, - EuiTitle, EuiToolTip, } from '@elastic/eui'; -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -129,49 +124,42 @@ export class RoleMappingsGridPage extends Component { if (loadState === 'finished' && roleMappings && roleMappings.length === 0) { return ( - + ); } return ( - - - - -

- -

-
- -

- - - - ), - }} - /> -

-
-
- + <> + + } + description={ + + + + ), + }} + /> + } + rightSideItems={[ @@ -179,21 +167,20 @@ export class RoleMappingsGridPage extends Component { id="xpack.security.management.roleMappings.createRoleMappingButtonLabel" defaultMessage="Create role mapping" /> - - -
- - - {!this.state.hasCompatibleRealms && ( - <> - - - - )} - {this.renderTable()} - - -
+ , + ]} + /> + + + + {!this.state.hasCompatibleRealms && ( + <> + + + + )} + {this.renderTable()} + ); } diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 436f1573639c83..7e03d9b61fc10e 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -254,10 +254,10 @@ interface HostInfo { version: number; }; }; - configuration: { + configuration?: { isolation: boolean; }; - state: { + state?: { isolation: boolean; }; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 4367c0d90af79c..e006944eb914fe 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -473,14 +473,14 @@ export type HostMetadata = Immutable<{ version: number; }; }; - configuration: { + configuration?: { /** * Shows whether the endpoint is set up to be isolated. (e.g. a user has isolated a host, * and the endpoint successfully received that action and applied the setting) */ isolation?: boolean; }; - state: { + state?: { /** * Shows what the current state of the host is. This could differ from `Endpoint.configuration.isolation` * in some cases, but normally they will match diff --git a/x-pack/plugins/security_solution/public/common/utils/validators/is_endpoint_host_isolated.ts b/x-pack/plugins/security_solution/public/common/utils/validators/is_endpoint_host_isolated.ts index 6ca187c52475e9..c22f33d5240295 100644 --- a/x-pack/plugins/security_solution/public/common/utils/validators/is_endpoint_host_isolated.ts +++ b/x-pack/plugins/security_solution/public/common/utils/validators/is_endpoint_host_isolated.ts @@ -13,5 +13,5 @@ import { HostMetadata } from '../../../../common/endpoint/types'; * @param endpointMetadata */ export const isEndpointHostIsolated = (endpointMetadata: HostMetadata): boolean => { - return Boolean(endpointMetadata.Endpoint.state.isolation); + return Boolean(endpointMetadata.Endpoint.state?.isolation); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx index 7c9de3bb231f06..296e4c85be88a7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -8,10 +8,17 @@ import React from 'react'; import { mount } from 'enzyme'; -import { NewTimeline, NewTimelineProps } from './helpers'; +import { AddToFavoritesButton, NewTimeline, NewTimelineProps } from './helpers'; import { useCreateTimelineButton } from './use_create_timeline'; - -jest.mock('../../../../common/hooks/use_selector'); +import { kibanaObservable, TestProviders } from '../../../../common/mock/test_providers'; +import { timelineActions } from '../../../../timelines/store/timeline'; +import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; +import { + createSecuritySolutionStorageMock, + mockGlobalState, + SUB_PLUGINS_REDUCER, +} from '../../../../common/mock'; +import { createStore } from '../../../../common/store'; jest.mock('./use_create_timeline'); @@ -84,3 +91,143 @@ describe('NewTimeline', () => { }); }); }); + +describe('Favorite Button', () => { + describe('Non Elastic prebuilt templates', () => { + test('should render favorite button', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="timeline-favorite-empty-star"]').exists()).toBeTruthy(); + }); + + test('Favorite button should be enabled ', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="timeline-favorite-empty-star"]').first().prop('disabled') + ).toEqual(false); + }); + + test('Should update isFavorite after clicking on favorite button', async () => { + const spy = jest.spyOn(timelineActions, 'updateIsFavorite'); + const wrapper = mount( + + + + ); + + wrapper.simulate('click'); + + expect(spy).toHaveBeenCalled(); + }); + + test('should disable favorite button with filled star', () => { + const { storage } = createSecuritySolutionStorageMock(); + + const store = createStore( + { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + test: { + ...mockGlobalState.timeline.timelineById.test, + isFavorite: true, + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="timeline-favorite-filled-star"]').exists() + ).toBeTruthy(); + }); + }); + + describe('Elast prebuilt templates', () => { + test('should disable favorite button', () => { + const { storage } = createSecuritySolutionStorageMock(); + + const store = createStore( + { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + test: { + ...mockGlobalState.timeline.timelineById.test, + status: TimelineStatus.immutable, + timelineType: TimelineType.template, + templateTimelineId: 'mock-template-timeline-id', + templateTimelineVersion: 1, + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const wrapper = mount( + + + + ); + expect( + wrapper.find('[data-test-subj="timeline-favorite-empty-star"]').first().prop('disabled') + ).toEqual(true); + }); + }); + + describe('Custom templates', () => { + test('should enable favorite button', () => { + const { storage } = createSecuritySolutionStorageMock(); + + const store = createStore( + { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + test: { + ...mockGlobalState.timeline.timelineById.test, + status: TimelineStatus.active, + timelineType: TimelineType.template, + templateTimelineId: 'mock-template-timeline-id', + templateTimelineVersion: 1, + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const wrapper = mount( + + + + ); + expect( + wrapper.find('[data-test-subj="timeline-favorite-empty-star"]').first().prop('disabled') + ).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 165de178768f2a..5f5cab00fcbbed 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -10,7 +10,11 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; -import { TimelineTypeLiteral, TimelineType } from '../../../../../common/types/timeline'; +import { + TimelineTypeLiteral, + TimelineType, + TimelineStatus, +} from '../../../../../common/types/timeline'; import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; import { useShallowEqualSelector } from '../../../../common/hooks/use_selector'; @@ -36,6 +40,12 @@ const AddToFavoritesButtonComponent: React.FC = ({ ti (state) => (getTimeline(state, timelineId) ?? timelineDefaults).isFavorite ); + const status = useShallowEqualSelector( + (state) => (getTimeline(state, timelineId) ?? timelineDefaults).status + ); + + const disableFavoriteButton = status === TimelineStatus.immutable; + const handleClick = useCallback( () => dispatch(timelineActions.updateIsFavorite({ id: timelineId, isFavorite: !isFavorite })), [dispatch, timelineId, isFavorite] @@ -48,6 +58,7 @@ const AddToFavoritesButtonComponent: React.FC = ({ ti iconType={isFavorite ? 'starFilled' : 'starEmpty'} onClick={handleClick} data-test-subj={`timeline-favorite-${isFavorite ? 'filled' : 'empty'}-star`} + disabled={disableFavoriteButton} > {isFavorite ? i18n.REMOVE_FROM_FAVORITES : i18n.ADD_TO_FAVORITES} diff --git a/x-pack/plugins/telemetry_collection_xpack/tsconfig.json b/x-pack/plugins/telemetry_collection_xpack/tsconfig.json index 1221200c7548cf..f4c17c4317a9f0 100644 --- a/x-pack/plugins/telemetry_collection_xpack/tsconfig.json +++ b/x-pack/plugins/telemetry_collection_xpack/tsconfig.json @@ -11,7 +11,10 @@ "include": [ "common/**/*", "server/**/*", - "../../../typings/*" + "../../../typings/*", + "schema/xpack_monitoring.json", + "schema/xpack_plugins.json", + "schema/xpack_root.json", ], "references": [ { "path": "../../../src/core/tsconfig.json" }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c571e2da6f76f5..26521b420ae259 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -114,7 +114,6 @@ "advancedSettings.form.cancelButtonLabel": "変更をキャンセル", "advancedSettings.form.clearNoSearchResultText": " (検索結果を消去) ", "advancedSettings.form.clearSearchResultText": " (検索結果を消去) ", - "advancedSettings.form.noSearchResultText": "設定が見つかりませんでした {clearSearch}", "advancedSettings.form.requiresPageReloadToastButtonLabel": "ページを再読み込み", "advancedSettings.form.requiresPageReloadToastDescription": "設定を有効にするためにページの再読み込みが必要です。", "advancedSettings.form.saveButtonLabel": "変更を保存", @@ -3248,7 +3247,6 @@ "management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibanaの設定、その他を管理します。", "management.landing.text": "アプリの一覧は左側のメニューにあります。", "management.nav.label": "管理", - "management.nav.menu": "管理メニュー", "management.sections.dataTip": "クラスターデータとバックアップを管理します", "management.sections.dataTitle": "データ", "management.sections.ingestTip": "データを変換し、クラスターに読み込む方法を管理します", @@ -4915,17 +4913,6 @@ "visTypeVislib.heatmap.metricTitle": "値", "visTypeVislib.heatmap.segmentTitle": "X 軸", "visTypeVislib.heatmap.splitTitle": "チャートを分割", - "visTypePie.pie.metricTitle": "スライスサイズ", - "visTypePie.pie.pieDescription": "全体に対する比率でデータを比較します。", - "visTypePie.pie.pieTitle": "円", - "visTypePie.pie.segmentTitle": "スライスの分割", - "visTypePie.pie.splitTitle": "チャートを分割", - "visTypePie.editors.pie.donutLabel": "ドーナッツ", - "visTypePie.editors.pie.labelsSettingsTitle": "ラベル設定", - "visTypePie.editors.pie.pieSettingsTitle": "パイ設定", - "visTypePie.editors.pie.showLabelsLabel": "ラベルを表示", - "visTypePie.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", - "visTypePie.editors.pie.showValuesLabel": "値を表示", "visTypeVislib.vislib.errors.noResultsFoundTitle": "結果が見つかりませんでした", "visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}) 。構成されている最大値は {max} です。", "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", @@ -4937,8 +4924,56 @@ "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", "visTypeVislib.vislib.tooltip.valueLabel": "値", + "visTypePie.pie.metricTitle": "スライスサイズ", + "visTypePie.pie.pieDescription": "全体に対する比率でデータを比較します。", + "visTypePie.pie.pieTitle": "円", + "visTypePie.pie.segmentTitle": "スライスの分割", + "visTypePie.pie.splitTitle": "チャートを分割", + "visTypePie.editors.pie.donutLabel": "ドーナッツ", + "visTypePie.editors.pie.labelsSettingsTitle": "ラベル設定", + "visTypePie.editors.pie.pieSettingsTitle": "パイ設定", + "visTypePie.editors.pie.showLabelsLabel": "ラベルを表示", + "visTypePie.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", + "visTypePie.editors.pie.showValuesLabel": "値を表示", "visualizations.advancedSettings.visualization.legacyChartsLibrary.description": "Visualizeでエリア、折れ線、棒グラフのレガシーグラフライブラリを有効にします。", "visualizations.advancedSettings.visualization.legacyChartsLibrary.name": "レガシーグラフライブラリ", + "visualizations.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", + "visualizations.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", + "visualizations.disabledLabVisualizationLink": "ドキュメンテーションを表示", + "visualizations.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。", + "visualizations.disabledLabVisualizationTitle": "{title} はラボビジュアライゼーションです。", + "visualizations.displayName": "ビジュアライゼーション", + "visualizations.embeddable.placeholderTitle": "プレースホルダータイトル", + "visualizations.function.range.from.help": "範囲の開始", + "visualizations.function.range.help": "範囲オブジェクトを生成します", + "visualizations.function.range.to.help": "範囲の終了", + "visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名) ", + "visualizations.function.visDimension.error.accessor": "入力された列名は無効です。", + "visualizations.function.visDimension.format.help": "フォーマット", + "visualizations.function.visDimension.formatParams.help": "フォーマットパラメーター", + "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", + "visualizations.initializeWithoutIndexPatternErrorMessage": "インデックスパターンなしで集約を初期化しようとしています", + "visualizations.newVisWizard.aggBasedGroupDescription": "クラシック Visualize ライブラリを使用して、アグリゲーションに基づいてグラフを作成します。", + "visualizations.newVisWizard.aggBasedGroupTitle": "アグリゲーションに基づく", + "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", + "visualizations.newVisWizard.experimentalTitle": "実験的", + "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", + "visualizations.newVisWizard.exploreOptionLinkText": "探索オプション", + "visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング", + "visualizations.newVisWizard.goBackLink": "別のビジュアライゼーションを選択", + "visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。", + "visualizations.newVisWizard.learnMoreText": "詳細について", + "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", + "visualizations.newVisWizard.readDocumentationLink": "ドキュメンテーションを表示", + "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。", + "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", + "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索", + "visualizations.newVisWizard.title": "新規ビジュアライゼーション", + "visualizations.newVisWizard.toolsGroupTitle": "ツール", + "visualizations.noResultsFoundTitle": "結果が見つかりませんでした", + "visualizations.savedObjectName": "ビジュアライゼーション", + "visualizations.savingVisualizationFailed.errorMsg": "ビジュアライゼーションの保存が失敗しました", + "visualizations.visualizationTypeInvalidMessage": "無効なビジュアライゼーションタイプ \"{visType}\"", "visTypeXy.aggResponse.allDocsTitle": "すべてのドキュメント", "visTypeXy.area.areaDescription": "軸と線の間のデータを強調します。", "visTypeXy.area.areaTitle": "エリア", @@ -5060,43 +5095,6 @@ "visTypeXy.thresholdLine.style.dashedText": "鎖線", "visTypeXy.thresholdLine.style.dotdashedText": "点線", "visTypeXy.thresholdLine.style.fullText": "完全", - "visualizations.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", - "visualizations.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", - "visualizations.disabledLabVisualizationLink": "ドキュメンテーションを表示", - "visualizations.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。", - "visualizations.disabledLabVisualizationTitle": "{title} はラボビジュアライゼーションです。", - "visualizations.displayName": "ビジュアライゼーション", - "visualizations.embeddable.placeholderTitle": "プレースホルダータイトル", - "visualizations.function.range.from.help": "範囲の開始", - "visualizations.function.range.help": "範囲オブジェクトを生成します", - "visualizations.function.range.to.help": "範囲の終了", - "visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名) ", - "visualizations.function.visDimension.error.accessor": "入力された列名は無効です。", - "visualizations.function.visDimension.format.help": "フォーマット", - "visualizations.function.visDimension.formatParams.help": "フォーマットパラメーター", - "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", - "visualizations.initializeWithoutIndexPatternErrorMessage": "インデックスパターンなしで集約を初期化しようとしています", - "visualizations.newVisWizard.aggBasedGroupDescription": "クラシック Visualize ライブラリを使用して、アグリゲーションに基づいてグラフを作成します。", - "visualizations.newVisWizard.aggBasedGroupTitle": "アグリゲーションに基づく", - "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", - "visualizations.newVisWizard.experimentalTitle": "実験的", - "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", - "visualizations.newVisWizard.exploreOptionLinkText": "探索オプション", - "visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング", - "visualizations.newVisWizard.goBackLink": "別のビジュアライゼーションを選択", - "visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。", - "visualizations.newVisWizard.learnMoreText": "詳細について", - "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", - "visualizations.newVisWizard.readDocumentationLink": "ドキュメンテーションを表示", - "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。", - "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", - "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索", - "visualizations.newVisWizard.title": "新規ビジュアライゼーション", - "visualizations.newVisWizard.toolsGroupTitle": "ツール", - "visualizations.noResultsFoundTitle": "結果が見つかりませんでした", - "visualizations.savedObjectName": "ビジュアライゼーション", - "visualizations.savingVisualizationFailed.errorMsg": "ビジュアライゼーションの保存が失敗しました", - "visualizations.visualizationTypeInvalidMessage": "無効なビジュアライゼーションタイプ \"{visType}\"", "visualize.badge.readOnly.text": "読み取り専用", "visualize.badge.readOnly.tooltip": "ビジュアライゼーションをライブラリに保存できません", "visualize.byValue_pageHeading": "{originatingApp}アプリに埋め込まれた{chartType}タイプのビジュアライゼーション", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index bb0b740609594b..bbc5554bbb6a27 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -115,7 +115,6 @@ "advancedSettings.form.clearNoSearchResultText": " (清除搜索) ", "advancedSettings.form.clearSearchResultText": " (清除搜索) ", "advancedSettings.form.countOfSettingsChanged": "{unsavedCount} 个未保存{unsavedCount, plural, other {设置} }{hiddenCount, plural, =0 {} other {,# 个已隐藏} }", - "advancedSettings.form.noSearchResultText": "未找到设置{clearSearch}", "advancedSettings.form.requiresPageReloadToastButtonLabel": "重新加载页面", "advancedSettings.form.requiresPageReloadToastDescription": "一个或多个设置需要您重新加载页面才能生效。", "advancedSettings.form.saveButtonLabel": "保存更改", @@ -3270,7 +3269,6 @@ "management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", "management.landing.text": "应用的完整列表位于左侧菜单中。", "management.nav.label": "管理", - "management.nav.menu": "管理菜单", "management.sections.dataTip": "管理您的集群数据和备份", "management.sections.dataTitle": "数据", "management.sections.ingestTip": "管理如何转换数据并将其加载到集群中", @@ -4942,17 +4940,6 @@ "visTypeVislib.heatmap.metricTitle": "值", "visTypeVislib.heatmap.segmentTitle": "X 轴", "visTypeVislib.heatmap.splitTitle": "拆分图表", - "visTypePie.pie.metricTitle": "切片大小", - "visTypePie.pie.pieDescription": "以整体的比例比较数据。", - "visTypePie.pie.pieTitle": "饼图", - "visTypePie.pie.segmentTitle": "拆分切片", - "visTypePie.pie.splitTitle": "拆分图表", - "visTypePie.editors.pie.donutLabel": "圆环图", - "visTypePie.editors.pie.labelsSettingsTitle": "标签设置", - "visTypePie.editors.pie.pieSettingsTitle": "饼图设置", - "visTypePie.editors.pie.showLabelsLabel": "显示标签", - "visTypePie.editors.pie.showTopLevelOnlyLabel": "仅显示顶级", - "visTypePie.editors.pie.showValuesLabel": "显示值", "visTypeVislib.vislib.errors.noResultsFoundTitle": "找不到结果", "visTypeVislib.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "筛留值 {legendDataLabel}", @@ -4964,8 +4951,57 @@ "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}, 切换选项", "visTypeVislib.vislib.tooltip.fieldLabel": "字段", "visTypeVislib.vislib.tooltip.valueLabel": "值", + "visTypePie.pie.metricTitle": "切片大小", + "visTypePie.pie.pieDescription": "以整体的比例比较数据。", + "visTypePie.pie.pieTitle": "饼图", + "visTypePie.pie.segmentTitle": "拆分切片", + "visTypePie.pie.splitTitle": "拆分图表", + "visTypePie.editors.pie.donutLabel": "圆环图", + "visTypePie.editors.pie.labelsSettingsTitle": "标签设置", + "visTypePie.editors.pie.pieSettingsTitle": "饼图设置", + "visTypePie.editors.pie.showLabelsLabel": "显示标签", + "visTypePie.editors.pie.showTopLevelOnlyLabel": "仅显示顶级", + "visTypePie.editors.pie.showValuesLabel": "显示值", "visualizations.advancedSettings.visualization.legacyChartsLibrary.description": "在 Visualize 中启用面积图、折线图和条形图的旧版图表库。", "visualizations.advancedSettings.visualization.legacyChartsLibrary.name": "旧版图表库", + "visualizations.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。", + "visualizations.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化", + "visualizations.disabledLabVisualizationLink": "阅读文档", + "visualizations.disabledLabVisualizationMessage": "请在高级设置中打开实验模式,以查看实验性可视化。", + "visualizations.disabledLabVisualizationTitle": "{title} 为实验室可视化。", + "visualizations.displayName": "可视化", + "visualizations.embeddable.placeholderTitle": "占位符标题", + "visualizations.function.range.from.help": "范围起始", + "visualizations.function.range.help": "生成范围对象", + "visualizations.function.range.to.help": "范围结束", + "visualizations.function.visDimension.accessor.help": "要使用的数据集列 (列索引或列名称) ", + "visualizations.function.visDimension.error.accessor": "提供的列名称无效", + "visualizations.function.visDimension.format.help": "格式", + "visualizations.function.visDimension.formatParams.help": "格式参数", + "visualizations.function.visDimension.help": "生成 visConfig 维度对象", + "visualizations.initializeWithoutIndexPatternErrorMessage": "正在尝试在不使用索引模式的情况下初始化聚合", + "visualizations.newVisWizard.aggBasedGroupDescription": "使用我们的经典可视化库,基于聚合创建图表。", + "visualizations.newVisWizard.aggBasedGroupTitle": "基于聚合", + "visualizations.newVisWizard.chooseSourceTitle": "选择源", + "visualizations.newVisWizard.experimentalTitle": "实验性", + "visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", + "visualizations.newVisWizard.exploreOptionLinkText": "浏览选项", + "visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型", + "visualizations.newVisWizard.goBackLink": "选择不同的可视化", + "visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。", + "visualizations.newVisWizard.learnMoreText": "希望了解详情?", + "visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}", + "visualizations.newVisWizard.readDocumentationLink": "阅读文档", + "visualizations.newVisWizard.resultsFound": "{resultCount, plural, other {类型}}已找到", + "visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。", + "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式", + "visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索", + "visualizations.newVisWizard.title": "新建可视化", + "visualizations.newVisWizard.toolsGroupTitle": "工具", + "visualizations.noResultsFoundTitle": "找不到结果", + "visualizations.savedObjectName": "可视化", + "visualizations.savingVisualizationFailed.errorMsg": "保存可视化失败", + "visualizations.visualizationTypeInvalidMessage": "无效的可视化类型“{visType}”", "visTypeXy.aggResponse.allDocsTitle": "所有文档", "visTypeXy.area.areaDescription": "突出轴与线之间的数据。", "visTypeXy.area.areaTitle": "面积图", @@ -5087,44 +5123,6 @@ "visTypeXy.thresholdLine.style.dashedText": "虚线", "visTypeXy.thresholdLine.style.dotdashedText": "点虚线", "visTypeXy.thresholdLine.style.fullText": "实线", - "visualizations.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。", - "visualizations.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化", - "visualizations.disabledLabVisualizationLink": "阅读文档", - "visualizations.disabledLabVisualizationMessage": "请在高级设置中打开实验模式,以查看实验性可视化。", - "visualizations.disabledLabVisualizationTitle": "{title} 为实验室可视化。", - "visualizations.displayName": "可视化", - "visualizations.embeddable.placeholderTitle": "占位符标题", - "visualizations.function.range.from.help": "范围起始", - "visualizations.function.range.help": "生成范围对象", - "visualizations.function.range.to.help": "范围结束", - "visualizations.function.visDimension.accessor.help": "要使用的数据集列 (列索引或列名称) ", - "visualizations.function.visDimension.error.accessor": "提供的列名称无效", - "visualizations.function.visDimension.format.help": "格式", - "visualizations.function.visDimension.formatParams.help": "格式参数", - "visualizations.function.visDimension.help": "生成 visConfig 维度对象", - "visualizations.initializeWithoutIndexPatternErrorMessage": "正在尝试在不使用索引模式的情况下初始化聚合", - "visualizations.newVisWizard.aggBasedGroupDescription": "使用我们的经典可视化库,基于聚合创建图表。", - "visualizations.newVisWizard.aggBasedGroupTitle": "基于聚合", - "visualizations.newVisWizard.chooseSourceTitle": "选择源", - "visualizations.newVisWizard.experimentalTitle": "实验性", - "visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", - "visualizations.newVisWizard.exploreOptionLinkText": "浏览选项", - "visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型", - "visualizations.newVisWizard.goBackLink": "选择不同的可视化", - "visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。", - "visualizations.newVisWizard.learnMoreText": "希望了解详情?", - "visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}", - "visualizations.newVisWizard.readDocumentationLink": "阅读文档", - "visualizations.newVisWizard.resultsFound": "{resultCount, plural, other {类型}}已找到", - "visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。", - "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式", - "visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索", - "visualizations.newVisWizard.title": "新建可视化", - "visualizations.newVisWizard.toolsGroupTitle": "工具", - "visualizations.noResultsFoundTitle": "找不到结果", - "visualizations.savedObjectName": "可视化", - "visualizations.savingVisualizationFailed.errorMsg": "保存可视化失败", - "visualizations.visualizationTypeInvalidMessage": "无效的可视化类型“{visType}”", "visualize.badge.readOnly.text": "只读", "visualize.badge.readOnly.tooltip": "无法将可视化保存到库", "visualize.byValue_pageHeading": "已嵌入到 {originatingApp} 应用中的 {chartType} 类型可视化", diff --git a/x-pack/test/functional/es_archives/banners/multispace/mappings.json b/x-pack/test/functional/es_archives/banners/multispace/mappings.json index f3793c7ca6780b..9f3201d73abc12 100644 --- a/x-pack/test/functional/es_archives/banners/multispace/mappings.json +++ b/x-pack/test/functional/es_archives/banners/multispace/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json b/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json index ee860fe973f602..5001bb053e01be 100644 --- a/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json b/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json index 210fade40c648f..a842c20b6965e2 100644 --- a/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json b/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json index c66a45084441fa..29c56a751c038b 100644 --- a/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/feature_controls/security/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json b/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json index dafef0fa9c2c7e..0cd1a29f922414 100644 --- a/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/feature_controls/spaces/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json b/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json index 210fade40c648f..a842c20b6965e2 100644 --- a/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/dashboard_view_mode/mappings.json b/x-pack/test/functional/es_archives/dashboard_view_mode/mappings.json index 3558e89558a56a..0f58add932b0c7 100644 --- a/x-pack/test/functional/es_archives/dashboard_view_mode/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard_view_mode/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json index 61305d640fe3e8..9c27bc1458c7f1 100644 --- a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json +++ b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/functional/es_archives/discover/default/mappings.json b/x-pack/test/functional/es_archives/discover/default/mappings.json index 53bbe8a5baa5bd..519af2dd75b9e1 100644 --- a/x-pack/test/functional/es_archives/discover/default/mappings.json +++ b/x-pack/test/functional/es_archives/discover/default/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/discover/feature_controls/security/mappings.json b/x-pack/test/functional/es_archives/discover/feature_controls/security/mappings.json index 5e01af673ef2fc..69fceb7c9d209d 100644 --- a/x-pack/test/functional/es_archives/discover/feature_controls/security/mappings.json +++ b/x-pack/test/functional/es_archives/discover/feature_controls/security/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/es_archives/discover/feature_controls/spaces/mappings.json b/x-pack/test/functional/es_archives/discover/feature_controls/spaces/mappings.json index dafef0fa9c2c7e..0cd1a29f922414 100644 --- a/x-pack/test/functional/es_archives/discover/feature_controls/spaces/mappings.json +++ b/x-pack/test/functional/es_archives/discover/feature_controls/spaces/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/es_archives/empty_kibana/mappings.json b/x-pack/test/functional/es_archives/empty_kibana/mappings.json index 77eac534850a55..499c994780285b 100644 --- a/x-pack/test/functional/es_archives/empty_kibana/mappings.json +++ b/x-pack/test/functional/es_archives/empty_kibana/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json b/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json index 0024c6943ed1c0..63cc283f96d328 100644 --- a/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json +++ b/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/functional/es_archives/logstash/empty/mappings.json b/x-pack/test/functional/es_archives/logstash/empty/mappings.json index 096c68aefcc3cb..98fb761389a244 100644 --- a/x-pack/test/functional/es_archives/logstash/empty/mappings.json +++ b/x-pack/test/functional/es_archives/logstash/empty/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json b/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json index 9e56ff8a7a43a5..56a2e144ff89cb 100644 --- a/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json +++ b/x-pack/test/functional/es_archives/logstash/example_pipelines/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/maps/kibana/mappings.json b/x-pack/test/functional/es_archives/maps/kibana/mappings.json index 4786fb1f30f43b..d9a4ed16b51bd0 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/mappings.json +++ b/x-pack/test/functional/es_archives/maps/kibana/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/functional/es_archives/reporting/logs/mappings.json b/x-pack/test/functional/es_archives/reporting/logs/mappings.json index adf4050bb88c43..ffb8c85e8fd8bd 100644 --- a/x-pack/test/functional/es_archives/reporting/logs/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/logs/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/reporting/sales/mappings.json b/x-pack/test/functional/es_archives/reporting/sales/mappings.json index 3249708537b737..dbb8d396eb4963 100644 --- a/x-pack/test/functional/es_archives/reporting/sales/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/sales/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json b/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json index 6a416126d7f263..05ca4d8e8307e5 100644 --- a/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json +++ b/x-pack/test/functional/es_archives/saved_objects_management/feature_controls/security/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/es_archives/security/discover/mappings.json b/x-pack/test/functional/es_archives/security/discover/mappings.json index 3558e89558a56a..0f58add932b0c7 100644 --- a/x-pack/test/functional/es_archives/security/discover/mappings.json +++ b/x-pack/test/functional/es_archives/security/discover/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/security/flstest/kibana/mappings.json b/x-pack/test/functional/es_archives/security/flstest/kibana/mappings.json index fb2ed91f3ce140..ca97d0505f5b1b 100644 --- a/x-pack/test/functional/es_archives/security/flstest/kibana/mappings.json +++ b/x-pack/test/functional/es_archives/security/flstest/kibana/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json b/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json index 0d54500e83eb71..d860c19ceb64c0 100644 --- a/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/copy_saved_objects/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json b/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json index d0ad5570373c49..23a63e372a8558 100644 --- a/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/disabled_features/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json b/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json index f3793c7ca6780b..9f3201d73abc12 100644 --- a/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/enter_space/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json b/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json index 77eac534850a55..499c994780285b 100644 --- a/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/multi_space/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/spaces/selector/mappings.json b/x-pack/test/functional/es_archives/spaces/selector/mappings.json index 77eac534850a55..499c994780285b 100644 --- a/x-pack/test/functional/es_archives/spaces/selector/mappings.json +++ b/x-pack/test/functional/es_archives/spaces/selector/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json b/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json index d0ad5570373c49..23a63e372a8558 100644 --- a/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json +++ b/x-pack/test/functional/es_archives/timelion/feature_controls/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/functional/page_objects/account_settings_page.ts b/x-pack/test/functional/page_objects/account_settings_page.ts index 2aeb255ad7eb74..5bf5be9030b75b 100644 --- a/x-pack/test/functional/page_objects/account_settings_page.ts +++ b/x-pack/test/functional/page_objects/account_settings_page.ts @@ -6,33 +6,30 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function AccountSettingProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const userMenu = getService('userMenu'); +export class AccountSettingsPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly userMenu = this.ctx.getService('userMenu'); - class AccountSettingsPage { - async verifyAccountSettings(expectedEmail: string, expectedUserName: string) { - await userMenu.clickProvileLink(); + async verifyAccountSettings(expectedEmail: string, expectedUserName: string) { + await this.userMenu.clickProvileLink(); - const usernameField = await testSubjects.find('username'); - const userName = await usernameField.getVisibleText(); - expect(userName).to.be(expectedUserName); + const usernameField = await this.testSubjects.find('username'); + const userName = await usernameField.getVisibleText(); + expect(userName).to.be(expectedUserName); - const emailIdField = await testSubjects.find('email'); - const emailField = await emailIdField.getVisibleText(); - expect(emailField).to.be(expectedEmail); - await userMenu.closeMenu(); - } + const emailIdField = await this.testSubjects.find('email'); + const emailField = await emailIdField.getVisibleText(); + expect(emailField).to.be(expectedEmail); + await this.userMenu.closeMenu(); + } - async changePassword(currentPassword: string, newPassword: string) { - await testSubjects.setValue('currentPassword', currentPassword); - await testSubjects.setValue('newPassword', newPassword); - await testSubjects.setValue('confirmNewPassword', newPassword); - await testSubjects.click('changePasswordButton'); - await testSubjects.existOrFail('passwordUpdateSuccess'); - } + async changePassword(currentPassword: string, newPassword: string) { + await this.testSubjects.setValue('currentPassword', currentPassword); + await this.testSubjects.setValue('newPassword', newPassword); + await this.testSubjects.setValue('confirmNewPassword', newPassword); + await this.testSubjects.click('changePasswordButton'); + await this.testSubjects.existOrFail('passwordUpdateSuccess'); } - return new AccountSettingsPage(); } diff --git a/x-pack/test/functional/page_objects/banners_page.ts b/x-pack/test/functional/page_objects/banners_page.ts index d2e4e43cec1171..b9e9b29b4eeb2a 100644 --- a/x-pack/test/functional/page_objects/banners_page.ts +++ b/x-pack/test/functional/page_objects/banners_page.ts @@ -5,26 +5,22 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function BannersPageProvider({ getService }: FtrProviderContext) { - const find = getService('find'); +export class BannersPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); - class BannersPage { - isTopBannerVisible() { - return find.existsByCssSelector('.header__topBanner .kbnUserBanner__container'); - } + isTopBannerVisible() { + return this.find.existsByCssSelector('.header__topBanner .kbnUserBanner__container'); + } - async getTopBannerText() { - if (!(await this.isTopBannerVisible())) { - return ''; - } - const bannerContainer = await find.byCssSelector( - '.header__topBanner .kbnUserBanner__container' - ); - return bannerContainer.getVisibleText(); + async getTopBannerText() { + if (!(await this.isTopBannerVisible())) { + return ''; } + const bannerContainer = await this.find.byCssSelector( + '.header__topBanner .kbnUserBanner__container' + ); + return bannerContainer.getVisibleText(); } - - return new BannersPage(); } diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 99d7172651a4d1..c2c92b2370381a 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -7,717 +7,718 @@ import _ from 'lodash'; import { APP_ID } from '../../../plugins/maps/common/constants'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function GisPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'header', 'timePicker', 'visualize']); - - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const inspector = getService('inspector'); - const find = getService('find'); - const queryBar = getService('queryBar'); - const comboBox = getService('comboBox'); - const renderable = getService('renderable'); - const browser = getService('browser'); - const menuToggle = getService('menuToggle'); - const listingTable = getService('listingTable'); - const monacoEditor = getService('monacoEditor'); - const dashboardPanelActions = getService('dashboardPanelActions'); - - const setViewPopoverToggle = menuToggle.create({ +import { FtrService } from '../ftr_provider_context'; + +function escapeLayerName(layerName: string) { + return layerName.split(' ').join('_'); +} + +export class GisPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly timePicker = this.ctx.getPageObject('timePicker'); + private readonly visualize = this.ctx.getPageObject('visualize'); + + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly inspector = this.ctx.getService('inspector'); + private readonly find = this.ctx.getService('find'); + private readonly queryBar = this.ctx.getService('queryBar'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly renderable = this.ctx.getService('renderable'); + private readonly browser = this.ctx.getService('browser'); + private readonly listingTable = this.ctx.getService('listingTable'); + private readonly monacoEditor = this.ctx.getService('monacoEditor'); + private readonly dashboardPanelActions = this.ctx.getService('dashboardPanelActions'); + + private readonly setViewPopoverToggle = this.ctx.getService('menuToggle').create({ name: 'SetView Popover', menuTestSubject: 'mapSetViewForm', toggleButtonTestSubject: 'toggleSetViewVisibilityButton', }); - function escapeLayerName(layerName: string) { - return layerName.split(' ').join('_'); - } + basePath = ''; - class GisPage { - basePath; - - constructor() { - this.basePath = ''; - } + setBasePath(basePath: string) { + this.basePath = basePath; + } - setBasePath(basePath: string) { - this.basePath = basePath; - } + async setAbsoluteRange(start: string, end: string) { + await this.timePicker.setAbsoluteRange(start, end); + await this.waitForLayersToLoad(); + } - async setAbsoluteRange(start: string, end: string) { - await PageObjects.timePicker.setAbsoluteRange(start, end); - await this.waitForLayersToLoad(); - } + async setAndSubmitQuery(query: string) { + await this.queryBar.setQuery(query); + await this.queryBar.submitQuery(); + await this.waitForLayersToLoad(); + } - async setAndSubmitQuery(query: string) { - await queryBar.setQuery(query); - await queryBar.submitQuery(); - await this.waitForLayersToLoad(); - } + async refreshQuery() { + await this.queryBar.submitQuery(); + await this.waitForLayersToLoad(); + } - async refreshQuery() { - await queryBar.submitQuery(); - await this.waitForLayersToLoad(); - } + async enterFullScreen() { + this.log.debug(`enterFullScreen`); + await this.testSubjects.click('mapsFullScreenMode'); + await this.retry.try(async () => { + await this.testSubjects.exists('exitFullScreenModeLogo'); + }); + await this.waitForLayersToLoad(); + } - async enterFullScreen() { - log.debug(`enterFullScreen`); - await testSubjects.click('mapsFullScreenMode'); - await retry.try(async () => { - await testSubjects.exists('exitFullScreenModeLogo'); - }); - await this.waitForLayersToLoad(); + // TODO combine with dashboard full screen into a service + async existFullScreen() { + this.log.debug(`existFullScreen`); + const isFullScreen = await this.testSubjects.exists('exitFullScreenModeLogo'); + if (isFullScreen) { + await this.testSubjects.click('exitFullScreenModeLogo'); } + } - // TODO combine with dashboard full screen into a service - async existFullScreen() { - log.debug(`existFullScreen`); - const isFullScreen = await testSubjects.exists('exitFullScreenModeLogo'); - if (isFullScreen) { - await testSubjects.click('exitFullScreenModeLogo'); + // Since there are no DOM indicators that signal when map pan and zoom actions are complete, + // this method waits until the map view has stabilized, signaling that the panning/zooming is complete. + // Pass origView parameter when the new map view determinition is async + // so method knows when panning/zooming has started. + async waitForMapPanAndZoom(origView?: { lon: number; lat: number; zoom: number }) { + await this.retry.try(async () => { + this.log.debug('Waiting for map pan and zoom to complete'); + const prevView = await this.getView(); + await this.common.sleep(1000); + const currentView = await this.getView(); + if (origView && _.isEqual(origView, currentView)) { + throw new Error('Map pan and zoom has not started yet'); } - } + if (!_.isEqual(prevView, currentView)) { + throw new Error('Map is still panning and zooming'); + } + }); + await this.waitForLayersToLoad(); + } - // Since there are no DOM indicators that signal when map pan and zoom actions are complete, - // this method waits until the map view has stabilized, signaling that the panning/zooming is complete. - // Pass origView parameter when the new map view determinition is async - // so method knows when panning/zooming has started. - async waitForMapPanAndZoom(origView?: { lon: number; lat: number; zoom: number }) { - await retry.try(async () => { - log.debug('Waiting for map pan and zoom to complete'); - const prevView = await this.getView(); - await PageObjects.common.sleep(1000); - const currentView = await this.getView(); - if (origView && _.isEqual(origView, currentView)) { - throw new Error('Map pan and zoom has not started yet'); - } - if (!_.isEqual(prevView, currentView)) { - throw new Error('Map is still panning and zooming'); - } - }); - await this.waitForLayersToLoad(); - } + async waitForLayersToLoad() { + this.log.debug('Wait for layers to load'); + await this.retry.try(async () => { + const tableOfContents = await this.testSubjects.find('mapLayerTOC'); + await tableOfContents.waitForDeletedByCssSelector('.euiLoadingSpinner'); + }); + } - async waitForLayersToLoad() { - log.debug('Wait for layers to load'); - await retry.try(async () => { - const tableOfContents = await testSubjects.find('mapLayerTOC'); - await tableOfContents.waitForDeletedByCssSelector('.euiLoadingSpinner'); - }); - } + async waitForLayerDeleted(layerName: string) { + this.log.debug('Wait for layer deleted'); + await this.retry.waitFor('Layer to be deleted', async () => { + const doesLayerExist = await this.doesLayerExist(layerName); + return !doesLayerExist; + }); + } - async waitForLayerDeleted(layerName: string) { - log.debug('Wait for layer deleted'); - await retry.waitFor('Layer to be deleted', async () => { - const doesLayerExist = await this.doesLayerExist(layerName); - return !doesLayerExist; - }); - } + // use the search filter box to narrow the results down to a single + // entry, or at least to a single page of results + async loadSavedMap(name: string) { + this.log.debug(`Load Saved Map ${name}`); - // use the search filter box to narrow the results down to a single - // entry, or at least to a single page of results - async loadSavedMap(name: string) { - log.debug(`Load Saved Map ${name}`); - - await retry.try(async () => { - await this.searchForMapWithName(name); - await listingTable.clickItemLink('map', name); - await PageObjects.header.waitUntilLoadingHasFinished(); - // check Map landing page is not present - await testSubjects.missingOrFail('mapLandingPage', { timeout: 10000 }); - }); + await this.retry.try(async () => { + await this.searchForMapWithName(name); + await this.listingTable.clickItemLink('map', name); + await this.header.waitUntilLoadingHasFinished(); + // check Map landing page is not present + await this.testSubjects.missingOrFail('mapLandingPage', { timeout: 10000 }); + }); - await this.waitForLayersToLoad(); - } + await this.waitForLayersToLoad(); + } - async deleteSavedMaps(search: string) { - await this.searchForMapWithName(search); - await listingTable.checkListingSelectAllCheckbox(); - await listingTable.clickDeleteSelected(); - await PageObjects.common.clickConfirmOnModal(); + async deleteSavedMaps(search: string) { + await this.searchForMapWithName(search); + await this.listingTable.checkListingSelectAllCheckbox(); + await this.listingTable.clickDeleteSelected(); + await this.common.clickConfirmOnModal(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + await this.header.waitUntilLoadingHasFinished(); + } - async openNewMap() { - log.debug(`Open new Map`); + async openNewMap() { + this.log.debug(`Open new Map`); - // Navigate directly because we don't need to go through the map listing - // page. The listing page is skipped if there are no saved objects - await PageObjects.common.navigateToUrlWithBrowserHistory(APP_ID, '/map'); - await renderable.waitForRender(); - } + // Navigate directly because we don't need to go through the map listing + // page. The listing page is skipped if there are no saved objects + await this.common.navigateToUrlWithBrowserHistory(APP_ID, '/map'); + await this.renderable.waitForRender(); + } - async saveMap(name: string, redirectToOrigin = true, tags?: string[]) { - await testSubjects.click('mapSaveButton'); - await testSubjects.setValue('savedObjectTitle', name); - await PageObjects.visualize.setSaveModalValues(name, { - addToDashboard: false, - redirectToOrigin, - saveAsNew: true, - }); - if (tags) { - await testSubjects.click('savedObjectTagSelector'); - for (const tagName of tags) { - await testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); - } - await testSubjects.click('savedObjectTitle'); + async saveMap(name: string, redirectToOrigin = true, tags?: string[]) { + await this.testSubjects.click('mapSaveButton'); + await this.testSubjects.setValue('savedObjectTitle', name); + await this.visualize.setSaveModalValues(name, { + addToDashboard: false, + redirectToOrigin, + saveAsNew: true, + }); + if (tags) { + await this.testSubjects.click('savedObjectTagSelector'); + for (const tagName of tags) { + await this.testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); } - await testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton'); - } - - async clickSaveAndReturnButton() { - await testSubjects.click('mapSaveAndReturnButton'); - } - - async expectMissingSaveAndReturnButton() { - await testSubjects.missingOrFail('mapSaveAndReturnButton'); + await this.testSubjects.click('savedObjectTitle'); } + await this.testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton'); + } - async expectMissingSaveButton() { - await testSubjects.missingOrFail('mapSaveButton'); - } + async clickSaveAndReturnButton() { + await this.testSubjects.click('mapSaveAndReturnButton'); + } - async expectMissingCreateNewButton() { - await testSubjects.missingOrFail('newItemButton'); - } + async expectMissingSaveAndReturnButton() { + await this.testSubjects.missingOrFail('mapSaveAndReturnButton'); + } - async expectMissingAddLayerButton() { - await testSubjects.missingOrFail('addLayerButton'); - } + async expectMissingSaveButton() { + await this.testSubjects.missingOrFail('mapSaveButton'); + } - async expectExistAddLayerButton() { - await testSubjects.existOrFail('addLayerButton'); - } + async expectMissingCreateNewButton() { + await this.testSubjects.missingOrFail('newItemButton'); + } - async onMapListingPage() { - log.debug(`onMapListingPage`); - return await listingTable.onListingPage('map'); - } + async expectMissingAddLayerButton() { + await this.testSubjects.missingOrFail('addLayerButton'); + } - async onMapPage() { - log.debug(`onMapPage`); - return await testSubjects.exists('mapLayerTOC', { - timeout: 5000, - }); - } + async expectExistAddLayerButton() { + await this.testSubjects.existOrFail('addLayerButton'); + } - async searchForMapWithName(name: string) { - log.debug(`searchForMapWithName: ${name}`); + async onMapListingPage() { + this.log.debug(`onMapListingPage`); + return await this.listingTable.onListingPage('map'); + } - await this.gotoMapListingPage(); + async onMapPage() { + this.log.debug(`onMapPage`); + return await this.testSubjects.exists('mapLayerTOC', { + timeout: 5000, + }); + } - await listingTable.searchForItemWithName(name); + async searchForMapWithName(name: string) { + this.log.debug(`searchForMapWithName: ${name}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + await this.gotoMapListingPage(); - async getHits() { - await inspector.open(); - await inspector.openInspectorRequestsView(); - const requestStats = await inspector.getTableData(); - const hits = this.getInspectorStatRowHit(requestStats, 'Hits'); - await inspector.close(); - return hits; - } + await this.listingTable.searchForItemWithName(name); - async gotoMapListingPage() { - log.debug('gotoMapListingPage'); - const onPage = await this.onMapListingPage(); - if (!onPage) { - await retry.try(async () => { - await PageObjects.common.navigateToUrlWithBrowserHistory(APP_ID, '/'); - const onMapListingPage = await this.onMapListingPage(); - if (!onMapListingPage) throw new Error('Not on map listing page.'); - }); - } - } + await this.header.waitUntilLoadingHasFinished(); + } - async searchAndExpectItemsCount(name: string, count: number) { - await this.gotoMapListingPage(); + async getHits() { + await this.inspector.open(); + await this.inspector.openInspectorRequestsView(); + const requestStats = await this.inspector.getTableData(); + const hits = this.getInspectorStatRowHit(requestStats, 'Hits'); + await this.inspector.close(); + return hits; + } - log.debug(`searchAndExpectItemsCount: ${name}`); - await listingTable.searchAndExpectItemsCount('map', name, count); + async gotoMapListingPage() { + this.log.debug('gotoMapListingPage'); + const onPage = await this.onMapListingPage(); + if (!onPage) { + await this.retry.try(async () => { + await this.common.navigateToUrlWithBrowserHistory(APP_ID, '/'); + const onMapListingPage = await this.onMapListingPage(); + if (!onMapListingPage) throw new Error('Not on map listing page.'); + }); } + } - async setView(lat: number, lon: number, zoom: number) { - log.debug( - `Set view lat: ${lat.toString()}, lon: ${lon.toString()}, zoom: ${zoom.toString()}` - ); - await setViewPopoverToggle.open(); - await testSubjects.setValue('latitudeInput', lat.toString()); - await testSubjects.setValue('longitudeInput', lon.toString()); - await testSubjects.setValue('zoomInput', zoom.toString()); - await testSubjects.click('submitViewButton'); - await this.waitForMapPanAndZoom(); - } + async searchAndExpectItemsCount(name: string, count: number) { + await this.gotoMapListingPage(); - async getView() { - log.debug('Get view'); - await setViewPopoverToggle.open(); - // this method is regularly called within a retry, so we need to reduce the timeouts - // of the retries done within the getAttribute method in order to ensure that they fail - // early enough to retry getView() - const getAttributeOptions = { - tryTimeout: 5000, - findTimeout: 1000, - }; - - const lat = await testSubjects.getAttribute('latitudeInput', 'value', getAttributeOptions); - const lon = await testSubjects.getAttribute('longitudeInput', 'value', getAttributeOptions); - const zoom = await testSubjects.getAttribute('zoomInput', 'value', getAttributeOptions); - - await setViewPopoverToggle.close(); - return { - lat: parseFloat(lat), - lon: parseFloat(lon), - zoom: parseFloat(zoom), - }; - } + this.log.debug(`searchAndExpectItemsCount: ${name}`); + await this.listingTable.searchAndExpectItemsCount('map', name, count); + } - async toggleLayerVisibility(layerName: string) { - log.debug(`Toggle layer visibility, layer: ${layerName}`); - await this.openLayerTocActionsPanel(layerName); - await testSubjects.click('layerVisibilityToggleButton'); - } + async setView(lat: number, lon: number, zoom: number) { + this.log.debug( + `Set view lat: ${lat.toString()}, lon: ${lon.toString()}, zoom: ${zoom.toString()}` + ); + await this.setViewPopoverToggle.open(); + await this.testSubjects.setValue('latitudeInput', lat.toString()); + await this.testSubjects.setValue('longitudeInput', lon.toString()); + await this.testSubjects.setValue('zoomInput', zoom.toString()); + await this.testSubjects.click('submitViewButton'); + await this.waitForMapPanAndZoom(); + } - async closeLegend() { - const isOpen = await testSubjects.exists('mapLayerTOC'); - if (isOpen) { - await testSubjects.click('mapToggleLegendButton'); - await testSubjects.waitForDeleted('mapLayerTOC'); - } - } + async getView() { + this.log.debug('Get view'); + await this.setViewPopoverToggle.open(); + // this method is regularly called within a retry, so we need to reduce the timeouts + // of the retries done within the getAttribute method in order to ensure that they fail + // early enough to retry getView() + const getAttributeOptions = { + tryTimeout: 5000, + findTimeout: 1000, + }; + + const lat = await this.testSubjects.getAttribute('latitudeInput', 'value', getAttributeOptions); + const lon = await this.testSubjects.getAttribute( + 'longitudeInput', + 'value', + getAttributeOptions + ); + const zoom = await this.testSubjects.getAttribute('zoomInput', 'value', getAttributeOptions); + + await this.setViewPopoverToggle.close(); + return { + lat: parseFloat(lat), + lon: parseFloat(lon), + zoom: parseFloat(zoom), + }; + } - async clickFitToBounds(layerName: string) { - log.debug(`Fit to bounds, layer: ${layerName}`); - const origView = await this.getView(); - await this.openLayerTocActionsPanel(layerName); - await testSubjects.click('fitToBoundsButton'); - await this.waitForMapPanAndZoom(origView); - } + async toggleLayerVisibility(layerName: string) { + this.log.debug(`Toggle layer visibility, layer: ${layerName}`); + await this.openLayerTocActionsPanel(layerName); + await this.testSubjects.click('layerVisibilityToggleButton'); + } - async openLayerTocActionsPanel(layerName: string) { - const escapedDisplayName = escapeLayerName(layerName); - const isOpen = await testSubjects.exists(`layerTocActionsPanel${escapedDisplayName}`); - if (!isOpen) { - await testSubjects.click(`layerTocActionsPanelToggleButton${escapedDisplayName}`); - } + async closeLegend() { + const isOpen = await this.testSubjects.exists('mapLayerTOC'); + if (isOpen) { + await this.testSubjects.click('mapToggleLegendButton'); + await this.testSubjects.waitForDeleted('mapLayerTOC'); } + } - async openLayerPanel(layerName: string) { - log.debug(`Open layer panel, layer: ${layerName}`); - await this.openLayerTocActionsPanel(layerName); - await testSubjects.click('editLayerButton'); - } + async clickFitToBounds(layerName: string) { + this.log.debug(`Fit to bounds, layer: ${layerName}`); + const origView = await this.getView(); + await this.openLayerTocActionsPanel(layerName); + await this.testSubjects.click('fitToBoundsButton'); + await this.waitForMapPanAndZoom(origView); + } - async closeLayerPanel() { - await testSubjects.click('layerPanelCancelButton'); - await this.waitForLayersToLoad(); + async openLayerTocActionsPanel(layerName: string) { + const escapedDisplayName = escapeLayerName(layerName); + const isOpen = await this.testSubjects.exists(`layerTocActionsPanel${escapedDisplayName}`); + if (!isOpen) { + await this.testSubjects.click(`layerTocActionsPanelToggleButton${escapedDisplayName}`); } + } - async getLayerTOCDetails(layerName: string) { - return await testSubjects.getVisibleText(`mapLayerTOCDetails${escapeLayerName(layerName)}`); - } + async openLayerPanel(layerName: string) { + this.log.debug(`Open layer panel, layer: ${layerName}`); + await this.openLayerTocActionsPanel(layerName); + await this.testSubjects.click('editLayerButton'); + } - async disableApplyGlobalQuery() { - const isSelected = await testSubjects.getAttribute( - 'mapLayerPanelApplyGlobalQueryCheckbox', - 'aria-checked' - ); - if (isSelected === 'true') { - await retry.try(async () => { - log.debug(`disabling applyGlobalQuery`); - await testSubjects.click('mapLayerPanelApplyGlobalQueryCheckbox'); - const isStillSelected = await testSubjects.getAttribute( - 'mapLayerPanelApplyGlobalQueryCheckbox', - 'aria-checked' - ); - if (isStillSelected === 'true') { - throw new Error('applyGlobalQuery not disabled'); - } - }); - await this.waitForLayersToLoad(); - } - } + async closeLayerPanel() { + await this.testSubjects.click('layerPanelCancelButton'); + await this.waitForLayersToLoad(); + } - async getNumberOfLayers() { - const tocEntries = await find.allByCssSelector('.mapTocEntry'); - return tocEntries.length; - } + async getLayerTOCDetails(layerName: string) { + return await this.testSubjects.getVisibleText( + `mapLayerTOCDetails${escapeLayerName(layerName)}` + ); + } - async doesLayerExist(layerName: string) { - return await testSubjects.exists( - `layerTocActionsPanelToggleButton${escapeLayerName(layerName)}` - ); + async disableApplyGlobalQuery() { + const isSelected = await this.testSubjects.getAttribute( + 'mapLayerPanelApplyGlobalQueryCheckbox', + 'aria-checked' + ); + if (isSelected === 'true') { + await this.retry.try(async () => { + this.log.debug(`disabling applyGlobalQuery`); + await this.testSubjects.click('mapLayerPanelApplyGlobalQueryCheckbox'); + const isStillSelected = await this.testSubjects.getAttribute( + 'mapLayerPanelApplyGlobalQueryCheckbox', + 'aria-checked' + ); + if (isStillSelected === 'true') { + throw new Error('applyGlobalQuery not disabled'); + } + }); + await this.waitForLayersToLoad(); } + } - async hasFilePickerLoadedFile(fileName: string) { - log.debug(`Has file picker loaded file ${fileName}`); - const filePickerText = await find.byCssSelector('.euiFilePicker__promptText'); - const filePickerTextContent = await filePickerText.getVisibleText(); + async getNumberOfLayers() { + const tocEntries = await this.find.allByCssSelector('.mapTocEntry'); + return tocEntries.length; + } - return fileName === filePickerTextContent; - } + async doesLayerExist(layerName: string) { + return await this.testSubjects.exists( + `layerTocActionsPanelToggleButton${escapeLayerName(layerName)}` + ); + } - /* - * Layer panel utility functions - */ - async isLayerAddPanelOpen() { - log.debug(`Is layer add panel open`); - return await testSubjects.exists('layerAddForm'); - } + async hasFilePickerLoadedFile(fileName: string) { + this.log.debug(`Has file picker loaded file ${fileName}`); + const filePickerText = await this.find.byCssSelector('.euiFilePicker__promptText'); + const filePickerTextContent = await filePickerText.getVisibleText(); - async waitForLayerAddPanelClosed() { - let layerAddPanelOpen = false; - await retry.waitForWithTimeout('Layer add panel closed', 1000, async () => { - layerAddPanelOpen = await this.isLayerAddPanelOpen(); - return !layerAddPanelOpen; - }); - } + return fileName === filePickerTextContent; + } - async clickAddLayer() { - log.debug('Click add layer'); - await retry.try(async () => { - await testSubjects.click('addLayerButton'); - const isOpen = await this.isLayerAddPanelOpen(); - if (!isOpen) { - throw new Error('Add layer panel still not open, trying again.'); - } - }); - } + /* + * Layer panel utility functions + */ + async isLayerAddPanelOpen() { + this.log.debug(`Is layer add panel open`); + return await this.testSubjects.exists('layerAddForm'); + } - async cancelLayerAdd(layerName: string) { - log.debug(`Cancel layer add`); - const cancelExists = await testSubjects.exists('layerAddCancelButton'); - if (cancelExists) { - await testSubjects.click('layerAddCancelButton'); - await this.waitForLayerAddPanelClosed(); - if (layerName) { - await this.waitForLayerDeleted(layerName); - } - } - } + async waitForLayerAddPanelClosed() { + let layerAddPanelOpen = false; + await this.retry.waitForWithTimeout('Layer add panel closed', 1000, async () => { + layerAddPanelOpen = await this.isLayerAddPanelOpen(); + return !layerAddPanelOpen; + }); + } - async closeOrCancelLayer(layerName: string) { - log.debug(`Close or cancel layer add`); - const cancelExists = await testSubjects.exists('layerAddCancelButton'); - const closeExists = await testSubjects.exists('layerPanelCancelButton'); - if (cancelExists) { - log.debug(`Cancel layer add.`); - await testSubjects.click('layerAddCancelButton'); - } else if (closeExists) { - log.debug(`Close layer add.`); - await testSubjects.click('layerPanelCancelButton'); - } else { - log.debug(`No need to close or cancel.`); - return; + async clickAddLayer() { + this.log.debug('Click add layer'); + await this.retry.try(async () => { + await this.testSubjects.click('addLayerButton'); + const isOpen = await this.isLayerAddPanelOpen(); + if (!isOpen) { + throw new Error('Add layer panel still not open, trying again.'); } + }); + } + async cancelLayerAdd(layerName: string) { + this.log.debug(`Cancel layer add`); + const cancelExists = await this.testSubjects.exists('layerAddCancelButton'); + if (cancelExists) { + await this.testSubjects.click('layerAddCancelButton'); await this.waitForLayerAddPanelClosed(); if (layerName) { await this.waitForLayerDeleted(layerName); } } + } - async importFileButtonEnabled() { - log.debug(`Check "Import file" button enabled`); - const importFileButton = await testSubjects.find('importFileButton'); - const isDisabled = await importFileButton.getAttribute('disabled'); - return !isDisabled; + async closeOrCancelLayer(layerName: string) { + this.log.debug(`Close or cancel layer add`); + const cancelExists = await this.testSubjects.exists('layerAddCancelButton'); + const closeExists = await this.testSubjects.exists('layerPanelCancelButton'); + if (cancelExists) { + this.log.debug(`Cancel layer add.`); + await this.testSubjects.click('layerAddCancelButton'); + } else if (closeExists) { + this.log.debug(`Close layer add.`); + await this.testSubjects.click('layerPanelCancelButton'); + } else { + this.log.debug(`No need to close or cancel.`); + return; + } + + await this.waitForLayerAddPanelClosed(); + if (layerName) { + await this.waitForLayerDeleted(layerName); } + } - async importLayerReadyForAdd() { - log.debug(`Wait until import complete`); - await testSubjects.find('indexRespCopyButton', 5000); - let layerAddReady = false; - await retry.waitForWithTimeout('Add layer button ready', 2000, async () => { - layerAddReady = await this.importFileButtonEnabled(); - return layerAddReady; - }); - return layerAddReady; - } + async importFileButtonEnabled() { + this.log.debug(`Check "Import file" button enabled`); + const importFileButton = await this.testSubjects.find('importFileButton'); + const isDisabled = await importFileButton.getAttribute('disabled'); + return !isDisabled; + } - async clickImportFileButton() { - log.debug(`Click "Import file" button`); - await testSubjects.click('importFileButton'); - } + async importLayerReadyForAdd() { + this.log.debug(`Wait until import complete`); + await this.testSubjects.find('indexRespCopyButton', 5000); + let layerAddReady = false; + await this.retry.waitForWithTimeout('Add layer button ready', 2000, async () => { + layerAddReady = await this.importFileButtonEnabled(); + return layerAddReady; + }); + return layerAddReady; + } - async setIndexName(indexName: string) { - log.debug(`Set index name to: ${indexName}`); - await testSubjects.setValue('fileUploadIndexNameInput', indexName); - } + async clickImportFileButton() { + this.log.debug(`Click "Import file" button`); + await this.testSubjects.click('importFileButton'); + } - async setIndexType(indexType: string) { - log.debug(`Set index type to: ${indexType}`); - await testSubjects.selectValue('fileImportIndexSelect', indexType); - } + async setIndexName(indexName: string) { + this.log.debug(`Set index name to: ${indexName}`); + await this.testSubjects.setValue('fileUploadIndexNameInput', indexName); + } - async indexTypeOptionExists(indexType: string) { - log.debug(`Check index type "${indexType}" available`); - return await find.existsByCssSelector( - `select[data-test-subj="fileImportIndexSelect"] > option[value="${indexType}"]` - ); - } + async setIndexType(indexType: string) { + this.log.debug(`Set index type to: ${indexType}`); + await this.testSubjects.selectValue('fileImportIndexSelect', indexType); + } - async clickCopyButton(dataTestSubj: string): Promise { - log.debug(`Click ${dataTestSubj} copy button`); + async indexTypeOptionExists(indexType: string) { + this.log.debug(`Check index type "${indexType}" available`); + return await this.find.existsByCssSelector( + `select[data-test-subj="fileImportIndexSelect"] > option[value="${indexType}"]` + ); + } - await testSubjects.click(dataTestSubj); + async clickCopyButton(dataTestSubj: string): Promise { + this.log.debug(`Click ${dataTestSubj} copy button`); - return await browser.getClipboardValue(); - } + await this.testSubjects.click(dataTestSubj); - async getIndexResults() { - return JSON.parse(await this.clickCopyButton('indexRespCopyButton')); - } + return await this.browser.getClipboardValue(); + } - async getIndexPatternResults() { - return JSON.parse(await this.clickCopyButton('indexPatternRespCopyButton')); - } + async getIndexResults() { + return JSON.parse(await this.clickCopyButton('indexRespCopyButton')); + } - async setLayerQuery(layerName: string, query: string) { - await this.openLayerPanel(layerName); - await testSubjects.click('mapLayerPanelOpenFilterEditorButton'); - const filterEditorContainer = await testSubjects.find('mapFilterEditor'); - const queryBarInFilterEditor = await testSubjects.findDescendant( - 'queryInput', - filterEditorContainer - ); - await queryBarInFilterEditor.click(); - const input = await find.activeElement(); - await retry.try(async () => { - await input.clearValue(); - await input.type(query); - const value = await input.getAttribute('value'); - if (value !== query) { - throw new Error(`Layer query set to ${value} instead of ${query}`); - } - }); - await testSubjects.click('mapFilterEditorSubmitButton'); - await this.waitForLayersToLoad(); - } + async getIndexPatternResults() { + return JSON.parse(await this.clickCopyButton('indexPatternRespCopyButton')); + } - async setJoinWhereQuery(layerName: string, query: string) { - await this.openLayerPanel(layerName); - await testSubjects.click('mapJoinWhereExpressionButton'); - const filterEditorContainer = await testSubjects.find('mapJoinWhereFilterEditor'); - const queryBarInFilterEditor = await testSubjects.findDescendant( - 'queryInput', - filterEditorContainer - ); - await queryBarInFilterEditor.click(); - const input = await find.activeElement(); + async setLayerQuery(layerName: string, query: string) { + await this.openLayerPanel(layerName); + await this.testSubjects.click('mapLayerPanelOpenFilterEditorButton'); + const filterEditorContainer = await this.testSubjects.find('mapFilterEditor'); + const queryBarInFilterEditor = await this.testSubjects.findDescendant( + 'queryInput', + filterEditorContainer + ); + await queryBarInFilterEditor.click(); + const input = await this.find.activeElement(); + await this.retry.try(async () => { await input.clearValue(); await input.type(query); - await testSubjects.click('mapWhereFilterEditorSubmitButton'); - await this.waitForLayersToLoad(); - } + const value = await input.getAttribute('value'); + if (value !== query) { + throw new Error(`Layer query set to ${value} instead of ${query}`); + } + }); + await this.testSubjects.click('mapFilterEditorSubmitButton'); + await this.waitForLayersToLoad(); + } - async selectEMSBoundariesSource() { - log.debug(`Select EMS boundaries source`); - await testSubjects.click('emsBoundaries'); - } + async setJoinWhereQuery(layerName: string, query: string) { + await this.openLayerPanel(layerName); + await this.testSubjects.click('mapJoinWhereExpressionButton'); + const filterEditorContainer = await this.testSubjects.find('mapJoinWhereFilterEditor'); + const queryBarInFilterEditor = await this.testSubjects.findDescendant( + 'queryInput', + filterEditorContainer + ); + await queryBarInFilterEditor.click(); + const input = await this.find.activeElement(); + await input.clearValue(); + await input.type(query); + await this.testSubjects.click('mapWhereFilterEditorSubmitButton'); + await this.waitForLayersToLoad(); + } - async selectGeoJsonUploadSource() { - log.debug(`Select upload geojson source`); - await testSubjects.click('uploadGeoJson'); - } + async selectEMSBoundariesSource() { + this.log.debug(`Select EMS boundaries source`); + await this.testSubjects.click('emsBoundaries'); + } - async uploadJsonFileForIndexing(path: string) { - await PageObjects.common.setFileInputPath(path); - log.debug(`File selected`); + async selectGeoJsonUploadSource() { + this.log.debug(`Select upload geojson source`); + await this.testSubjects.click('uploadGeoJson'); + } - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.waitForLayersToLoad(); - } + async uploadJsonFileForIndexing(path: string) { + await this.common.setFileInputPath(path); + this.log.debug(`File selected`); - // Returns first layer by default - async selectVectorLayer(vectorLayerName: string) { - log.debug(`Select EMS vector layer ${vectorLayerName}`); - if (!vectorLayerName) { - throw new Error(`You did not provide the EMS layer to select`); - } - await comboBox.set('emsVectorComboBox', vectorLayerName); - await this.waitForLayersToLoad(); - } + await this.header.waitUntilLoadingHasFinished(); + await this.waitForLayersToLoad(); + } - async removeLayer(layerName: string) { - log.debug(`Remove layer ${layerName}`); - await this.openLayerPanel(layerName); - await testSubjects.click(`mapRemoveLayerButton`); - await this.waitForLayerDeleted(layerName); + // Returns first layer by default + async selectVectorLayer(vectorLayerName: string) { + this.log.debug(`Select EMS vector layer ${vectorLayerName}`); + if (!vectorLayerName) { + throw new Error(`You did not provide the EMS layer to select`); } + await this.comboBox.set('emsVectorComboBox', vectorLayerName); + await this.waitForLayersToLoad(); + } - async getLayerErrorText(layerName: string) { - log.debug(`Remove layer ${layerName}`); - await this.openLayerPanel(layerName); - return await testSubjects.getVisibleText(`layerErrorMessage`); - } + async removeLayer(layerName: string) { + this.log.debug(`Remove layer ${layerName}`); + await this.openLayerPanel(layerName); + await this.testSubjects.click(`mapRemoveLayerButton`); + await this.waitForLayerDeleted(layerName); + } - async fullScreenModeMenuItemExists() { - return await testSubjects.exists('mapsFullScreenMode'); - } + async getLayerErrorText(layerName: string) { + this.log.debug(`Remove layer ${layerName}`); + await this.openLayerPanel(layerName); + return await this.testSubjects.getVisibleText(`layerErrorMessage`); + } - async clickFullScreenMode() { - log.debug(`clickFullScreenMode`); - await testSubjects.click('mapsFullScreenMode'); - } + async fullScreenModeMenuItemExists() { + return await this.testSubjects.exists('mapsFullScreenMode'); + } - async exitFullScreenLogoButtonExists() { - return await testSubjects.exists('exitFullScreenModeLogo'); - } + async clickFullScreenMode() { + this.log.debug(`clickFullScreenMode`); + await this.testSubjects.click('mapsFullScreenMode'); + } - async getExitFullScreenLogoButton() { - return await testSubjects.find('exitFullScreenModeLogo'); - } + async exitFullScreenLogoButtonExists() { + return await this.testSubjects.exists('exitFullScreenModeLogo'); + } - async clickExitFullScreenTextButton() { - await testSubjects.click('exitFullScreenModeText'); - } + async getExitFullScreenLogoButton() { + return await this.testSubjects.find('exitFullScreenModeLogo'); + } - async openInspectorMapView() { - await inspector.openInspectorView('~inspectorViewChooserMap'); - } + async clickExitFullScreenTextButton() { + await this.testSubjects.click('exitFullScreenModeText'); + } - // Method should only be used when multiple requests are expected - // RequestSelector will only display inspectorRequestChooser when there is more than one request - async openInspectorRequest(requestName: string) { - await inspector.open(); - await inspector.openInspectorRequestsView(); - log.debug(`Open Inspector request ${requestName}`); - await testSubjects.click('inspectorRequestChooser'); - await testSubjects.click(`inspectorRequestChooser${requestName}`); - } + async openInspectorMapView() { + await this.inspector.openInspectorView('~inspectorViewChooserMap'); + } - async doesInspectorHaveRequests() { - await inspector.open(); - await inspector.openInspectorRequestsView(); - return await testSubjects.exists('inspectorNoRequestsMessage'); - } + // Method should only be used when multiple requests are expected + // RequestSelector will only display inspectorRequestChooser when there is more than one request + async openInspectorRequest(requestName: string) { + await this.inspector.open(); + await this.inspector.openInspectorRequestsView(); + this.log.debug(`Open Inspector request ${requestName}`); + await this.testSubjects.click('inspectorRequestChooser'); + await this.testSubjects.click(`inspectorRequestChooser${requestName}`); + } - async getMapboxStyle() { - log.debug('getMapboxStyle'); - await inspector.open(); - await this.openInspectorMapView(); - await testSubjects.click('mapboxStyleTab'); - const mapboxStyleContainer = await testSubjects.find('mapboxStyleContainer'); - const mapboxStyleJson = await mapboxStyleContainer.getVisibleText(); - await inspector.close(); - let mapboxStyle; - try { - mapboxStyle = JSON.parse(mapboxStyleJson); - } catch (err) { - throw new Error(`Unable to parse mapbox style, error: ${err.message}`); - } - return mapboxStyle; - } + async doesInspectorHaveRequests() { + await this.inspector.open(); + await this.inspector.openInspectorRequestsView(); + return await this.testSubjects.exists('inspectorNoRequestsMessage'); + } - async getResponse(requestName: string) { - await inspector.open(); - const response = await this._getResponse(requestName); - await inspector.close(); - return response; - } + async getMapboxStyle() { + this.log.debug('getMapboxStyle'); + await this.inspector.open(); + await this.openInspectorMapView(); + await this.testSubjects.click('mapboxStyleTab'); + const mapboxStyleContainer = await this.testSubjects.find('mapboxStyleContainer'); + const mapboxStyleJson = await mapboxStyleContainer.getVisibleText(); + await this.inspector.close(); + let mapboxStyle; + try { + mapboxStyle = JSON.parse(mapboxStyleJson); + } catch (err) { + throw new Error(`Unable to parse mapbox style, error: ${err.message}`); + } + return mapboxStyle; + } - async _getResponse(requestName: string) { - if (requestName) { - await testSubjects.click('inspectorRequestChooser'); - await testSubjects.click(`inspectorRequestChooser${requestName}`); - } - await inspector.openInspectorRequestsView(); - await testSubjects.click('inspectorRequestDetailResponse'); - await find.byCssSelector('.react-monaco-editor-container'); - const responseBody = await monacoEditor.getCodeEditorValue(); - return JSON.parse(responseBody); - } + async getResponse(requestName: string) { + await this.inspector.open(); + const response = await this._getResponse(requestName); + await this.inspector.close(); + return response; + } - async getResponseFromDashboardPanel(panelTitle: string, requestName: string) { - await dashboardPanelActions.openInspectorByTitle(panelTitle); - const response = await this._getResponse(requestName); - await inspector.close(); - return response; + async _getResponse(requestName: string) { + if (requestName) { + await this.testSubjects.click('inspectorRequestChooser'); + await this.testSubjects.click(`inspectorRequestChooser${requestName}`); } + await this.inspector.openInspectorRequestsView(); + await this.testSubjects.click('inspectorRequestDetailResponse'); + await this.find.byCssSelector('.react-monaco-editor-container'); + const responseBody = await this.monacoEditor.getCodeEditorValue(); + return JSON.parse(responseBody); + } - getInspectorStatRowHit(stats: string[][], rowName: string) { - const STATS_ROW_NAME_INDEX = 0; - const STATS_ROW_VALUE_INDEX = 1; + async getResponseFromDashboardPanel(panelTitle: string, requestName: string) { + await this.dashboardPanelActions.openInspectorByTitle(panelTitle); + const response = await this._getResponse(requestName); + await this.inspector.close(); + return response; + } - const statsRow = stats.find((row) => { - return row[STATS_ROW_NAME_INDEX] === rowName; - }); - if (!statsRow) { - throw new Error(`Unable to find value for row ${rowName} in ${stats}`); - } + getInspectorStatRowHit(stats: string[][], rowName: string) { + const STATS_ROW_NAME_INDEX = 0; + const STATS_ROW_VALUE_INDEX = 1; - return statsRow[STATS_ROW_VALUE_INDEX]; + const statsRow = stats.find((row) => { + return row[STATS_ROW_NAME_INDEX] === rowName; + }); + if (!statsRow) { + throw new Error(`Unable to find value for row ${rowName} in ${stats}`); } - async triggerSingleRefresh(refreshInterval: number) { - log.debug(`triggerSingleRefresh, refreshInterval: ${refreshInterval}`); - await PageObjects.timePicker.resumeAutoRefresh(); - log.debug('waiting to give time for refresh timer to fire'); - await PageObjects.common.sleep(refreshInterval + refreshInterval / 2); - await PageObjects.timePicker.pauseAutoRefresh(); - await this.waitForLayersToLoad(); - } + return statsRow[STATS_ROW_VALUE_INDEX]; + } - async lockTooltipAtPosition(xOffset: number, yOffset: number) { - await retry.try(async () => { - const mapContainerElement = await testSubjects.find('mapContainer'); - await mapContainerElement.moveMouseTo({ xOffset, yOffset }); - await mapContainerElement.clickMouseButton({ xOffset, yOffset }); - // Close button is only displayed with tooltip is locked - const hasCloseButton = await testSubjects.exists('mapTooltipCloseButton'); - if (!hasCloseButton) { - throw new Error('Tooltip is not locked at position'); - } - }); - } + async triggerSingleRefresh(refreshInterval: number) { + this.log.debug(`triggerSingleRefresh, refreshInterval: ${refreshInterval}`); + await this.timePicker.resumeAutoRefresh(); + this.log.debug('waiting to give time for refresh timer to fire'); + await this.common.sleep(refreshInterval + refreshInterval / 2); + await this.timePicker.pauseAutoRefresh(); + await this.waitForLayersToLoad(); + } - async setStyleByValue(styleName: string, fieldName: string) { - await testSubjects.selectValue(`staticDynamicSelect_${styleName}`, 'DYNAMIC'); - await comboBox.set(`styleFieldSelect_${styleName}`, fieldName); - } + async lockTooltipAtPosition(xOffset: number, yOffset: number) { + await this.retry.try(async () => { + const mapContainerElement = await this.testSubjects.find('mapContainer'); + await mapContainerElement.moveMouseTo({ xOffset, yOffset }); + await mapContainerElement.clickMouseButton({ xOffset, yOffset }); + // Close button is only displayed with tooltip is locked + const hasCloseButton = await this.testSubjects.exists('mapTooltipCloseButton'); + if (!hasCloseButton) { + throw new Error('Tooltip is not locked at position'); + } + }); + } - async selectCustomColorRamp(styleName: string) { - // open super select menu - await testSubjects.click(`colorMapSelect_${styleName}`); - // Click option - await testSubjects.click(`colorMapSelectOption_CUSTOM_COLOR_MAP`); - } + async setStyleByValue(styleName: string, fieldName: string) { + await this.testSubjects.selectValue(`staticDynamicSelect_${styleName}`, 'DYNAMIC'); + await this.comboBox.set(`styleFieldSelect_${styleName}`, fieldName); + } - async getCategorySuggestions() { - return await comboBox.getOptionsList(`colorStopInput1`); - } + async selectCustomColorRamp(styleName: string) { + // open super select menu + await this.testSubjects.click(`colorMapSelect_${styleName}`); + // Click option + await this.testSubjects.click(`colorMapSelectOption_CUSTOM_COLOR_MAP`); + } - async enableAutoFitToBounds() { - await testSubjects.click('openSettingsButton'); - const isEnabled = await testSubjects.getAttribute('autoFitToDataBoundsSwitch', 'checked'); - if (!isEnabled) { - await retry.try(async () => { - await testSubjects.click('autoFitToDataBoundsSwitch'); - const ensureEnabled = await testSubjects.getAttribute( - 'autoFitToDataBoundsSwitch', - 'checked' - ); - if (!ensureEnabled) { - throw new Error('autoFitToDataBoundsSwitch is not enabled'); - } - }); - } - await testSubjects.click('mapSettingSubmitButton'); - } + async getCategorySuggestions() { + return await this.comboBox.getOptionsList(`colorStopInput1`); + } - async refreshAndClearUnsavedChangesWarning() { - await browser.refresh(); - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); + async enableAutoFitToBounds() { + await this.testSubjects.click('openSettingsButton'); + const isEnabled = await this.testSubjects.getAttribute('autoFitToDataBoundsSwitch', 'checked'); + if (!isEnabled) { + await this.retry.try(async () => { + await this.testSubjects.click('autoFitToDataBoundsSwitch'); + const ensureEnabled = await this.testSubjects.getAttribute( + 'autoFitToDataBoundsSwitch', + 'checked' + ); + if (!ensureEnabled) { + throw new Error('autoFitToDataBoundsSwitch is not enabled'); + } + }); } + await this.testSubjects.click('mapSettingSubmitButton'); + } + + async refreshAndClearUnsavedChangesWarning() { + await this.browser.refresh(); + // accept alert if it pops up + const alert = await this.browser.getAlert(); + await alert?.accept(); } - return new GisPage(); } diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 28d72ef8446159..bd9e5100e0c572 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -6,7 +6,7 @@ */ import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; interface Node { circle: WebElementWrapper; @@ -20,264 +20,263 @@ interface Edge { element: WebElementWrapper; } -export function GraphPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header']); - const retry = getService('retry'); - const browser = getService('browser'); - - class GraphPage { - async selectIndexPattern(pattern: string) { - await testSubjects.click('graphDatasourceButton'); - await testSubjects.click(`savedObjectTitle${pattern.split(' ').join('-')}`); - // wait till add fields button becomes available, then the index pattern is loaded completely - await testSubjects.waitForAttributeToChange( - 'graph-add-field-button', - 'aria-disabled', - 'false' - ); - // Need document focus to not be on `graphDatasourceButton` so its tooltip does not - // obscure the next intended click area. Focus the adjaecnt input instead. - await testSubjects.click('queryInput'); - } +export class GraphPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + + async selectIndexPattern(pattern: string) { + await this.testSubjects.click('graphDatasourceButton'); + await this.testSubjects.click(`savedObjectTitle${pattern.split(' ').join('-')}`); + // wait till add fields button becomes available, then the index pattern is loaded completely + await this.testSubjects.waitForAttributeToChange( + 'graph-add-field-button', + 'aria-disabled', + 'false' + ); + // Need document focus to not be on `graphDatasourceButton` so its tooltip does not + // obscure the next intended click area. Focus the adjaecnt input instead. + await this.testSubjects.click('queryInput'); + } - async clickAddField() { - await retry.try(async () => { - await testSubjects.click('graph-add-field-button'); - await testSubjects.existOrFail('graph-field-search', { timeout: 3000 }); - }); - } + async clickAddField() { + await this.retry.try(async () => { + await this.testSubjects.click('graph-add-field-button'); + await this.testSubjects.existOrFail('graph-field-search', { timeout: 3000 }); + }); + } - async selectField(field: string) { - await testSubjects.setValue('graph-field-search', field); - await find.clickDisplayedByCssSelector(`[title="${field}"]`); - } + async selectField(field: string) { + await this.testSubjects.setValue('graph-field-search', field); + await this.find.clickDisplayedByCssSelector(`[title="${field}"]`); + } - async addFields(fields: string[]) { - log.debug('click Add Field icon'); - await this.clickAddField(); - for (const field of fields) { - log.debug('select field ' + field); - await this.selectField(field); - } + async addFields(fields: string[]) { + this.log.debug('click Add Field icon'); + await this.clickAddField(); + for (const field of fields) { + this.log.debug('select field ' + field); + await this.selectField(field); } + } - async query(str: string) { - await testSubjects.click('queryInput'); - await testSubjects.setValue('queryInput', str); - await testSubjects.click('graph-explore-button'); - } + async query(str: string) { + await this.testSubjects.click('queryInput'); + await this.testSubjects.setValue('queryInput', str); + await this.testSubjects.click('graph-explore-button'); + } - private getPositionAsString(x: string, y: string) { - return `${x}-${y}`; - } + private getPositionAsString(x: string, y: string) { + return `${x}-${y}`; + } - private async getCirclePosition(element: WebElementWrapper) { - const x = await element.getAttribute('cx'); - const y = await element.getAttribute('cy'); - return this.getPositionAsString(x, y); - } + private async getCirclePosition(element: WebElementWrapper) { + const x = await element.getAttribute('cx'); + const y = await element.getAttribute('cy'); + return this.getPositionAsString(x, y); + } - private async getLinePositions(element: WebElementWrapper) { - const x1 = await element.getAttribute('x1'); - const y1 = await element.getAttribute('y1'); - const x2 = await element.getAttribute('x2'); - const y2 = await element.getAttribute('y2'); - return [this.getPositionAsString(x1, y1), this.getPositionAsString(x2, y2)]; - } + private async getLinePositions(element: WebElementWrapper) { + const x1 = await element.getAttribute('x1'); + const y1 = await element.getAttribute('y1'); + const x2 = await element.getAttribute('x2'); + const y2 = await element.getAttribute('y2'); + return [this.getPositionAsString(x1, y1), this.getPositionAsString(x2, y2)]; + } - async isolateEdge(from: string, to: string) { - // select all nodes - await testSubjects.click('graphSelectAll'); - - // go through all nodes and remove every node not source or target - const selections = await find.allByCssSelector('.gphSelectionList__field'); - for (const selection of selections) { - const labelElement = await selection.findByTagName('span'); - const selectionLabel = await labelElement.getVisibleText(); - log.debug('Looking at selection ' + selectionLabel); - if (selectionLabel !== from && selectionLabel !== to) { - (await selection.findByClassName('gphNode__text')).click(); - await PageObjects.common.sleep(200); - } + async isolateEdge(from: string, to: string) { + // select all nodes + await this.testSubjects.click('graphSelectAll'); + + // go through all nodes and remove every node not source or target + const selections = await this.find.allByCssSelector('.gphSelectionList__field'); + for (const selection of selections) { + const labelElement = await selection.findByTagName('span'); + const selectionLabel = await labelElement.getVisibleText(); + this.log.debug('Looking at selection ' + selectionLabel); + if (selectionLabel !== from && selectionLabel !== to) { + (await selection.findByClassName('gphNode__text')).click(); + await this.common.sleep(200); } + } - // invert selection to select all nodes not source or target - await testSubjects.click('graphInvertSelection'); + // invert selection to select all nodes not source or target + await this.testSubjects.click('graphInvertSelection'); - // remove all other nodes - await testSubjects.click('graphRemoveSelection'); - } + // remove all other nodes + await this.testSubjects.click('graphRemoveSelection'); + } - async stopLayout() { - if (await testSubjects.exists('graphPauseLayout')) { - await testSubjects.click('graphPauseLayout'); - } + async stopLayout() { + if (await this.testSubjects.exists('graphPauseLayout')) { + await this.testSubjects.click('graphPauseLayout'); } + } - async startLayout() { - if (await testSubjects.exists('graphResumeLayout')) { - await testSubjects.click('graphResumeLayout'); - } + async startLayout() { + if (await this.testSubjects.exists('graphResumeLayout')) { + await this.testSubjects.click('graphResumeLayout'); } + } - async getGraphObjects() { - await this.stopLayout(); - // read node labels directly from DOM because getVisibleText is not reliable for the way the graph is rendered - const nodeNames: string[] = await browser.execute(` - const elements = document.querySelectorAll('#graphSvg text.gphNode__label'); - return [...elements].map(element => element.innerHTML); - `); - const graphElements = await find.allByCssSelector('#graphSvg line, #graphSvg circle'); - const nodes: Node[] = []; - const nodePositionMap: Record = {}; - const edges: Edge[] = []; - - // find all nodes and save their positions - for (const element of graphElements) { - const tagName: string = await element.getTagName(); - // check the position of the circle element - if (tagName === 'circle') { - nodes.push({ circle: element, label: nodeNames[nodes.length] }); - const position = await this.getCirclePosition(element); - nodePositionMap[position] = nodes.length - 1; - } + async getGraphObjects() { + await this.stopLayout(); + // read node labels directly from DOM because getVisibleText is not reliable for the way the graph is rendered + const nodeNames: string[] = await this.browser.execute(` + const elements = document.querySelectorAll('#graphSvg text.gphNode__label'); + return [...elements].map(element => element.innerHTML); + `); + const graphElements = await this.find.allByCssSelector('#graphSvg line, #graphSvg circle'); + const nodes: Node[] = []; + const nodePositionMap: Record = {}; + const edges: Edge[] = []; + + // find all nodes and save their positions + for (const element of graphElements) { + const tagName: string = await element.getTagName(); + // check the position of the circle element + if (tagName === 'circle') { + nodes.push({ circle: element, label: nodeNames[nodes.length] }); + const position = await this.getCirclePosition(element); + nodePositionMap[position] = nodes.length - 1; } + } - // find all edges - for (const element of graphElements) { - const tagName: string = await element.getTagName(); - if (tagName === 'line') { - const [sourcePosition, targetPosition] = await this.getLinePositions(element); - const lineStyle = await element.getAttribute('style'); - // grep out the width of the connection from the style attribute - const strokeWidth = Number(/stroke-width: ?(\d+(\.\d+)?)/.exec(lineStyle)![1]); - edges.push({ - element, - width: strokeWidth, - // look up source and target node by matching start and end coordinates - // of the edges and the nodes - sourceNode: nodes[nodePositionMap[sourcePosition]], - targetNode: nodes[nodePositionMap[targetPosition]], - }); - } + // find all edges + for (const element of graphElements) { + const tagName: string = await element.getTagName(); + if (tagName === 'line') { + const [sourcePosition, targetPosition] = await this.getLinePositions(element); + const lineStyle = await element.getAttribute('style'); + // grep out the width of the connection from the style attribute + const strokeWidth = Number(/stroke-width: ?(\d+(\.\d+)?)/.exec(lineStyle)![1]); + edges.push({ + element, + width: strokeWidth, + // look up source and target node by matching start and end coordinates + // of the edges and the nodes + sourceNode: nodes[nodePositionMap[sourcePosition]], + targetNode: nodes[nodePositionMap[targetPosition]], + }); } + } - await this.startLayout(); + await this.startLayout(); - return { - nodes, - edges, - }; - } + return { + nodes, + edges, + }; + } - async createWorkspace() { - await testSubjects.click('graphCreateGraphPromptButton'); - } + async createWorkspace() { + await this.testSubjects.click('graphCreateGraphPromptButton'); + } - async newGraph() { - log.debug('Click New Workspace'); - await retry.try(async () => { - await testSubjects.click('graphNewButton'); - await testSubjects.existOrFail('confirmModal', { timeout: 3000 }); - }); - await PageObjects.common.clickConfirmOnModal(); - await testSubjects.existOrFail('graphGuidancePanel'); - } + async newGraph() { + this.log.debug('Click New Workspace'); + await this.retry.try(async () => { + await this.testSubjects.click('graphNewButton'); + await this.testSubjects.existOrFail('confirmModal', { timeout: 3000 }); + }); + await this.common.clickConfirmOnModal(); + await this.testSubjects.existOrFail('graphGuidancePanel'); + } - async saveGraph(name: string) { - await retry.try(async () => { - await testSubjects.click('graphSaveButton'); - await testSubjects.existOrFail('savedObjectTitle', { timeout: 3000 }); - }); - await testSubjects.setValue('savedObjectTitle', name); - await testSubjects.click('confirmSaveSavedObjectButton'); + async saveGraph(name: string) { + await this.retry.try(async () => { + await this.testSubjects.click('graphSaveButton'); + await this.testSubjects.existOrFail('savedObjectTitle', { timeout: 3000 }); + }); + await this.testSubjects.setValue('savedObjectTitle', name); + await this.testSubjects.click('confirmSaveSavedObjectButton'); - // Confirm that the Graph has been saved. - return await testSubjects.exists('saveGraphSuccess', { timeout: 10000 }); - } + // Confirm that the Graph has been saved. + return await this.testSubjects.exists('saveGraphSuccess', { timeout: 10000 }); + } - async getSearchFilter() { - const searchFilter = await find.allByCssSelector('main .euiFieldSearch'); - return searchFilter[0]; - } + async getSearchFilter() { + const searchFilter = await this.find.allByCssSelector('main .euiFieldSearch'); + return searchFilter[0]; + } - async searchForWorkspaceWithName(name: string) { - await retry.try(async () => { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - await searchFilter.type(name); - await PageObjects.common.pressEnterKey(); - await find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); - }); - - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async searchForWorkspaceWithName(name: string) { + await this.retry.try(async () => { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); + await searchFilter.type(name); + await this.common.pressEnterKey(); + await this.find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); + }); + + await this.header.waitUntilLoadingHasFinished(); + } - async goToListingPage() { - await retry.try(async () => { - await testSubjects.click('breadcrumb graphHomeBreadcrumb first'); - await testSubjects.existOrFail('graphLandingPage', { timeout: 3000 }); - }); - } + async goToListingPage() { + await this.retry.try(async () => { + await this.testSubjects.click('breadcrumb graphHomeBreadcrumb first'); + await this.testSubjects.existOrFail('graphLandingPage', { timeout: 3000 }); + }); + } - async openGraph(name: string) { - await this.goToListingPage(); - await this.searchForWorkspaceWithName(name); - await find.clickByLinkText(name); - // wait for nodes to show up - if (!(await find.existsByCssSelector('.gphNode', 10000))) { - throw new Error('nodes did not show up'); - } - // let force simulation settle down before continuing - await PageObjects.common.sleep(5000); + async openGraph(name: string) { + await this.goToListingPage(); + await this.searchForWorkspaceWithName(name); + await this.find.clickByLinkText(name); + // wait for nodes to show up + if (!(await this.find.existsByCssSelector('.gphNode', 10000))) { + throw new Error('nodes did not show up'); } + // let force simulation settle down before continuing + await this.common.sleep(5000); + } - async deleteGraph(name: string) { - await testSubjects.click('checkboxSelectAll'); - await this.clickDeleteSelectedWorkspaces(); - await PageObjects.common.clickConfirmOnModal(); - await testSubjects.find('graphCreateGraphPromptButton'); - } + async deleteGraph(name: string) { + await this.testSubjects.click('checkboxSelectAll'); + await this.clickDeleteSelectedWorkspaces(); + await this.common.clickConfirmOnModal(); + await this.testSubjects.find('graphCreateGraphPromptButton'); + } - async getWorkspaceCount() { - const workspaceTitles = await find.allByCssSelector( - '[data-test-subj^="graphListingTitleLink"]' - ); - return workspaceTitles.length; - } + async getWorkspaceCount() { + const workspaceTitles = await this.find.allByCssSelector( + '[data-test-subj^="graphListingTitleLink"]' + ); + return workspaceTitles.length; + } - async clickDeleteSelectedWorkspaces() { - await testSubjects.click('deleteSelectedItems'); - } + async clickDeleteSelectedWorkspaces() { + await this.testSubjects.click('deleteSelectedItems'); + } - async getVennTerm1() { - const el = await find.byCssSelector('span.gphLinkSummary__term--1'); - return await el.getVisibleText(); - } + async getVennTerm1() { + const el = await this.find.byCssSelector('span.gphLinkSummary__term--1'); + return await el.getVisibleText(); + } - async getVennTerm2() { - const el = await find.byCssSelector('span.gphLinkSummary__term--2'); - return await el.getVisibleText(); - } + async getVennTerm2() { + const el = await this.find.byCssSelector('span.gphLinkSummary__term--2'); + return await el.getVisibleText(); + } - async getSmallVennTerm1() { - const el = await find.byCssSelector('small.gphLinkSummary__term--1'); - return await el.getVisibleText(); - } + async getSmallVennTerm1() { + const el = await this.find.byCssSelector('small.gphLinkSummary__term--1'); + return await el.getVisibleText(); + } - async getSmallVennTerm12() { - const el = await find.byCssSelector('small.gphLinkSummary__term--1-2'); - return await el.getVisibleText(); - } + async getSmallVennTerm12() { + const el = await this.find.byCssSelector('small.gphLinkSummary__term--1-2'); + return await el.getVisibleText(); + } - async getSmallVennTerm2() { - const el = await find.byCssSelector('small.gphLinkSummary__term--2'); - return await el.getVisibleText(); - } + async getSmallVennTerm2() { + const el = await this.find.byCssSelector('small.gphLinkSummary__term--2'); + return await el.getVisibleText(); } - return new GraphPage(); } diff --git a/x-pack/test/functional/page_objects/grok_debugger_page.ts b/x-pack/test/functional/page_objects/grok_debugger_page.ts index dc875588f381ee..89017683f632ff 100644 --- a/x-pack/test/functional/page_objects/grok_debugger_page.ts +++ b/x-pack/test/functional/page_objects/grok_debugger_page.ts @@ -5,16 +5,14 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function GrokDebuggerPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['common']); - const grokDebugger = getService('grokDebugger'); +export class GrokDebuggerPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly grokDebugger = this.ctx.getService('grokDebugger'); - return new (class LogstashPage { - async gotoGrokDebugger() { - await PageObjects.common.navigateToApp('grokDebugger'); - await grokDebugger.assertExists(); - } - })(); + async gotoGrokDebugger() { + await this.common.navigateToApp('grokDebugger'); + await this.grokDebugger.assertExists(); + } } diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index e83420a9cea1d9..a362fd7e4b7c2a 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -8,22 +8,21 @@ import { pageObjects as kibanaFunctionalPageObjects } from '../../../../test/functional/page_objects'; import { CanvasPageProvider } from './canvas_page'; -import { SecurityPageProvider } from './security_page'; -import { MonitoringPageProvider } from './monitoring_page'; -// @ts-ignore not ts yet -import { LogstashPageProvider } from './logstash_page'; -import { GraphPageProvider } from './graph_page'; -import { GrokDebuggerPageProvider } from './grok_debugger_page'; -import { WatcherPageProvider } from './watcher_page'; -import { ReportingPageProvider } from './reporting_page'; -import { AccountSettingProvider } from './account_settings_page'; +import { SecurityPageObject } from './security_page'; +import { MonitoringPageObject } from './monitoring_page'; +import { LogstashPageObject } from './logstash_page'; +import { GraphPageObject } from './graph_page'; +import { GrokDebuggerPageObject } from './grok_debugger_page'; +import { WatcherPageObject } from './watcher_page'; +import { ReportingPageObject } from './reporting_page'; +import { AccountSettingsPageObject } from './account_settings_page'; import { InfraHomePageProvider } from './infra_home_page'; import { InfraLogsPageProvider } from './infra_logs_page'; -import { GisPageProvider } from './gis_page'; -import { StatusPagePageProvider } from './status_page'; -import { UpgradeAssistantPageProvider } from './upgrade_assistant_page'; -import { RollupPageProvider } from './rollup_page'; -import { UptimePageProvider } from './uptime_page'; +import { GisPageObject } from './gis_page'; +import { StatusPageObject } from './status_page'; +import { UpgradeAssistantPageObject } from './upgrade_assistant_page'; +import { RollupPageObject } from './rollup_page'; +import { UptimePageObject } from './uptime_page'; import { SyntheticsIntegrationPageProvider } from './synthetics_integration_page'; import { ApiKeysPageProvider } from './api_keys_page'; import { LicenseManagementPageProvider } from './license_management_page'; @@ -36,43 +35,43 @@ import { CopySavedObjectsToSpacePageProvider } from './copy_saved_objects_to_spa import { LensPageProvider } from './lens_page'; import { InfraMetricExplorerProvider } from './infra_metric_explorer'; import { RoleMappingsPageProvider } from './role_mappings_page'; -import { SpaceSelectorPageProvider } from './space_selector_page'; +import { SpaceSelectorPageObject } from './space_selector_page'; import { IngestPipelinesPageProvider } from './ingest_pipelines_page'; -import { TagManagementPageProvider } from './tag_management_page'; -import { NavigationalSearchProvider } from './navigational_search'; +import { TagManagementPageObject } from './tag_management_page'; +import { NavigationalSearchPageObject } from './navigational_search'; import { SearchSessionsPageProvider } from './search_sessions_management_page'; -import { DetectionsPageProvider } from '../../security_solution_ftr/page_objects/detections'; -import { BannersPageProvider } from './banners_page'; +import { DetectionsPageObject } from '../../security_solution_ftr/page_objects/detections'; +import { BannersPageObject } from './banners_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones export const pageObjects = { ...kibanaFunctionalPageObjects, canvas: CanvasPageProvider, - security: SecurityPageProvider, - accountSetting: AccountSettingProvider, - monitoring: MonitoringPageProvider, - logstash: LogstashPageProvider, - graph: GraphPageProvider, - grokDebugger: GrokDebuggerPageProvider, - watcher: WatcherPageProvider, - reporting: ReportingPageProvider, - spaceSelector: SpaceSelectorPageProvider, + security: SecurityPageObject, + accountSetting: AccountSettingsPageObject, + monitoring: MonitoringPageObject, + logstash: LogstashPageObject, + graph: GraphPageObject, + grokDebugger: GrokDebuggerPageObject, + watcher: WatcherPageObject, + reporting: ReportingPageObject, + spaceSelector: SpaceSelectorPageObject, infraHome: InfraHomePageProvider, infraMetricExplorer: InfraMetricExplorerProvider, infraLogs: InfraLogsPageProvider, - maps: GisPageProvider, - statusPage: StatusPagePageProvider, - upgradeAssistant: UpgradeAssistantPageProvider, - uptime: UptimePageProvider, + maps: GisPageObject, + statusPage: StatusPageObject, + upgradeAssistant: UpgradeAssistantPageObject, + uptime: UptimePageObject, syntheticsIntegration: SyntheticsIntegrationPageProvider, - rollup: RollupPageProvider, + rollup: RollupPageObject, apiKeys: ApiKeysPageProvider, licenseManagement: LicenseManagementPageProvider, indexManagement: IndexManagementPageProvider, searchSessionsManagement: SearchSessionsPageProvider, indexLifecycleManagement: IndexLifecycleManagementPageProvider, - tagManagement: TagManagementPageProvider, + tagManagement: TagManagementPageObject, snapshotRestore: SnapshotRestorePageProvider, crossClusterReplication: CrossClusterReplicationPageProvider, remoteClusters: RemoteClustersPageProvider, @@ -80,7 +79,7 @@ export const pageObjects = { lens: LensPageProvider, roleMappings: RoleMappingsPageProvider, ingestPipelines: IngestPipelinesPageProvider, - navigationalSearch: NavigationalSearchProvider, - banners: BannersPageProvider, - detections: DetectionsPageProvider, + navigationalSearch: NavigationalSearchPageObject, + banners: BannersPageObject, + detections: DetectionsPageObject, }; diff --git a/x-pack/test/functional/page_objects/logstash_page.js b/x-pack/test/functional/page_objects/logstash_page.js deleted file mode 100644 index 3b12728d296109..00000000000000 --- a/x-pack/test/functional/page_objects/logstash_page.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export function LogstashPageProvider({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['common']); - const pipelineList = getService('pipelineList'); - const pipelineEditor = getService('pipelineEditor'); - - return new (class LogstashPage { - async gotoPipelineList() { - await PageObjects.common.navigateToApp('logstashPipelines'); - await pipelineList.assertExists(); - } - - async gotoNewPipelineEditor() { - await this.gotoPipelineList(); - await pipelineList.clickAdd(); - await pipelineEditor.assertExists(); - } - })(); -} diff --git a/x-pack/test/functional/page_objects/logstash_page.ts b/x-pack/test/functional/page_objects/logstash_page.ts new file mode 100644 index 00000000000000..37b907b34fa325 --- /dev/null +++ b/x-pack/test/functional/page_objects/logstash_page.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrService } from '../ftr_provider_context'; + +export class LogstashPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly pipelineList = this.ctx.getService('pipelineList'); + private readonly pipelineEditor = this.ctx.getService('pipelineEditor'); + + async gotoPipelineList() { + await this.common.navigateToApp('logstashPipelines'); + await this.pipelineList.assertExists(); + } + + async gotoNewPipelineEditor() { + await this.gotoPipelineList(); + await this.pipelineList.clickAdd(); + await this.pipelineEditor.assertExists(); + } +} diff --git a/x-pack/test/functional/page_objects/monitoring_page.ts b/x-pack/test/functional/page_objects/monitoring_page.ts index d32528f44613c5..151b6733509e6a 100644 --- a/x-pack/test/functional/page_objects/monitoring_page.ts +++ b/x-pack/test/functional/page_objects/monitoring_page.ts @@ -5,45 +5,45 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; - -export function MonitoringPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'header', 'security', 'login']); - const testSubjects = getService('testSubjects'); - return new (class MonitoringPage { - async getAccessDeniedMessage() { - return testSubjects.getVisibleText('accessDeniedTitle'); - } - - async clickBreadcrumb(subj: string) { - return testSubjects.click(subj); - } - - async assertTableNoData(subj: string) { - if (!(await testSubjects.exists(subj))) { - throw new Error('Expected to find the no data message'); - } - } +import { FtrService } from '../ftr_provider_context'; - async tableGetRows(subj: string) { - const table = await testSubjects.find(subj); - return table.findAllByTagName('tr'); - } +export class MonitoringPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - async tableGetRowsFromContainer(subj: string) { - const table = await testSubjects.find(subj); - const tbody = await table.findByTagName('tbody'); - return tbody.findAllByTagName('tr'); - } + async getAccessDeniedMessage() { + return this.testSubjects.getVisibleText('accessDeniedTitle'); + } - async tableSetFilter(subj: string, text: string) { - await testSubjects.setValue(subj, text); - await PageObjects.common.pressEnterKey(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clickBreadcrumb(subj: string) { + return this.testSubjects.click(subj); + } - async tableClearFilter(subj: string) { - return await testSubjects.setValue(subj, ' \uE003'); // space and backspace to trigger onChange event + async assertTableNoData(subj: string) { + if (!(await this.testSubjects.exists(subj))) { + throw new Error('Expected to find the no data message'); } - })(); + } + + async tableGetRows(subj: string) { + const table = await this.testSubjects.find(subj); + return table.findAllByTagName('tr'); + } + + async tableGetRowsFromContainer(subj: string) { + const table = await this.testSubjects.find(subj); + const tbody = await table.findByTagName('tbody'); + return tbody.findAllByTagName('tr'); + } + + async tableSetFilter(subj: string, text: string) { + await this.testSubjects.setValue(subj, text); + await this.common.pressEnterKey(); + await this.header.waitUntilLoadingHasFinished(); + } + + async tableClearFilter(subj: string) { + return await this.testSubjects.setValue(subj, ' \uE003'); // space and backspace to trigger onChange event + } } diff --git a/x-pack/test/functional/page_objects/navigational_search.ts b/x-pack/test/functional/page_objects/navigational_search.ts index a3f91e7921cc41..46fed2814c0dfe 100644 --- a/x-pack/test/functional/page_objects/navigational_search.ts +++ b/x-pack/test/functional/page_objects/navigational_search.ts @@ -6,7 +6,7 @@ */ import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; interface SearchResult { label: string; @@ -14,88 +14,84 @@ interface SearchResult { const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); -export function NavigationalSearchProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const testSubjects = getService('testSubjects'); +export class NavigationalSearchPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - class NavigationalSearch { - async focus() { - const field = await testSubjects.find('nav-search-input'); - await field.click(); - } + async focus() { + const field = await this.testSubjects.find('nav-search-input'); + await field.click(); + } - async blur() { - await testSubjects.click('helpMenuButton'); - await testSubjects.click('helpMenuButton'); - await find.waitForDeletedByCssSelector('.navSearch__panel'); - } + async blur() { + await this.testSubjects.click('helpMenuButton'); + await this.testSubjects.click('helpMenuButton'); + await this.find.waitForDeletedByCssSelector('.navSearch__panel'); + } - async searchFor( - term: string, - { clear = true, wait = true }: { clear?: boolean; wait?: boolean } = {} - ) { - if (clear) { - await this.clearField(); - } - const field = await testSubjects.find('nav-search-input'); - await field.type(term); - if (wait) { - await this.waitForResultsLoaded(); - } + async searchFor( + term: string, + { clear = true, wait = true }: { clear?: boolean; wait?: boolean } = {} + ) { + if (clear) { + await this.clearField(); } - - async getFieldValue() { - const field = await testSubjects.find('nav-search-input'); - return field.getAttribute('value'); - } - - async clearField() { - const field = await testSubjects.find('nav-search-input'); - await field.clearValueWithKeyboard(); + const field = await this.testSubjects.find('nav-search-input'); + await field.type(term); + if (wait) { + await this.waitForResultsLoaded(); } + } - async isPopoverDisplayed() { - return await find.existsByCssSelector('.navSearch__panel'); - } + async getFieldValue() { + const field = await this.testSubjects.find('nav-search-input'); + return field.getAttribute('value'); + } - async clickOnOption(index: number) { - const options = await testSubjects.findAll('nav-search-option'); - await options[index].click(); - } + async clearField() { + const field = await this.testSubjects.find('nav-search-input'); + await field.clearValueWithKeyboard(); + } - async waitForResultsLoaded(waitUntil: number = 3000) { - await testSubjects.exists('nav-search-option'); - // results are emitted in multiple batches. Each individual batch causes a re-render of - // the component, causing the current elements to become stale. We can't perform DOM access - // without heavy flakiness in this situation. - // there is NO ui indication of any kind to detect when all the emissions are done, - // so we are forced to fallback to awaiting a given amount of time once the first options are displayed. - await delay(waitUntil); - } + async isPopoverDisplayed() { + return await this.find.existsByCssSelector('.navSearch__panel'); + } - async getDisplayedResults() { - const resultElements = await testSubjects.findAll('nav-search-option'); - return Promise.all(resultElements.map((el) => this.convertResultElement(el))); - } + async clickOnOption(index: number) { + const options = await this.testSubjects.findAll('nav-search-option'); + await options[index].click(); + } - async isNoResultsPlaceholderDisplayed(checkAfter: number = 3000) { - // see comment in `waitForResultsLoaded` - await delay(checkAfter); - return testSubjects.exists('nav-search-no-results'); - } + async waitForResultsLoaded(waitUntil: number = 3000) { + await this.testSubjects.exists('nav-search-option'); + // results are emitted in multiple batches. Each individual batch causes a re-render of + // the component, causing the current elements to become stale. We can't perform DOM access + // without heavy flakiness in this situation. + // there is NO ui indication of any kind to detect when all the emissions are done, + // so we are forced to fallback to awaiting a given amount of time once the first options are displayed. + await delay(waitUntil); + } - private async convertResultElement(resultEl: WebElementWrapper): Promise { - const labelEl = await find.allDescendantDisplayedByCssSelector( - '.euiSelectableTemplateSitewide__listItemTitle', - resultEl - ); - const label = await labelEl[0].getVisibleText(); + async getDisplayedResults() { + const resultElements = await this.testSubjects.findAll('nav-search-option'); + return Promise.all(resultElements.map((el) => this.convertResultElement(el))); + } - return { - label, - }; - } + async isNoResultsPlaceholderDisplayed(checkAfter: number = 3000) { + // see comment in `waitForResultsLoaded` + await delay(checkAfter); + return this.testSubjects.exists('nav-search-no-results'); } - return new NavigationalSearch(); + private async convertResultElement(resultEl: WebElementWrapper): Promise { + const labelEl = await this.find.allDescendantDisplayedByCssSelector( + '.euiSelectableTemplateSitewide__listItemTitle', + resultEl + ); + const label = await labelEl[0].getVisibleText(); + + return { + label, + }; + } } diff --git a/x-pack/test/functional/page_objects/reporting_page.ts b/x-pack/test/functional/page_objects/reporting_page.ts index 746df14d31ac4d..e8999999ce50b6 100644 --- a/x-pack/test/functional/page_objects/reporting_page.ts +++ b/x-pack/test/functional/page_objects/reporting_page.ts @@ -9,148 +9,152 @@ import expect from '@kbn/expect'; import { format as formatUrl } from 'url'; import supertestAsPromised from 'supertest-as-promised'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function ReportingPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const config = getService('config'); - const log = getService('log'); - const retry = getService('retry'); - const security = getService('security'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['security', 'share', 'timePicker']); - - class ReportingPage { - async forceSharedItemsContainerSize({ width }: { width: number }) { - await browser.execute(` - var el = document.querySelector('[data-shared-items-container]'); - el.style.flex="none"; - el.style.width="${width}px"; - `); - } +import { FtrService } from '../ftr_provider_context'; + +export class ReportingPageObject extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly config = this.ctx.getService('config'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly security = this.ctx.getService('security'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly share = this.ctx.getPageObject('share'); + private readonly timePicker = this.ctx.getPageObject('timePicker'); + + async forceSharedItemsContainerSize({ width }: { width: number }) { + await this.browser.execute(` + var el = document.querySelector('[data-shared-items-container]'); + el.style.flex="none"; + el.style.width="${width}px"; + `); + } - async getReportURL(timeout: number) { - log.debug('getReportURL'); + async getReportURL(timeout: number) { + this.log.debug('getReportURL'); - const url = await testSubjects.getAttribute('downloadCompletedReportButton', 'href', timeout); + const url = await this.testSubjects.getAttribute( + 'downloadCompletedReportButton', + 'href', + timeout + ); - log.debug(`getReportURL got url: ${url}`); + this.log.debug(`getReportURL got url: ${url}`); - return url; - } + return url; + } - async removeForceSharedItemsContainerSize() { - await browser.execute(` - var el = document.querySelector('[data-shared-items-container]'); - el.style.flex = null; - el.style.width = null; - `); - } + async removeForceSharedItemsContainerSize() { + await this.browser.execute(` + var el = document.querySelector('[data-shared-items-container]'); + el.style.flex = null; + el.style.width = null; + `); + } - async getResponse(fullUrl: string): Promise { - log.debug(`getResponse for ${fullUrl}`); - const kibanaServerConfig = config.get('servers.kibana'); - const baseURL = formatUrl({ - ...kibanaServerConfig, - auth: false, - }); - const urlWithoutBase = fullUrl.replace(baseURL, ''); - const res = await security.testUserSupertest.get(urlWithoutBase); - return res; - } + async getResponse(fullUrl: string): Promise { + this.log.debug(`getResponse for ${fullUrl}`); + const kibanaServerConfig = this.config.get('servers.kibana'); + const baseURL = formatUrl({ + ...kibanaServerConfig, + auth: false, + }); + const urlWithoutBase = fullUrl.replace(baseURL, ''); + const res = await this.security.testUserSupertest.get(urlWithoutBase); + return res; + } - async getRawPdfReportData(url: string): Promise { - log.debug(`getRawPdfReportData for ${url}`); - const response = await this.getResponse(url); - expect(response.body).to.be.a(Buffer); - return response.body as Buffer; - } + async getRawPdfReportData(url: string): Promise { + this.log.debug(`getRawPdfReportData for ${url}`); + const response = await this.getResponse(url); + expect(response.body).to.be.a(Buffer); + return response.body as Buffer; + } - async openCsvReportingPanel() { - log.debug('openCsvReportingPanel'); - await PageObjects.share.openShareMenuItem('CSV Reports'); - } + async openCsvReportingPanel() { + this.log.debug('openCsvReportingPanel'); + await this.share.openShareMenuItem('CSV Reports'); + } - async openPdfReportingPanel() { - log.debug('openPdfReportingPanel'); - await PageObjects.share.openShareMenuItem('PDF Reports'); - } + async openPdfReportingPanel() { + this.log.debug('openPdfReportingPanel'); + await this.share.openShareMenuItem('PDF Reports'); + } - async openPngReportingPanel() { - log.debug('openPngReportingPanel'); - await PageObjects.share.openShareMenuItem('PNG Reports'); - } + async openPngReportingPanel() { + this.log.debug('openPngReportingPanel'); + await this.share.openShareMenuItem('PNG Reports'); + } - async clearToastNotifications() { - const toasts = await testSubjects.findAll('toastCloseButton'); - await Promise.all(toasts.map(async (t) => await t.click())); - } + async clearToastNotifications() { + const toasts = await this.testSubjects.findAll('toastCloseButton'); + await Promise.all(toasts.map(async (t) => await t.click())); + } - async getQueueReportError() { - return await testSubjects.exists('queueReportError'); - } + async getQueueReportError() { + return await this.testSubjects.exists('queueReportError'); + } - async getGenerateReportButton() { - return await retry.try(async () => await testSubjects.find('generateReportButton')); - } + async getGenerateReportButton() { + return await this.retry.try(async () => await this.testSubjects.find('generateReportButton')); + } - async isGenerateReportButtonDisabled() { - const generateReportButton = await this.getGenerateReportButton(); - return await retry.try(async () => { - const isDisabled = await generateReportButton.getAttribute('disabled'); - return isDisabled; - }); - } + async isGenerateReportButtonDisabled() { + const generateReportButton = await this.getGenerateReportButton(); + return await this.retry.try(async () => { + const isDisabled = await generateReportButton.getAttribute('disabled'); + return isDisabled; + }); + } - async canReportBeCreated() { - await this.clickGenerateReportButton(); - const success = await this.checkForReportingToasts(); - return success; - } + async canReportBeCreated() { + await this.clickGenerateReportButton(); + const success = await this.checkForReportingToasts(); + return success; + } - async checkUsePrintLayout() { - // The print layout checkbox slides in as part of an animation, and tests can - // attempt to click it too quickly, leading to flaky tests. The 500ms wait allows - // the animation to complete before we attempt a click. - const menuAnimationDelay = 500; - await retry.tryForTime(menuAnimationDelay, () => testSubjects.click('usePrintLayout')); - } + async checkUsePrintLayout() { + // The print layout checkbox slides in as part of an animation, and tests can + // attempt to click it too quickly, leading to flaky tests. The 500ms wait allows + // the animation to complete before we attempt a click. + const menuAnimationDelay = 500; + await this.retry.tryForTime(menuAnimationDelay, () => + this.testSubjects.click('usePrintLayout') + ); + } - async clickGenerateReportButton() { - await testSubjects.click('generateReportButton'); - } + async clickGenerateReportButton() { + await this.testSubjects.click('generateReportButton'); + } - async toggleReportMode() { - await testSubjects.click('reportModeToggle'); - } + async toggleReportMode() { + await this.testSubjects.click('reportModeToggle'); + } - async checkForReportingToasts() { - log.debug('Reporting:checkForReportingToasts'); - const isToastPresent = await testSubjects.exists('completeReportSuccess', { - allowHidden: true, - timeout: 90000, - }); - // Close toast so it doesn't obscure the UI. - if (isToastPresent) { - await testSubjects.click('completeReportSuccess > toastCloseButton'); - } - - return isToastPresent; + async checkForReportingToasts() { + this.log.debug('Reporting:checkForReportingToasts'); + const isToastPresent = await this.testSubjects.exists('completeReportSuccess', { + allowHidden: true, + timeout: 90000, + }); + // Close toast so it doesn't obscure the UI. + if (isToastPresent) { + await this.testSubjects.click('completeReportSuccess > toastCloseButton'); } - async setTimepickerInDataRange() { - log.debug('Reporting:setTimepickerInDataRange'); - const fromTime = 'Apr 27, 2019 @ 23:56:51.374'; - const toTime = 'Aug 23, 2019 @ 16:18:51.821'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - } + return isToastPresent; + } - async setTimepickerInNoDataRange() { - log.debug('Reporting:setTimepickerInNoDataRange'); - const fromTime = 'Sep 19, 1999 @ 06:31:44.000'; - const toTime = 'Sep 23, 1999 @ 18:31:44.000'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - } + async setTimepickerInDataRange() { + this.log.debug('Reporting:setTimepickerInDataRange'); + const fromTime = 'Apr 27, 2019 @ 23:56:51.374'; + const toTime = 'Aug 23, 2019 @ 16:18:51.821'; + await this.timePicker.setAbsoluteRange(fromTime, toTime); + } + + async setTimepickerInNoDataRange() { + this.log.debug('Reporting:setTimepickerInNoDataRange'); + const fromTime = 'Sep 19, 1999 @ 06:31:44.000'; + const toTime = 'Sep 23, 1999 @ 18:31:44.000'; + await this.timePicker.setAbsoluteRange(fromTime, toTime); } - return new ReportingPage(); } diff --git a/x-pack/test/functional/page_objects/rollup_page.ts b/x-pack/test/functional/page_objects/rollup_page.ts index dbdfa4e19c5557..0740a8f015da18 100644 --- a/x-pack/test/functional/page_objects/rollup_page.ts +++ b/x-pack/test/functional/page_objects/rollup_page.ts @@ -7,137 +7,130 @@ import expect from '@kbn/expect'; import { map as mapAsync } from 'bluebird'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function RollupPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const find = getService('find'); - const PageObjects = getPageObjects(['header', 'common']); - - class RollupJobPage { - async createNewRollUpJob( - jobName: string, - indexPattern: string, - indexName: string, - interval: string, - delay = '1d', - startImmediately = false, - scheduledTime = { time: 'minute', cron: true } - ) { - let stepNum = 1; - // Step 1 - await testSubjects.click('createRollupJobButton'); - await this.verifyStepIsActive(stepNum); - await this.addRollupNameandIndexPattern(jobName, indexPattern); - await this.verifyIndexPatternAccepted(); - await this.setIndexName(indexName); - await this.setScheduleTime(scheduledTime.time, scheduledTime.cron); - await this.setRollupDelay(delay); - stepNum = await this.moveToNextStep(stepNum); - - // Step 2: Histogram - await this.verifyStepIsActive(stepNum); - await this.setJobInterval(interval); - stepNum = await this.moveToNextStep(stepNum); - - // Step 3: Terms (optional) - await this.verifyStepIsActive(stepNum); - stepNum = await this.moveToNextStep(); - - // Step 4: Histogram(optional) - await this.verifyStepIsActive(stepNum); - stepNum = await this.moveToNextStep(); - - // Step 5: Metrics(optional) - await this.verifyStepIsActive(stepNum); - stepNum = await this.moveToNextStep(); - - // Step 6: saveJob and verify the name in the list - await this.verifyStepIsActive(stepNum); - await this.saveJob(startImmediately); - } +import { FtrService } from '../ftr_provider_context'; + +export class RollupPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); + private readonly header = this.ctx.getPageObject('header'); + + async createNewRollUpJob( + jobName: string, + indexPattern: string, + indexName: string, + interval: string, + delay = '1d', + startImmediately = false, + scheduledTime = { time: 'minute', cron: true } + ) { + let stepNum = 1; + // Step 1 + await this.testSubjects.click('createRollupJobButton'); + await this.verifyStepIsActive(stepNum); + await this.addRollupNameandIndexPattern(jobName, indexPattern); + await this.verifyIndexPatternAccepted(); + await this.setIndexName(indexName); + await this.setScheduleTime(scheduledTime.time, scheduledTime.cron); + await this.setRollupDelay(delay); + stepNum = await this.moveToNextStep(stepNum); + + // Step 2: Histogram + await this.verifyStepIsActive(stepNum); + await this.setJobInterval(interval); + stepNum = await this.moveToNextStep(stepNum); + + // Step 3: Terms (optional) + await this.verifyStepIsActive(stepNum); + stepNum = await this.moveToNextStep(); + + // Step 4: Histogram(optional) + await this.verifyStepIsActive(stepNum); + stepNum = await this.moveToNextStep(); + + // Step 5: Metrics(optional) + await this.verifyStepIsActive(stepNum); + stepNum = await this.moveToNextStep(); + + // Step 6: saveJob and verify the name in the list + await this.verifyStepIsActive(stepNum); + await this.saveJob(startImmediately); + } - async verifyStepIsActive(stepNumber = 0) { - await testSubjects.exists(`createRollupStep${stepNumber}--active`); - } + async verifyStepIsActive(stepNumber = 0) { + await this.testSubjects.exists(`createRollupStep${stepNumber}--active`); + } - async setScheduleTime(time: string, isCron: boolean) { - if (isCron) { - await testSubjects.click('rollupShowAdvancedCronLink'); - await testSubjects.setValue('rollupAdvancedCron', time); - } - // TODO: Add handling for if Cron is false to go through clicking options. + async setScheduleTime(time: string, isCron: boolean) { + if (isCron) { + await this.testSubjects.click('rollupShowAdvancedCronLink'); + await this.testSubjects.setValue('rollupAdvancedCron', time); } + // TODO: Add handling for if Cron is false to go through clicking options. + } - async addRollupNameandIndexPattern(name: string, indexPattern: string) { - log.debug(`Adding name ${name} to form`); - await testSubjects.setValue('rollupJobName', name); - await testSubjects.setValue('rollupIndexPattern', indexPattern); - } + async addRollupNameandIndexPattern(name: string, indexPattern: string) { + this.log.debug(`Adding name ${name} to form`); + await this.testSubjects.setValue('rollupJobName', name); + await this.testSubjects.setValue('rollupIndexPattern', indexPattern); + } - async setRollupDelay(time: string) { - log.debug(`Setting rollup delay to "${time}"`); - await testSubjects.setValue('rollupDelay', time); - } + async setRollupDelay(time: string) { + this.log.debug(`Setting rollup delay to "${time}"`); + await this.testSubjects.setValue('rollupDelay', time); + } - async verifyIndexPatternAccepted() { - const span = await testSubjects.find('fieldIndexPatternSuccessMessage'); - const message = await span.findByCssSelector('p'); - const text = await message.getVisibleText(); - expect(text).to.be.equal('Success! Index pattern has matching indices.'); - } + async verifyIndexPatternAccepted() { + const span = await this.testSubjects.find('fieldIndexPatternSuccessMessage'); + const message = await span.findByCssSelector('p'); + const text = await message.getVisibleText(); + expect(text).to.be.equal('Success! Index pattern has matching indices.'); + } - async setIndexName(name: string) { - await testSubjects.setValue('rollupIndexName', name); - } + async setIndexName(name: string) { + await this.testSubjects.setValue('rollupIndexName', name); + } - async moveToNextStep(stepNum = 0) { - await testSubjects.click('rollupJobNextButton'); - return stepNum + 1; - } + async moveToNextStep(stepNum = 0) { + await this.testSubjects.click('rollupJobNextButton'); + return stepNum + 1; + } - async setJobInterval(time: string) { - await testSubjects.setValue('rollupJobInterval', time); - } + async setJobInterval(time: string) { + await this.testSubjects.setValue('rollupJobInterval', time); + } - async saveJob(startImmediately: boolean) { - if (startImmediately) { - const checkbox = await find.byCssSelector('.euiCheckbox'); - await checkbox.click(); - } - await testSubjects.click('rollupJobSaveButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); + async saveJob(startImmediately: boolean) { + if (startImmediately) { + const checkbox = await this.find.byCssSelector('.euiCheckbox'); + await checkbox.click(); } + await this.testSubjects.click('rollupJobSaveButton'); + await this.header.waitUntilLoadingHasFinished(); + } - async getJobList() { - const jobs = await testSubjects.findAll('jobTableRow'); - return mapAsync(jobs, async (job) => { - const jobNameElement = await job.findByTestSubject('jobTableCell-id'); - const jobStatusElement = await job.findByTestSubject('jobTableCell-status'); - const jobIndexPatternElement = await job.findByTestSubject('jobTableCell-indexPattern'); - const jobRollUpIndexPatternElement = await job.findByTestSubject( - 'jobTableCell-rollupIndex' - ); - const jobDelayElement = await job.findByTestSubject('jobTableCell-rollupDelay'); - const jobIntervalElement = await job.findByTestSubject( - 'jobTableCell-dateHistogramInterval' - ); - const jobGroupElement = await job.findByTestSubject('jobTableCell-groups'); - const jobMetricsElement = await job.findByTestSubject('jobTableCell-metrics'); - - return { - jobName: await jobNameElement.getVisibleText(), - jobStatus: await jobStatusElement.getVisibleText(), - jobIndexPattern: await jobIndexPatternElement.getVisibleText(), - jobRollUpIndexPattern: await jobRollUpIndexPatternElement.getVisibleText(), - jobDelayElement: await jobDelayElement.getVisibleText(), - jobInterval: await jobIntervalElement.getVisibleText(), - jobGroup: await jobGroupElement.getVisibleText(), - jobMetrics: await jobMetricsElement.getVisibleText(), - }; - }); - } + async getJobList() { + const jobs = await this.testSubjects.findAll('jobTableRow'); + return mapAsync(jobs, async (job) => { + const jobNameElement = await job.findByTestSubject('jobTableCell-id'); + const jobStatusElement = await job.findByTestSubject('jobTableCell-status'); + const jobIndexPatternElement = await job.findByTestSubject('jobTableCell-indexPattern'); + const jobRollUpIndexPatternElement = await job.findByTestSubject('jobTableCell-rollupIndex'); + const jobDelayElement = await job.findByTestSubject('jobTableCell-rollupDelay'); + const jobIntervalElement = await job.findByTestSubject('jobTableCell-dateHistogramInterval'); + const jobGroupElement = await job.findByTestSubject('jobTableCell-groups'); + const jobMetricsElement = await job.findByTestSubject('jobTableCell-metrics'); + + return { + jobName: await jobNameElement.getVisibleText(), + jobStatus: await jobStatusElement.getVisibleText(), + jobIndexPattern: await jobIndexPatternElement.getVisibleText(), + jobRollUpIndexPattern: await jobRollUpIndexPatternElement.getVisibleText(), + jobDelayElement: await jobDelayElement.getVisibleText(), + jobInterval: await jobIntervalElement.getVisibleText(), + jobGroup: await jobGroupElement.getVisibleText(), + jobMetrics: await jobMetricsElement.getVisibleText(), + }; + }); } - return new RollupJobPage(); } diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 15704a1d10671d..437749d31c15e3 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -6,130 +6,49 @@ */ import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { AuthenticatedUser, Role } from '../../../plugins/security/common/model'; import type { UserFormValues } from '../../../plugins/security/public/management/users/edit_user/user_form'; -export function SecurityPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const config = getService('config'); - const retry = getService('retry'); - const find = getService('find'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const esArchiver = getService('esArchiver'); - const userMenu = getService('userMenu'); - const comboBox = getService('comboBox'); - const supertest = getService('supertestWithoutAuth'); - const deployment = getService('deployment'); - const PageObjects = getPageObjects(['common', 'header', 'error']); - - interface LoginOptions { - expectSpaceSelector?: boolean; - expectSuccess?: boolean; - expectForbidden?: boolean; - } - - type LoginExpectedResult = 'spaceSelector' | 'error' | 'chrome'; - - async function waitForLoginPage() { - log.debug('Waiting for Login Page to appear.'); - await retry.waitForWithTimeout('login page', config.get('timeouts.waitFor') * 5, async () => { - // As a part of the cleanup flow tests usually try to log users out, but there are cases when - // browser/Kibana would like users to confirm that they want to navigate away from the current - // page and lose the state (e.g. unsaved changes) via native alert dialog. - const alert = await browser.getAlert(); - if (alert && alert.accept) { - await alert.accept(); - } - return await find.existsByDisplayedByCssSelector('.login-form'); - }); - } - - async function isLoginFormVisible() { - return await testSubjects.exists('loginForm'); - } - - async function waitForLoginForm() { - log.debug('Waiting for Login Form to appear.'); - await retry.waitForWithTimeout('login form', config.get('timeouts.waitFor') * 5, async () => { - return await isLoginFormVisible(); - }); - } - - async function waitForLoginSelector() { - log.debug('Waiting for Login Selector to appear.'); - await retry.waitForWithTimeout( - 'login selector', - config.get('timeouts.waitFor') * 5, - async () => { - return await testSubjects.exists('loginSelector'); - } - ); - } - - async function waitForLoginHelp(helpText: string) { - log.debug(`Waiting for Login Help to appear with text: ${helpText}.`); - await retry.waitForWithTimeout('login help', config.get('timeouts.waitFor') * 5, async () => { - return (await testSubjects.getVisibleText('loginHelp')) === helpText; - }); - } - - async function waitForLoginResult(expectedResult?: LoginExpectedResult) { - log.debug(`Waiting for login result, expected: ${expectedResult}.`); - - // wait for either space selector, kibanaChrome or loginErrorMessage - if (expectedResult === 'spaceSelector') { - await retry.try(() => testSubjects.find('kibanaSpaceSelector')); - log.debug( - `Finished login process, landed on space selector. currentUrl = ${await browser.getCurrentUrl()}` - ); - return; - } - - if (expectedResult === 'error') { - const rawDataTabLocator = 'a[id=rawdata-tab]'; - if (await find.existsByCssSelector(rawDataTabLocator)) { - // Firefox has 3 tabs and requires navigation to see Raw output - await find.clickByCssSelector(rawDataTabLocator); - } - await retry.try(async () => { - if (await find.existsByCssSelector(rawDataTabLocator)) { - await find.clickByCssSelector(rawDataTabLocator); - } - await testSubjects.existOrFail('ResetSessionButton'); - }); - log.debug( - `Finished login process, found reset session button message. currentUrl = ${await browser.getCurrentUrl()}` - ); - return; - } - - if (expectedResult === 'chrome') { - await find.byCssSelector( - '[data-test-subj="kibanaChrome"] .kbnAppWrapper:not(.kbnAppWrapper--hiddenChrome)', - 20000 - ); - log.debug(`Finished login process currentUrl = ${await browser.getCurrentUrl()}`); - } - } +interface LoginOptions { + expectSpaceSelector?: boolean; + expectSuccess?: boolean; + expectForbidden?: boolean; +} - const loginPage = Object.freeze({ - async login(username?: string, password?: string, options: LoginOptions = {}) { - if (!(await isLoginFormVisible())) { - await PageObjects.common.navigateToApp('login'); +type LoginExpectedResult = 'spaceSelector' | 'error' | 'chrome'; + +export class SecurityPageObject extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly config = this.ctx.getService('config'); + private readonly retry = this.ctx.getService('retry'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly esArchiver = this.ctx.getService('esArchiver'); + private readonly userMenu = this.ctx.getService('userMenu'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly supertest = this.ctx.getService('supertestWithoutAuth'); + private readonly deployment = this.ctx.getService('deployment'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + + public loginPage = Object.freeze({ + login: async (username?: string, password?: string, options: LoginOptions = {}) => { + if (!(await this.isLoginFormVisible())) { + await this.common.navigateToApp('login'); } // ensure welcome screen won't be shown. This is relevant for environments which don't allow // to use the yml setting, e.g. cloud - await browser.setLocalStorageItem('home:welcome:show', 'false'); - await waitForLoginForm(); + await this.browser.setLocalStorageItem('home:welcome:show', 'false'); + await this.waitForLoginForm(); - await testSubjects.setValue('loginUsername', username || adminTestUser.username); - await testSubjects.setValue('loginPassword', password || adminTestUser.password); - await testSubjects.click('loginSubmit'); + await this.testSubjects.setValue('loginUsername', username || adminTestUser.username); + await this.testSubjects.setValue('loginPassword', password || adminTestUser.password); + await this.testSubjects.click('loginSubmit'); - await waitForLoginResult( + await this.waitForLoginResult( options.expectSpaceSelector ? 'spaceSelector' : options.expectForbidden @@ -140,9 +59,11 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider ); }, - async getErrorMessage() { - return await retry.try(async () => { - const errorMessageContainer = await retry.try(() => testSubjects.find('loginErrorMessage')); + getErrorMessage: async () => { + return await this.retry.try(async () => { + const errorMessageContainer = await this.retry.try(() => + this.testSubjects.find('loginErrorMessage') + ); const errorMessageText = await errorMessageContainer.getVisibleText(); if (!errorMessageText) { @@ -154,380 +75,477 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider }, }); - const loginSelector = Object.freeze({ - async login(providerType: string, providerName: string, options?: Record) { - log.debug(`Starting login flow for ${providerType}/${providerName}`); + public loginSelector = Object.freeze({ + login: async (providerType: string, providerName: string, options?: Record) => { + this.log.debug(`Starting login flow for ${providerType}/${providerName}`); - await this.verifyLoginSelectorIsVisible(); - await this.selectLoginMethod(providerType, providerName); + await this.loginSelector.verifyLoginSelectorIsVisible(); + await this.loginSelector.selectLoginMethod(providerType, providerName); if (providerType === 'basic' || providerType === 'token') { - await waitForLoginForm(); + await this.waitForLoginForm(); - await testSubjects.setValue('loginUsername', options?.username ?? adminTestUser.username); - await testSubjects.setValue('loginPassword', options?.password ?? adminTestUser.password); - await testSubjects.click('loginSubmit'); + await this.testSubjects.setValue( + 'loginUsername', + options?.username ?? adminTestUser.username + ); + await this.testSubjects.setValue( + 'loginPassword', + options?.password ?? adminTestUser.password + ); + await this.testSubjects.click('loginSubmit'); } - await waitForLoginResult('chrome'); + await this.waitForLoginResult('chrome'); - log.debug(`Finished login process currentUrl = ${await browser.getCurrentUrl()}`); + this.log.debug(`Finished login process currentUrl = ${await this.browser.getCurrentUrl()}`); }, - async selectLoginMethod(providerType: string, providerName: string) { + selectLoginMethod: async (providerType: string, providerName: string) => { // Ensure welcome screen won't be shown. This is relevant for environments which don't allow // to use the yml setting, e.g. cloud. - await browser.setLocalStorageItem('home:welcome:show', 'false'); - await testSubjects.click(`loginCard-${providerType}/${providerName}`); + await this.browser.setLocalStorageItem('home:welcome:show', 'false'); + await this.testSubjects.click(`loginCard-${providerType}/${providerName}`); }, - async verifyLoginFormIsVisible() { - await waitForLoginForm(); + verifyLoginFormIsVisible: async () => { + await this.waitForLoginForm(); }, - async verifyLoginSelectorIsVisible() { - await waitForLoginSelector(); + verifyLoginSelectorIsVisible: async () => { + await this.waitForLoginSelector(); }, - async verifyLoginHelpIsVisible(helpText: string) { - await waitForLoginHelp(helpText); + verifyLoginHelpIsVisible: async (helpText: string) => { + await this.waitForLoginHelp(helpText); }, }); - class SecurityPage { - public loginPage = loginPage; - public loginSelector = loginSelector; - - async initTests() { - log.debug('SecurityPage:initTests'); - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); - await browser.setWindowSize(1600, 1000); - } + private async waitForLoginPage() { + this.log.debug('Waiting for Login Page to appear.'); + await this.retry.waitForWithTimeout( + 'login page', + this.config.get('timeouts.waitFor') * 5, + async () => { + // As a part of the cleanup flow tests usually try to log users out, but there are cases when + // browser/Kibana would like users to confirm that they want to navigate away from the current + // page and lose the state (e.g. unsaved changes) via native alert dialog. + const alert = await this.browser.getAlert(); + if (alert && alert.accept) { + await alert.accept(); + } + return await this.find.existsByDisplayedByCssSelector('.login-form'); + } + ); + } - async login(username?: string, password?: string, options: LoginOptions = {}) { - await this.loginPage.login(username, password, options); + private async isLoginFormVisible() { + return await this.testSubjects.exists('loginForm'); + } - if (options.expectSpaceSelector || options.expectForbidden) { - return; + private async waitForLoginForm() { + this.log.debug('Waiting for Login Form to appear.'); + await this.retry.waitForWithTimeout( + 'login form', + this.config.get('timeouts.waitFor') * 5, + async () => { + return await this.isLoginFormVisible(); } + ); + } - await retry.waitFor('logout button visible', async () => await userMenu.logoutLinkExists()); - } - - async logout() { - log.debug('SecurityPage.logout'); + private async waitForLoginSelector() { + this.log.debug('Waiting for Login Selector to appear.'); + await this.retry.waitForWithTimeout( + 'login selector', + this.config.get('timeouts.waitFor') * 5, + async () => { + return await this.testSubjects.exists('loginSelector'); + } + ); + } - if (!(await userMenu.logoutLinkExists())) { - log.debug('Logout not found'); - return; + private async waitForLoginHelp(helpText: string) { + this.log.debug(`Waiting for Login Help to appear with text: ${helpText}.`); + await this.retry.waitForWithTimeout( + 'login help', + this.config.get('timeouts.waitFor') * 5, + async () => { + return (await this.testSubjects.getVisibleText('loginHelp')) === helpText; } + ); + } + + private async waitForLoginResult(expectedResult?: LoginExpectedResult) { + this.log.debug(`Waiting for login result, expected: ${expectedResult}.`); - await userMenu.clickLogoutButton(); - await waitForLoginPage(); + // wait for either space selector, kibanaChrome or loginErrorMessage + if (expectedResult === 'spaceSelector') { + await this.retry.try(() => this.testSubjects.find('kibanaSpaceSelector')); + this.log.debug( + `Finished login process, landed on space selector. currentUrl = ${await this.browser.getCurrentUrl()}` + ); + return; } - async getCurrentUser() { - const sidCookie = await browser.getCookie('sid'); - if (!sidCookie?.value) { - log.debug('User is not authenticated yet.'); - return null; + if (expectedResult === 'error') { + const rawDataTabLocator = 'a[id=rawdata-tab]'; + if (await this.find.existsByCssSelector(rawDataTabLocator)) { + // Firefox has 3 tabs and requires navigation to see Raw output + await this.find.clickByCssSelector(rawDataTabLocator); } + await this.retry.try(async () => { + if (await this.find.existsByCssSelector(rawDataTabLocator)) { + await this.find.clickByCssSelector(rawDataTabLocator); + } + await this.testSubjects.existOrFail('ResetSessionButton'); + }); + this.log.debug( + `Finished login process, found reset session button message. currentUrl = ${await this.browser.getCurrentUrl()}` + ); + return; + } - const { body: user } = await supertest - .get('/internal/security/me') - .set('kbn-xsrf', 'xxx') - .set('Cookie', `sid=${sidCookie.value}`) - .expect(200); - return user as AuthenticatedUser; + if (expectedResult === 'chrome') { + await this.find.byCssSelector( + '[data-test-subj="kibanaChrome"] .kbnAppWrapper:not(.kbnAppWrapper--hiddenChrome)', + 20000 + ); + this.log.debug(`Finished login process currentUrl = ${await this.browser.getCurrentUrl()}`); } + } - async forceLogout() { - log.debug('SecurityPage.forceLogout'); - if (await find.existsByDisplayedByCssSelector('.login-form', 100)) { - log.debug('Already on the login page, not forcing anything'); - return; - } + async initTests() { + this.log.debug('SecurityPage:initTests'); + await this.esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await this.esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + await this.browser.setWindowSize(1600, 1000); + } - log.debug('Redirecting to /logout to force the logout'); - const url = deployment.getHostPort() + '/logout'; - await browser.get(url); - log.debug('Waiting on the login form to appear'); - await waitForLoginPage(); - } + async login(username?: string, password?: string, options: LoginOptions = {}) { + await this.loginPage.login(username, password, options); - async clickRolesSection() { - await testSubjects.click('roles'); + if (options.expectSpaceSelector || options.expectForbidden) { + return; } - async clickUsersSection() { - await testSubjects.click('users'); - } + await this.retry.waitFor( + 'logout button visible', + async () => await this.userMenu.logoutLinkExists() + ); + } - async clickCreateNewUser() { - await retry.try(() => testSubjects.click('createUserButton')); - } + async logout() { + this.log.debug('SecurityPage.logout'); - async clickCreateNewRole() { - await retry.try(() => testSubjects.click('createRoleButton')); + if (!(await this.userMenu.logoutLinkExists())) { + this.log.debug('Logout not found'); + return; } - async clickCloneRole(roleName: string) { - await retry.try(() => testSubjects.click(`clone-role-action-${roleName}`)); - } + await this.userMenu.clickLogoutButton(); + await this.waitForLoginPage(); + } - async clickCancelEditUser() { - await find.clickByButtonText('Cancel'); + async getCurrentUser() { + const sidCookie = await this.browser.getCookie('sid'); + if (!sidCookie?.value) { + this.log.debug('User is not authenticated yet.'); + return null; } - async clickCancelEditRole() { - await testSubjects.click('roleFormCancelButton'); - } + const { body: user } = await this.supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', `sid=${sidCookie.value}`) + .expect(200); + return user as AuthenticatedUser; + } - async clickSaveEditUser() { - await find.clickByButtonText('Update user'); - await PageObjects.header.waitUntilLoadingHasFinished(); + async forceLogout() { + this.log.debug('SecurityPage.forceLogout'); + if (await this.find.existsByDisplayedByCssSelector('.login-form', 100)) { + this.log.debug('Already on the login page, not forcing anything'); + return; } - async clickSaveCreateUser() { - await find.clickByButtonText('Create user'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + this.log.debug('Redirecting to /logout to force the logout'); + const url = this.deployment.getHostPort() + '/logout'; + await this.browser.get(url); + this.log.debug('Waiting on the login form to appear'); + await this.waitForLoginPage(); + } - async clickSaveEditRole() { - const saveButton = await retry.try(() => testSubjects.find('roleFormSaveButton')); - await saveButton.moveMouseTo(); - await saveButton.click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clickRolesSection() { + await this.testSubjects.click('roles'); + } - async addIndexToRole(index: string) { - log.debug(`Adding index ${index} to role`); - await comboBox.setCustom('indicesInput0', index); - } + async clickUsersSection() { + await this.testSubjects.click('users'); + } - async addPrivilegeToRole(privilege: string) { - log.debug(`Adding privilege ${privilege} to role`); - const privilegeInput = await retry.try(() => - find.byCssSelector('[data-test-subj="privilegesInput0"] input') - ); - await privilegeInput.type(privilege); + async clickCreateNewUser() { + await this.retry.try(() => this.testSubjects.click('createUserButton')); + } - const btn = await find.byButtonText(privilege); - await btn.click(); + async clickCreateNewRole() { + await this.retry.try(() => this.testSubjects.click('createRoleButton')); + } - // const options = await find.byCssSelector(`.euiFilterSelectItem`); - // Object.entries(options).forEach(([key, prop]) => { - // console.log({ key, proto: prop.__proto__ }); - // }); + async clickCloneRole(roleName: string) { + await this.retry.try(() => this.testSubjects.click(`clone-role-action-${roleName}`)); + } - // await options.click(); - } + async clickCancelEditUser() { + await this.find.clickByButtonText('Cancel'); + } - async assignRoleToUser(role: string) { - await this.selectRole(role); - } + async clickCancelEditRole() { + await this.testSubjects.click('roleFormCancelButton'); + } - async navigateTo() { - await PageObjects.common.navigateToApp('settings'); - } + async clickSaveEditUser() { + await this.find.clickByButtonText('Update user'); + await this.header.waitUntilLoadingHasFinished(); + } - async clickElasticsearchUsers() { - await this.navigateTo(); - await this.clickUsersSection(); - } + async clickSaveCreateUser() { + await this.find.clickByButtonText('Create user'); + await this.header.waitUntilLoadingHasFinished(); + } - async clickElasticsearchRoles() { - await this.navigateTo(); - await this.clickRolesSection(); - } + async clickSaveEditRole() { + const saveButton = await this.retry.try(() => this.testSubjects.find('roleFormSaveButton')); + await saveButton.moveMouseTo(); + await saveButton.click(); + await this.header.waitUntilLoadingHasFinished(); + } - async getElasticsearchUsers() { - const users = []; - await testSubjects.click('tablePaginationPopoverButton'); - await testSubjects.click('tablePagination-100-rows'); - for (const user of await testSubjects.findAll('userRow')) { - const fullnameElement = await user.findByTestSubject('userRowFullName'); - const usernameElement = await user.findByTestSubject('userRowUserName'); - const emailElement = await user.findByTestSubject('userRowEmail'); - const rolesElement = await user.findByTestSubject('userRowRoles'); - // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases - const isUserReserved = (await user.findAllByTestSubject('userReserved', 1)).length > 0; - const isUserDeprecated = (await user.findAllByTestSubject('userDeprecated', 1)).length > 0; - - users.push({ - username: await usernameElement.getVisibleText(), - fullname: await fullnameElement.getVisibleText(), - email: await emailElement.getVisibleText(), - roles: (await rolesElement.getVisibleText()).split('\n').map((role) => role.trim()), - reserved: isUserReserved, - deprecated: isUserDeprecated, - }); - } + async addIndexToRole(index: string) { + this.log.debug(`Adding index ${index} to role`); + await this.comboBox.setCustom('indicesInput0', index); + } - return users; - } + async addPrivilegeToRole(privilege: string) { + this.log.debug(`Adding privilege ${privilege} to role`); + const privilegeInput = await this.retry.try(() => + this.find.byCssSelector('[data-test-subj="privilegesInput0"] input') + ); + await privilegeInput.type(privilege); - async getElasticsearchRoles() { - const roles = []; - await testSubjects.click('tablePaginationPopoverButton'); - await testSubjects.click('tablePagination-100-rows'); - for (const role of await testSubjects.findAll('roleRow')) { - const [rolename, reserved, deprecated] = await Promise.all([ - role.findByTestSubject('roleRowName').then((el) => el.getVisibleText()), - // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases - role.findAllByTestSubject('roleReserved', 1).then((el) => el.length > 0), - // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases - role.findAllByTestSubject('roleDeprecated', 1).then((el) => el.length > 0), - ]); - - roles.push({ rolename, reserved, deprecated }); - } + const btn = await this.find.byButtonText(privilege); + await btn.click(); - return roles; - } + // const options = await this.find.byCssSelector(`.euiFilterSelectItem`); + // Object.entries(options).forEach(([key, prop]) => { + // console.log({ key, proto: prop.__proto__ }); + // }); - /** - * @deprecated Use `PageObjects.security.clickCreateNewUser` instead - */ - async clickNewUser() { - return await testSubjects.click('createUserButton'); - } + // await options.click(); + } - /** - * @deprecated Use `PageObjects.security.clickCreateNewUser` instead - */ - async clickNewRole() { - return await testSubjects.click('createRoleButton'); - } + async assignRoleToUser(role: string) { + await this.selectRole(role); + } - async fillUserForm(user: UserFormValues) { - if (user.username) { - await find.setValue('[name=username]', user.username); - } - if (user.password) { - await find.setValue('[name=password]', user.password); - } - if (user.confirm_password) { - await find.setValue('[name=confirm_password]', user.confirm_password); - } - if (user.full_name) { - await find.setValue('[name=full_name]', user.full_name); - } - if (user.email) { - await find.setValue('[name=email]', user.email); - } + async navigateTo() { + await this.common.navigateToApp('settings'); + } - const rolesToAdd = user.roles || []; - for (let i = 0; i < rolesToAdd.length; i++) { - await this.selectRole(rolesToAdd[i]); - } + async clickElasticsearchUsers() { + await this.navigateTo(); + await this.clickUsersSection(); + } + + async clickElasticsearchRoles() { + await this.navigateTo(); + await this.clickRolesSection(); + } + + async getElasticsearchUsers() { + const users = []; + await this.testSubjects.click('tablePaginationPopoverButton'); + await this.testSubjects.click('tablePagination-100-rows'); + for (const user of await this.testSubjects.findAll('userRow')) { + const fullnameElement = await user.findByTestSubject('userRowFullName'); + const usernameElement = await user.findByTestSubject('userRowUserName'); + const emailElement = await user.findByTestSubject('userRowEmail'); + const rolesElement = await user.findByTestSubject('userRowRoles'); + // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases + const isUserReserved = (await user.findAllByTestSubject('userReserved', 1)).length > 0; + const isUserDeprecated = (await user.findAllByTestSubject('userDeprecated', 1)).length > 0; + + users.push({ + username: await usernameElement.getVisibleText(), + fullname: await fullnameElement.getVisibleText(), + email: await emailElement.getVisibleText(), + roles: (await rolesElement.getVisibleText()).split('\n').map((role) => role.trim()), + reserved: isUserReserved, + deprecated: isUserDeprecated, + }); } - async submitCreateUserForm() { - await find.clickByButtonText('Create user'); + return users; + } + + async getElasticsearchRoles() { + const roles = []; + await this.testSubjects.click('tablePaginationPopoverButton'); + await this.testSubjects.click('tablePagination-100-rows'); + for (const role of await this.testSubjects.findAll('roleRow')) { + const [rolename, reserved, deprecated] = await Promise.all([ + role.findByTestSubject('roleRowName').then((el) => el.getVisibleText()), + // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases + role.findAllByTestSubject('roleReserved', 1).then((el) => el.length > 0), + // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases + role.findAllByTestSubject('roleDeprecated', 1).then((el) => el.length > 0), + ]); + + roles.push({ rolename, reserved, deprecated }); } - async createUser(user: UserFormValues) { - await this.clickElasticsearchUsers(); - await this.clickCreateNewUser(); - await this.fillUserForm(user); - await this.submitCreateUserForm(); + return roles; + } + + /** + * @deprecated Use `this.security.clickCreateNewUser` instead + */ + async clickNewUser() { + return await this.testSubjects.click('createUserButton'); + } + + /** + * @deprecated Use `this.security.clickCreateNewUser` instead + */ + async clickNewRole() { + return await this.testSubjects.click('createRoleButton'); + } + + async fillUserForm(user: UserFormValues) { + if (user.username) { + await this.find.setValue('[name=username]', user.username); + } + if (user.password) { + await this.find.setValue('[name=password]', user.password); + } + if (user.confirm_password) { + await this.find.setValue('[name=confirm_password]', user.confirm_password); + } + if (user.full_name) { + await this.find.setValue('[name=full_name]', user.full_name); + } + if (user.email) { + await this.find.setValue('[name=email]', user.email); } - async addRole(roleName: string, roleObj: Role) { - const self = this; + const rolesToAdd = user.roles || []; + for (let i = 0; i < rolesToAdd.length; i++) { + await this.selectRole(rolesToAdd[i]); + } + } - await this.clickCreateNewRole(); + async submitCreateUserForm() { + await this.find.clickByButtonText('Create user'); + } - // We have to use non-test-subject selectors because this markup is generated by ui-select. - log.debug('roleObj.indices[0].names = ' + roleObj.elasticsearch.indices[0].names); - await testSubjects.append('roleFormNameInput', roleName); + async createUser(user: UserFormValues) { + await this.clickElasticsearchUsers(); + await this.clickCreateNewUser(); + await this.fillUserForm(user); + await this.submitCreateUserForm(); + } - for (const indexName of roleObj.elasticsearch.indices[0].names) { - await comboBox.setCustom('indicesInput0', indexName); - } + async addRole(roleName: string, roleObj: Role) { + const self = this; - if (roleObj.elasticsearch.indices[0].query) { - await testSubjects.click('restrictDocumentsQuery0'); - await testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query); - } + await this.clickCreateNewRole(); - const globalPrivileges = (roleObj.kibana as any).global; - if (globalPrivileges) { - for (const privilegeName of globalPrivileges) { - await testSubjects.click('addSpacePrivilegeButton'); + // We have to use non-test-subject selectors because this markup is generated by ui-select. + this.log.debug('roleObj.indices[0].names = ' + roleObj.elasticsearch.indices[0].names); + await this.testSubjects.append('roleFormNameInput', roleName); - await testSubjects.click('spaceSelectorComboBox'); + for (const indexName of roleObj.elasticsearch.indices[0].names) { + await this.comboBox.setCustom('indicesInput0', indexName); + } - const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`); - await globalSpaceOption.click(); + if (roleObj.elasticsearch.indices[0].query) { + await this.testSubjects.click('restrictDocumentsQuery0'); + await this.testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query); + } - await testSubjects.click(`basePrivilege_${privilegeName}`); + const globalPrivileges = (roleObj.kibana as any).global; + if (globalPrivileges) { + for (const privilegeName of globalPrivileges) { + await this.testSubjects.click('addSpacePrivilegeButton'); - await testSubjects.click('createSpacePrivilegeButton'); - } - } + await this.testSubjects.click('spaceSelectorComboBox'); - function addPrivilege(privileges: string[]) { - return privileges.reduce(function (promise: Promise, privilegeName: string) { - return promise - .then(() => self.addPrivilegeToRole(privilegeName)) - .then(() => PageObjects.common.sleep(250)); - }, Promise.resolve()); - } + const globalSpaceOption = await this.find.byCssSelector(`#spaceOption_\\*`); + await globalSpaceOption.click(); - await addPrivilege(roleObj.elasticsearch.indices[0].privileges); + await this.testSubjects.click(`basePrivilege_${privilegeName}`); - async function addGrantedField(fields: string[]) { - for (const entry of fields) { - await comboBox.setCustom('fieldInput0', entry); - } + await this.testSubjects.click('createSpacePrivilegeButton'); } + } - // clicking the Granted fields and removing the asterix - if (roleObj.elasticsearch.indices[0].field_security) { - // Toggle FLS switch - await testSubjects.click('restrictFieldsQuery0'); + const addPrivilege = (privileges: string[]) => { + return privileges.reduce((promise: Promise, privilegeName: string) => { + return promise + .then(() => self.addPrivilegeToRole(privilegeName)) + .then(() => this.common.sleep(250)); + }, Promise.resolve()); + }; - // have to remove the '*' - await find.clickByCssSelector( - 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' - ); + await addPrivilege(roleObj.elasticsearch.indices[0].privileges); - await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!); + const addGrantedField = async (fields: string[]) => { + for (const entry of fields) { + await this.comboBox.setCustom('fieldInput0', entry); } + }; - log.debug('click save button'); - await testSubjects.click('roleFormSaveButton'); + // clicking the Granted fields and removing the asterix + if (roleObj.elasticsearch.indices[0].field_security) { + // Toggle FLS switch + await this.testSubjects.click('restrictFieldsQuery0'); - // Signifies that the role management page redirected back to the role grid page, - // and successfully refreshed the grid - await testSubjects.existOrFail('roleRow'); - } + // have to remove the '*' + await this.find.clickByCssSelector( + 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' + ); - async selectRole(role: string) { - const dropdown = await testSubjects.find('rolesDropdown'); - const input = await dropdown.findByCssSelector('input'); - await input.type(role); - await find.clickByCssSelector(`[role=option][title="${role}"]`); - await testSubjects.click('comboBoxToggleListButton'); + await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!); } - async deleteUser(username: string) { - log.debug('Delete user ' + username); - await find.clickByDisplayedLinkText(username); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + this.log.debug('click save button'); + await this.testSubjects.click('roleFormSaveButton'); + + // Signifies that the role management page redirected back to the role grid page, + // and successfully refreshed the grid + await this.testSubjects.existOrFail('roleRow'); + } - log.debug('Find delete button and click'); - await find.clickByButtonText('Delete user'); - await PageObjects.common.sleep(2000); + async selectRole(role: string) { + const dropdown = await this.testSubjects.find('rolesDropdown'); + const input = await dropdown.findByCssSelector('input'); + await input.type(role); + await this.find.clickByCssSelector(`[role=option][title="${role}"]`); + await this.testSubjects.click('comboBoxToggleListButton'); + } - const confirmText = await testSubjects.getVisibleText('confirmModalBodyText'); - log.debug('Delete user alert text = ' + confirmText); - await testSubjects.click('confirmModalConfirmButton'); - return confirmText; - } + async deleteUser(username: string) { + this.log.debug('Delete user ' + username); + await this.find.clickByDisplayedLinkText(username); + await this.header.awaitGlobalLoadingIndicatorHidden(); + + this.log.debug('Find delete button and click'); + await this.find.clickByButtonText('Delete user'); + await this.common.sleep(2000); + + const confirmText = await this.testSubjects.getVisibleText('confirmModalBodyText'); + this.log.debug('Delete user alert text = ' + confirmText); + await this.testSubjects.click('confirmModalConfirmButton'); + return confirmText; } - return new SecurityPage(); } diff --git a/x-pack/test/functional/page_objects/space_selector_page.ts b/x-pack/test/functional/page_objects/space_selector_page.ts index 0a41e4b90287f7..9e05ae1f2c42b9 100644 --- a/x-pack/test/functional/page_objects/space_selector_page.ts +++ b/x-pack/test/functional/page_objects/space_selector_page.ts @@ -6,191 +6,187 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function SpaceSelectorPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const browser = getService('browser'); - const find = getService('find'); - const PageObjects = getPageObjects(['common']); - - class SpaceSelectorPage { - async initTests() { - log.debug('SpaceSelectorPage:initTests'); - } - - async clickSpaceCard(spaceId: string) { - return await retry.try(async () => { - log.info(`SpaceSelectorPage:clickSpaceCard(${spaceId})`); - await testSubjects.click(`space-card-${spaceId}`); - await PageObjects.common.sleep(1000); - }); - } +import { FtrService } from '../ftr_provider_context'; + +export class SpaceSelectorPageObject extends FtrService { + private readonly retry = this.ctx.getService('retry'); + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly browser = this.ctx.getService('browser'); + private readonly find = this.ctx.getService('find'); + private readonly common = this.ctx.getPageObject('common'); + + async initTests() { + this.log.debug('SpaceSelectorPage:initTests'); + } - async expectHomePage(spaceId: string) { - return await this.expectRoute(spaceId, `/app/home#/`); - } + async clickSpaceCard(spaceId: string) { + return await this.retry.try(async () => { + this.log.info(`SpaceSelectorPage:clickSpaceCard(${spaceId})`); + await this.testSubjects.click(`space-card-${spaceId}`); + await this.common.sleep(1000); + }); + } - async expectRoute(spaceId: string, route: string) { - return await retry.try(async () => { - log.debug(`expectRoute(${spaceId}, ${route})`); - await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000); - const url = await browser.getCurrentUrl(); - if (spaceId === 'default') { - expect(url).to.contain(route); - } else { - expect(url).to.contain(`/s/${spaceId}${route}`); - } - }); - } + async expectHomePage(spaceId: string) { + return await this.expectRoute(spaceId, `/app/home#/`); + } - async openSpacesNav() { - log.debug('openSpacesNav()'); - return await testSubjects.click('spacesNavSelector'); - } + async expectRoute(spaceId: string, route: string) { + return await this.retry.try(async () => { + this.log.debug(`expectRoute(${spaceId}, ${route})`); + await this.find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000); + const url = await this.browser.getCurrentUrl(); + if (spaceId === 'default') { + expect(url).to.contain(route); + } else { + expect(url).to.contain(`/s/${spaceId}${route}`); + } + }); + } - async clickManageSpaces() { - await testSubjects.click('manageSpaces'); - } + async openSpacesNav() { + this.log.debug('openSpacesNav()'); + return await this.testSubjects.click('spacesNavSelector'); + } - async clickCreateSpace() { - await testSubjects.click('createSpace'); - } + async clickManageSpaces() { + await this.testSubjects.click('manageSpaces'); + } - async clickEnterSpaceName() { - await testSubjects.click('addSpaceName'); - } + async clickCreateSpace() { + await this.testSubjects.click('createSpace'); + } - async addSpaceName(spaceName: string) { - await testSubjects.setValue('addSpaceName', spaceName); - } + async clickEnterSpaceName() { + await this.testSubjects.click('addSpaceName'); + } - async clickCustomizeSpaceAvatar(spaceId: string) { - await testSubjects.click(`space-avatar-${spaceId}`); - } + async addSpaceName(spaceName: string) { + await this.testSubjects.setValue('addSpaceName', spaceName); + } - async clickSpaceInitials() { - await testSubjects.click('spaceLetterInitial'); - } + async clickCustomizeSpaceAvatar(spaceId: string) { + await this.testSubjects.click(`space-avatar-${spaceId}`); + } - async addSpaceInitials(spaceInitials: string) { - await testSubjects.setValue('spaceLetterInitial', spaceInitials); - } + async clickSpaceInitials() { + await this.testSubjects.click('spaceLetterInitial'); + } - async clickColorPicker() { - await testSubjects.click('colorPickerAnchor'); - } + async addSpaceInitials(spaceInitials: string) { + await this.testSubjects.setValue('spaceLetterInitial', spaceInitials); + } - async setColorinPicker(hexValue: string) { - await testSubjects.setValue('colorPickerAnchor', hexValue); - } + async clickColorPicker() { + await this.testSubjects.click('colorPickerAnchor'); + } - async clickShowFeatures() { - await testSubjects.click('show-hide-section-link'); - } + async setColorinPicker(hexValue: string) { + await this.testSubjects.setValue('colorPickerAnchor', hexValue); + } - async clickChangeAllPriv() { - await testSubjects.click('changeAllPrivilegesButton'); - } + async clickShowFeatures() { + await this.testSubjects.click('show-hide-section-link'); + } - async clickSaveSpaceCreation() { - await testSubjects.click('save-space-button'); - } + async clickChangeAllPriv() { + await this.testSubjects.click('changeAllPrivilegesButton'); + } - async clickSpaceEditButton(spaceName: string) { - await testSubjects.click(`${spaceName}-editSpace`); - } + async clickSaveSpaceCreation() { + await this.testSubjects.click('save-space-button'); + } - async clickGoToRolesPage() { - await testSubjects.click('rolesManagementPage'); - } + async clickSpaceEditButton(spaceName: string) { + await this.testSubjects.click(`${spaceName}-editSpace`); + } - async clickCancelSpaceCreation() { - await testSubjects.click('cancel-space-button'); - } + async clickGoToRolesPage() { + await this.testSubjects.click('rolesManagementPage'); + } - async clickOnCustomizeURL() { - await testSubjects.click('CustomizeOrReset'); - } + async clickCancelSpaceCreation() { + await this.testSubjects.click('cancel-space-button'); + } - async clickOnSpaceURLDisplay() { - await testSubjects.click('spaceURLDisplay'); - } + async clickOnCustomizeURL() { + await this.testSubjects.click('CustomizeOrReset'); + } - async setSpaceURL(spaceURL: string) { - await testSubjects.setValue('spaceURLDisplay', spaceURL); - } + async clickOnSpaceURLDisplay() { + await this.testSubjects.click('spaceURLDisplay'); + } - async clickHideAllFeatures() { - await testSubjects.click('spc-toggle-all-features-hide'); - } + async setSpaceURL(spaceURL: string) { + await this.testSubjects.setValue('spaceURLDisplay', spaceURL); + } - async clickShowAllFeatures() { - await testSubjects.click('spc-toggle-all-features-show'); - } + async clickHideAllFeatures() { + await this.testSubjects.click('spc-toggle-all-features-hide'); + } - async openFeatureCategory(categoryName: string) { - const category = await find.byCssSelector( - `button[aria-controls=featureCategory_${categoryName}]` - ); - const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; - if (!isCategoryExpanded) { - await category.click(); - } - } + async clickShowAllFeatures() { + await this.testSubjects.click('spc-toggle-all-features-show'); + } - async closeFeatureCategory(categoryName: string) { - const category = await find.byCssSelector( - `button[aria-controls=featureCategory_${categoryName}]` - ); - const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; - if (isCategoryExpanded) { - await category.click(); - } + async openFeatureCategory(categoryName: string) { + const category = await this.find.byCssSelector( + `button[aria-controls=featureCategory_${categoryName}]` + ); + const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; + if (!isCategoryExpanded) { + await category.click(); } + } - async toggleFeatureCategoryVisibility(categoryName: string) { - await testSubjects.click(`featureCategoryButton_${categoryName}`); + async closeFeatureCategory(categoryName: string) { + const category = await this.find.byCssSelector( + `button[aria-controls=featureCategory_${categoryName}]` + ); + const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; + if (isCategoryExpanded) { + await category.click(); } + } - async clickOnDescriptionOfSpace() { - await testSubjects.click('descriptionSpaceText'); - } + async toggleFeatureCategoryVisibility(categoryName: string) { + await this.testSubjects.click(`featureCategoryButton_${categoryName}`); + } - async setOnDescriptionOfSpace(descriptionSpace: string) { - await testSubjects.setValue('descriptionSpaceText', descriptionSpace); - } + async clickOnDescriptionOfSpace() { + await this.testSubjects.click('descriptionSpaceText'); + } - async clickOnDeleteSpaceButton(spaceName: string) { - await testSubjects.click(`${spaceName}-deleteSpace`); - } + async setOnDescriptionOfSpace(descriptionSpace: string) { + await this.testSubjects.setValue('descriptionSpaceText', descriptionSpace); + } - async cancelDeletingSpace() { - await testSubjects.click('confirmModalCancelButton'); - } + async clickOnDeleteSpaceButton(spaceName: string) { + await this.testSubjects.click(`${spaceName}-deleteSpace`); + } - async confirmDeletingSpace() { - await testSubjects.click('confirmModalConfirmButton'); - } + async cancelDeletingSpace() { + await this.testSubjects.click('confirmModalCancelButton'); + } - async clickOnSpaceb() { - await testSubjects.click('space-avatar-space_b'); - } + async confirmDeletingSpace() { + await this.testSubjects.click('confirmModalConfirmButton'); + } - async goToSpecificSpace(spaceName: string) { - await testSubjects.click(`${spaceName}-gotoSpace`); - } + async clickOnSpaceb() { + await this.testSubjects.click('space-avatar-space_b'); + } - async clickSpaceAvatar(spaceId: string) { - return await retry.try(async () => { - log.info(`SpaceSelectorPage:clickSpaceAvatar(${spaceId})`); - await testSubjects.click(`space-avatar-${spaceId}`); - await PageObjects.common.sleep(1000); - }); - } + async goToSpecificSpace(spaceName: string) { + await this.testSubjects.click(`${spaceName}-gotoSpace`); } - return new SpaceSelectorPage(); + async clickSpaceAvatar(spaceId: string) { + return await this.retry.try(async () => { + this.log.info(`SpaceSelectorPage:clickSpaceAvatar(${spaceId})`); + await this.testSubjects.click(`space-avatar-${spaceId}`); + await this.common.sleep(1000); + }); + } } diff --git a/x-pack/test/functional/page_objects/status_page.ts b/x-pack/test/functional/page_objects/status_page.ts index ed90aef9547706..fa9ff392f0aa15 100644 --- a/x-pack/test/functional/page_objects/status_page.ts +++ b/x-pack/test/functional/page_objects/status_page.ts @@ -5,20 +5,17 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function StatusPagePageProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - const find = getService('find'); - class StatusPage { - async initTests() { - log.debug('StatusPage:initTests'); - } +export class StatusPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); - async expectStatusPage(): Promise { - await find.byCssSelector('[data-test-subj="statusPageRoot"]', 20000); - } + async initTests() { + this.log.debug('StatusPage:initTests'); } - return new StatusPage(); + async expectStatusPage(): Promise { + await this.find.byCssSelector('[data-test-subj="statusPageRoot"]', 20000); + } } diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts index c503d2282cf6ca..b3f6622b04a571 100644 --- a/x-pack/test/functional/page_objects/tag_management_page.ts +++ b/x-pack/test/functional/page_objects/tag_management_page.ts @@ -5,9 +5,10 @@ * 2.0. */ -// eslint-disable-next-line max-classes-per-file +/* eslint-disable max-classes-per-file */ + import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService, FtrProviderContext } from '../ftr_provider_context'; interface FillTagFormFields { name?: string; @@ -17,463 +18,467 @@ interface FillTagFormFields { type TagFormValidation = FillTagFormFields; -export function TagManagementPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const browser = getService('browser'); - const PageObjects = getPageObjects(['header', 'common', 'savedObjects', 'settings']); - const retry = getService('retry'); +/** + * Sub page object to manipulate the create/edit tag modal. + */ +class TagModal extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly header = this.ctx.getPageObject('header'); + + constructor(ctx: FtrProviderContext, private readonly page: TagManagementPageObject) { + super(ctx); + } /** - * Sub page object to manipulate the create/edit tag modal. + * Open the create tag modal, by clicking on the associated button. */ - class TagModal { - constructor(private readonly page: TagManagementPage) {} - /** - * Open the create tag modal, by clicking on the associated button. - */ - async openCreate() { - return await testSubjects.click('createTagButton'); - } - - /** - * Open the edit tag modal for given tag name. The tag must be in the currently displayed tags. - */ - async openEdit(tagName: string) { - await this.page.clickEdit(tagName); - } + async openCreate() { + return await this.testSubjects.click('createTagButton'); + } - /** - * Fills the given fields in the form. - * - * If a field is undefined, will not set the value (use a empty string for that) - * If `submit` is true, will call `clickConfirm` once the fields have been filled. - */ - async fillForm(fields: FillTagFormFields, { submit = false }: { submit?: boolean } = {}) { - if (fields.name !== undefined) { - await testSubjects.click('createModalField-name'); - await testSubjects.setValue('createModalField-name', fields.name); - } - if (fields.color !== undefined) { - // EuiColorPicker does not allow to specify data-test-subj for the colorpicker input - await testSubjects.setValue('colorPickerAnchor', fields.color); - } - if (fields.description !== undefined) { - await testSubjects.click('createModalField-description'); - await testSubjects.setValue('createModalField-description', fields.description); - } + /** + * Open the edit tag modal for given tag name. The tag must be in the currently displayed tags. + */ + async openEdit(tagName: string) { + await this.page.clickEdit(tagName); + } - if (submit) { - await this.clickConfirm(); - } + /** + * Fills the given fields in the form. + * + * If a field is undefined, will not set the value (use a empty string for that) + * If `submit` is true, will call `clickConfirm` once the fields have been filled. + */ + async fillForm(fields: FillTagFormFields, { submit = false }: { submit?: boolean } = {}) { + if (fields.name !== undefined) { + await this.testSubjects.click('createModalField-name'); + await this.testSubjects.setValue('createModalField-name', fields.name); + } + if (fields.color !== undefined) { + // EuiColorPicker does not allow to specify data-test-subj for the colorpicker input + await this.testSubjects.setValue('colorPickerAnchor', fields.color); + } + if (fields.description !== undefined) { + await this.testSubjects.click('createModalField-description'); + await this.testSubjects.setValue('createModalField-description', fields.description); } - /** - * Return the values currently displayed in the form. - */ - async getFormValues(): Promise> { - return { - name: await testSubjects.getAttribute('createModalField-name', 'value'), - color: await testSubjects.getAttribute('colorPickerAnchor', 'value'), - description: await testSubjects.getAttribute('createModalField-description', 'value'), - }; + if (submit) { + await this.clickConfirm(); } + } - /** - * Return the validation errors currently displayed for each field. - */ - async getValidationErrors(): Promise { - const errors: TagFormValidation = {}; + /** + * Return the values currently displayed in the form. + */ + async getFormValues(): Promise> { + return { + name: await this.testSubjects.getAttribute('createModalField-name', 'value'), + color: await this.testSubjects.getAttribute('colorPickerAnchor', 'value'), + description: await this.testSubjects.getAttribute('createModalField-description', 'value'), + }; + } - const getError = async (rowDataTestSubj: string) => { - const row = await testSubjects.find(rowDataTestSubj); - const errorElements = await row.findAllByClassName('euiFormErrorText'); - return errorElements.length ? await errorElements[0].getVisibleText() : undefined; - }; + /** + * Return the validation errors currently displayed for each field. + */ + async getValidationErrors(): Promise { + const errors: TagFormValidation = {}; - errors.name = await getError('createModalRow-name'); - errors.color = await getError('createModalRow-color'); - errors.description = await getError('createModalRow-description'); + const getError = async (rowDataTestSubj: string) => { + const row = await this.testSubjects.find(rowDataTestSubj); + const errorElements = await row.findAllByClassName('euiFormErrorText'); + return errorElements.length ? await errorElements[0].getVisibleText() : undefined; + }; - return errors; - } + errors.name = await getError('createModalRow-name'); + errors.color = await getError('createModalRow-color'); + errors.description = await getError('createModalRow-description'); - /** - * Returns true if the form as at least one error displayed, false otherwise - */ - async hasError() { - const errors = await this.getValidationErrors(); - return Boolean(errors.name || errors.color || errors.description); - } + return errors; + } - /** - * Click on the 'cancel' button in the create/edit modal. - */ - async clickCancel() { - await testSubjects.click('createModalCancelButton'); - } + /** + * Returns true if the form as at least one error displayed, false otherwise + */ + async hasError() { + const errors = await this.getValidationErrors(); + return Boolean(errors.name || errors.color || errors.description); + } - /** - * Click on the 'confirm' button in the create/edit modal if not disabled. - */ - async clickConfirm() { - await testSubjects.click('createModalConfirmButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + /** + * Click on the 'cancel' button in the create/edit modal. + */ + async clickCancel() { + await this.testSubjects.click('createModalCancelButton'); + } - /** - * Return true if the confirm button is disabled, false otherwise. - */ - async isConfirmDisabled() { - const disabled = await testSubjects.getAttribute('createModalConfirmButton', 'disabled'); - return disabled === 'true'; - } + /** + * Click on the 'confirm' button in the create/edit modal if not disabled. + */ + async clickConfirm() { + await this.testSubjects.click('createModalConfirmButton'); + await this.header.waitUntilLoadingHasFinished(); + } - /** - * Return true if the modal is currently opened. - */ - async isOpened() { - return await testSubjects.exists('tagModalForm'); - } + /** + * Return true if the confirm button is disabled, false otherwise. + */ + async isConfirmDisabled() { + const disabled = await this.testSubjects.getAttribute('createModalConfirmButton', 'disabled'); + return disabled === 'true'; + } - /** - * Wait until the modal is closed. - */ - async waitUntilClosed() { - await retry.try(async () => { - if (await this.isOpened()) { - throw new Error('save modal still open'); - } - }); - } + /** + * Return true if the modal is currently opened. + */ + async isOpened() { + return await this.testSubjects.exists('tagModalForm'); + } - /** - * Close the modal if currently opened. - */ - async closeIfOpened() { + /** + * Wait until the modal is closed. + */ + async waitUntilClosed() { + await this.retry.try(async () => { if (await this.isOpened()) { - await this.clickCancel(); + throw new Error('save modal still open'); } - } + }); } /** - * Sub page object to manipulate the assign flyout. + * Close the modal if currently opened. */ - class TagAssignmentFlyout { - constructor(private readonly page: TagManagementPage) {} - - /** - * Open the tag assignment flyout, by selected given `tagNames` in the table, then clicking on the `assign` - * action in the bulk action menu. - */ - async open(tagNames: string[]) { - for (const tagName of tagNames) { - await this.page.selectTagByName(tagName); - } - await this.page.clickOnBulkAction('assign'); - await this.waitUntilResultsAreLoaded(); + async closeIfOpened() { + if (await this.isOpened()) { + await this.clickCancel(); } + } +} - /** - * Click on the 'cancel' button in the assign flyout. - */ - async clickCancel() { - await testSubjects.click('assignFlyoutCancelButton'); - await this.page.waitUntilTableIsLoaded(); - } +/** + * Sub page object to manipulate the assign flyout. + */ +class TagAssignmentFlyout extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); - /** - * Click on the 'confirm' button in the assign flyout. - */ - async clickConfirm() { - await testSubjects.click('assignFlyoutConfirmButton'); - await this.waitForFlyoutToClose(); - await this.page.waitUntilTableIsLoaded(); - } + constructor(ctx: FtrProviderContext, private readonly page: TagManagementPageObject) { + super(ctx); + } - /** - * Click on an assignable object result line in the flyout result list. - */ - async clickOnResult(type: string, id: string) { - await testSubjects.click(`assign-result-${type}-${id}`); + /** + * Open the tag assignment flyout, by selected given `tagNames` in the table, then clicking on the `assign` + * action in the bulk action menu. + */ + async open(tagNames: string[]) { + for (const tagName of tagNames) { + await this.page.selectTagByName(tagName); } + await this.page.clickOnBulkAction('assign'); + await this.waitUntilResultsAreLoaded(); + } - /** - * Wait until the assignable object results are displayed in the flyout. - */ - async waitUntilResultsAreLoaded() { - return find.waitForDeletedByCssSelector( - '*[data-test-subj="assignFlyoutResultList"] .euiLoadingSpinner' - ); - } + /** + * Click on the 'cancel' button in the assign flyout. + */ + async clickCancel() { + await this.testSubjects.click('assignFlyoutCancelButton'); + await this.page.waitUntilTableIsLoaded(); + } - /** - * Wait until the flyout is closed. - */ - async waitForFlyoutToClose() { - return testSubjects.waitForDeleted('assignFlyoutResultList'); - } + /** + * Click on the 'confirm' button in the assign flyout. + */ + async clickConfirm() { + await this.testSubjects.click('assignFlyoutConfirmButton'); + await this.waitForFlyoutToClose(); + await this.page.waitUntilTableIsLoaded(); } /** - * Tag management page object. - * - * @remarks All the table manipulation helpers makes the assumption - * that all tags are displayed on a single page. Pagination - * and finding / interacting with a tag on another page is not supported. - */ - class TagManagementPage { - public readonly tagModal = new TagModal(this); - public readonly assignFlyout = new TagAssignmentFlyout(this); - - /** - * Navigate to the tag management section, by accessing the management app, then clicking - * on the `tags` link. - */ - async navigateTo() { - await PageObjects.settings.navigateTo(); - await testSubjects.click('tags'); - await this.waitUntilTableIsLoaded(); - } + * Click on an assignable object result line in the flyout result list. + */ + async clickOnResult(type: string, id: string) { + await this.testSubjects.click(`assign-result-${type}-${id}`); + } - /** - * Wait until the tags table is displayed and is not in a the loading state - */ - async waitUntilTableIsLoaded() { - return retry.try(async () => { - const isLoaded = await find.existsByDisplayedByCssSelector( - '*[data-test-subj="tagsManagementTable"]:not(.euiBasicTable-loading)' - ); - - if (isLoaded) { - return true; - } else { - throw new Error('Waiting'); - } - }); - } + /** + * Wait until the assignable object results are displayed in the flyout. + */ + async waitUntilResultsAreLoaded() { + return this.find.waitForDeletedByCssSelector( + '*[data-test-subj="assignFlyoutResultList"] .euiLoadingSpinner' + ); + } - /** - * Type given `term` in the table's search bar - */ - async searchForTerm(term: string) { - const searchBox = await testSubjects.find('tagsManagementSearchBar'); - await searchBox.clearValue(); - await searchBox.type(term); - await searchBox.pressKeys(browser.keys.ENTER); - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.waitUntilTableIsLoaded(); - } + /** + * Wait until the flyout is closed. + */ + async waitForFlyoutToClose() { + return this.testSubjects.waitForDeleted('assignFlyoutResultList'); + } +} - /** - * Return true if the `Create new tag` button is visible, false otherwise. - */ - async isCreateButtonVisible() { - return await testSubjects.exists('createTagButton'); - } +/** + * Tag management page object. + * + * @remarks All the table manipulation helpers makes the assumption + * that all tags are displayed on a single page. Pagination + * and finding / interacting with a tag on another page is not supported. + */ +export class TagManagementPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly browser = this.ctx.getService('browser'); + private readonly retry = this.ctx.getService('retry'); + private readonly header = this.ctx.getPageObject('header'); + private readonly settings = this.ctx.getPageObject('settings'); - /** - * Returns true if given action is available from the table action column - */ - async isActionAvailable(action: string) { - const rows = await testSubjects.findAll('tagsTableRow'); - const firstRow = rows[0]; - // if there is more than 2 actions, they are wrapped in a popover that opens from a new action. - const menuActionPresent = await testSubjects.descendantExists( - 'euiCollapsedItemActionsButton', - firstRow - ); - if (menuActionPresent) { - const actionButton = await testSubjects.findDescendant( - 'euiCollapsedItemActionsButton', - firstRow - ); - await actionButton.click(); - const actionPresent = await testSubjects.exists(`tagsTableAction-${action}`); - await actionButton.click(); - return actionPresent; - } else { - return await testSubjects.exists(`tagsTableAction-${action}`); - } - } + public readonly tagModal = new TagModal(this.ctx, this); + public readonly assignFlyout = new TagAssignmentFlyout(this.ctx, this); - /** - * Return the (table ordered) name of the tags currently displayed in the table. - */ - async getDisplayedTagNames() { - const tags = await this.getDisplayedTagsInfo(); - return tags.map((tag) => tag.name); - } + /** + * Navigate to the tag management section, by accessing the management app, then clicking + * on the `tags` link. + */ + async navigateTo() { + await this.settings.navigateTo(); + await this.testSubjects.click('tags'); + await this.waitUntilTableIsLoaded(); + } - /** - * Return true if the 'connections' link is displayed for given tag name. - * - * Having the link not displayed can either mean that the user don't have the view - * permission to see the connections, or that the tag don't have any. - */ - async isConnectionLinkDisplayed(tagName: string) { - const tags = await this.getDisplayedTagsInfo(); - const tag = tags.find((t) => t.name === tagName); - return tag ? tag.connectionCount !== undefined : false; - } + /** + * Wait until the tags table is displayed and is not in a the loading state + */ + async waitUntilTableIsLoaded() { + return this.retry.try(async () => { + const isLoaded = await this.find.existsByDisplayedByCssSelector( + '*[data-test-subj="tagsManagementTable"]:not(.euiBasicTable-loading)' + ); - /** - * Click the 'edit' action button in the table for given tag name. - */ - async clickEdit(tagName: string) { - const tagRow = await this.getRowByName(tagName); - if (tagRow) { - const editButton = await testSubjects.findDescendant('tagsTableAction-edit', tagRow); - await editButton?.click(); + if (isLoaded) { + return true; + } else { + throw new Error('Waiting'); } - } + }); + } - /** - * Return the raw `WebElementWrapper` of the table's row for given tag name. - */ - async getRowByName(tagName: string) { - const tagNames = await this.getDisplayedTagNames(); - const tagIndex = tagNames.indexOf(tagName); - const rows = await testSubjects.findAll('tagsTableRow'); - return rows[tagIndex]; - } + /** + * Type given `term` in the table's search bar + */ + async searchForTerm(term: string) { + const searchBox = await this.testSubjects.find('tagsManagementSearchBar'); + await searchBox.clearValue(); + await searchBox.type(term); + await searchBox.pressKeys(this.browser.keys.ENTER); + await this.header.waitUntilLoadingHasFinished(); + await this.waitUntilTableIsLoaded(); + } - /** - * Click on the 'connections' link in the table for given tag name. - */ - async clickOnConnectionsLink(tagName: string) { - const tagRow = await this.getRowByName(tagName); - const connectionLink = await testSubjects.findDescendant( - 'tagsTableRowConnectionsLink', - tagRow - ); - await connectionLink.click(); - } + /** + * Return true if the `Create new tag` button is visible, false otherwise. + */ + async isCreateButtonVisible() { + return await this.testSubjects.exists('createTagButton'); + } - /** - * Return true if the selection column is displayed on the table, false otherwise. - */ - async isSelectionColumnDisplayed() { - const firstRow = await testSubjects.find('tagsTableRow'); - const checkbox = await firstRow.findAllByCssSelector( - '.euiTableRowCellCheckbox .euiCheckbox__input' + /** + * Returns true if given action is available from the table action column + */ + async isActionAvailable(action: string) { + const rows = await this.testSubjects.findAll('tagsTableRow'); + const firstRow = rows[0]; + // if there is more than 2 actions, they are wrapped in a popover that opens from a new action. + const menuActionPresent = await this.testSubjects.descendantExists( + 'euiCollapsedItemActionsButton', + firstRow + ); + if (menuActionPresent) { + const actionButton = await this.testSubjects.findDescendant( + 'euiCollapsedItemActionsButton', + firstRow ); - return Boolean(checkbox.length); + await actionButton.click(); + const actionPresent = await this.testSubjects.exists(`tagsTableAction-${action}`); + await actionButton.click(); + return actionPresent; + } else { + return await this.testSubjects.exists(`tagsTableAction-${action}`); } + } - /** - * Click on the selection checkbox of the tag matching given tag name. - */ - async selectTagByName(tagName: string) { - const tagRow = await this.getRowByName(tagName); - const checkbox = await tagRow.findByCssSelector( - '.euiTableRowCellCheckbox .euiCheckbox__input' - ); - await checkbox.click(); - } + /** + * Return the (table ordered) name of the tags currently displayed in the table. + */ + async getDisplayedTagNames() { + const tags = await this.getDisplayedTagsInfo(); + return tags.map((tag) => tag.name); + } - /** - * Returns true if the tag bulk action menu is displayed, false otherwise. - */ - async isActionMenuButtonDisplayed() { - return testSubjects.exists('actionBar-contextMenuButton'); - } + /** + * Return true if the 'connections' link is displayed for given tag name. + * + * Having the link not displayed can either mean that the user don't have the view + * permission to see the connections, or that the tag don't have any. + */ + async isConnectionLinkDisplayed(tagName: string) { + const tags = await this.getDisplayedTagsInfo(); + const tag = tags.find((t) => t.name === tagName); + return tag ? tag.connectionCount !== undefined : false; + } - /** - * Open the bulk action menu if not already opened. - */ - async openActionMenu() { - if (!(await this.isActionMenuOpened())) { - await this.toggleActionMenu(); - } + /** + * Click the 'edit' action button in the table for given tag name. + */ + async clickEdit(tagName: string) { + const tagRow = await this.getRowByName(tagName); + if (tagRow) { + const editButton = await this.testSubjects.findDescendant('tagsTableAction-edit', tagRow); + await editButton?.click(); } + } - /** - * Check if the action for given `actionId` is present in the bulk action menu. - * - * The menu will automatically be opened if not already, but the test must still - * select tags to make the action menu button appear. - */ - async isBulkActionPresent(actionId: string) { - if (!(await this.isActionMenuButtonDisplayed())) { - return false; - } - const menuWasOpened = await this.isActionMenuOpened(); - if (!menuWasOpened) { - await this.openActionMenu(); - } + /** + * Return the raw `WebElementWrapper` of the table's row for given tag name. + */ + async getRowByName(tagName: string) { + const tagNames = await this.getDisplayedTagNames(); + const tagIndex = tagNames.indexOf(tagName); + const rows = await this.testSubjects.findAll('tagsTableRow'); + return rows[tagIndex]; + } + + /** + * Click on the 'connections' link in the table for given tag name. + */ + async clickOnConnectionsLink(tagName: string) { + const tagRow = await this.getRowByName(tagName); + const connectionLink = await this.testSubjects.findDescendant( + 'tagsTableRowConnectionsLink', + tagRow + ); + await connectionLink.click(); + } - const actionExists = await testSubjects.exists(`actionBar-button-${actionId}`); + /** + * Return true if the selection column is displayed on the table, false otherwise. + */ + async isSelectionColumnDisplayed() { + const firstRow = await this.testSubjects.find('tagsTableRow'); + const checkbox = await firstRow.findAllByCssSelector( + '.euiTableRowCellCheckbox .euiCheckbox__input' + ); + return Boolean(checkbox.length); + } - if (!menuWasOpened) { - await this.toggleActionMenu(); - } + /** + * Click on the selection checkbox of the tag matching given tag name. + */ + async selectTagByName(tagName: string) { + const tagRow = await this.getRowByName(tagName); + const checkbox = await tagRow.findByCssSelector('.euiTableRowCellCheckbox .euiCheckbox__input'); + await checkbox.click(); + } + + /** + * Returns true if the tag bulk action menu is displayed, false otherwise. + */ + async isActionMenuButtonDisplayed() { + return this.testSubjects.exists('actionBar-contextMenuButton'); + } - return actionExists; + /** + * Open the bulk action menu if not already opened. + */ + async openActionMenu() { + if (!(await this.isActionMenuOpened())) { + await this.toggleActionMenu(); } + } - /** - * Click on given bulk action button - */ - async clickOnBulkAction(actionId: string) { + /** + * Check if the action for given `actionId` is present in the bulk action menu. + * + * The menu will automatically be opened if not already, but the test must still + * select tags to make the action menu button appear. + */ + async isBulkActionPresent(actionId: string) { + if (!(await this.isActionMenuButtonDisplayed())) { + return false; + } + const menuWasOpened = await this.isActionMenuOpened(); + if (!menuWasOpened) { await this.openActionMenu(); - await testSubjects.click(`actionBar-button-${actionId}`); } - /** - * Toggle (close if opened, open if closed) the bulk action menu. - */ - async toggleActionMenu() { - await testSubjects.click('actionBar-contextMenuButton'); - } + const actionExists = await this.testSubjects.exists(`actionBar-button-${actionId}`); - /** - * Return true if the bulk action menu is opened, false otherwise. - */ - async isActionMenuOpened() { - return testSubjects.exists('actionBar-contextMenuPopover'); + if (!menuWasOpened) { + await this.toggleActionMenu(); } - /** - * Return the info of all the tags currently displayed in the table (in table's order) - */ - async getDisplayedTagsInfo() { - const rows = await testSubjects.findAll('tagsTableRow'); - return Promise.all([...rows.map(parseTableRow)]); - } + return actionExists; + } - async getDisplayedTagInfo(tagName: string) { - const rows = await this.getDisplayedTagsInfo(); - return rows.find((row) => row.name === tagName); - } + /** + * Click on given bulk action button + */ + async clickOnBulkAction(actionId: string) { + await this.openActionMenu(); + await this.testSubjects.click(`actionBar-button-${actionId}`); + } - /** - * Converts the tagName to the format used in test subjects - * @param tagName - */ - testSubjFriendly(tagName: string) { - return tagName.replace(' ', '_'); - } + /** + * Toggle (close if opened, open if closed) the bulk action menu. + */ + async toggleActionMenu() { + await this.testSubjects.click('actionBar-contextMenuButton'); } - const parseTableRow = async (row: WebElementWrapper) => { - const dom = await row.parseDomContent(); + /** + * Return true if the bulk action menu is opened, false otherwise. + */ + async isActionMenuOpened() { + return this.testSubjects.exists('actionBar-contextMenuPopover'); + } - const connectionsText = dom.findTestSubject('tagsTableRowConnectionsLink').text(); - const rawConnectionCount = connectionsText.replace(/[^0-9]/g, ''); - const connectionCount = rawConnectionCount ? parseInt(rawConnectionCount, 10) : undefined; + /** + * Return the info of all the tags currently displayed in the table (in table's order) + */ + async getDisplayedTagsInfo() { + const rows = await this.testSubjects.findAll('tagsTableRow'); + return Promise.all([...rows.map(parseTableRow)]); + } - // ideally we would also return the color, but it can't be easily retrieved from the DOM - return { - name: dom.findTestSubject('tagsTableRowName').find('.euiTableCellContent').text(), - description: dom - .findTestSubject('tagsTableRowDescription') - .find('.euiTableCellContent') - .text(), - connectionCount, - }; - }; + async getDisplayedTagInfo(tagName: string) { + const rows = await this.getDisplayedTagsInfo(); + return rows.find((row) => row.name === tagName); + } - return new TagManagementPage(); + /** + * Converts the tagName to the format used in test subjects + * @param tagName + */ + testSubjFriendly(tagName: string) { + return tagName.replace(' ', '_'); + } } + +const parseTableRow = async (row: WebElementWrapper) => { + const dom = await row.parseDomContent(); + + const connectionsText = dom.findTestSubject('tagsTableRowConnectionsLink').text(); + const rawConnectionCount = connectionsText.replace(/[^0-9]/g, ''); + const connectionCount = rawConnectionCount ? parseInt(rawConnectionCount, 10) : undefined; + + // ideally we would also return the color, but it can't be easily retrieved from the DOM + return { + name: dom.findTestSubject('tagsTableRowName').find('.euiTableCellContent').text(), + description: dom.findTestSubject('tagsTableRowDescription').find('.euiTableCellContent').text(), + connectionCount, + }; +}; diff --git a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts index 1c4a85450a8da4..211bcbbd592190 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts +++ b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts @@ -5,76 +5,72 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function UpgradeAssistantPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const retry = getService('retry'); - const log = getService('log'); - const browser = getService('browser'); - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const { common } = getPageObjects(['common']); +export class UpgradeAssistantPageObject extends FtrService { + private readonly retry = this.ctx.getService('retry'); + private readonly log = this.ctx.getService('log'); + private readonly browser = this.ctx.getService('browser'); + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly common = this.ctx.getPageObject('common'); - class UpgradeAssistant { - async initTests() { - log.debug('UpgradeAssistant:initTests'); - } + async initTests() { + this.log.debug('UpgradeAssistant:initTests'); + } - async navigateToPage() { - return await retry.try(async () => { - await common.navigateToApp('settings'); - await testSubjects.click('upgrade_assistant'); - await retry.waitFor('url to contain /upgrade_assistant', async () => { - const url = await browser.getCurrentUrl(); - return url.includes('/upgrade_assistant'); - }); + async navigateToPage() { + return await this.retry.try(async () => { + await this.common.navigateToApp('settings'); + await this.testSubjects.click('upgrade_assistant'); + await this.retry.waitFor('url to contain /upgrade_assistant', async () => { + const url = await this.browser.getCurrentUrl(); + return url.includes('/upgrade_assistant'); }); - } - - async toggleDeprecationLogging() { - log.debug('toggleDeprecationLogging()'); - await testSubjects.click('upgradeAssistantDeprecationToggle'); - } + }); + } - async isDeprecationLoggingEnabled() { - const isDeprecationEnabled = await testSubjects.getAttribute( - 'upgradeAssistantDeprecationToggle', - 'aria-checked' - ); - log.debug(`Deprecation enabled == ${isDeprecationEnabled}`); - return isDeprecationEnabled === 'true'; - } + async toggleDeprecationLogging() { + this.log.debug('toggleDeprecationLogging()'); + await this.testSubjects.click('upgradeAssistantDeprecationToggle'); + } - async deprecationLoggingEnabledLabel() { - const loggingEnabledLabel = await find.byCssSelector( - '[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span' - ); - return await loggingEnabledLabel.getVisibleText(); - } + async isDeprecationLoggingEnabled() { + const isDeprecationEnabled = await this.testSubjects.getAttribute( + 'upgradeAssistantDeprecationToggle', + 'aria-checked' + ); + this.log.debug(`Deprecation enabled == ${isDeprecationEnabled}`); + return isDeprecationEnabled === 'true'; + } - async clickTab(tabId: string) { - return await retry.try(async () => { - log.debug('clickTab()'); - await find.clickByCssSelector(`.euiTabs .euiTab#${tabId}`); - }); - } + async deprecationLoggingEnabledLabel() { + const loggingEnabledLabel = await this.find.byCssSelector( + '[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span' + ); + return await loggingEnabledLabel.getVisibleText(); + } - async waitForTelemetryHidden() { - const self = this; - await retry.waitFor('Telemetry to disappear.', async () => { - return (await self.isTelemetryExists()) === false; - }); - } + async clickTab(tabId: string) { + return await this.retry.try(async () => { + this.log.debug('clickTab()'); + await this.find.clickByCssSelector(`.euiTabs .euiTab#${tabId}`); + }); + } - async issueSummaryText() { - log.debug('expectIssueSummary()'); - return await testSubjects.getVisibleText('upgradeAssistantIssueSummary'); - } + async waitForTelemetryHidden() { + const self = this; + await this.retry.waitFor('Telemetry to disappear.', async () => { + return (await self.isTelemetryExists()) === false; + }); + } - async isTelemetryExists() { - return await testSubjects.exists('upgradeAssistantTelemetryRunning'); - } + async issueSummaryText() { + this.log.debug('expectIssueSummary()'); + return await this.testSubjects.getVisibleText('upgradeAssistantIssueSummary'); } - return new UpgradeAssistant(); + async isTelemetryExists() { + return await this.testSubjects.exists('upgradeAssistantTelemetryRunning'); + } } diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 03e1513eac0438..366511f7f43c53 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -6,117 +6,121 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function UptimePageProvider({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['timePicker', 'header']); - const { common: commonService, monitor, navigation } = getService('uptime'); - const retry = getService('retry'); - - return new (class UptimePage { - public async goToRoot(refresh?: boolean) { - await navigation.goToUptime(); - if (refresh) { - await navigation.refreshApp(); - } - } - - public async setDateRange(start: string, end: string) { - const { start: prevStart, end: prevEnd } = await pageObjects.timePicker.getTimeConfig(); - if (start !== prevStart || prevEnd !== end) { - await pageObjects.timePicker.setAbsoluteRange(start, end); - } else { - await navigation.refreshApp(); - } - } - - public async goToUptimeOverviewAndLoadData( - dateStart: string, - dateEnd: string, - monitorIdToCheck?: string - ) { - await navigation.goToUptime(); - await this.setDateRange(dateStart, dateEnd); - if (monitorIdToCheck) { - await commonService.monitorIdExists(monitorIdToCheck); - } - await pageObjects.header.waitUntilLoadingHasFinished(); - } - - public async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { - await pageObjects.header.waitUntilLoadingHasFinished(); - await this.setDateRange(dateStart, dateEnd); - await navigation.goToMonitor(monitorId); - } - - public async inputFilterQuery(filterQuery: string) { - await commonService.setFilterText(filterQuery); - } - - public async pageHasDataMissing() { - return await commonService.pageHasDataMissing(); - } - - public async pageHasExpectedIds(monitorIdsToCheck: string[]): Promise { - return retry.tryForTime(15000, async () => { - await Promise.all(monitorIdsToCheck.map((id) => commonService.monitorPageLinkExists(id))); - }); - } - - public async pageUrlContains(value: string, expected: boolean = true): Promise { - return retry.tryForTime(12000, async () => { - expect(await commonService.urlContains(value)).to.eql(expected); - }); - } - - public async changePage(direction: 'next' | 'prev') { - if (direction === 'next') { - await commonService.goToNextPage(); - } else if (direction === 'prev') { - await commonService.goToPreviousPage(); - } - } - - public async setStatusFilter(value: 'up' | 'down') { - if (value === 'up') { - await commonService.setStatusFilterUp(); - } else if (value === 'down') { - await commonService.setStatusFilterDown(); - } - } - - public async selectFilterItems(filters: Record) { - for (const key in filters) { - if (filters.hasOwnProperty(key)) { - const values = filters[key]; - for (let i = 0; i < values.length; i++) { - await commonService.selectFilterItem(key, values[i]); - } +import { FtrService } from '../ftr_provider_context'; + +export class UptimePageObject extends FtrService { + private readonly timePicker = this.ctx.getPageObject('timePicker'); + private readonly header = this.ctx.getPageObject('header'); + + private readonly commonService = this.ctx.getService('uptime').common; + private readonly monitor = this.ctx.getService('uptime').monitor; + private readonly navigation = this.ctx.getService('uptime').navigation; + private readonly retry = this.ctx.getService('retry'); + + public async goToRoot(refresh?: boolean) { + await this.navigation.goToUptime(); + if (refresh) { + await this.navigation.refreshApp(); + } + } + + public async setDateRange(start: string, end: string) { + const { start: prevStart, end: prevEnd } = await this.timePicker.getTimeConfig(); + if (start !== prevStart || prevEnd !== end) { + await this.timePicker.setAbsoluteRange(start, end); + } else { + await this.navigation.refreshApp(); + } + } + + public async goToUptimeOverviewAndLoadData( + dateStart: string, + dateEnd: string, + monitorIdToCheck?: string + ) { + await this.navigation.goToUptime(); + await this.setDateRange(dateStart, dateEnd); + if (monitorIdToCheck) { + await this.commonService.monitorIdExists(monitorIdToCheck); + } + await this.header.waitUntilLoadingHasFinished(); + } + + public async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { + await this.header.waitUntilLoadingHasFinished(); + await this.setDateRange(dateStart, dateEnd); + await this.navigation.goToMonitor(monitorId); + } + + public async inputFilterQuery(filterQuery: string) { + await this.commonService.setFilterText(filterQuery); + } + + public async pageHasDataMissing() { + return await this.commonService.pageHasDataMissing(); + } + + public async pageHasExpectedIds(monitorIdsToCheck: string[]): Promise { + return this.retry.tryForTime(15000, async () => { + await Promise.all( + monitorIdsToCheck.map((id) => this.commonService.monitorPageLinkExists(id)) + ); + }); + } + + public async pageUrlContains(value: string, expected: boolean = true): Promise { + return this.retry.tryForTime(12000, async () => { + expect(await this.commonService.urlContains(value)).to.eql(expected); + }); + } + + public async changePage(direction: 'next' | 'prev') { + if (direction === 'next') { + await this.commonService.goToNextPage(); + } else if (direction === 'prev') { + await this.commonService.goToPreviousPage(); + } + } + + public async setStatusFilter(value: 'up' | 'down') { + if (value === 'up') { + await this.commonService.setStatusFilterUp(); + } else if (value === 'down') { + await this.commonService.setStatusFilterDown(); + } + } + + public async selectFilterItems(filters: Record) { + for (const key in filters) { + if (filters.hasOwnProperty(key)) { + const values = filters[key]; + for (let i = 0; i < values.length; i++) { + await this.commonService.selectFilterItem(key, values[i]); } } } + } - public async getSnapshotCount() { - return await commonService.getSnapshotCount(); - } + public async getSnapshotCount() { + return await this.commonService.getSnapshotCount(); + } - public async setAlertKueryBarText(filters: string) { - const { setKueryBarText } = commonService; - await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters); - } + public async setAlertKueryBarText(filters: string) { + const { setKueryBarText } = this.commonService; + await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters); + } - public async setMonitorListPageSize(size: number): Promise { - await commonService.openPageSizeSelectPopover(); - return commonService.clickPageSizeSelectPopoverItem(size); - } + public async setMonitorListPageSize(size: number): Promise { + await this.commonService.openPageSizeSelectPopover(); + return this.commonService.clickPageSizeSelectPopoverItem(size); + } - public async checkPingListInteractions(timestamps: string[]): Promise { - return monitor.checkForPingListTimestamps(timestamps); - } + public async checkPingListInteractions(timestamps: string[]): Promise { + return this.monitor.checkForPingListTimestamps(timestamps); + } - public async resetFilters() { - await this.inputFilterQuery(''); - await commonService.resetStatusFilter(); - } - })(); + public async resetFilters() { + await this.inputFilterQuery(''); + await this.commonService.resetStatusFilter(); + } } diff --git a/x-pack/test/functional/page_objects/watcher_page.ts b/x-pack/test/functional/page_objects/watcher_page.ts index d0db5c8c3b2671..5aeaddffff581d 100644 --- a/x-pack/test/functional/page_objects/watcher_page.ts +++ b/x-pack/test/functional/page_objects/watcher_page.ts @@ -6,64 +6,61 @@ */ import { map as mapAsync } from 'bluebird'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function WatcherPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['header']); - const find = getService('find'); - const testSubjects = getService('testSubjects'); +export class WatcherPageObject extends FtrService { + private readonly header = this.ctx.getPageObject('header'); + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - class WatcherPage { - async clearAllWatches() { - const checkBoxExists = await testSubjects.exists('checkboxSelectAll'); - if (checkBoxExists) { - await testSubjects.click('checkboxSelectAll'); - await testSubjects.click('btnDeleteWatches'); - await testSubjects.click('confirmModalConfirmButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clearAllWatches() { + const checkBoxExists = await this.testSubjects.exists('checkboxSelectAll'); + if (checkBoxExists) { + await this.testSubjects.click('checkboxSelectAll'); + await this.testSubjects.click('btnDeleteWatches'); + await this.testSubjects.click('confirmModalConfirmButton'); + await this.header.waitUntilLoadingHasFinished(); } + } - async createWatch(watchName: string, name: string) { - await testSubjects.click('createWatchButton'); - await testSubjects.click('jsonWatchCreateLink'); - await find.setValue('#id', watchName); - await find.setValue('#watchName', name); - await find.clickByCssSelector('[type="submit"]'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async createWatch(watchName: string, name: string) { + await this.testSubjects.click('createWatchButton'); + await this.testSubjects.click('jsonWatchCreateLink'); + await this.find.setValue('#id', watchName); + await this.find.setValue('#watchName', name); + await this.find.clickByCssSelector('[type="submit"]'); + await this.header.waitUntilLoadingHasFinished(); + } - async getWatch(watchID: string) { - const watchIdColumn = await testSubjects.find(`watchIdColumn-${watchID}`); - const watchNameColumn = await testSubjects.find(`watchNameColumn-${watchID}`); - const id = await watchIdColumn.getVisibleText(); - const name = await watchNameColumn.getVisibleText(); - return { - id, - name, - }; - } + async getWatch(watchID: string) { + const watchIdColumn = await this.testSubjects.find(`watchIdColumn-${watchID}`); + const watchNameColumn = await this.testSubjects.find(`watchNameColumn-${watchID}`); + const id = await watchIdColumn.getVisibleText(); + const name = await watchNameColumn.getVisibleText(); + return { + id, + name, + }; + } - async deleteWatch() { - await testSubjects.click('checkboxSelectAll'); - await testSubjects.click('btnDeleteWatches'); - } + async deleteWatch() { + await this.testSubjects.click('checkboxSelectAll'); + await this.testSubjects.click('btnDeleteWatches'); + } - // get all the watches in the list - async getWatches() { - const watches = await find.allByCssSelector('.euiTableRow'); - return mapAsync(watches, async (watch) => { - const checkBox = await watch.findByCssSelector('td:nth-child(1)'); - const id = await watch.findByCssSelector('td:nth-child(2)'); - const name = await watch.findByCssSelector('td:nth-child(3)'); + // get all the watches in the list + async getWatches() { + const watches = await this.find.allByCssSelector('.euiTableRow'); + return mapAsync(watches, async (watch) => { + const checkBox = await watch.findByCssSelector('td:nth-child(1)'); + const id = await watch.findByCssSelector('td:nth-child(2)'); + const name = await watch.findByCssSelector('td:nth-child(3)'); - return { - checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'), - id: await id.getVisibleText(), - name: (await name.getVisibleText()).split(',').map((role) => role.trim()), - }; - }); - } + return { + checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'), + id: await id.getVisibleText(), + name: (await name.getVisibleText()).split(',').map((role) => role.trim()), + }; + }); } - return new WatcherPage(); } diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 0da518a5e89673..99293c71676b47 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -58,7 +58,7 @@ import { DashboardDrilldownsManageProvider, DashboardPanelTimeRangeProvider, } from './dashboard'; -import { SearchSessionsProvider } from './search_sessions'; +import { SearchSessionsService } from './search_sessions'; // define the name and providers for services that should be // available to your tests. If you don't specify anything here @@ -107,5 +107,5 @@ export const services = { dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider, dashboardDrilldownsManage: DashboardDrilldownsManageProvider, dashboardPanelTimeRange: DashboardPanelTimeRangeProvider, - searchSessions: SearchSessionsProvider, + searchSessions: SearchSessionsService, }; diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index b61bc8871482ff..31cf17575bdd9a 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import type { CanvasElementColorStats } from '../canvas_element'; -interface SetValueOptions { +export interface SetValueOptions { clearWithKeyboard?: boolean; typeCharByChar?: boolean; } diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts index 2990f700597671..7df09652f89537 100644 --- a/x-pack/test/functional/services/ml/job_wizard_common.ts +++ b/x-pack/test/functional/services/ml/job_wizard_common.ts @@ -11,6 +11,10 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { MlCommonUI } from './common_ui'; import { MlCustomUrls } from './custom_urls'; +export interface SectionOptions { + withAdvancedSection: boolean; +} + export function MachineLearningJobWizardCommonProvider( { getService }: FtrProviderContext, mlCommonUI: MlCommonUI, @@ -20,10 +24,6 @@ export function MachineLearningJobWizardCommonProvider( const retry = getService('retry'); const testSubjects = getService('testSubjects'); - interface SectionOptions { - withAdvancedSection: boolean; - } - function advancedSectionSelector(subSelector?: string) { const subj = 'mlJobWizardAdvancedSection'; return !subSelector ? subj : `${subj} > ${subSelector}`; diff --git a/x-pack/test/functional/services/search_sessions.ts b/x-pack/test/functional/services/search_sessions.ts index 63dc880fda3804..4490b870068334 100644 --- a/x-pack/test/functional/services/search_sessions.ts +++ b/x-pack/test/functional/services/search_sessions.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; const SEARCH_SESSION_INDICATOR_TEST_SUBJ = 'searchSessionIndicator'; const SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; @@ -25,155 +25,153 @@ type SessionStateType = | 'restored' | 'canceled'; -export function SearchSessionsProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const retry = getService('retry'); - const browser = getService('browser'); - const supertest = getService('supertest'); - - return new (class SearchSessionsService { - public async find(): Promise { - return testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - } - - public async exists(): Promise { - return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - } - - public async missingOrFail(): Promise { - return testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - } - - public async disabledOrFail() { - await this.exists(); - await expect(await (await this.find()).getAttribute('data-save-disabled')).to.be('true'); - } - - public async expectState(state: SessionStateType) { - return retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { - const currentState = await ( - await testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ) - ).getAttribute('data-state'); - log.info(`searchSessions state current: ${currentState} expected: ${state}`); - return currentState === state; +export class SearchSessionsService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + private readonly supertest = this.ctx.getService('supertest'); + + public async find(): Promise { + return this.testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async exists(): Promise { + return this.testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async missingOrFail(): Promise { + return this.testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async disabledOrFail() { + await this.exists(); + await expect(await (await this.find()).getAttribute('data-save-disabled')).to.be('true'); + } + + public async expectState(state: SessionStateType) { + return this.retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { + const currentState = await ( + await this.testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ) + ).getAttribute('data-state'); + this.log.info(`searchSessions state current: ${currentState} expected: ${state}`); + return currentState === state; + }); + } + + public async viewSearchSessions() { + this.log.debug('viewSearchSessions'); + await this.ensurePopoverOpened(); + await this.testSubjects.click('searchSessionIndicatorViewSearchSessionsLink'); + } + + public async save({ searchSessionName }: { searchSessionName?: string } = {}) { + this.log.debug('save the search session'); + await this.ensurePopoverOpened(); + await this.testSubjects.click('searchSessionIndicatorSaveBtn'); + + if (searchSessionName) { + await this.testSubjects.click('searchSessionNameEdit'); + await this.testSubjects.setValue('searchSessionNameInput', searchSessionName, { + clearWithKeyboard: true, }); - } - - public async viewSearchSessions() { - log.debug('viewSearchSessions'); - await this.ensurePopoverOpened(); - await testSubjects.click('searchSessionIndicatorViewSearchSessionsLink'); - } - - public async save({ searchSessionName }: { searchSessionName?: string } = {}) { - log.debug('save the search session'); - await this.ensurePopoverOpened(); - await testSubjects.click('searchSessionIndicatorSaveBtn'); - - if (searchSessionName) { - await testSubjects.click('searchSessionNameEdit'); - await testSubjects.setValue('searchSessionNameInput', searchSessionName, { - clearWithKeyboard: true, - }); - await testSubjects.click('searchSessionNameSave'); + await this.testSubjects.click('searchSessionNameSave'); + } + + await this.ensurePopoverClosed(); + } + + public async cancel() { + this.log.debug('cancel the search session'); + await this.ensurePopoverOpened(); + await this.testSubjects.click('searchSessionIndicatorCancelBtn'); + await this.ensurePopoverClosed(); + } + + public async openPopover() { + await this.ensurePopoverOpened(); + } + + public async openedOrFail() { + return this.testSubjects.existOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { + timeout: 15000, // because popover auto opens after search takes 10s + }); + } + + public async closedOrFail() { + return this.testSubjects.missingOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { + timeout: 15000, // because popover auto opens after search takes 10s + }); + } + + private async ensurePopoverOpened() { + this.log.debug('ensurePopoverOpened'); + const isAlreadyOpen = await this.testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); + if (isAlreadyOpen) { + this.log.debug('Popover is already open'); + return; + } + return this.retry.waitFor(`searchSessions popover opened`, async () => { + await this.testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + return await this.testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); + }); + } + + private async ensurePopoverClosed() { + this.log.debug('ensurePopoverClosed'); + const isAlreadyClosed = !(await this.testSubjects.exists( + SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ + )); + if (isAlreadyClosed) return; + return this.retry.waitFor(`searchSessions popover closed`, async () => { + await this.browser.pressKeys(this.browser.keys.ESCAPE); + return !(await this.testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ)); + }); + } + + /* + * This cleanup function should be used by tests that create new search sessions. + * Tests should not end with new search sessions remaining in storage since that interferes with functional tests that check the _find API. + * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. + */ + public async deleteAllSearchSessions() { + this.log.debug('Deleting created search sessions'); + // ignores 409 errs and keeps retrying + await this.retry.tryForTime(10000, async () => { + const { body } = await this.supertest + .post('/internal/session/_find') + .set('kbn-xsrf', 'anything') + .set('kbn-system-request', 'true') + .send({ page: 1, perPage: 10000, sortField: 'created', sortOrder: 'asc' }) + .expect(200); + + const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; + if (savedObjects.length) { + this.log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); } - - await this.ensurePopoverClosed(); - } - - public async cancel() { - log.debug('cancel the search session'); - await this.ensurePopoverOpened(); - await testSubjects.click('searchSessionIndicatorCancelBtn'); - await this.ensurePopoverClosed(); - } - - public async openPopover() { - await this.ensurePopoverOpened(); - } - - public async openedOrFail() { - return testSubjects.existOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { - timeout: 15000, // because popover auto opens after search takes 10s - }); - } - - public async closedOrFail() { - return testSubjects.missingOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { - timeout: 15000, // because popover auto opens after search takes 10s - }); - } - - private async ensurePopoverOpened() { - log.debug('ensurePopoverOpened'); - const isAlreadyOpen = await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); - if (isAlreadyOpen) { - log.debug('Popover is already open'); - return; - } - return retry.waitFor(`searchSessions popover opened`, async () => { - await testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - return await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); - }); - } - - private async ensurePopoverClosed() { - log.debug('ensurePopoverClosed'); - const isAlreadyClosed = !(await testSubjects.exists( - SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ - )); - if (isAlreadyClosed) return; - return retry.waitFor(`searchSessions popover closed`, async () => { - await browser.pressKeys(browser.keys.ESCAPE); - return !(await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ)); - }); - } - - /* - * This cleanup function should be used by tests that create new search sessions. - * Tests should not end with new search sessions remaining in storage since that interferes with functional tests that check the _find API. - * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. - */ - public async deleteAllSearchSessions() { - log.debug('Deleting created search sessions'); - // ignores 409 errs and keeps retrying - await retry.tryForTime(10000, async () => { - const { body } = await supertest - .post('/internal/session/_find') - .set('kbn-xsrf', 'anything') - .set('kbn-system-request', 'true') - .send({ page: 1, perPage: 10000, sortField: 'created', sortOrder: 'asc' }) - .expect(200); - - const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; - if (savedObjects.length) { - log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); - } - await Promise.all( - savedObjects.map(async (so) => { - log.debug(`Deleting search session: ${so.id}`); - await supertest - .delete(`/internal/session/${so.id}`) - .set(`kbn-xsrf`, `anything`) - .expect(200); - }) - ); - }); - } - - public async markTourDone() { - await Promise.all([ - browser.setLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY, 'true'), - browser.setLocalStorageItem(TOUR_RESTORE_STEP_KEY, 'true'), - ]); - } - - public async markTourUndone() { - await Promise.all([ - browser.removeLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY), - browser.removeLocalStorageItem(TOUR_RESTORE_STEP_KEY), - ]); - } - })(); + await Promise.all( + savedObjects.map(async (so) => { + this.log.debug(`Deleting search session: ${so.id}`); + await this.supertest + .delete(`/internal/session/${so.id}`) + .set(`kbn-xsrf`, `anything`) + .expect(200); + }) + ); + }); + } + + public async markTourDone() { + await Promise.all([ + this.browser.setLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY, 'true'), + this.browser.setLocalStorageItem(TOUR_RESTORE_STEP_KEY, 'true'), + ]); + } + + public async markTourUndone() { + await Promise.all([ + this.browser.removeLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY), + this.browser.removeLocalStorageItem(TOUR_RESTORE_STEP_KEY), + ]); + } } diff --git a/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json b/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json index c66a45084441fa..29c56a751c038b 100644 --- a/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json +++ b/x-pack/test/plugin_functional/es_archives/global_search/basic/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "settings": { "index": { "number_of_shards": "1", diff --git a/x-pack/test/plugin_functional/es_archives/global_search/search_syntax/mappings.json b/x-pack/test/plugin_functional/es_archives/global_search/search_syntax/mappings.json index ec28b51de1d10c..35537a97555101 100644 --- a/x-pack/test/plugin_functional/es_archives/global_search/search_syntax/mappings.json +++ b/x-pack/test/plugin_functional/es_archives/global_search/search_syntax/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/reporting_api_integration/services/usage.ts b/x-pack/test/reporting_api_integration/services/usage.ts index ababbbf03e4c10..4e8d464467191a 100644 --- a/x-pack/test/reporting_api_integration/services/usage.ts +++ b/x-pack/test/reporting_api_integration/services/usage.ts @@ -31,7 +31,7 @@ export interface ReportingUsageStats { [jobType: string]: any; } -interface UsageStats { +export interface UsageStats { reporting: ReportingUsageStats; } diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts index 088e594a41b888..b712c2882ee0f9 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts @@ -191,6 +191,64 @@ export const expectResponses = { }, }; +const commonUsers = { + noAccess: { + ...NOT_A_KIBANA_USER, + description: 'user with no access', + authorizedAtSpaces: [], + }, + superuser: { + ...SUPERUSER, + description: 'superuser', + authorizedAtSpaces: ['*'], + }, + legacyAll: { ...KIBANA_LEGACY_USER, description: 'legacy user', authorizedAtSpaces: [] }, + allGlobally: { + ...KIBANA_RBAC_USER, + description: 'rbac user with all globally', + authorizedAtSpaces: ['*'], + }, + readGlobally: { + ...KIBANA_RBAC_DASHBOARD_ONLY_USER, + description: 'rbac user with read globally', + authorizedAtSpaces: ['*'], + }, + dualAll: { + ...KIBANA_DUAL_PRIVILEGES_USER, + description: 'dual-privileges user', + authorizedAtSpaces: ['*'], + }, + dualRead: { + ...KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, + description: 'dual-privileges readonly user', + authorizedAtSpaces: ['*'], + }, +}; + +interface Security { + modifier?: T; + users: Record< + | keyof typeof commonUsers + | 'allAtDefaultSpace' + | 'readAtDefaultSpace' + | 'allAtSpace1' + | 'readAtSpace1', + TestUser + >; +} +interface SecurityAndSpaces { + modifier?: T; + users: Record< + keyof typeof commonUsers | 'allAtSpace' | 'readAtSpace' | 'allAtOtherSpace', + TestUser + >; + spaceId: string; +} +interface Spaces { + modifier?: T; + spaceId: string; +} + /** * Get test scenarios for each type of suite. * @param modifier Use this to generate additional permutations of test scenarios. @@ -203,66 +261,10 @@ export const expectResponses = { * ] */ export const getTestScenarios = (modifiers?: T[]) => { - const commonUsers = { - noAccess: { - ...NOT_A_KIBANA_USER, - description: 'user with no access', - authorizedAtSpaces: [], - }, - superuser: { - ...SUPERUSER, - description: 'superuser', - authorizedAtSpaces: ['*'], - }, - legacyAll: { ...KIBANA_LEGACY_USER, description: 'legacy user', authorizedAtSpaces: [] }, - allGlobally: { - ...KIBANA_RBAC_USER, - description: 'rbac user with all globally', - authorizedAtSpaces: ['*'], - }, - readGlobally: { - ...KIBANA_RBAC_DASHBOARD_ONLY_USER, - description: 'rbac user with read globally', - authorizedAtSpaces: ['*'], - }, - dualAll: { - ...KIBANA_DUAL_PRIVILEGES_USER, - description: 'dual-privileges user', - authorizedAtSpaces: ['*'], - }, - dualRead: { - ...KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, - description: 'dual-privileges readonly user', - authorizedAtSpaces: ['*'], - }, - }; - - interface Security { - modifier?: T; - users: Record< - | keyof typeof commonUsers - | 'allAtDefaultSpace' - | 'readAtDefaultSpace' - | 'allAtSpace1' - | 'readAtSpace1', - TestUser - >; - } - interface SecurityAndSpaces { - modifier?: T; - users: Record< - keyof typeof commonUsers | 'allAtSpace' | 'readAtSpace' | 'allAtOtherSpace', - TestUser - >; - spaceId: string; - } - interface Spaces { - modifier?: T; - spaceId: string; - } - - let spaces: Spaces[] = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID].map((x) => ({ spaceId: x })); - let security: Security[] = [ + let spaces: Array> = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID].map((x) => ({ + spaceId: x, + })); + let security: Array> = [ { users: { ...commonUsers, @@ -289,7 +291,7 @@ export const getTestScenarios = (modifiers?: T[]) => { }, }, ]; - let securityAndSpaces: SecurityAndSpaces[] = [ + let securityAndSpaces: Array> = [ { spaceId: DEFAULT_SPACE_ID, users: { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/bulk_assign/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/bulk_assign/mappings.json index 9cf628bef47675..2e465869f9ae5b 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/bulk_assign/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/bulk_assign/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json index 4156cc9e2373dc..47f23dc57e361f 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/dashboard/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "_meta": { "migrationMappingPropertyHashes": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/delete_with_references/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/delete_with_references/mappings.json index 4ea82eb30e06ac..5ccad08bc21fa6 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/delete_with_references/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/delete_with_references/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/functional_base/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/functional_base/mappings.json index 4ea82eb30e06ac..5ccad08bc21fa6 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/functional_base/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/functional_base/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/rbac_tags/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/rbac_tags/mappings.json index cd0d076258721e..cd57c26dfc3376 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/rbac_tags/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/rbac_tags/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/so_management/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/so_management/mappings.json index 4ea82eb30e06ac..5ccad08bc21fa6 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/so_management/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/so_management/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/usage_collection/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/usage_collection/mappings.json index 9cf628bef47675..2e465869f9ae5b 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/usage_collection/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/usage_collection/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/visualize/mappings.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/visualize/mappings.json index 4ea82eb30e06ac..5ccad08bc21fa6 100644 --- a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/visualize/mappings.json +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/visualize/mappings.json @@ -1,8 +1,10 @@ { "type": "index", "value": { - "aliases": {}, - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "dynamic": "strict", "properties": { diff --git a/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json b/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json index 77eac534850a55..499c994780285b 100644 --- a/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/empty_kibana/mappings.json @@ -1,7 +1,10 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": {} + }, + "index": ".kibana_1", "mappings": { "properties": { "config": { diff --git a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts index dd17548df6e3fd..6f26e0b3ad3640 100644 --- a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts +++ b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts @@ -5,145 +5,144 @@ * 2.0. */ -import { FtrProviderContext } from '../../../functional/ftr_provider_context'; +import { FtrService } from '../../../functional/ftr_provider_context'; import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; -export function DetectionsPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const { common } = getPageObjects(['common']); - const testSubjects = getService('testSubjects'); - - class DetectionsPage { - async navigateHome(): Promise { - await this.navigateToDetectionsPage(); - } - - async navigateToRules(): Promise { - await this.navigateToDetectionsPage('rules'); - } - - async navigateToRuleMonitoring(): Promise { - await common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table'); - } - - async navigateToExceptionList(): Promise { - await common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table'); - } - - async navigateToCreateRule(): Promise { - await this.navigateToDetectionsPage('rules/create'); - } - - async replaceIndexPattern(): Promise { - const buttons = await find.allByCssSelector('[data-test-subj="comboBoxInput"] button'); - await buttons.map(async (button: WebElementWrapper) => await button.click()); - await testSubjects.setValue('comboBoxSearchInput', '*'); - } - - async openImportQueryModal(): Promise { - const element = await testSubjects.find('importQueryFromSavedTimeline'); - await element.click(500); - await testSubjects.exists('open-timeline-modal-body-filter-default'); - } - - async viewTemplatesInImportQueryModal(): Promise { - await common.clickAndValidate('open-timeline-modal-body-filter-template', 'timelines-table'); - } - - async closeImportQueryModal(): Promise { - await find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon'); - } - - async selectMachineLearningJob(): Promise { - await find.clickByCssSelector('[data-test-subj="mlJobSelect"] button'); - await find.clickByCssSelector('#high_distinct_count_error_message'); - } - - async openAddFilterPopover(): Promise { - const addButtons = await testSubjects.findAll('addFilter'); - await addButtons[1].click(); - await testSubjects.exists('saveFilter'); - } - - async closeAddFilterPopover(): Promise { - await testSubjects.click('cancelSaveFilter'); - } - - async toggleFilterActions(): Promise { - const filterActions = await testSubjects.findAll('addFilter'); - await filterActions[1].click(); - } - - async toggleSavedQueries(): Promise { - const filterActions = await find.allByCssSelector( - '[data-test-subj="saved-query-management-popover-button"]' - ); - await filterActions[1].click(); - } - - async addNameAndDescription( - name: string = 'test rule name', - description: string = 'test rule description' - ): Promise { - await find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500); - await find.setValue( - `[aria-describedby="detectionEngineStepAboutRuleDescription"]`, - description, - 500 - ); - } - - async goBackToAllRules(): Promise { - await common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule'); - } - - async revealAdvancedSettings(): Promise { - await common.clickAndValidate( - 'advancedSettings', - 'detectionEngineStepAboutRuleReferenceUrls' - ); - } - - async preview(): Promise { - await common.clickAndValidate( - 'queryPreviewButton', - 'queryPreviewCustomHistogram', - undefined, - 500 - ); - } - - async continue(prefix: string): Promise { - await testSubjects.click(`${prefix}-continue`); - } - - async addCustomQuery(query: string): Promise { - await testSubjects.setValue('queryInput', query, undefined, 500); - } - - async selectMLRule(): Promise { - await common.clickAndValidate('machineLearningRuleType', 'mlJobSelect'); - } - - async selectEQLRule(): Promise { - await common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput'); - } - - async selectIndicatorMatchRule(): Promise { - await common.clickAndValidate('threatMatchRuleType', 'comboBoxInput'); - } - - async selectThresholdRule(): Promise { - await common.clickAndValidate('thresholdRuleType', 'input'); - } - - private async navigateToDetectionsPage(path: string = ''): Promise { - const subUrl = `detections${path ? `/${path}` : ''}`; - await common.navigateToUrl('securitySolution', subUrl, { - shouldUseHashForSubUrl: false, - }); - } - } - - return new DetectionsPage(); +export class DetectionsPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly common = this.ctx.getPageObject('common'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + + async navigateHome(): Promise { + await this.navigateToDetectionsPage(); + } + + async navigateToRules(): Promise { + await this.navigateToDetectionsPage('rules'); + } + + async navigateToRuleMonitoring(): Promise { + await this.common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table'); + } + + async navigateToExceptionList(): Promise { + await this.common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table'); + } + + async navigateToCreateRule(): Promise { + await this.navigateToDetectionsPage('rules/create'); + } + + async replaceIndexPattern(): Promise { + const buttons = await this.find.allByCssSelector('[data-test-subj="comboBoxInput"] button'); + await buttons.map(async (button: WebElementWrapper) => await button.click()); + await this.testSubjects.setValue('comboBoxSearchInput', '*'); + } + + async openImportQueryModal(): Promise { + const element = await this.testSubjects.find('importQueryFromSavedTimeline'); + await element.click(500); + await this.testSubjects.exists('open-timeline-modal-body-filter-default'); + } + + async viewTemplatesInImportQueryModal(): Promise { + await this.common.clickAndValidate( + 'open-timeline-modal-body-filter-template', + 'timelines-table' + ); + } + + async closeImportQueryModal(): Promise { + await this.find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon'); + } + + async selectMachineLearningJob(): Promise { + await this.find.clickByCssSelector('[data-test-subj="mlJobSelect"] button'); + await this.find.clickByCssSelector('#high_distinct_count_error_message'); + } + + async openAddFilterPopover(): Promise { + const addButtons = await this.testSubjects.findAll('addFilter'); + await addButtons[1].click(); + await this.testSubjects.exists('saveFilter'); + } + + async closeAddFilterPopover(): Promise { + await this.testSubjects.click('cancelSaveFilter'); + } + + async toggleFilterActions(): Promise { + const filterActions = await this.testSubjects.findAll('addFilter'); + await filterActions[1].click(); + } + + async toggleSavedQueries(): Promise { + const filterActions = await this.find.allByCssSelector( + '[data-test-subj="saved-query-management-popover-button"]' + ); + await filterActions[1].click(); + } + + async addNameAndDescription( + name: string = 'test rule name', + description: string = 'test rule description' + ): Promise { + await this.find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500); + await this.find.setValue( + `[aria-describedby="detectionEngineStepAboutRuleDescription"]`, + description, + 500 + ); + } + + async goBackToAllRules(): Promise { + await this.common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule'); + } + + async revealAdvancedSettings(): Promise { + await this.common.clickAndValidate( + 'advancedSettings', + 'detectionEngineStepAboutRuleReferenceUrls' + ); + } + + async preview(): Promise { + await this.common.clickAndValidate( + 'queryPreviewButton', + 'queryPreviewCustomHistogram', + undefined, + 500 + ); + } + + async continue(prefix: string): Promise { + await this.testSubjects.click(`${prefix}-continue`); + } + + async addCustomQuery(query: string): Promise { + await this.testSubjects.setValue('queryInput', query, undefined, 500); + } + + async selectMLRule(): Promise { + await this.common.clickAndValidate('machineLearningRuleType', 'mlJobSelect'); + } + + async selectEQLRule(): Promise { + await this.common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput'); + } + + async selectIndicatorMatchRule(): Promise { + await this.common.clickAndValidate('threatMatchRuleType', 'comboBoxInput'); + } + + async selectThresholdRule(): Promise { + await this.common.clickAndValidate('thresholdRuleType', 'input'); + } + + private async navigateToDetectionsPage(path: string = ''): Promise { + const subUrl = `detections${path ? `/${path}` : ''}`; + await this.common.navigateToUrl('securitySolution', subUrl, { + shouldUseHashForSubUrl: false, + }); + } } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 7c95a53b75ee78..0424891064cd33 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - // overhead is too significant - "incremental": false, + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, "types": ["node"] }, - "include": ["**/*", "../../typings/**/*", "../../packages/kbn-test/types/ftr_globals/**/*"], + "include": [ + "**/*", + "./api_integration/apis/logstash/pipeline/fixtures/*.json", + "./api_integration/apis/logstash/pipelines/fixtures/*.json", + "./api_integration/apis/telemetry/fixtures/*.json", + "../../typings/**/*", + "../../packages/kbn-test/types/ftr_globals/**/*", + ], + "exclude": ["target/**/*"], "references": [ + { "path": "../../test/tsconfig.json" }, { "path": "../../src/core/tsconfig.json" }, { "path": "../../src/plugins/bfetch/tsconfig.json" }, { "path": "../../src/plugins/charts/tsconfig.json" }, @@ -84,6 +96,7 @@ { "path": "../plugins/stack_alerts/tsconfig.json" }, { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "../plugins/timelines/tsconfig.json" }, { "path": "../plugins/transform/tsconfig.json" }, { "path": "../plugins/triggers_actions_ui/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 5a9b30fbd9dd28..3afbffbac6eb1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1871,10 +1871,10 @@ "@hapi/hoek" "9.x.x" fast-safe-stringify "2.x.x" -"@hapi/h2o2@^9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.0.2.tgz#e9f1dfe789257c80d6ee37ec9fe358f8c69f855a" - integrity sha512-V7RsmVyl7uyWeuEko4uaSZbFpBHKcSFSui6PXNRaRLJHFX+iPbqWmeH6m1pW/WJ8DuaCVJFKhluDCDI9l4+1cw== +"@hapi/h2o2@^9.1.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.1.0.tgz#b223f4978b6f2b0d7d9db10a84a567606c4c3551" + integrity sha512-B7E58bMhxmpiDI22clxTexoAaVShNBk1Ez6S8SQjQZu5FxxD6Tqa44sXeZQBtWrdJF7ZRbsY60/C8AHLRxagNA== dependencies: "@hapi/boom" "9.x.x" "@hapi/hoek" "9.x.x" @@ -4732,7 +4732,7 @@ resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" integrity sha512-jtV6Bv/j+xk4gcXeLlESwNc/m/I/dIZA0xrt29g0uKcjyPob8iisj/5z0ARE+Ldfx4MxjNFNECG0z++J7zJgqg== -"@types/cheerio@*", "@types/cheerio@^0.22.22", "@types/cheerio@^0.22.28": +"@types/cheerio@*", "@types/cheerio@^0.22.22": version "0.22.28" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.28.tgz#90808aabb44fec40fa2950f4c72351e3e4eb065b" integrity sha512-ehUMGSW5IeDxJjbru4awKYMlKGmo1wSSGUVqXtYwlgmUM8X1a0PZttEIm6yEY7vHsY/hh6iPnklF213G0UColw== @@ -9220,27 +9220,16 @@ check-more-types@2.24.0, check-more-types@^2.24.0: resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -cheerio@0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4= +cheerio-select@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.4.0.tgz#3a16f21e37a2ef0f211d6d1aa4eff054bb22cdc9" + integrity sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew== dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash.assignin "^4.0.9" - lodash.bind "^4.1.4" - lodash.defaults "^4.0.1" - lodash.filter "^4.4.0" - lodash.flatten "^4.2.0" - lodash.foreach "^4.3.0" - lodash.map "^4.4.0" - lodash.merge "^4.4.0" - lodash.pick "^4.2.1" - lodash.reduce "^4.4.0" - lodash.reject "^4.4.0" - lodash.some "^4.4.0" + css-select "^4.1.2" + css-what "^5.0.0" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.6.0" cheerio@^1.0.0-rc.3: version "1.0.0-rc.3" @@ -9254,6 +9243,19 @@ cheerio@^1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" +cheerio@^1.0.0-rc.9: + version "1.0.0-rc.9" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.9.tgz#a3ae6b7ce7af80675302ff836f628e7cb786a67f" + integrity sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng== + dependencies: + cheerio-select "^1.4.0" + dom-serializer "^1.3.1" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + chokidar@3.4.3, chokidar@^2.0.0, chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.2.2, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" @@ -10491,6 +10493,17 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" +css-select@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286" + integrity sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw== + dependencies: + boolbase "^1.0.0" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" + nth-check "^2.0.0" + css-to-react-native@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" @@ -10526,6 +10539,11 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css-what@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" + integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== + css.escape@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" @@ -11837,7 +11855,7 @@ dom-helpers@^5.0.0, dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^2.6.7" -dom-serializer@0, dom-serializer@~0.1.0, dom-serializer@~0.1.1: +dom-serializer@0, dom-serializer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== @@ -11845,6 +11863,15 @@ dom-serializer@0, dom-serializer@~0.1.0, dom-serializer@~0.1.1: domelementtype "^1.3.0" entities "^1.1.1" +dom-serializer@^1.0.1, dom-serializer@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -11860,6 +11887,11 @@ domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" @@ -11888,6 +11920,13 @@ domhandler@^2.3.0, domhandler@^2.4.2: dependencies: domelementtype "1" +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== + dependencies: + domelementtype "^2.2.0" + domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" @@ -11911,6 +11950,15 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +domutils@^2.5.2, domutils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7" + integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + dot-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" @@ -12309,6 +12357,11 @@ entities@^1.1.1, entities@^1.1.2, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + entities@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" @@ -15500,6 +15553,16 @@ htmlparser2@^3.10.0, htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.1.1" +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -18342,16 +18405,6 @@ lodash.assign@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.assignin@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= - -lodash.bind@^4.1.4: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" - integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -18372,7 +18425,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.defaults@^4.0.1, lodash.defaults@^4.2.0: +lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= @@ -18392,17 +18445,12 @@ lodash.fill@^3.4.0: resolved "https://registry.yarnpkg.com/lodash.fill/-/lodash.fill-3.4.0.tgz#a3c74ae640d053adf0dc2079f8720788e8bfef85" integrity sha1-o8dK5kDQU63w3CB5+HIHiOi/74U= -lodash.filter@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= - lodash.flatmap@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4= -lodash.flatten@^4.2.0, lodash.flatten@^4.4.0: +lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= @@ -18412,11 +18460,6 @@ lodash.flattendeep@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= -lodash.foreach@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" - integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= - lodash.get@^4.0.0, lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -18477,11 +18520,6 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= -lodash.map@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -18492,7 +18530,7 @@ lodash.memoize@~3.0.3: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= -lodash.merge@^4.4.0, lodash.merge@^4.6.1: +lodash.merge@^4.6.1: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -18512,27 +18550,17 @@ lodash.partialright@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.partialright/-/lodash.partialright-4.2.1.tgz#0130d80e83363264d40074f329b8a3e7a8a1cc4b" integrity sha1-ATDYDoM2MmTUAHTzKbij56ihzEs= -lodash.pick@^4.2.1, lodash.pick@^4.4.0: +lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.reduce@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= - -lodash.reject@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" - integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= -lodash.some@^4.4.0, lodash.some@^4.6.0: +lodash.some@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= @@ -20327,6 +20355,13 @@ nth-check@^1.0.2, nth-check@~1.0.1: dependencies: boolbase "~1.0.0" +nth-check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" + integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== + dependencies: + boolbase "^1.0.0" + null-loader@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-3.0.0.tgz#3e2b6c663c5bda8c73a54357d8fa0708dc61b245" @@ -21121,6 +21156,13 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + parse5@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" @@ -21138,7 +21180,7 @@ parse5@^3.0.1: dependencies: "@types/node" "*" -parse5@^6.0.0: +parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -27135,15 +27177,10 @@ tslib@^1, tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== -tslib@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" - integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== - -tslib@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" - integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== tslib@~2.1.0: version "2.1.0"