diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04b79b995c54..fa69e6ed6ca3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -463,7 +463,7 @@ To include your change in the Release Notes: * For a new feature or functionality, use `release_note:enhancement`. * For an external-facing fix, use `release_note:fix`. Exception: docs, build, and test fixes do not go in the Release Notes. * For a deprecated feature, use `release_note:deprecation`. - * For a breaking change, use `release-breaking:note`. + * For a breaking change, use `release_note:breaking`. To NOT include your changes in the Release Notes, please use label`non-issue`. PRs with the following labels also won't be included in the Release Notes: `build`, `docs`, `test`, `non-issue`, `jenkins`, `backport`, and `chore`. diff --git a/docs/management/upgrade-assistant/index.asciidoc b/docs/management/upgrade-assistant/index.asciidoc index ba9445de15b0..ed59aa10389f 100644 --- a/docs/management/upgrade-assistant/index.asciidoc +++ b/docs/management/upgrade-assistant/index.asciidoc @@ -38,10 +38,13 @@ continue by reindexing fewer indices at a time. Additional considerations: -* During a reindex of a Watcher (`.watches`) index, the Watcher process -pauses and no alerts are triggered. - -* During a reindex of a Machine Learning (`.ml-state`) index, the Machine -Learning job pauses and models are not trained or updated. +* If you use {alert-features}, when you reindex the internal indices +(`.watches`), the {watcher} process pauses and no alerts are triggered. +* If you use {ml-features}, when you reindex the internal indices (`.ml-state`), +the {ml} jobs pause and models are not trained or updated. +* If you use {security-features}, before you reindex the internal indices +(`.security*`), it is a good idea to create a temporary superuser account in the +`file` realm. For more information, see +{ref}/configuring-file-realm.html[Configuring a file realm]. diff --git a/package.json b/package.json index dc608240e8ae..f6ef8194e11b 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ }, "dependencies": { "@elastic/datemath": "5.0.2", - "@elastic/eui": "9.2.1", + "@elastic/eui": "9.4.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.2", diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js index d650e29cd662..0cc5ad3cc536 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js @@ -82,7 +82,7 @@ It allows you to monitor the performance of thousands of applications in real ti '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', }, }), - euiIconType: 'apmApp', + euiIconType: 'logoAPM', artifacts: artifacts, onPrem: onPremInstructions(config), elasticCloud: createElasticCloudInstructions(config), diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js b/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js index b47405afc38f..7608a93a4878 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js @@ -43,7 +43,7 @@ export function natsLogsSpecProvider(server, context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-nats.html', }, }), - euiIconType: 'logoNats', + // euiIconType: 'logoNats', artifacts: { dashboards: [ { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js index cba7cd947760..86f2497e303f 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js @@ -39,7 +39,7 @@ export function natsMetricsSpecProvider(server, context) { learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-nats.html', }, }), - euiIconType: 'logoNats', + // euiIconType: 'logoNats', artifacts: { dashboards: [ { diff --git a/src/legacy/ui/public/chrome/api/nav.d.ts b/src/legacy/ui/public/chrome/api/nav.d.ts index 6d5154c824cd..f7c639bc5733 100644 --- a/src/legacy/ui/public/chrome/api/nav.d.ts +++ b/src/legacy/ui/public/chrome/api/nav.d.ts @@ -28,6 +28,7 @@ export interface NavLink { subUrlBase: string; id: string; euiIconType: IconType; + icon?: string; active: boolean; lastSubUrl?: string; hidden?: boolean; diff --git a/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx b/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx index 25d20814a477..be0028bf2b2d 100644 --- a/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx +++ b/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx @@ -19,8 +19,7 @@ import Url from 'url'; -import classNames from 'classnames'; -import React, { Component, Fragment } from 'react'; +import React, { Component, createRef, Fragment } from 'react'; import * as Rx from 'rxjs'; import { @@ -39,16 +38,14 @@ import { EuiHideFor, EuiHorizontalRule, EuiIcon, - EuiListGroup, + // @ts-ignore + EuiImage, // @ts-ignore EuiListGroupItem, // @ts-ignore EuiNavDrawer, // @ts-ignore - EuiNavDrawerFlyout, - // @ts-ignore - EuiNavDrawerMenu, - EuiOutsideClickDetector, + EuiNavDrawerGroup, // @ts-ignore EuiShowFor, } from '@elastic/eui'; @@ -79,6 +76,11 @@ interface Props { intl: InjectedIntl; } +// Providing a buffer between the limit and the cut off index +// protects from truncating just the last couple (6) characters +const TRUNCATE_LIMIT: number = 64; +const TRUNCATE_AT: number = 58; + function extendRecentlyAccessedHistoryItem( navLinks: NavLink[], recentlyAccessed: RecentlyAccessedHistoryItem @@ -115,16 +117,15 @@ function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { } } +function truncateRecentItemLabel(label: string): string { + if (label.length > TRUNCATE_LIMIT) { + label = `${label.substring(0, TRUNCATE_AT)}…`; + } + + return label; +} + interface State { - isCollapsed: boolean; - flyoutIsCollapsed: boolean; - flyoutIsAnimating: boolean; - navFlyoutTitle: string; - navFlyoutContent: []; - mobileIsHidden: boolean; - showScrollbar: boolean; - outsideClickDisabled: boolean; - isManagingFocus: boolean; navLinks: Array>; recentlyAccessed: Array>; forceNavigation: boolean; @@ -132,23 +133,12 @@ interface State { class HeaderUI extends Component { private subscription?: Rx.Subscription; - private timeoutID?: ReturnType; - private timeoutExpand?: ReturnType; - private timeoutScrollbar?: ReturnType; + private navDrawerRef = createRef(); constructor(props: Props) { super(props); this.state = { - isCollapsed: true, - flyoutIsCollapsed: true, - flyoutIsAnimating: false, - navFlyoutTitle: '', - navFlyoutContent: [], - mobileIsHidden: true, - showScrollbar: false, - outsideClickDisabled: true, - isManagingFocus: false, navLinks: [], recentlyAccessed: [], forceNavigation: false, @@ -197,14 +187,17 @@ class HeaderUI extends Component { public renderMenuTrigger() { return ( - + this.navDrawerRef.current.toggleOpen()} + > ); } public render() { - const { appTitle, breadcrumbs$, isVisible, navControls, helpExtension$ } = this.props; + const { appTitle, breadcrumbs$, isVisible, navControls, helpExtension$, intl } = this.props; const { navLinks, recentlyAccessed } = this.state; if (!isVisible) { @@ -214,6 +207,52 @@ class HeaderUI extends Component { const leftNavControls = navControls.bySide[NavControlSide.Left]; const rightNavControls = navControls.bySide[NavControlSide.Right]; + let navLinksArray = navLinks.map(navLink => + navLink.hidden + ? null + : { + key: navLink.id, + label: navLink.title, + href: navLink.href, + iconType: navLink.euiIconType, + icon: + !navLink.euiIconType && navLink.icon ? ( + + ) : ( + undefined + ), + isActive: navLink.active, + 'data-test-subj': 'navDrawerAppsMenuLink', + } + ); + // filter out the null items + navLinksArray = navLinksArray.filter(item => item !== null); + + const recentLinksArray = [ + { + label: intl.formatMessage({ + id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsLabel', + defaultMessage: 'Recently viewed', + }), + iconType: 'clock', + isDisabled: recentlyAccessed.length > 0 ? false : true, + flyoutMenu: { + title: intl.formatMessage({ + id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle', + defaultMessage: 'Recent items', + }), + listItems: recentlyAccessed.map(item => ({ + label: truncateRecentItemLabel(item.label), + // TODO: Add what type of app/saved object to title attr + title: `${item.label}`, + 'aria-label': item.label, + href: item.href, + iconType: item.euiIconType, + })), + }, + }, + ]; + return ( @@ -238,85 +277,11 @@ class HeaderUI extends Component { - this.collapseDrawer()} - isDisabled={this.state.outsideClickDisabled} - > - - - - this.expandFlyout()} - isDisabled={recentlyAccessed.length > 0 ? false : true} - extraAction={{ - color: 'subdued', - iconType: 'arrowRight', - iconSize: 's', - 'aria-label': 'Expand to view recent apps and objects', - onClick: () => this.expandFlyout(), - alwaysShow: true, - }} - /> - - - - {navLinks.map(navLink => - navLink.hidden ? null : ( - - ) - )} - - - ({ - label: item.label, - href: item.href, - iconType: item.euiIconType, - size: 's', - style: { color: 'inherit' }, - 'aria-label': item.label, - }))} - onMouseLeave={this.collapseFlyout} - wrapText={true} - /> - - + + + + + ); } @@ -361,120 +326,6 @@ class HeaderUI extends Component { event.stopPropagation(); } }; - - private toggleOpen = () => { - this.setState({ - mobileIsHidden: !this.state.mobileIsHidden, - }); - - setTimeout(() => { - this.setState({ - outsideClickDisabled: this.state.mobileIsHidden ? true : false, - }); - }, this.getTimeoutMs(350)); - }; - - private expandDrawer = () => { - this.timeoutExpand = setTimeout(() => { - this.setState({ - isCollapsed: false, - }); - }, this.getTimeoutMs(750)); - - this.timeoutScrollbar = setTimeout(() => { - this.setState({ - showScrollbar: true, - }); - }, this.getTimeoutMs(1200)); - - // This prevents the drawer from collapsing when tabbing through children - // by clearing the timeout thus cancelling the onBlur event (see focusOut). - // This means isManagingFocus remains true as long as a child element - // has focus. This is the case since React bubbles up onFocus and onBlur - // events from the child elements. - - if (this.timeoutID) { - clearTimeout(this.timeoutID); - } - - if (!this.state.isManagingFocus) { - this.setState({ - isManagingFocus: true, - }); - } - }; - - private collapseDrawer = () => { - // Stop the expand animation - if (this.timeoutExpand) { - clearTimeout(this.timeoutExpand); - } - - if (this.timeoutScrollbar) { - clearTimeout(this.timeoutScrollbar); - } - - this.setState({ - flyoutIsAnimating: false, - isCollapsed: true, - flyoutIsCollapsed: true, - mobileIsHidden: true, - showScrollbar: false, - outsideClickDisabled: true, - }); - - // Scrolls the menu and flyout back to top when the nav drawer collapses - const menuEl = document.getElementById('navDrawerMenu'); - if (menuEl) { - menuEl.scrollTop = 0; - } - - const flyoutEl = document.getElementById('navDrawerFlyout'); - if (flyoutEl) { - flyoutEl.scrollTop = 0; - } - }; - - private focusOut = () => { - // This collapses the drawer when no children have focus (i.e. tabbed out). - // In other words, if focus does not bubble up from a child element, then - // the drawer will collapse. See the corresponding block in expandDrawer - // (called by onFocus) which cancels this operation via clearTimeout. - this.timeoutID = setTimeout(() => { - if (this.state.isManagingFocus) { - this.setState({ - isManagingFocus: false, - }); - - this.collapseDrawer(); - } - }, 0); - }; - - private expandFlyout = () => { - this.setState(() => ({ - flyoutIsCollapsed: !this.state.flyoutIsCollapsed, - })); - - this.setState({ - flyoutIsAnimating: true, - }); - }; - - private collapseFlyout = () => { - this.setState({ flyoutIsAnimating: true }); - - setTimeout(() => { - this.setState({ - flyoutIsCollapsed: true, - }); - }, this.getTimeoutMs(250)); - }; - - private getTimeoutMs = (defaultTimeout: number) => { - const uiSettings = chrome.getUiSettingsClient(); - return uiSettings.get('accessibility:disableAnimations') ? 0 : defaultTimeout; - }; } export const Header = injectI18n(HeaderUI); diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index eca064ef0464..8c2ef2ea37a1 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -283,7 +283,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); await pieChart.expectPieSliceCount(5); - await PageObjects.visualize.saveVisualization('Rendering Test: animal sounds pie'); + await PageObjects.visualize.saveVisualizationExpectSuccess('Rendering Test: animal sounds pie'); await PageObjects.header.clickDashboard(); await pieChart.expectPieSliceCount(5); diff --git a/test/functional/services/apps_menu.ts b/test/functional/services/apps_menu.ts index 3926bca01681..39a50adc1fd2 100644 --- a/test/functional/services/apps_menu.ts +++ b/test/functional/services/apps_menu.ts @@ -22,22 +22,19 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function AppsMenuProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); - const retry = getService('retry'); - const globalNav = getService('globalNav'); return new class AppsMenu { /** * Get the text and href from each of the links in the apps menu */ public async readLinks() { - await this.ensureMenuOpen(); - const appMenu = await testSubjects.find('navDrawer&expanded appsMenu'); + const appMenu = await testSubjects.find('navDrawer'); const $ = await appMenu.parseDomContent(); const links: Array<{ text: string; href: string; - }> = $.findTestSubjects('appLink') + }> = $.findTestSubjects('navDrawerAppsMenuLink') .toArray() .map((link: any) => { return { @@ -46,7 +43,6 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) { }; }); - await this.ensureMenuClosed(); return links; } @@ -65,30 +61,12 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) { public async clickLink(name: string) { try { log.debug(`click "${name}" app link`); - await this.ensureMenuOpen(); - const container = await testSubjects.find('navDrawer&expanded appsMenu'); - const link = await container.findByPartialLinkText(name); + const container = await testSubjects.find('navDrawer'); + // Text content is not visible or selectable (0px width) so we use an attr with th same value + const link = await container.findByCssSelector(`[aria-label='${name}']`); await link.click(); } finally { - await this.ensureMenuClosed(); - } - } - - private async ensureMenuClosed() { - await globalNav.moveMouseToLogo(); - await retry.waitFor( - 'apps drawer closed', - async () => await testSubjects.exists('navDrawer&collapsed') - ); - } - - private async ensureMenuOpen() { - if (!(await testSubjects.exists('navDrawer&expanded'))) { - await testSubjects.moveMouseTo('navDrawer'); - await retry.waitFor( - 'apps drawer open', - async () => await testSubjects.exists('navDrawer&expanded') - ); + // Intentionally empty } } }(); diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 228ce357f16e..181865ed3ac5 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "5.0.0", - "react": "^16.3.0" + "@elastic/eui": "9.4.0", + "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index 6f0ce8eaea87..ae68ac4f5c54 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "5.0.0", - "react": "^16.3.0" + "@elastic/eui": "9.4.0", + "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index eb67a8a876c5..6286abc45f06 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,8 +7,8 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "5.0.0", - "react": "^16.3.0", - "react-dom": "^16.3.0" + "@elastic/eui": "9.4.0", + "react": "^16.8.0", + "react-dom": "^16.8.0" } } diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js index 7d4eddda4bcd..1adcde72e1aa 100644 --- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js +++ b/test/plugin_functional/test_suites/app_plugins/app_navigation.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.common.navigateToApp('settings'); }); - it('should should nav link that navigates to the app', async () => { + it('should show nav link that navigates to the app', async () => { await appsMenu.clickLink('Test Plugin App'); const pluginContent = await testSubjects.find('pluginContent'); expect(await pluginContent.getVisibleText()).to.be('Super simple app plugin'); diff --git a/x-pack/package.json b/x-pack/package.json index 6c4af380c619..ea247ab1a355 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -154,7 +154,7 @@ }, "dependencies": { "@elastic/datemath": "5.0.2", - "@elastic/eui": "9.2.1", + "@elastic/eui": "9.4.0", "@elastic/javascript-typescript-langserver": "^0.1.17", "@elastic/lsp-extension": "^0.1.1", "@elastic/node-crypto": "0.1.2", diff --git a/x-pack/plugins/rollup/public/search/rollup_search_strategy.js b/x-pack/plugins/rollup/public/search/rollup_search_strategy.js index 3802018b9246..826292c67b0a 100644 --- a/x-pack/plugins/rollup/public/search/rollup_search_strategy.js +++ b/x-pack/plugins/rollup/public/search/rollup_search_strategy.js @@ -18,7 +18,7 @@ function getAllFetchParams(searchRequests, Promise) { }); } -async function serializeAllFetchParams(fetchParams, searchRequests) { +function serializeAllFetchParams(fetchParams, searchRequests) { const searchRequestsWithFetchParams = []; const failedSearchRequests = []; diff --git a/x-pack/plugins/rollup/server/lib/jobs_compatibility.js b/x-pack/plugins/rollup/server/lib/jobs_compatibility.js index 8f4164f40b26..23060aa7beb2 100644 --- a/x-pack/plugins/rollup/server/lib/jobs_compatibility.js +++ b/x-pack/plugins/rollup/server/lib/jobs_compatibility.js @@ -53,12 +53,12 @@ export function mergeJobConfigurations(jobs = []) { const aggName = agg.agg; const aggDoesntExist = !allAggs[aggName]; const fieldDoesntExist = allAggs[aggName] && !allAggs[aggName][fieldName]; - const aggIsntDateHistogram = aggName !== 'date_histogram'; + const isDateHistogramAgg = aggName === 'date_histogram'; // If we currently don't have this aggregation, add it. // Special case for date histogram, since there can only be one // date histogram field. - if(aggDoesntExist || (fieldDoesntExist && aggIsntDateHistogram)) { + if(aggDoesntExist || (fieldDoesntExist && !isDateHistogramAgg)) { allAggs[aggName] = allAggs[aggName] || {}; allAggs[aggName][fieldName] = { ...agg }; } diff --git a/x-pack/test/api_integration/apis/management/rollup/constants.js b/x-pack/test/api_integration/apis/management/rollup/constants.js index c2cc93ba37b3..4cf518e3525e 100644 --- a/x-pack/test/api_integration/apis/management/rollup/constants.js +++ b/x-pack/test/api_integration/apis/management/rollup/constants.js @@ -5,11 +5,12 @@ */ export const API_BASE_PATH = '/api/rollup'; +export const INDEX_PATTERNS_EXTENSION_BASE_PATH = '/api/index_patterns/rollup'; export const ROLLUP_INDEX_NAME = 'rollup_index'; export const INDEX_TO_ROLLUP_MAPPINGS = { properties: { - testTotalField: { 'type': 'long' }, - testTagField: { 'type': 'keyword' }, - testCreatedField: { 'type': 'date' }, + testTotalField: { type: 'long' }, + testTagField: { type: 'keyword' }, + testCreatedField: { type: 'date' }, } }; diff --git a/x-pack/test/api_integration/apis/management/rollup/index.js b/x-pack/test/api_integration/apis/management/rollup/index.js index c154564ad1ab..3c55ce6a7a70 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index.js +++ b/x-pack/test/api_integration/apis/management/rollup/index.js @@ -7,5 +7,7 @@ export default function ({ loadTestFile }) { describe('rollup', () => { loadTestFile(require.resolve('./rollup')); + loadTestFile(require.resolve('./index_patterns_extensions')); + loadTestFile(require.resolve('./rollup_search')); }); } diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js new file mode 100644 index 000000000000..0237a5821380 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import querystring from 'querystring'; + +import { registerHelpers } from './rollup.test_helpers'; +import { INDEX_TO_ROLLUP_MAPPINGS, INDEX_PATTERNS_EXTENSION_BASE_PATH } from './constants'; +import { getRandomString } from './lib'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const es = getService('es'); + + const { + createIndexWithMappings, + getJobPayload, + createJob, + cleanUp, + } = registerHelpers({ supertest, es }); + + describe('index patterns extension', () => { + describe('Fields for wildcards', () => { + const BASE_URI = `${INDEX_PATTERNS_EXTENSION_BASE_PATH}/_fields_for_wildcard`; + + describe('query params validation', () => { + let uri; + let body; + let params; + + it('"pattern" is required', async () => { + uri = `${BASE_URI}`; + ({ body } = await supertest.get(uri).expect(400)); + expect(body.message).to.contain('"pattern" is required'); + }); + + it('"params" is required', async () => { + params = { pattern: 'foo' }; + uri = `${BASE_URI}?${querystring.stringify(params)}`; + ({ body } = await supertest.get(uri).expect(400)); + expect(body.message).to.contain('"params" is required'); + }); + + it('"params" must be an object', async () => { + params = { pattern: 'foo', params: 'bar' }; + uri = `${BASE_URI}?${querystring.stringify(params)}`; + ({ body } = await supertest.get(uri).expect(400)); + expect(body.message).to.contain('"params" must be an object'); + }); + + it('"params" must be an object that only accepts a "rollup_index" property', async () => { + params = { pattern: 'foo', params: JSON.stringify({ someProp: 'bar' }) }; + uri = `${BASE_URI}?${querystring.stringify(params)}`; + ({ body } = await supertest.get(uri).expect(400)); + expect(body.message).to.contain('"someProp" is not allowed'); + }); + + it('"meta_fields" must be an Array', async () => { + params = { + pattern: 'foo', + params: JSON.stringify({ rollup_index: 'bar' }), + meta_fields: 'stringValue' + }; + uri = `${BASE_URI}?${querystring.stringify(params)}`; + ({ body } = await supertest.get(uri).expect(400)); + expect(body.message).to.contain('"meta_fields" must be an array'); + }); + + it('should return 404 the rollup index to query does not exist', async () => { + uri = `${BASE_URI}?${querystring.stringify({ pattern: 'foo', params: JSON.stringify({ rollup_index: 'bar' }) })}`; + ({ body } = await supertest.get(uri).expect(404)); + expect(body.message).to.contain('no such index [bar]'); + }); + }); + + it('should return the correct fields', async () => { + // Create a Rollup job on an index with the INDEX_TO_ROLLUP_MAPPINGS + const indexName = await createIndexWithMappings(); + const rollupIndex = getRandomString(); + const payload = getJobPayload(indexName, undefined, rollupIndex); + await createJob(payload); + + // Query for wildcard + const params = { + pattern: indexName, + params: JSON.stringify({ rollup_index: rollupIndex }) + }; + const uri = `${BASE_URI}?${querystring.stringify(params)}`; + const { body } = await supertest.get(uri).expect(200); + + // Verify that the fields for wildcard correspond to our declared mappings + const propertiesWithMappings = Object.keys(INDEX_TO_ROLLUP_MAPPINGS.properties); + const fieldsForWildcard = body.fields.map(field => field.name); + expect(fieldsForWildcard.sort()).eql(propertiesWithMappings.sort()); + + // Cleanup + await cleanUp(); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/management/rollup/lib/index.js b/x-pack/test/api_integration/apis/management/rollup/lib/index.js index 8d7f7a9c2101..ae24a4c445d2 100644 --- a/x-pack/test/api_integration/apis/management/rollup/lib/index.js +++ b/x-pack/test/api_integration/apis/management/rollup/lib/index.js @@ -11,3 +11,7 @@ export { export { getRandomString, } from './random'; + +export { + wait +} from './utils'; diff --git a/x-pack/test/api_integration/apis/management/rollup/lib/utils.js b/x-pack/test/api_integration/apis/management/rollup/lib/utils.js new file mode 100644 index 000000000000..f83493922fa7 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/rollup/lib/utils.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + +export const wait = (time = 1000) => ( + new Promise((resolve) => ( + setTimeout(resolve, time) + )) +); diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.js b/x-pack/test/api_integration/apis/management/rollup/rollup.js index 5300208ac699..f00ea63a60db 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.js @@ -23,10 +23,10 @@ export default function ({ getService }) { cleanUp, } = registerHelpers({ supertest, es }); - describe('Rollup jobs', () => { + describe('jobs', () => { after(() => cleanUp()); - describe('Rollup indices', () => { + describe('indices', () => { it('should return an empty object when there are no rollup indices', async () => { const uri = `${API_BASE_PATH}/indices`; @@ -74,7 +74,7 @@ export default function ({ getService }) { }); }); - describe('Crud', () => { + describe('crud', () => { describe('list', () => { it('should return an empty array when there are no jobs', async () => { const { body } = await supertest @@ -206,7 +206,7 @@ export default function ({ getService }) { }); }); - describe('Actions', () => { + describe('actions', () => { describe('start', () => { let job; diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js b/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js index 531732422194..cee70461a7bd 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { initElasticsearchIndicesHelpers, getRandomString } from './lib'; +import { initElasticsearchIndicesHelpers, getRandomString, wait } from './lib'; import { API_BASE_PATH, ROLLUP_INDEX_NAME, INDEX_TO_ROLLUP_MAPPINGS } from './constants'; const jobsCreated = []; @@ -17,11 +17,11 @@ export const registerHelpers = ({ supertest, es }) => { return createIndex(indexName, { mappings }); }; - const getJobPayload = (indexName, id = getRandomString()) => ({ + const getJobPayload = (indexName, id = getRandomString(), rollupIndex = ROLLUP_INDEX_NAME) => ({ job: { id, index_pattern: indexName, - rollup_index: ROLLUP_INDEX_NAME, + rollup_index: rollupIndex, cron: '0 0 0 ? * 7', page_size: 1000, groups: { @@ -86,9 +86,29 @@ export const registerHelpers = ({ supertest, es }) => { .send({ jobIds }); }; + const loadJobs = () => supertest.get(`${API_BASE_PATH}/jobs`); + + const waitForJobsToStop = (attempt = 0) => ( + loadJobs() + .then(async ({ body: { jobs } }) => { + const jobBeingStopped = jobs.filter(job => job.status.job_state !== 'stopped' && job.status.job_state !== 'started'); + + if (!jobBeingStopped.length) { + return; + } + + if (attempt < 3 && jobBeingStopped.length) { + await wait(500); + return waitForJobsToStop(++attempt); + } + + throw new Error('Error while waiting for Rollup Jobs to stop'); + })); + const stopAllJobStarted = (jobIds = jobsStarted, attempt = 0) => ( stopJob(jobIds) - .then(() => supertest.get(`${API_BASE_PATH}/jobs`)) + .then(waitForJobsToStop) + .then(loadJobs) .then(({ body: { jobs } }) => { // We make sure that there are no more jobs started // as trying to delete a job that is started will throw an exception @@ -109,7 +129,7 @@ export const registerHelpers = ({ supertest, es }) => { throw response; } }) - .then(() => supertest.get(`${API_BASE_PATH}/jobs`)) + .then(loadJobs) .then(({ body: { jobs } }) => { if (jobs.length && attempt < 3) { // There are still some jobs left to delete. @@ -135,7 +155,7 @@ export const registerHelpers = ({ supertest, es }) => { const cleanUp = () => ( Promise.all([ deleteAllIndices(), - stopAllJobStarted().then(() => deleteJobsCreated()), + stopAllJobStarted().then(deleteJobsCreated), deleteIndicesGeneratedByJobs(), ]).catch(err => { console.log('ERROR cleaning up!'); diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js new file mode 100644 index 000000000000..4455ae8db925 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; + +import { registerHelpers } from './rollup.test_helpers'; +import { API_BASE_PATH } from './constants'; +import { getRandomString } from './lib'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const es = getService('es'); + + const { + createIndexWithMappings, + getJobPayload, + createJob, + cleanUp, + } = registerHelpers({ supertest, es }); + + describe('search', () => { + const URI = `${API_BASE_PATH}/search`; + + it('return a 404 if the rollup index does not exist', async () => { + const { body } = await supertest + .post(URI) + .set('kbn-xsrf', 'xxx') + .send([{ index: 'unknown', query: {} } ]) + .expect(404); + + expect(body.message).to.contain('no such index [unknown]'); + }); + + it('should return a 200 when searching on existing rollup index', async () => { + // Create a Rollup job on an index with the INDEX_TO_ROLLUP_MAPPINGS + const indexName = await createIndexWithMappings(); + const rollupIndex = getRandomString(); + await createJob(getJobPayload(indexName, undefined, rollupIndex)); + + const { body } = await supertest + .post(URI) + .set('kbn-xsrf', 'xxx') + .send([{ index: rollupIndex, query: { size: 0 } } ]) + .expect(200); + + // make sure total hits is an integer and not an object + expect(body[0].hits.total).to.equal(0); + + await cleanUp(); + }); + }); +} diff --git a/yarn.lock b/yarn.lock index c2fa32245daf..2269cc503980 100644 --- a/yarn.lock +++ b/yarn.lock @@ -796,34 +796,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-5.0.0.tgz#e6fe9e1aa8b00c93045178f78a6dd0d457d56fa8" - integrity sha512-WL6sp6u2Rt1O7a2exLU/RuDcRnpluPN6aQ2JexBl+G6mVyF8F5I3RGJKTJp3jOozOaODRY2ev+Nq57EydkjrKg== - dependencies: - classnames "^2.2.5" - core-js "^2.5.1" - focus-trap-react "^3.0.4" - highlight.js "^9.12.0" - html "^1.0.0" - keymirror "^0.1.1" - lodash "npm:@elastic/lodash@3.10.1-kibana1" - numeral "^2.0.6" - prop-types "^15.6.0" - react-ace "^5.5.0" - react-color "^2.13.8" - react-datepicker v1.5.0 - react-input-autosize "^2.2.1" - react-virtualized "^9.18.5" - react-vis "1.10.2" - resize-observer-polyfill "^1.5.0" - tabbable "^1.1.0" - uuid "^3.1.0" - -"@elastic/eui@9.2.1": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.2.1.tgz#3860a7219f0ee8f33ec43447edb2eda3c96a00a7" - integrity sha512-U92s0nh6vBS6NBiHDCq6e+49Dam8It+Iy81b2NProf1OIRk/nB0RE781x6zgUzmOFpJ1T0xiThWiiDP776v0LQ== +"@elastic/eui@9.4.0": + version "9.4.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.4.0.tgz#e5f83c612674fc6f59e990f557e563be1409d25e" + integrity sha512-cQnsU3UeQ1s6F427vY53lzlh7mvNoPmIvPL24B5jH3JYaWULhIL1xSJXJgDSOSdrdHCxkXDD9Q8Y7256x4Xf4Q== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" @@ -18609,11 +18585,6 @@ polished@^1.9.2: resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6" integrity sha512-mPocQrVUSiqQdHNZFGL1iHJmsR/etiv05Nf2oZUbya+GMsQkZVEBl5wonN+Sr/e9zQBEhT6yrMjxAUJ06eyocQ== -popper.js@^1.14.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" - integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= - popper.js@^1.14.3: version "1.14.6" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.6.tgz#ab20dd4edf9288b8b3b6531c47c361107b60b4b0" @@ -19500,16 +19471,6 @@ react-color@^2.13.8, react-color@^2.14.1: reactcss "^1.2.0" tinycolor2 "^1.4.1" -react-datepicker@v1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-1.5.0.tgz#7eacd9609313189c84a21bb7421486054939a4b2" - integrity sha512-Neh1rz0d1QeR7KuoTiYeR6oj73DJkqt0vuNSgfMuxXEwGmz/4sPynouYGo6gdKiQbxIXBJJ/FLDLHJEr5XNThw== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - react-onclickoutside "^6.7.1" - react-popper "^0.9.1" - react-datetime@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/react-datetime/-/react-datetime-2.15.0.tgz#a8f7da6c58b6b45dbeea32d4e8485db17614e12c" @@ -19590,7 +19551,7 @@ react-docgen@^3.0.0-rc.1: node-dir "^0.1.10" recast "^0.16.0" -react-dom@^16.2.0, react-dom@^16.3.0, react-dom@^16.8.0: +react-dom@^16.2.0, react-dom@^16.8.0: version "16.8.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.2.tgz#7c8a69545dd554d45d66442230ba04a6a0a3c3d3" integrity sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg== @@ -19777,19 +19738,11 @@ react-motion@^0.5.2: prop-types "^15.5.8" raf "^3.1.0" -react-onclickoutside@^6.5.0, react-onclickoutside@^6.7.1: +react-onclickoutside@^6.5.0: version "6.7.1" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz#6a5b5b8b4eae6b776259712c89c8a2b36b17be93" integrity sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg== -react-popper@^0.9.1: - version "0.9.5" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.9.5.tgz#02a24ef3eec33af9e54e8358ab70eb0e331edd05" - integrity sha1-AqJO8+7DOvnlToNYq3DrDjMe3QU= - dependencies: - popper.js "^1.14.1" - prop-types "^15.6.1" - react-portal@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043" @@ -20055,7 +20008,7 @@ react-vis@^1.8.1: prop-types "^15.5.8" react-motion "^0.4.8" -react@^16.2.0, react@^16.3.0, react@^16.6.0, react@^16.8.0: +react@^16.2.0, react@^16.6.0, react@^16.8.0: version "16.8.2" resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c" integrity sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==