From b2b60ff061ad8eec2ea7b1fae351eb9888937005 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 9 Feb 2022 11:42:53 +0100 Subject: [PATCH 01/85] [Screenshotting] Fix potential race condition when screenshotting (#123820) * extract message from error objects * only warn for 400 and up status codes * naively wait for vis ready after resizing the browser viewport * use a single default viewport size, enable layout to set default page viewport for every page that is created * refactor viewport -> windowSize in chromium args * allow overriding defaults and use new windowSize arg for chromium args * always round page dimension numbers. note: this will break if we ever have a "undefined" set as a key value * added comment * update snapshot to new width value * make defaultViewport a required field on createPage * added comment * style: use async-await rather than .then chaining. also added a comment Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../browsers/chromium/driver_factory/args.ts | 15 ++-- .../chromium/driver_factory/index.test.ts | 14 ++-- .../browsers/chromium/driver_factory/index.ts | 18 ++++- .../server/layouts/create_layout.ts | 12 +++- .../server/screenshots/index.test.ts | 2 +- .../server/screenshots/index.ts | 71 +++++++++++-------- .../server/screenshots/observable.ts | 24 +++---- 7 files changed, 97 insertions(+), 59 deletions(-) diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts index e5985082b3c1c..964b8298151f5 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts @@ -7,7 +7,7 @@ import type { ConfigType } from '../../../config'; -interface Viewport { +interface WindowSize { height: number; width: number; } @@ -16,12 +16,17 @@ type Proxy = ConfigType['browser']['chromium']['proxy']; interface LaunchArgs { userDataDir: string; - viewport?: Viewport; + windowSize?: WindowSize; disableSandbox?: boolean; proxy: Proxy; } -export const args = ({ userDataDir, disableSandbox, viewport, proxy: proxyConfig }: LaunchArgs) => { +export const args = ({ + userDataDir, + disableSandbox, + windowSize, + proxy: proxyConfig, +}: LaunchArgs) => { const flags = [ // Disable built-in Google Translate service '--disable-translate', @@ -50,11 +55,11 @@ export const args = ({ userDataDir, disableSandbox, viewport, proxy: proxyConfig `--mainFrameClipsContent=false`, ]; - if (viewport) { + if (windowSize) { // NOTE: setting the window size does NOT set the viewport size: viewport and window size are different. // The viewport may later need to be resized depending on the position of the clip area. // These numbers come from the job parameters, so this is a close guess. - flags.push(`--window-size=${Math.floor(viewport.width)},${Math.floor(viewport.height)}`); + flags.push(`--window-size=${Math.floor(windowSize.width)},${Math.floor(windowSize.height)}`); } if (proxyConfig.enabled) { diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts index 7d9813928f924..bf8a1786735eb 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts @@ -11,7 +11,7 @@ import { mergeMap, take } from 'rxjs/operators'; import type { Logger } from 'src/core/server'; import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; import { ConfigType } from '../../../config'; -import { HeadlessChromiumDriverFactory } from '.'; +import { HeadlessChromiumDriverFactory, DEFAULT_VIEWPORT } from '.'; jest.mock('puppeteer'); @@ -70,7 +70,10 @@ describe('HeadlessChromiumDriverFactory', () => { describe('createPage', () => { it('returns browser driver, unexpected process exit observable, and close callback', async () => { await expect( - factory.createPage({ openUrlTimeout: 0 }).pipe(take(1)).toPromise() + factory + .createPage({ openUrlTimeout: 0, defaultViewport: DEFAULT_VIEWPORT }) + .pipe(take(1)) + .toPromise() ).resolves.toEqual( expect.objectContaining({ driver: expect.anything(), @@ -85,7 +88,10 @@ describe('HeadlessChromiumDriverFactory', () => { `Puppeteer Launch mock fail.` ); expect(() => - factory.createPage({ openUrlTimeout: 0 }).pipe(take(1)).toPromise() + factory + .createPage({ openUrlTimeout: 0, defaultViewport: DEFAULT_VIEWPORT }) + .pipe(take(1)) + .toPromise() ).rejects.toThrowErrorMatchingInlineSnapshot( `"Error spawning Chromium browser! Puppeteer Launch mock fail."` ); @@ -94,7 +100,7 @@ describe('HeadlessChromiumDriverFactory', () => { describe('close behaviour', () => { it('does not allow close to be called on the browse more than once', async () => { await factory - .createPage({ openUrlTimeout: 0 }) + .createPage({ openUrlTimeout: 0, defaultViewport: DEFAULT_VIEWPORT }) .pipe( take(1), mergeMap(async ({ close }) => { diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts index 787bd8fbfca99..d26d948beee16 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts @@ -7,6 +7,7 @@ import { getDataPath } from '@kbn/utils'; import { spawn } from 'child_process'; +import _ from 'lodash'; import del from 'del'; import fs from 'fs'; import { uniq } from 'lodash'; @@ -36,6 +37,12 @@ import { getMetrics, PerformanceMetrics } from './metrics'; interface CreatePageOptions { browserTimezone?: string; + defaultViewport: { + /** Size in pixels */ + width?: number; + /** Size in pixels */ + height?: number; + }; openUrlTimeout: number; } @@ -110,7 +117,7 @@ export class HeadlessChromiumDriverFactory { userDataDir: this.userDataDir, disableSandbox: this.config.browser.chromium.disableSandbox, proxy: this.config.browser.chromium.proxy, - viewport: DEFAULT_VIEWPORT, + windowSize: DEFAULT_VIEWPORT, // Approximate the default viewport size }); } @@ -118,7 +125,7 @@ export class HeadlessChromiumDriverFactory { * Return an observable to objects which will drive screenshot capture for a page */ createPage( - { browserTimezone, openUrlTimeout }: CreatePageOptions, + { browserTimezone, openUrlTimeout, defaultViewport }: CreatePageOptions, pLogger = this.logger ): Rx.Observable { // FIXME: 'create' is deprecated @@ -139,6 +146,13 @@ export class HeadlessChromiumDriverFactory { ignoreHTTPSErrors: true, handleSIGHUP: false, args: chromiumArgs, + + // We optionally set this at page creation to reduce the chances of + // browser reflow. In most cases only the height needs to be adjusted + // before taking a screenshot. + // NOTE: _.defaults assigns to the target object, so we copy it. + // NOTE NOTE: _.defaults is not the same as { ...DEFAULT_VIEWPORT, ...defaultViewport } + defaultViewport: _.defaults({ ...defaultViewport }, DEFAULT_VIEWPORT), env: { TZ: browserTimezone, }, diff --git a/x-pack/plugins/screenshotting/server/layouts/create_layout.ts b/x-pack/plugins/screenshotting/server/layouts/create_layout.ts index 29a34a07e696f..fa4b3a40e2c79 100644 --- a/x-pack/plugins/screenshotting/server/layouts/create_layout.ts +++ b/x-pack/plugins/screenshotting/server/layouts/create_layout.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { map as mapRecord } from 'fp-ts/lib/Record'; import type { LayoutParams } from '../../common/layout'; import { LayoutTypes } from '../../common'; import type { Layout } from '.'; @@ -12,13 +13,20 @@ import { CanvasLayout } from './canvas_layout'; import { PreserveLayout } from './preserve_layout'; import { PrintLayout } from './print_layout'; +/** + * We naively round all numeric values in the object, this will break screenshotting + * if ever a have a non-number set as a value, but this points to an issue + * in the code responsible for creating the dimensions object. + */ +const roundNumbers = mapRecord(Math.round); + export function createLayout({ id, dimensions, selectors, ...config }: LayoutParams): Layout { if (dimensions && id === LayoutTypes.PRESERVE_LAYOUT) { - return new PreserveLayout(dimensions, selectors); + return new PreserveLayout(roundNumbers(dimensions), selectors); } if (dimensions && id === LayoutTypes.CANVAS) { - return new CanvasLayout(dimensions); + return new CanvasLayout(roundNumbers(dimensions)); } // layoutParams is optional as PrintLayout doesn't use it diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index c49f2289ba959..858e1ae9d6093 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -376,7 +376,7 @@ describe('Screenshot Observable Pipeline', () => { "height": 1200, "left": 0, "top": 0, - "width": 1800, + "width": 1950, }, "scroll": Object { "x": 0, diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.ts b/x-pack/plugins/screenshotting/server/screenshots/index.ts index 363d59ccca950..d7332217e78a5 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.ts @@ -68,38 +68,47 @@ export function getScreenshots( timeouts: { openUrl: openUrlTimeout }, } = options; - return browserDriverFactory.createPage({ browserTimezone, openUrlTimeout }, logger).pipe( - mergeMap(({ driver, unexpectedExit$, metrics$, close }) => { - apmCreatePage?.end(); - metrics$.subscribe(({ cpu, memory }) => { - apmTrans?.setLabel('cpu', cpu, false); - apmTrans?.setLabel('memory', memory, false); - }); - unexpectedExit$.subscribe({ error: () => apmTrans?.end() }); + return browserDriverFactory + .createPage( + { + browserTimezone, + openUrlTimeout, + defaultViewport: { height: layout.height, width: layout.width }, + }, + logger + ) + .pipe( + mergeMap(({ driver, unexpectedExit$, metrics$, close }) => { + apmCreatePage?.end(); + metrics$.subscribe(({ cpu, memory }) => { + apmTrans?.setLabel('cpu', cpu, false); + apmTrans?.setLabel('memory', memory, false); + }); + unexpectedExit$.subscribe({ error: () => apmTrans?.end() }); - const screen = new ScreenshotObservableHandler(driver, logger, layout, options); + const screen = new ScreenshotObservableHandler(driver, logger, layout, options); - return from(options.urls).pipe( - concatMap((url, index) => - screen.setupPage(index, url, apmTrans).pipe( - catchError((error) => { - screen.checkPageIsOpen(); // this fails the job if the browser has closed + return from(options.urls).pipe( + concatMap((url, index) => + screen.setupPage(index, url, apmTrans).pipe( + catchError((error) => { + screen.checkPageIsOpen(); // this fails the job if the browser has closed - logger.error(error); - return of({ ...DEFAULT_SETUP_RESULT, error }); // allow failover screenshot capture - }), - takeUntil(unexpectedExit$), - screen.getScreenshots() - ) - ), - take(options.urls.length), - toArray(), - mergeMap((results) => { - // At this point we no longer need the page, close it. - return close().pipe(mapTo({ layout, metrics$, results })); - }) - ); - }), - first() - ); + logger.error(error); + return of({ ...DEFAULT_SETUP_RESULT, error }); // allow failover screenshot capture + }), + takeUntil(unexpectedExit$), + screen.getScreenshots() + ) + ), + take(options.urls.length), + toArray(), + mergeMap((results) => { + // At this point we no longer need the page, close it. + return close().pipe(mapTo({ layout, metrics$, results })); + }) + ); + }), + first() + ); } diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.ts index b77180a9399b1..a238af5bcc25b 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/observable.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/observable.ts @@ -11,7 +11,7 @@ import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators'; import type { Logger } from 'src/core/server'; import type { Layout as ScreenshotModeLayout } from 'src/plugins/screenshot_mode/common'; import type { ConditionalHeaders, HeadlessChromiumDriver } from '../browsers'; -import { getChromiumDisconnectedError } from '../browsers'; +import { getChromiumDisconnectedError, DEFAULT_VIEWPORT } from '../browsers'; import type { Layout } from '../layouts'; import type { ElementsPositionAndAttribute } from './get_element_position_data'; import { getElementPositionAndAttributes } from './get_element_position_data'; @@ -107,12 +107,9 @@ interface PageSetupResults { error?: Error; } -const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200; -const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800; - const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => { - const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT; - const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH; + const height = dimensions?.height || DEFAULT_VIEWPORT.height; + const width = dimensions?.width || DEFAULT_VIEWPORT.width; return [ { @@ -130,8 +127,7 @@ const getDefaultElementPosition = (dimensions: { height?: number; width?: number * provided by the browser. */ const getDefaultViewPort = () => ({ - height: DEFAULT_SCREENSHOT_CLIP_HEIGHT, - width: DEFAULT_SCREENSHOT_CLIP_WIDTH, + ...DEFAULT_VIEWPORT, zoom: 1, }); @@ -180,14 +176,14 @@ export class ScreenshotObservableHandler { const waitTimeout = this.options.timeouts.waitForElements; return defer(() => getNumberOfItems(driver, this.logger, waitTimeout, this.layout)).pipe( - mergeMap((itemsCount) => { - // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout + mergeMap(async (itemsCount) => { + // set the viewport to the dimensions from the job, to allow elements to flow into the expected layout const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort(); - return forkJoin([ - driver.setViewport(viewport, this.logger), - waitForVisualizations(driver, this.logger, waitTimeout, itemsCount, this.layout), - ]); + // Set the viewport allowing time for the browser to handle reflow and redraw + // before checking for readiness of visualizations. + await driver.setViewport(viewport, this.logger); + await waitForVisualizations(driver, this.logger, waitTimeout, itemsCount, this.layout); }), this.waitUntil(waitTimeout, 'wait for elements') ); From ceb14e68429db6d3bb7ef8336b936f35084f1db7 Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Wed, 9 Feb 2022 12:33:39 +0100 Subject: [PATCH 02/85] [Fleet] Test new privileges system via cypress (#124797) * Tests new roles introduced with superuser removal * Use login and roles utilities from security-solution cypress library * Add some more tests * expand tests * Fix failing test * Fix linter check --- .../cypress/integration/fleet_startup.spec.ts | 50 +-- .../integration/integrations_mock.spec.ts | 3 +- .../integration/integrations_real.spec.ts | 3 +- ...ileges_fleet_all_integrations_none.spec.ts | 42 +++ ...ileges_fleet_all_integrations_read.spec.ts | 88 +++++ ...ileges_fleet_none_integrations_all.spec.ts | 40 ++ x-pack/plugins/fleet/cypress/screens/fleet.ts | 11 + .../fleet/cypress/screens/integrations.ts | 2 + .../fleet/cypress/screens/navigation.ts | 3 +- x-pack/plugins/fleet/cypress/tasks/fleet.ts | 59 +++ x-pack/plugins/fleet/cypress/tasks/login.ts | 341 ++++++++++++++++++ .../plugins/fleet/cypress/tasks/navigation.ts | 13 +- .../plugins/fleet/cypress/tasks/privileges.ts | 232 ++++++++++++ .../fleet/public/applications/fleet/app.tsx | 4 +- .../package_policies_table.tsx | 1 + .../fleet_server_missing_privileges.tsx | 4 +- .../package_policy_actions_menu.tsx | 4 + 17 files changed, 847 insertions(+), 53 deletions(-) create mode 100644 x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_none.spec.ts create mode 100644 x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_read.spec.ts create mode 100644 x-pack/plugins/fleet/cypress/integration/privileges_fleet_none_integrations_all.spec.ts create mode 100644 x-pack/plugins/fleet/cypress/tasks/fleet.ts create mode 100644 x-pack/plugins/fleet/cypress/tasks/login.ts create mode 100644 x-pack/plugins/fleet/cypress/tasks/privileges.ts diff --git a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts index 83423d62e2a43..5c14ee1df6d4e 100644 --- a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts @@ -5,47 +5,17 @@ * 2.0. */ -import { AGENTS_TAB, AGENT_POLICIES_TAB, ENROLLMENT_TOKENS_TAB } from '../screens/fleet'; +import { + AGENTS_TAB, + ADD_AGENT_BUTTON_TOP, + AGENT_FLYOUT_CLOSE_BUTTON, + STANDALONE_TAB, +} from '../screens/fleet'; import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup'; +import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet'; import { FLEET, navigateTo } from '../tasks/navigation'; describe('Fleet startup', () => { - function navigateToTab(tab: string) { - cy.getBySel(tab).click(); - cy.get('.euiBasicTable-loading').should('not.exist'); - } - - function navigateToAgentPolicy(name: string) { - cy.get('.euiLink').contains(name).click(); - cy.get('.euiLoadingSpinner').should('not.exist'); - } - - function navigateToEnrollmentTokens() { - cy.getBySel(ENROLLMENT_TOKENS_TAB).click(); - cy.get('.euiBasicTable-loading').should('not.exist'); - cy.get('.euiButtonIcon--danger'); // wait for trash icon - } - - function verifyPolicy(name: string, integrations: string[]) { - navigateToTab(AGENT_POLICIES_TAB); - - navigateToAgentPolicy(name); - integrations.forEach((integration) => { - cy.get('.euiLink').contains(integration); - }); - - cy.get('.euiButtonEmpty').contains('View all agent policies').click(); - - navigateToEnrollmentTokens(); - - cy.get('.euiTableCellContent').contains(name); - } - - function verifyAgentPackage() { - cy.visit('/app/integrations/installed'); - cy.getBySel('integration-card:epr:elastic_agent'); - } - // skipping Fleet Server enroll, to enable, comment out runner.ts line 23 describe.skip('Fleet Server', () => { it('should display Add agent button and Healthy agent once Fleet Agent page loaded', () => { @@ -77,8 +47,8 @@ describe('Fleet startup', () => { }); it('should create agent policy', () => { - cy.getBySel('addAgentBtnTop').click(); - cy.getBySel('standaloneTab').click(); + cy.getBySel(ADD_AGENT_BUTTON_TOP).click(); + cy.getBySel(STANDALONE_TAB).click(); cy.intercept('POST', '/api/fleet/agent_policies?sys_monitoring=true').as('createAgentPolicy'); @@ -97,7 +67,7 @@ describe('Fleet startup', () => { // verify agent.yml code block has new policy id cy.get('.euiCodeBlock__code').contains(`id: ${agentPolicyId}`); - cy.getBySel('euiFlyoutCloseButton').click(); + cy.getBySel(AGENT_FLYOUT_CLOSE_BUTTON).click(); // verify policy is created and has system package verifyPolicy('Agent policy 1', ['System']); diff --git a/x-pack/plugins/fleet/cypress/integration/integrations_mock.spec.ts b/x-pack/plugins/fleet/cypress/integration/integrations_mock.spec.ts index 080b01458e18f..1b969e1a8ca2e 100644 --- a/x-pack/plugins/fleet/cypress/integration/integrations_mock.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/integrations_mock.spec.ts @@ -7,6 +7,7 @@ import { navigateTo } from '../tasks/navigation'; import { UPDATE_PACKAGE_BTN } from '../screens/integrations'; +import { AGENT_POLICY_SAVE_INTEGRATION } from '../screens/fleet'; describe('Add Integration - Mock API', () => { describe('upgrade package and upgrade package policy', () => { @@ -141,7 +142,7 @@ describe('Add Integration - Mock API', () => { ); cy.getBySel('toastCloseButton').click(); - cy.getBySel('saveIntegration').click(); + cy.getBySel(AGENT_POLICY_SAVE_INTEGRATION).click(); cy.wait('@updateApachePolicy').then((interception) => { expect(interception.request.body.package.version).to.equal(newVersion); diff --git a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts index ffb0f14c97a7f..e06b3d3ed5670 100644 --- a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts @@ -24,6 +24,7 @@ import { SETTINGS_TAB, UPDATE_PACKAGE_BTN, } from '../screens/integrations'; +import { ADD_PACKAGE_POLICY_BTN } from '../screens/fleet'; import { cleanupAgentPolicies } from '../tasks/cleanup'; describe('Add Integration - Real API', () => { @@ -75,7 +76,7 @@ describe('Add Integration - Real API', () => { cy.visit(`/app/fleet/policies/${agentPolicyId}`); cy.intercept('GET', '/api/fleet/epm/packages?*').as('packages'); - cy.getBySel('addPackagePolicyButton').click(); + cy.getBySel(ADD_PACKAGE_POLICY_BTN).click(); cy.wait('@packages'); cy.get('.euiLoadingSpinner').should('not.exist'); cy.get('input[placeholder="Search for integrations"]').type('Apache'); diff --git a/x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_none.spec.ts b/x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_none.spec.ts new file mode 100644 index 0000000000000..f9ae802d3b426 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_none.spec.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FLEET } from '../tasks/navigation'; +import { + createUsersAndRoles, + FleetAllIntegrNoneRole, + FleetAllIntegrNoneUser, + deleteUsersAndRoles, +} from '../tasks/privileges'; +import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; + +import { MISSING_PRIVILEGES_TITLE, MISSING_PRIVILEGES_MESSAGE } from '../screens/fleet'; +const rolesToCreate = [FleetAllIntegrNoneRole]; +const usersToCreate = [FleetAllIntegrNoneUser]; + +describe('When the user has All privilege for Fleet but None for integrations', () => { + before(() => { + createUsersAndRoles(usersToCreate, rolesToCreate); + }); + + afterEach(() => { + logout(); + }); + + after(() => { + deleteUsersAndRoles(usersToCreate, rolesToCreate); + }); + + it('Fleet access is blocked with a callout', () => { + loginWithUserAndWaitForPage(FLEET, FleetAllIntegrNoneUser); + cy.getBySel(MISSING_PRIVILEGES_TITLE).should('have.text', 'Permission denied'); + cy.getBySel(MISSING_PRIVILEGES_MESSAGE).should( + 'contain', + 'You are not authorized to access Fleet.' + ); + }); +}); diff --git a/x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_read.spec.ts b/x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_read.spec.ts new file mode 100644 index 0000000000000..327ba39e65377 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/integration/privileges_fleet_all_integrations_read.spec.ts @@ -0,0 +1,88 @@ +/* + * 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 { FLEET, INTEGRATIONS, navigateTo } from '../tasks/navigation'; +import { + createUsersAndRoles, + FleetAllIntegrReadRole, + FleetAllIntegrReadUser, + deleteUsersAndRoles, +} from '../tasks/privileges'; +import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; +import { navigateToTab, createAgentPolicy } from '../tasks/fleet'; +import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup'; + +import { + FLEET_SERVER_MISSING_PRIVILEGES_TITLE, + FLEET_SERVER_MISSING_PRIVILEGES_MESSAGE, + ADD_AGENT_BUTTON_TOP, + AGENT_POLICIES_TAB, + AGENT_POLICY_SAVE_INTEGRATION, + ADD_PACKAGE_POLICY_BTN, +} from '../screens/fleet'; +import { ADD_POLICY_BTN, AGENT_POLICY_NAME_LINK } from '../screens/integrations'; + +const rolesToCreate = [FleetAllIntegrReadRole]; +const usersToCreate = [FleetAllIntegrReadUser]; + +describe('When the user has All privilege for Fleet but Read for integrations', () => { + before(() => { + createUsersAndRoles(usersToCreate, rolesToCreate); + }); + + after(() => { + deleteUsersAndRoles(usersToCreate, rolesToCreate); + }); + + afterEach(() => { + logout(); + }); + + describe('When there are agent policies', () => { + before(() => { + navigateTo(FLEET); + createAgentPolicy(); + }); + + it('Some elements in the UI are not enabled', () => { + logout(); + loginWithUserAndWaitForPage(FLEET, FleetAllIntegrReadUser); + navigateToTab(AGENT_POLICIES_TAB); + + cy.getBySel(AGENT_POLICY_NAME_LINK).click(); + cy.getBySel(ADD_PACKAGE_POLICY_BTN).should('be.disabled'); + + cy.get('a[title="system-1"]').click(); + cy.getBySel(AGENT_POLICY_SAVE_INTEGRATION).should('be.disabled'); + }); + + after(() => { + unenrollAgent(); + cleanupAgentPolicies(); + }); + }); + + describe('When there are no agent policies', () => { + it('If fleet server is not set up, Fleet shows a callout', () => { + loginWithUserAndWaitForPage(FLEET, FleetAllIntegrReadUser); + cy.getBySel(FLEET_SERVER_MISSING_PRIVILEGES_TITLE).should('have.text', 'Permission denied'); + cy.getBySel(FLEET_SERVER_MISSING_PRIVILEGES_MESSAGE).should( + 'contain', + 'Fleet Server needs to be set up.' + ); + cy.getBySel(ADD_AGENT_BUTTON_TOP).should('not.be.disabled'); + }); + }); + + describe('Integrations', () => { + it('are visible but cannot be added', () => { + loginWithUserAndWaitForPage(INTEGRATIONS, FleetAllIntegrReadUser); + cy.getBySel('integration-card:epr:apache').click(); + cy.getBySel(ADD_POLICY_BTN).should('be.disabled'); + }); + }); +}); diff --git a/x-pack/plugins/fleet/cypress/integration/privileges_fleet_none_integrations_all.spec.ts b/x-pack/plugins/fleet/cypress/integration/privileges_fleet_none_integrations_all.spec.ts new file mode 100644 index 0000000000000..68fcecb76de21 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/integration/privileges_fleet_none_integrations_all.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { INTEGRATIONS } from '../tasks/navigation'; +import { + createUsersAndRoles, + FleetNoneIntegrAllRole, + FleetNoneIntegrAllUser, + deleteUsersAndRoles, +} from '../tasks/privileges'; +import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; + +import { ADD_POLICY_BTN } from '../screens/integrations'; + +const rolesToCreate = [FleetNoneIntegrAllRole]; +const usersToCreate = [FleetNoneIntegrAllUser]; + +describe('When the user has All privileges for Integrations but None for for Fleet', () => { + before(() => { + createUsersAndRoles(usersToCreate, rolesToCreate); + }); + + afterEach(() => { + logout(); + }); + + after(() => { + deleteUsersAndRoles(usersToCreate, rolesToCreate); + }); + + it('Integrations are visible but cannot be added', () => { + loginWithUserAndWaitForPage(INTEGRATIONS, FleetNoneIntegrAllUser); + cy.getBySel('integration-card:epr:apache').click(); + cy.getBySel(ADD_POLICY_BTN).should('be.disabled'); + }); +}); diff --git a/x-pack/plugins/fleet/cypress/screens/fleet.ts b/x-pack/plugins/fleet/cypress/screens/fleet.ts index 4c0bb7cea161e..32ecdc4f5da71 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet.ts @@ -6,8 +6,19 @@ */ export const ADD_AGENT_BUTTON = 'addAgentButton'; +export const ADD_AGENT_BUTTON_TOP = 'addAgentBtnTop'; +export const CREATE_POLICY_BUTTON = 'createPolicyBtn'; +export const AGENT_FLYOUT_CLOSE_BUTTON = 'euiFlyoutCloseButton'; export const AGENTS_TAB = 'fleet-agents-tab'; export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab'; export const ENROLLMENT_TOKENS_TAB = 'fleet-enrollment-tokens-tab'; export const SETTINGS_TAB = 'fleet-settings-tab'; +export const STANDALONE_TAB = 'standaloneTab'; +export const MISSING_PRIVILEGES_TITLE = 'missingPrivilegesPromptTitle'; +export const MISSING_PRIVILEGES_MESSAGE = 'missingPrivilegesPromptMessage'; +export const FLEET_SERVER_MISSING_PRIVILEGES_MESSAGE = 'fleetServerMissingPrivilegesMessage'; +export const FLEET_SERVER_MISSING_PRIVILEGES_TITLE = 'fleetServerMissingPrivilegesTitle'; +export const AGENT_POLICY_SAVE_INTEGRATION = 'saveIntegration'; +export const PACKAGE_POLICY_TABLE_LINK = 'PackagePoliciesTableLink'; +export const ADD_PACKAGE_POLICY_BTN = 'addPackagePolicyButton'; diff --git a/x-pack/plugins/fleet/cypress/screens/integrations.ts b/x-pack/plugins/fleet/cypress/screens/integrations.ts index 3c980723cc4df..dddede9e77f8d 100644 --- a/x-pack/plugins/fleet/cypress/screens/integrations.ts +++ b/x-pack/plugins/fleet/cypress/screens/integrations.ts @@ -11,6 +11,7 @@ export const INTEGRATIONS_CARD = '.euiCard__titleAnchor'; export const INTEGRATION_NAME_LINK = 'integrationNameLink'; export const AGENT_POLICY_NAME_LINK = 'agentPolicyNameLink'; +export const AGENT_ACTIONS_BTN = 'agentActionsBtn'; export const CONFIRM_MODAL_BTN = 'confirmModalConfirmButton'; export const CONFIRM_MODAL_BTN_SEL = `[data-test-subj=${CONFIRM_MODAL_BTN}]`; @@ -19,6 +20,7 @@ export const FLYOUT_CLOSE_BTN_SEL = '[data-test-subj="euiFlyoutCloseButton"]'; export const SETTINGS_TAB = 'tab-settings'; export const POLICIES_TAB = 'tab-policies'; +export const ADVANCED_TAB = 'tab-custom'; export const UPDATE_PACKAGE_BTN = 'updatePackageBtn'; export const LATEST_VERSION = 'latestVersion'; diff --git a/x-pack/plugins/fleet/cypress/screens/navigation.ts b/x-pack/plugins/fleet/cypress/screens/navigation.ts index fee38161b6b2b..76b73711db495 100644 --- a/x-pack/plugins/fleet/cypress/screens/navigation.ts +++ b/x-pack/plugins/fleet/cypress/screens/navigation.ts @@ -5,4 +5,5 @@ * 2.0. */ -export const TOGGLE_NAVIGATION_BTN = '[data-test-subj="toggleNavButton"]'; +export const TOGGLE_NAVIGATION_BTN = 'toggleNavButton'; +export const NAV_APP_LINK = 'collapsibleNavAppLink'; diff --git a/x-pack/plugins/fleet/cypress/tasks/fleet.ts b/x-pack/plugins/fleet/cypress/tasks/fleet.ts new file mode 100644 index 0000000000000..304ab7445d4e4 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tasks/fleet.ts @@ -0,0 +1,59 @@ +/* + * 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 { + AGENT_POLICIES_TAB, + ENROLLMENT_TOKENS_TAB, + ADD_AGENT_BUTTON_TOP, + CREATE_POLICY_BUTTON, + AGENT_FLYOUT_CLOSE_BUTTON, + STANDALONE_TAB, +} from '../screens/fleet'; + +export function createAgentPolicy() { + cy.getBySel(ADD_AGENT_BUTTON_TOP).click(); + cy.getBySel(STANDALONE_TAB).click(); + cy.getBySel(CREATE_POLICY_BUTTON).click(); + cy.getBySel('agentPolicyCreateStatusCallOut').contains('Agent policy created'); + cy.getBySel(AGENT_FLYOUT_CLOSE_BUTTON).click(); +} + +export function navigateToTab(tab: string) { + cy.getBySel(tab).click(); + cy.get('.euiBasicTable-loading').should('not.exist'); +} + +export function navigateToAgentPolicy(name: string) { + cy.get('.euiLink').contains(name).click(); + cy.get('.euiLoadingSpinner').should('not.exist'); +} + +export function navigateToEnrollmentTokens() { + cy.getBySel(ENROLLMENT_TOKENS_TAB).click(); + cy.get('.euiBasicTable-loading').should('not.exist'); + cy.get('.euiButtonIcon--danger'); // wait for trash icon +} + +export function verifyPolicy(name: string, integrations: string[]) { + navigateToTab(AGENT_POLICIES_TAB); + + navigateToAgentPolicy(name); + integrations.forEach((integration) => { + cy.get('.euiLink').contains(integration); + }); + + cy.get('.euiButtonEmpty').contains('View all agent policies').click(); + + navigateToEnrollmentTokens(); + + cy.get('.euiTableCellContent').contains(name); +} + +export function verifyAgentPackage() { + cy.visit('/app/integrations/installed'); + cy.getBySel('integration-card:epr:elastic_agent'); +} diff --git a/x-pack/plugins/fleet/cypress/tasks/login.ts b/x-pack/plugins/fleet/cypress/tasks/login.ts new file mode 100644 index 0000000000000..2df7b88f1607b --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tasks/login.ts @@ -0,0 +1,341 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Url from 'url'; +import type { UrlObject } from 'url'; + +import * as yaml from 'js-yaml'; + +import type { ROLES } from './privileges'; +import { hostDetailsUrl, LOGOUT_URL } from './navigation'; + +/** + * Credentials in the `kibana.dev.yml` config file will be used to authenticate + * with Kibana when credentials are not provided via environment variables + */ +const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml'; + +/** + * The configuration path in `kibana.dev.yml` to the username to be used when + * authenticating with Kibana. + */ +const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username'; + +/** + * The configuration path in `kibana.dev.yml` to the password to be used when + * authenticating with Kibana. + */ +const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password'; + +/** + * The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the + * username to be used when authenticating with Kibana + */ +const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME'; + +/** + * The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the + * username to be used when authenticating with Kibana + */ +const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; + +/** + * The Kibana server endpoint used for authentication + */ +const LOGIN_API_ENDPOINT = '/internal/security/login'; + +/** + * cy.visit will default to the baseUrl which uses the default kibana test user + * This function will override that functionality in cy.visit by building the baseUrl + * directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts + * + * @param role string role/user to log in with + * @param route string route to visit + */ +export const getUrlWithRoute = (role: ROLES, route: string) => { + const url = Cypress.config().baseUrl; + const kibana = new URL(String(url)); + const theUrl = `${Url.format({ + auth: `${role}:changeme`, + username: role, + password: 'changeme', + protocol: kibana.protocol.replace(':', ''), + hostname: kibana.hostname, + port: kibana.port, + } as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`; + cy.log(`origin: ${theUrl}`); + return theUrl; +}; + +interface User { + username: string; + password: string; +} + +/** + * Builds a URL with basic auth using the passed in user. + * + * @param user the user information to build the basic auth with + * @param route string route to visit + */ +export const constructUrlWithUser = (user: User, route: string) => { + const url = Cypress.config().baseUrl; + const kibana = new URL(String(url)); + const hostname = kibana.hostname; + const username = user.username; + const password = user.password; + const protocol = kibana.protocol.replace(':', ''); + const port = kibana.port; + + const path = `${route.startsWith('/') ? '' : '/'}${route}`; + const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`; + const builtUrl = new URL(strUrl); + + cy.log(`origin: ${builtUrl.href}`); + return builtUrl.href; +}; + +export const getCurlScriptEnvVars = () => ({ + ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'), + ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'), + ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'), + KIBANA_URL: Cypress.config().baseUrl, +}); + +export const postRoleAndUser = (role: ROLES) => { + const env = getCurlScriptEnvVars(); + const detectionsRoleScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`; + const detectionsRoleJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`; + const detectionsUserScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_user.sh`; + const detectionsUserJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_user.json`; + + // post the role + cy.exec(`bash ${detectionsRoleScriptPath} ${detectionsRoleJsonPath}`, { + env, + }); + + // post the user associated with the role to elasticsearch + cy.exec(`bash ${detectionsUserScriptPath} ${detectionsUserJsonPath}`, { + env, + }); +}; + +export const deleteRoleAndUser = (role: ROLES) => { + const env = getCurlScriptEnvVars(); + const detectionsUserDeleteScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`; + + // delete the role + cy.exec(`bash ${detectionsUserDeleteScriptPath}`, { + env, + }); +}; + +export const loginWithUser = (user: User) => { + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: user.username, + password: user.password, + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: constructUrlWithUser(user, LOGIN_API_ENDPOINT), + }); +}; + +export const loginWithRole = async (role: ROLES) => { + postRoleAndUser(role); + const theUrl = Url.format({ + auth: `${role}:changeme`, + username: role, + password: 'changeme', + protocol: Cypress.env('protocol'), + hostname: Cypress.env('hostname'), + port: Cypress.env('configport'), + } as UrlObject); + cy.log(`origin: ${theUrl}`); + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: role, + password: 'changeme', + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: getUrlWithRoute(role, LOGIN_API_ENDPOINT), + }); +}; + +/** + * Authenticates with Kibana using, if specified, credentials specified by + * environment variables. The credentials in `kibana.dev.yml` will be used + * for authentication when the environment variables are unset. + * + * To speed the execution of tests, prefer this non-interactive authentication, + * which is faster than authentication via Kibana's interactive login page. + */ +export const login = (role?: ROLES) => { + if (role != null) { + loginWithRole(role); + } else if (credentialsProvidedByEnvironment()) { + loginViaEnvironmentCredentials(); + } else { + loginViaConfig(); + } +}; + +/** + * Returns `true` if the credentials used to login to Kibana are provided + * via environment variables + */ +const credentialsProvidedByEnvironment = (): boolean => + Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null; + +/** + * Authenticates with Kibana by reading credentials from the + * `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD` + * environment variables, and POSTing the username and password directly to + * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). + */ +const loginViaEnvironmentCredentials = () => { + cy.log( + `Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables` + ); + + // programmatically authenticate without interacting with the Kibana login page + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: Cypress.env(ELASTICSEARCH_USERNAME), + password: Cypress.env(ELASTICSEARCH_PASSWORD), + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-env' }, + method: 'POST', + url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, + }); +}; + +/** + * Authenticates with Kibana by reading credentials from the + * `kibana.dev.yml` file and POSTing the username and password directly to + * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). + */ +const loginViaConfig = () => { + cy.log( + `Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\`` + ); + + // read the login details from `kibana.dev.yaml` + cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => { + const config = yaml.safeLoad(kibanaDevYml); + + // programmatically authenticate without interacting with the Kibana login page + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: config.elasticsearch.username, + password: config.elasticsearch.password, + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, + }); + }); +}; + +/** + * Get the configured auth details that were used to spawn cypress + * + * @returns the default Elasticsearch username and password for this environment + */ +export const getEnvAuth = (): User => { + if (credentialsProvidedByEnvironment()) { + return { + username: Cypress.env(ELASTICSEARCH_USERNAME), + password: Cypress.env(ELASTICSEARCH_PASSWORD), + }; + } else { + let user: User = { username: '', password: '' }; + cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => { + const config = yaml.safeLoad(devYml); + user = { username: config.elasticsearch.username, password: config.elasticsearch.password }; + }); + + return user; + } +}; + +/** + * Authenticates with Kibana, visits the specified `url`, and waits for the + * Kibana global nav to be displayed before continuing + */ +export const loginAndWaitForPage = ( + url: string, + role?: ROLES, + onBeforeLoadCallback?: (win: Cypress.AUTWindow) => void +) => { + login(role); + cy.visit( + `${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))`, + { + onBeforeLoad(win) { + if (onBeforeLoadCallback) { + onBeforeLoadCallback(win); + } + }, + } + ); + cy.get('[data-test-subj="headerGlobalNav"]'); +}; +export const waitForPage = (url: string) => { + cy.visit( + `${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))` + ); + cy.get('[data-test-subj="headerGlobalNav"]'); +}; + +export const loginAndWaitForPageWithoutDateRange = (url: string, role?: ROLES) => { + login(role); + cy.visit(role ? getUrlWithRoute(role, url) : url); + cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); +}; + +export const loginWithUserAndWaitForPage = (url: string, user: User) => { + loginWithUser(user); + cy.visit(constructUrlWithUser(user, url)); + cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); +}; + +export const loginAndWaitForHostDetailsPage = (hostName = 'suricata-iowa') => { + loginAndWaitForPage(hostDetailsUrl(hostName)); + cy.get('[data-test-subj="loading-spinner"]', { timeout: 12000 }).should('not.exist'); +}; + +export const waitForPageWithoutDateRange = (url: string, role?: ROLES) => { + cy.visit(role ? getUrlWithRoute(role, url) : url); + cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); +}; + +export const logout = () => { + cy.visit(LOGOUT_URL); +}; diff --git a/x-pack/plugins/fleet/cypress/tasks/navigation.ts b/x-pack/plugins/fleet/cypress/tasks/navigation.ts index a2dd131b647a6..741a2cf761e8c 100644 --- a/x-pack/plugins/fleet/cypress/tasks/navigation.ts +++ b/x-pack/plugins/fleet/cypress/tasks/navigation.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { TOGGLE_NAVIGATION_BTN } from '../screens/navigation'; - export const INTEGRATIONS = 'app/integrations#/'; export const FLEET = 'app/fleet/'; +export const LOGIN_API_ENDPOINT = '/internal/security/login'; +export const LOGOUT_API_ENDPOINT = '/api/security/logout'; +export const LOGIN_URL = '/login'; +export const LOGOUT_URL = '/logout'; + +export const hostDetailsUrl = (hostName: string) => + `/app/security/hosts/${hostName}/authentications`; export const navigateTo = (page: string) => { cy.visit(page); }; - -export const openNavigationFlyout = () => { - cy.get(TOGGLE_NAVIGATION_BTN).click(); -}; diff --git a/x-pack/plugins/fleet/cypress/tasks/privileges.ts b/x-pack/plugins/fleet/cypress/tasks/privileges.ts new file mode 100644 index 0000000000000..edcc8e3749689 --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tasks/privileges.ts @@ -0,0 +1,232 @@ +/* + * 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 { constructUrlWithUser, getEnvAuth } from './login'; + +interface User { + username: string; + password: string; + description?: string; + roles: string[]; +} + +interface UserInfo { + username: string; + full_name: string; + email: string; +} + +interface FeaturesPrivileges { + [featureId: string]: string[]; +} + +interface ElasticsearchIndices { + names: string[]; + privileges: string[]; +} + +interface ElasticSearchPrivilege { + cluster?: string[]; + indices?: ElasticsearchIndices[]; +} + +interface KibanaPrivilege { + spaces: string[]; + base?: string[]; + feature?: FeaturesPrivileges; +} + +interface Role { + name: string; + privileges: { + elasticsearch?: ElasticSearchPrivilege; + kibana?: KibanaPrivilege[]; + }; +} + +// Create roles with allowed combinations of Fleet and Integrations +export const FleetAllIntegrAllRole: Role = { + name: 'fleet_all_int_all_role', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + fleetv2: ['all'], + fleet: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + +export const FleetAllIntegrAllUser: User = { + username: 'fleet_all_int_all_user', + password: 'password', + roles: [FleetAllIntegrAllRole.name], +}; + +export const FleetAllIntegrReadRole: Role = { + name: 'fleet_all_int_read_user', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + fleetv2: ['all'], + fleet: ['read'], + }, + spaces: ['*'], + }, + ], + }, +}; +export const FleetAllIntegrReadUser: User = { + username: 'fleet_all_int_read_user', + password: 'password', + roles: [FleetAllIntegrReadRole.name], +}; +export const FleetAllIntegrNoneRole: Role = { + name: 'fleet_all_int_none_role', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + fleetv2: ['all'], + fleet: ['none'], + }, + spaces: ['*'], + }, + ], + }, +}; +export const FleetAllIntegrNoneUser: User = { + username: 'fleet_all_int_none_user', + password: 'password', + roles: [FleetAllIntegrNoneRole.name], +}; +export const FleetNoneIntegrAllRole: Role = { + name: 'fleet_none_int_all_role', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + fleetv2: ['none'], + fleet: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; +export const FleetNoneIntegrAllUser: User = { + username: 'fleet_none_int_all_user', + password: 'password', + roles: [FleetNoneIntegrAllRole.name], +}; + +const getUserInfo = (user: User): UserInfo => ({ + username: user.username, + full_name: user.username.replace('_', ' '), + email: `${user.username}@elastic.co`, +}); + +export enum ROLES { + elastic = 'elastic', +} + +export const createUsersAndRoles = (users: User[], roles: Role[]) => { + const envUser = getEnvAuth(); + for (const role of roles) { + cy.log(`Creating role: ${JSON.stringify(role)}`); + cy.request({ + body: role.privileges, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'PUT', + url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), + }) + .its('status') + .should('eql', 204); + } + + for (const user of users) { + const userInfo = getUserInfo(user); + cy.log(`Creating user: ${JSON.stringify(user)}`); + cy.request({ + body: { + username: user.username, + password: user.password, + roles: user.roles, + full_name: userInfo.full_name, + email: userInfo.email, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), + }) + .its('status') + .should('eql', 200); + } +}; + +export const deleteUsersAndRoles = (users: User[], roles: Role[]) => { + const envUser = getEnvAuth(); + for (const user of users) { + cy.log(`Deleting user: ${JSON.stringify(user)}`); + cy.request({ + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'DELETE', + url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), + failOnStatusCode: false, + }) + .its('status') + .should('oneOf', [204, 404]); + } + + for (const role of roles) { + cy.log(`Deleting role: ${JSON.stringify(role)}`); + cy.request({ + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'DELETE', + url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), + failOnStatusCode: false, + }) + .its('status') + .should('oneOf', [204, 404]); + } +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 9799561970e48..29a491fe0c932 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -88,7 +88,7 @@ const PermissionsError: React.FunctionComponent<{ error: string }> = memo(({ err +

= memo(({ err

} body={ -

+

= ({ { +

{

} body={ -

+

{ setIsActionsMenuOpen(false); @@ -75,6 +76,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ ] : []), , { return ( { From 49ec2c4459edb124da1b793b69e8d3315249e434 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Wed, 9 Feb 2022 13:03:23 +0100 Subject: [PATCH 03/85] Add github action for labeling Fleet issues for QA team (#124949) --- .github/workflows/label-qa-fixed-in.yml | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/label-qa-fixed-in.yml diff --git a/.github/workflows/label-qa-fixed-in.yml b/.github/workflows/label-qa-fixed-in.yml new file mode 100644 index 0000000000000..e1dafa061f623 --- /dev/null +++ b/.github/workflows/label-qa-fixed-in.yml @@ -0,0 +1,78 @@ +name: Add QA labels to Fleet issues +on: + pull_request: + types: + - closed + +jobs: + fetch_issues_to_label: + runs-on: ubuntu-latest + # Only run on PRs that were merged for the Fleet team + if: | + github.event.pull_request.merged_at && + contains(github.event.pull_request.labels.*.name, 'Team:Fleet') + outputs: + matrix: ${{ steps.issues_to_label.outputs.value }} + label_ids: ${{ steps.label_ids.outputs.value }} + steps: + - uses: octokit/graphql-action@v2.x + id: closing_issues + with: + query: | + query closingIssueNumbersQuery($prnumber: Int!) { + repository(owner: "elastic", name: "kibana") { + pullRequest(number: $prnumber) { + closingIssuesReferences(first: 10) { + nodes { + id + labels(first: 20) { + nodes { + id + name + } + } + } + } + } + } + } + prnumber: ${{ github.event.number }} + token: ${{ secrets.GITHUB_TOKEN }} + - uses: sergeysova/jq-action@v2 + id: issues_to_label + with: + # Map to the issues' node id + cmd: echo $CLOSING_ISSUES | jq -c '.repository.pullRequest.closingIssuesReferences.nodes | map(.id)' + multiline: true + env: + CLOSING_ISSUES: ${{ steps.closing_issues.outputs.data }} + - uses: sergeysova/jq-action@v2 + id: label_ids + with: + # Get list of version labels on pull request and map to label's node id, append 'QA:Ready For Testing' id ("MDU6TGFiZWwyNTQ1NjcwOTI4") + cmd: echo $PR_LABELS | jq -c 'map(select(.name | test("v[0-9]+\\.[0-9]+\\.[0-9]+")) | .node_id) + ["MDU6TGFiZWwyNTQ1NjcwOTI4"]' + multiline: true + env: + PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }} + + label_issues: + needs: fetch_issues_to_label + runs-on: ubuntu-latest + # For each issue closed by the PR run this job + strategy: + matrix: + issueNodeId: ${{ fromJSON(needs.fetch_issues_to_label.outputs.matrix) }} + name: Label issue ${{ matrix.issueNodeId }} + steps: + - uses: octokit/graphql-action@v2.x + id: add_labels_to_closed_issue + with: + query: | + mutation add_label($issueid:String!, $labelids:[String!]!) { + addLabelsToLabelable(input: {labelableId: $issueid, labelIds: $labelids}) { + clientMutationId + } + } + issueid: ${{ matrix.issueNodeId }} + labelids: ${{ needs.fetch_issues_to_label.outputs.label_ids }} + token: ${{ secrets.GITHUB_TOKEN }} From ed36a1322bc42dc29005bce36490713ce7029cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 9 Feb 2022 13:15:06 +0100 Subject: [PATCH 04/85] [Unified observability] Remove uptime team as codeowners from exploratory exploratory view (#125053) --- .github/CODEOWNERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3f36f4b67e56b..a8619643d1b2e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -138,13 +138,11 @@ # Uptime /x-pack/plugins/uptime @elastic/uptime /x-pack/plugins/ux @elastic/uptime -/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime /x-pack/test/functional_with_es_ssl/apps/uptime @elastic/uptime /x-pack/test/functional/apps/uptime @elastic/uptime /x-pack/test/functional/es_archives/uptime @elastic/uptime /x-pack/test/functional/services/uptime @elastic/uptime /x-pack/test/api_integration/apis/uptime @elastic/uptime -/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime # Client Side Monitoring / Uptime (lives in APM directories but owned by Uptime) /x-pack/plugins/apm/public/application/uxApp.tsx @elastic/uptime From 939bc3d01e8a19df738859bc27b9654486efa02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Wed, 9 Feb 2022 13:26:30 +0100 Subject: [PATCH 05/85] [Security Solution][Endpoint] Unify remove artifact from policy message and fix back buttons (#124936) * Updates delete messages for event filters and host isolation exceptions list * Fixes back buttons when click on view full details. Open create dialog by default from artifacts policy view * Fixes ts checks --- .../policy_event_filters_delete_modal.test.tsx | 1 + .../policy_event_filters_delete_modal.tsx | 6 ++++-- .../policy_event_filters_empty_unexisting.tsx | 2 +- .../use_policy_event_filters_empty_hooks.ts | 12 +++++++++--- .../list/policy_event_filters_list.tsx | 5 ++++- .../components/delete_modal.test.tsx | 1 + .../components/delete_modal.tsx | 7 +++++-- .../components/empty_unexisting.tsx | 2 +- .../components/list.test.tsx | 2 +- .../components/list.tsx | 16 +++++++++++++--- ...licy_host_isolation_exceptions_empty_hooks.ts | 12 +++++++++--- .../host_isolation_exceptions_tab.tsx | 2 +- .../policy_trusted_apps_empty_unexisting.tsx | 2 +- .../empty/use_policy_trusted_apps_empty_hooks.ts | 12 +++++++++--- .../layout/policy_trusted_apps_layout.tsx | 2 +- .../list/policy_trusted_apps_list.test.tsx | 2 +- .../list/policy_trusted_apps_list.tsx | 11 +++++++---- 17 files changed, 69 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx index 20522e35e8983..2e00dab303007 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx @@ -39,6 +39,7 @@ describe('Policy details event filter delete modal', () => { renderResult = mockedContext.render( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx index eca26a0026dd1..bfa2f09ab9773 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx @@ -16,10 +16,12 @@ import { useBulkUpdateEventFilters } from '../hooks'; export const PolicyEventFiltersDeleteModal = ({ policyId, + policyName, exception, onCancel, }: { policyId: string; + policyName: string; exception: ExceptionListItemSchema; onCancel: () => void; }) => { @@ -36,8 +38,8 @@ export const PolicyEventFiltersDeleteModal = ({ text: i18n.translate( 'xpack.securitySolution.endpoint.policy.eventFilters.list.removeDialog.successToastText', { - defaultMessage: '"{exception}" has been removed from policy', - values: { exception: exception.name }, + defaultMessage: '"{eventFilterName}" has been removed from {policyName} policy', + values: { eventFilterName: exception.name, policyName }, } ), }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/policy_event_filters_empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/policy_event_filters_empty_unexisting.tsx index c4b9e778664ef..7976fc8a566da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/policy_event_filters_empty_unexisting.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/policy_event_filters_empty_unexisting.tsx @@ -16,7 +16,7 @@ interface CommonProps { } export const PolicyEventFiltersEmptyUnexisting = memo(({ policyId, policyName }) => { - const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName); + const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName, { show: 'create' }); return ( { +export const useGetLinkTo = ( + policyId: string, + policyName: string, + location?: Partial +) => { const { getAppUrl } = useAppUrl(); const { toRoutePath, toRouteUrl } = useMemo(() => { - const path = getEventFiltersListPath(); + const path = getEventFiltersListPath(location); return { toRoutePath: path, toRouteUrl: getAppUrl({ path }), }; - }, [getAppUrl]); + }, [getAppUrl, location]); const policyEventFiltersPath = useMemo(() => getPolicyEventFiltersPath(policyId), [policyId]); const policyEventFilterRouteState = useMemo(() => { @@ -55,5 +60,6 @@ export const useGetLinkTo = (policyId: string, policyName: string) => { return { onClickHandler, toRouteUrl, + state: policyEventFilterRouteState, }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx index 5ab6f4bfb0eba..63930610f8aa7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx @@ -33,6 +33,7 @@ import { PolicyEventFiltersDeleteModal } from '../delete_modal'; import { isGlobalPolicyEffected } from '../../../../../components/effected_policy_select/utils'; import { getEventFiltersListPath } from '../../../../../common/routing'; import { useUserPrivileges } from '../../../../../../common/components/user_privileges'; +import { useGetLinkTo } from '../empty/use_policy_event_filters_empty_hooks'; interface PolicyEventFiltersListProps { policy: ImmutableObject; @@ -47,6 +48,7 @@ export const PolicyEventFiltersList = React.memo(({ const [exceptionItemToDelete, setExceptionItemToDelete] = useState< ExceptionListItemSchema | undefined >(); + const { state } = useGetLinkTo(policy.id, policy.name); const { data: eventFilters, @@ -116,7 +118,7 @@ export const PolicyEventFiltersList = React.memo(({ ), href: getAppUrl({ appId: APP_UI_ID, path: viewUrlPath }), navigateAppId: APP_UI_ID, - navigateOptions: { path: viewUrlPath }, + navigateOptions: { path: viewUrlPath, state }, 'data-test-subj': 'view-full-details-action', }; const item = artifact as ExceptionListItemSchema; @@ -159,6 +161,7 @@ export const PolicyEventFiltersList = React.memo(({ {exceptionItemToDelete && ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.test.tsx index bd9bfbf5d653d..5e750b5599d71 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.test.tsx @@ -43,6 +43,7 @@ describe('Policy details host isolation exceptions delete modal', () => { (renderResult = mockedContext.render( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.tsx index 655107b8f357d..ad8868bf68346 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.tsx @@ -17,10 +17,12 @@ import { updateOneHostIsolationExceptionItem } from '../../../../host_isolation_ export const PolicyHostIsolationExceptionsDeleteModal = ({ policyId, + policyName, exception, onCancel, }: { policyId: string; + policyName: string; exception: ExceptionListItemSchema; onCancel: () => void; }) => { @@ -51,8 +53,9 @@ export const PolicyHostIsolationExceptionsDeleteModal = ({ text: i18n.translate( 'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.successToastText', { - defaultMessage: '"{exception}" has been removed from policy', - values: { exception: exception.name }, + defaultMessage: + '"{hostIsolationExceptionName}" has been removed from {policyName} policy', + values: { hostIsolationExceptionName: exception.name, policyName }, } ), }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx index 34246caf7c313..94185904ce6cc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx @@ -16,7 +16,7 @@ export const PolicyHostIsolationExceptionsEmptyUnexisting = ({ }: { policy: PolicyData; }) => { - const { onClickHandler, toRouteUrl } = useGetLinkTo(policy.id, policy.name); + const { onClickHandler, toRouteUrl } = useGetLinkTo(policy.id, policy.name, { show: 'create' }); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx index 4ad3919fcc563..17e3ace9a6410 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx @@ -52,7 +52,7 @@ describe('Policy details host isolation exceptions tab', () => { ({ history } = mockedContext); render = () => (renderResult = mockedContext.render( - + )); act(() => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx index feee5491da314..3b5244aad30da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx @@ -33,14 +33,23 @@ import { getCurrentArtifactsLocation } from '../../../store/policy_details/selec import { usePolicyDetailsSelector } from '../../policy_hooks'; import { PolicyHostIsolationExceptionsDeleteModal } from './delete_modal'; import { useFetchHostIsolationExceptionsList } from '../../../../host_isolation_exceptions/view/hooks'; - -export const PolicyHostIsolationExceptionsList = ({ policyId }: { policyId: string }) => { +import { useGetLinkTo } from './use_policy_host_isolation_exceptions_empty_hooks'; + +export const PolicyHostIsolationExceptionsList = ({ + policyId, + policyName, +}: { + policyId: string; + policyName: string; +}) => { const history = useHistory(); const { getAppUrl } = useAppUrl(); const privileges = useUserPrivileges().endpointPrivileges; const location = usePolicyDetailsSelector(getCurrentArtifactsLocation); + const { state } = useGetLinkTo(policyId, policyName); + // load the list of policies> const policiesRequest = useGetEndpointSpecificPolicies(); const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation); @@ -127,7 +136,7 @@ export const PolicyHostIsolationExceptionsList = ({ policyId }: { policyId: stri ), href: getAppUrl({ appId: APP_UI_ID, path: viewUrlPath }), navigateAppId: APP_UI_ID, - navigateOptions: { path: viewUrlPath }, + navigateOptions: { path: viewUrlPath, state }, 'data-test-subj': 'view-full-details-action', }; @@ -172,6 +181,7 @@ export const PolicyHostIsolationExceptionsList = ({ policyId }: { policyId: stri {exceptionItemToDelete ? ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts index 6aba11a4499ea..494dfd9a7ae08 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts @@ -14,16 +14,21 @@ import { getHostIsolationExceptionsListPath, } from '../../../../../common/routing'; import { APP_UI_ID } from '../../../../../../../common/constants'; +import { HostIsolationExceptionsPageLocation } from '../../../../host_isolation_exceptions/types'; -export const useGetLinkTo = (policyId: string, policyName: string) => { +export const useGetLinkTo = ( + policyId: string, + policyName: string, + location?: Partial +) => { const { getAppUrl } = useAppUrl(); const { toRoutePath, toRouteUrl } = useMemo(() => { - const path = getHostIsolationExceptionsListPath(); + const path = getHostIsolationExceptionsListPath(location); return { toRoutePath: path, toRouteUrl: getAppUrl({ path }), }; - }, [getAppUrl]); + }, [getAppUrl, location]); const policyHostIsolationExceptionsPath = useMemo( () => getPolicyHostIsolationExceptionsPath(policyId), @@ -61,5 +66,6 @@ export const useGetLinkTo = (policyId: string, policyName: string) => { return { onClickHandler, toRouteUrl, + state: policyHostIsolationExceptionsRouteState, }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx index 1836997e74c16..f9ec756a8be76 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx @@ -181,7 +181,7 @@ export const PolicyHostIsolationExceptionsTab = ({ policy }: { policy: PolicyDat color="transparent" borderRadius="none" > - + ) : ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unexisting.tsx index 1c96398b8347e..1fe834a9fce46 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unexisting.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unexisting.tsx @@ -16,7 +16,7 @@ interface CommonProps { } export const PolicyTrustedAppsEmptyUnexisting = memo(({ policyId, policyName }) => { - const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName); + const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName, { show: 'create' }); return ( { +export const useGetLinkTo = ( + policyId: string, + policyName: string, + location?: Partial +) => { const { getAppUrl } = useAppUrl(); const { toRoutePath, toRouteUrl } = useMemo(() => { - const path = getTrustedAppsListPath(); + const path = getTrustedAppsListPath(location); return { toRoutePath: path, toRouteUrl: getAppUrl({ path }), }; - }, [getAppUrl]); + }, [getAppUrl, location]); const policyTrustedAppsPath = useMemo(() => getPolicyTrustedAppsPath(policyId), [policyId]); const policyTrustedAppRouteState = useMemo(() => { @@ -55,5 +60,6 @@ export const useGetLinkTo = (policyId: string, policyName: string) => { return { onClickHandler, toRouteUrl, + state: policyTrustedAppRouteState, }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx index 83fb3663104a6..dd89cca43c10d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx @@ -166,7 +166,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { /> ) ) : displayHeaderAndContent ? ( - + ) : ( )} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx index 67b435f4873a1..da304adc2db44 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx @@ -87,7 +87,7 @@ describe('when rendering the PolicyTrustedAppsList', () => { mockedApis = policyDetailsPageAllApiHttpMocks(appTestContext.coreStart.http); waitForAction = appTestContext.middlewareSpy.waitForAction; - componentRenderProps = {}; + componentRenderProps = { policyId: '9f08b220-342d-4c8d-8971-4cf96adcac29', policyName: 'test' }; render = async (waitForLoadedState: boolean = true) => { appTestContext.history.push( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx index fa4d4e40b3e52..54dabf87f474b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx @@ -22,7 +22,6 @@ import { getPolicyTrustedAppsListPagination, getTrustedAppsAllPoliciesById, isPolicyTrustedAppListLoading, - policyIdFromParams, getCurrentPolicyArtifactsFilter, } from '../../../store/policy_details/selectors'; import { @@ -39,21 +38,23 @@ import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/art import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; import { RemoveTrustedAppFromPolicyModal } from './remove_trusted_app_from_policy_modal'; import { useUserPrivileges } from '../../../../../../common/components/user_privileges'; +import { useGetLinkTo } from '../empty/use_policy_trusted_apps_empty_hooks'; const DATA_TEST_SUBJ = 'policyTrustedAppsGrid'; export interface PolicyTrustedAppsListProps { hideTotalShowingLabel?: boolean; + policyId: string; + policyName: string; } export const PolicyTrustedAppsList = memo( - ({ hideTotalShowingLabel = false }) => { + ({ hideTotalShowingLabel = false, policyId, policyName }) => { const getTestId = useTestIdGenerator(DATA_TEST_SUBJ); const toasts = useToasts(); const history = useHistory(); const { getAppUrl } = useAppUrl(); const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges; - const policyId = usePolicyDetailsSelector(policyIdFromParams); const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading); const defaultFilter = usePolicyDetailsSelector(getCurrentPolicyArtifactsFilter); const trustedAppItems = usePolicyDetailsSelector(getPolicyTrustedAppList); @@ -62,6 +63,7 @@ export const PolicyTrustedAppsList = memo( const allPoliciesById = usePolicyDetailsSelector(getTrustedAppsAllPoliciesById); const trustedAppsApiError = usePolicyDetailsSelector(getPolicyTrustedAppListError); const navigateCallback = usePolicyDetailsNavigateCallback(); + const { state } = useGetLinkTo(policyId, policyName); const [isCardExpanded, setCardExpanded] = useState>({}); const [trustedAppsForRemoval, setTrustedAppsForRemoval] = useState([]); @@ -152,7 +154,7 @@ export const PolicyTrustedAppsList = memo( ), href: getAppUrl({ appId: APP_UI_ID, path: viewUrlPath }), navigateAppId: APP_UI_ID, - navigateOptions: { path: viewUrlPath }, + navigateOptions: { path: viewUrlPath, state }, 'data-test-subj': getTestId('viewFullDetailsAction'), }, ]; @@ -201,6 +203,7 @@ export const PolicyTrustedAppsList = memo( isCardExpanded, trustedAppItems, canCreateArtifactsByPolicy, + state, ]); const provideCardProps = useCallback['cardComponentProps']>( From c5454536ad8d9e60544e02b0438c1c0488371e22 Mon Sep 17 00:00:00 2001 From: Michael Dokolin Date: Wed, 9 Feb 2022 14:45:50 +0100 Subject: [PATCH 06/85] [Screenshotting] Limit number of concurrently running Chromium instances (#124720) * Convert screenshots capturing function into a service * Update screenshots service to limit the number of concurrent sessions --- .../server/config/schema.test.ts | 2 + .../screenshotting/server/config/schema.ts | 1 + x-pack/plugins/screenshotting/server/mock.ts | 3 +- .../plugins/screenshotting/server/plugin.ts | 19 ++- .../server/screenshots/index.test.ts | 38 ++++-- .../server/screenshots/index.ts | 121 ++++++++++-------- .../screenshotting/server/screenshots/mock.ts | 8 +- .../server/screenshots/semaphore.test.ts | 73 +++++++++++ .../server/screenshots/semaphore.ts | 70 ++++++++++ 9 files changed, 255 insertions(+), 80 deletions(-) create mode 100644 x-pack/plugins/screenshotting/server/screenshots/semaphore.test.ts create mode 100644 x-pack/plugins/screenshotting/server/screenshots/semaphore.ts diff --git a/x-pack/plugins/screenshotting/server/config/schema.test.ts b/x-pack/plugins/screenshotting/server/config/schema.test.ts index 9180f0d180d5f..e25791b0ebad9 100644 --- a/x-pack/plugins/screenshotting/server/config/schema.test.ts +++ b/x-pack/plugins/screenshotting/server/config/schema.test.ts @@ -54,6 +54,7 @@ describe('ConfigSchema', () => { }, ], }, + "poolSize": 1, } `); }); @@ -105,6 +106,7 @@ describe('ConfigSchema', () => { }, ], }, + "poolSize": 1, } `); }); diff --git a/x-pack/plugins/screenshotting/server/config/schema.ts b/x-pack/plugins/screenshotting/server/config/schema.ts index bcf2fa9feead9..7711ffe1ea8e8 100644 --- a/x-pack/plugins/screenshotting/server/config/schema.ts +++ b/x-pack/plugins/screenshotting/server/config/schema.ts @@ -67,6 +67,7 @@ export const ConfigSchema = schema.object({ }), }), }), + poolSize: schema.number({ defaultValue: 1, min: 1 }), }); export type ConfigType = TypeOf; diff --git a/x-pack/plugins/screenshotting/server/mock.ts b/x-pack/plugins/screenshotting/server/mock.ts index 49d69521f2c19..f7d3421ca0f3e 100644 --- a/x-pack/plugins/screenshotting/server/mock.ts +++ b/x-pack/plugins/screenshotting/server/mock.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { Logger } from 'src/core/server'; import { createMockBrowserDriverFactory } from './browsers/mock'; import { createMockScreenshots } from './screenshots/mock'; import type { ScreenshottingStart } from '.'; @@ -17,6 +16,6 @@ export function createMockScreenshottingStart(): jest.Mocked getScreenshots(driver, {} as Logger, options)), + getScreenshots: jest.fn((options) => getScreenshots(options)), }; } diff --git a/x-pack/plugins/screenshotting/server/plugin.ts b/x-pack/plugins/screenshotting/server/plugin.ts index c7870322ccdfc..138193815debe 100755 --- a/x-pack/plugins/screenshotting/server/plugin.ts +++ b/x-pack/plugins/screenshotting/server/plugin.ts @@ -17,7 +17,7 @@ import type { import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; import { ChromiumArchivePaths, HeadlessChromiumDriverFactory, install } from './browsers'; import { ConfigType, createConfig } from './config'; -import { getScreenshots, ScreenshotOptions } from './screenshots'; +import { Screenshots } from './screenshots'; import { getChromiumPackage } from './utils'; interface SetupDeps { @@ -39,7 +39,7 @@ export interface ScreenshottingStart { * @param options Screenshots session options. * @returns Observable with screenshotting results. */ - getScreenshots(options: ScreenshotOptions): ReturnType; + getScreenshots: Screenshots['getScreenshots']; } export class ScreenshottingPlugin implements Plugin { @@ -47,6 +47,7 @@ export class ScreenshottingPlugin implements Plugin; + private screenshots!: Promise; constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); @@ -65,12 +66,20 @@ export class ScreenshottingPlugin implements Plugin { this.logger.error('Error in screenshotting setup, it may not function properly.'); this.logger.error(error); }); + this.screenshots = (async () => { + const browserDriverFactory = await this.browserDriverFactory; + const logger = this.logger.get('screenshot'); + + return new Screenshots(browserDriverFactory, logger, this.config); + })(); + // Already handled in `browserDriverFactory` + this.screenshots.catch(() => {}); + return {}; } @@ -79,8 +88,8 @@ export class ScreenshottingPlugin implements Plugin from(this.browserDriverFactory).pipe(switchMap((factory) => factory.diagnose())), getScreenshots: (options) => - from(this.browserDriverFactory).pipe( - switchMap((factory) => getScreenshots(factory, this.logger.get('screenshot'), options)) + from(this.screenshots).pipe( + switchMap((screenshots) => screenshots.getScreenshots(options)) ), }; } diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index 858e1ae9d6093..eae7a6a5bc031 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -7,12 +7,13 @@ import { of, throwError, NEVER } from 'rxjs'; import type { Logger } from 'src/core/server'; +import type { ConfigType } from '../config'; import { createMockBrowserDriver, createMockBrowserDriverFactory } from '../browsers/mock'; import type { HeadlessChromiumDriverFactory } from '../browsers'; import * as Layouts from '../layouts/create_layout'; import { createMockLayout } from '../layouts/mock'; import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; -import { getScreenshots, ScreenshotOptions } from '.'; +import { Screenshots, ScreenshotOptions } from '.'; /* * Tests @@ -23,6 +24,7 @@ describe('Screenshot Observable Pipeline', () => { let layout: ReturnType; let logger: jest.Mocked; let options: ScreenshotOptions; + let screenshots: Screenshots; beforeEach(async () => { driver = createMockBrowserDriver(); @@ -45,6 +47,7 @@ describe('Screenshot Observable Pipeline', () => { }, urls: ['/welcome/home/start/index.htm'], } as unknown as typeof options; + screenshots = new Screenshots(driverFactory, logger, { poolSize: 1 } as ConfigType); jest.spyOn(Layouts, 'createLayout').mockReturnValue(layout); @@ -56,7 +59,7 @@ describe('Screenshot Observable Pipeline', () => { }); it('pipelines a single url into screenshot and timeRange', async () => { - const result = await getScreenshots(driverFactory, logger, options).toPromise(); + const result = await screenshots.getScreenshots(options).toPromise(); expect(result).toHaveProperty('results'); expect(result.results).toMatchInlineSnapshot(` @@ -112,10 +115,12 @@ describe('Screenshot Observable Pipeline', () => { it('pipelines multiple urls into', async () => { driver.screenshot.mockResolvedValue(Buffer.from('some screenshots')); - const result = await getScreenshots(driverFactory, logger, { - ...options, - urls: ['/welcome/home/start/index2.htm', '/welcome/home/start/index.php3?page=./home.php'], - }).toPromise(); + const result = await screenshots + .getScreenshots({ + ...options, + urls: ['/welcome/home/start/index2.htm', '/welcome/home/start/index.php3?page=./home.php'], + }) + .toPromise(); expect(result).toHaveProperty('results'); expect(result.results).toMatchInlineSnapshot(` @@ -245,10 +250,15 @@ describe('Screenshot Observable Pipeline', () => { driver.waitForSelector.mockImplementation((selectorArg: string) => { throw new Error('Mock error!'); }); - const result = await getScreenshots(driverFactory, logger, { - ...options, - urls: ['/welcome/home/start/index2.htm', '/welcome/home/start/index.php3?page=./home.php3'], - }).toPromise(); + const result = await screenshots + .getScreenshots({ + ...options, + urls: [ + '/welcome/home/start/index2.htm', + '/welcome/home/start/index.php3?page=./home.php3', + ], + }) + .toPromise(); expect(result).toHaveProperty('results'); expect(result.results).toMatchInlineSnapshot(` @@ -351,9 +361,9 @@ describe('Screenshot Observable Pipeline', () => { }) ); - await expect( - getScreenshots(driverFactory, logger, options).toPromise() - ).rejects.toMatchInlineSnapshot(`"Instant timeout has fired!"`); + await expect(screenshots.getScreenshots(options).toPromise()).rejects.toMatchInlineSnapshot( + `"Instant timeout has fired!"` + ); }); it(`uses defaults for element positions and size when Kibana page is not ready`, async () => { @@ -362,7 +372,7 @@ describe('Screenshot Observable Pipeline', () => { ); layout.getViewport = () => null; - const result = await getScreenshots(driverFactory, logger, options).toPromise(); + const result = await screenshots.getScreenshots(options).toPromise(); expect(result).toHaveProperty('results'); expect(result.results).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.ts b/x-pack/plugins/screenshotting/server/screenshots/index.ts index d7332217e78a5..a43fd4549e482 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.ts @@ -19,11 +19,13 @@ import { } from 'rxjs/operators'; import type { Logger } from 'src/core/server'; import { LayoutParams } from '../../common'; +import type { ConfigType } from '../config'; import type { HeadlessChromiumDriverFactory, PerformanceMetrics } from '../browsers'; import { createLayout } from '../layouts'; import type { Layout } from '../layouts'; import { ScreenshotObservableHandler } from './observable'; import type { ScreenshotObservableOptions, ScreenshotObservableResult } from './observable'; +import { Semaphore } from './semaphore'; export interface ScreenshotOptions extends ScreenshotObservableOptions { layout: LayoutParams; @@ -51,64 +53,73 @@ const DEFAULT_SETUP_RESULT = { timeRange: null, }; -export function getScreenshots( - browserDriverFactory: HeadlessChromiumDriverFactory, - logger: Logger, - options: ScreenshotOptions -): Observable { - const apmTrans = apm.startTransaction('screenshot-pipeline', 'screenshotting'); - const apmCreateLayout = apmTrans?.startSpan('create-layout', 'setup'); - const layout = createLayout(options.layout); - logger.debug(`Layout: width=${layout.width} height=${layout.height}`); - apmCreateLayout?.end(); +export class Screenshots { + private semaphore: Semaphore; - const apmCreatePage = apmTrans?.startSpan('create-page', 'wait'); - const { - browserTimezone, - timeouts: { openUrl: openUrlTimeout }, - } = options; + constructor( + private readonly browserDriverFactory: HeadlessChromiumDriverFactory, + private readonly logger: Logger, + { poolSize }: ConfigType + ) { + this.semaphore = new Semaphore(poolSize); + } - return browserDriverFactory - .createPage( - { - browserTimezone, - openUrlTimeout, - defaultViewport: { height: layout.height, width: layout.width }, - }, - logger - ) - .pipe( - mergeMap(({ driver, unexpectedExit$, metrics$, close }) => { - apmCreatePage?.end(); - metrics$.subscribe(({ cpu, memory }) => { - apmTrans?.setLabel('cpu', cpu, false); - apmTrans?.setLabel('memory', memory, false); - }); - unexpectedExit$.subscribe({ error: () => apmTrans?.end() }); + getScreenshots(options: ScreenshotOptions): Observable { + const apmTrans = apm.startTransaction('screenshot-pipeline', 'screenshotting'); + const apmCreateLayout = apmTrans?.startSpan('create-layout', 'setup'); + const layout = createLayout(options.layout); + this.logger.debug(`Layout: width=${layout.width} height=${layout.height}`); + apmCreateLayout?.end(); - const screen = new ScreenshotObservableHandler(driver, logger, layout, options); + const apmCreatePage = apmTrans?.startSpan('create-page', 'wait'); + const { + browserTimezone, + timeouts: { openUrl: openUrlTimeout }, + } = options; - return from(options.urls).pipe( - concatMap((url, index) => - screen.setupPage(index, url, apmTrans).pipe( - catchError((error) => { - screen.checkPageIsOpen(); // this fails the job if the browser has closed + return this.browserDriverFactory + .createPage( + { + browserTimezone, + openUrlTimeout, + defaultViewport: { height: layout.height, width: layout.width }, + }, + this.logger + ) + .pipe( + this.semaphore.acquire(), + mergeMap(({ driver, unexpectedExit$, metrics$, close }) => { + apmCreatePage?.end(); + metrics$.subscribe(({ cpu, memory }) => { + apmTrans?.setLabel('cpu', cpu, false); + apmTrans?.setLabel('memory', memory, false); + }); + unexpectedExit$.subscribe({ error: () => apmTrans?.end() }); - logger.error(error); - return of({ ...DEFAULT_SETUP_RESULT, error }); // allow failover screenshot capture - }), - takeUntil(unexpectedExit$), - screen.getScreenshots() - ) - ), - take(options.urls.length), - toArray(), - mergeMap((results) => { - // At this point we no longer need the page, close it. - return close().pipe(mapTo({ layout, metrics$, results })); - }) - ); - }), - first() - ); + const screen = new ScreenshotObservableHandler(driver, this.logger, layout, options); + + return from(options.urls).pipe( + concatMap((url, index) => + screen.setupPage(index, url, apmTrans).pipe( + catchError((error) => { + screen.checkPageIsOpen(); // this fails the job if the browser has closed + + this.logger.error(error); + return of({ ...DEFAULT_SETUP_RESULT, error }); // allow failover screenshot capture + }), + takeUntil(unexpectedExit$), + screen.getScreenshots() + ) + ), + take(options.urls.length), + toArray(), + mergeMap((results) => { + // At this point we no longer need the page, close it. + return close().pipe(mapTo({ layout, metrics$, results })); + }) + ); + }), + first() + ); + } } diff --git a/x-pack/plugins/screenshotting/server/screenshots/mock.ts b/x-pack/plugins/screenshotting/server/screenshots/mock.ts index edef9c9044c9a..c4b5707243136 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/mock.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/mock.ts @@ -7,11 +7,11 @@ import { of, NEVER } from 'rxjs'; import { createMockLayout } from '../layouts/mock'; -import type { getScreenshots, ScreenshotResult } from '.'; +import type { Screenshots, ScreenshotResult } from '.'; -export function createMockScreenshots(): jest.Mocked<{ getScreenshots: typeof getScreenshots }> { +export function createMockScreenshots(): jest.Mocked { return { - getScreenshots: jest.fn((driverFactory, logger, options) => + getScreenshots: jest.fn((options) => of({ layout: createMockLayout(), metrics$: NEVER, @@ -27,5 +27,5 @@ export function createMockScreenshots(): jest.Mocked<{ getScreenshots: typeof ge })), } as ScreenshotResult) ), - }; + } as unknown as jest.Mocked; } diff --git a/x-pack/plugins/screenshotting/server/screenshots/semaphore.test.ts b/x-pack/plugins/screenshotting/server/screenshots/semaphore.test.ts new file mode 100644 index 0000000000000..6d6dd21347974 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/semaphore.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TestScheduler } from 'rxjs/testing'; +import { Semaphore } from './semaphore'; + +describe('Semaphore', () => { + let testScheduler: TestScheduler; + + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => { + return expect(actual).toStrictEqual(expected); + }); + }); + + describe('acquire', () => { + it('should limit the number of concurrently subscribed observables', () => { + testScheduler.run(({ cold, expectObservable }) => { + const semaphore = new Semaphore(2); + const observable1 = cold('500ms a|').pipe(semaphore.acquire()); + const observable2 = cold('500ms b|').pipe(semaphore.acquire()); + const observable3 = cold('500ms c|').pipe(semaphore.acquire()); + + expectObservable(observable1).toBe('500ms a|'); + expectObservable(observable2).toBe('500ms b|'); + expectObservable(observable3).toBe('1001ms c|'); + }); + }); + + it('should release semaphore on unsubscription', () => { + testScheduler.run(({ cold, expectObservable }) => { + const semaphore = new Semaphore(2); + const observable1 = cold('500ms a|').pipe(semaphore.acquire()); + const observable2 = cold('500ms b|').pipe(semaphore.acquire()); + const observable3 = cold('500ms c|').pipe(semaphore.acquire()); + + expectObservable(observable1).toBe('500ms a|'); + expectObservable(observable2, '^ 100ms !').toBe(''); + expectObservable(observable3).toBe('601ms c|'); + }); + }); + + it('should release semaphore on error', () => { + testScheduler.run(({ cold, expectObservable }) => { + const semaphore = new Semaphore(2); + const observable1 = cold('500ms a|').pipe(semaphore.acquire()); + const observable2 = cold('100ms #').pipe(semaphore.acquire()); + const observable3 = cold('500ms c|').pipe(semaphore.acquire()); + + expectObservable(observable1).toBe('500ms a|'); + expectObservable(observable2).toBe('100ms #'); + expectObservable(observable3).toBe('600ms c|'); + }); + }); + + it('should remove from the queue on unsubscription', () => { + testScheduler.run(({ cold, expectObservable }) => { + const semaphore = new Semaphore(1); + const observable1 = cold('500ms a|').pipe(semaphore.acquire()); + const observable2 = cold('500ms b').pipe(semaphore.acquire()); + const observable3 = cold('500ms c|').pipe(semaphore.acquire()); + + expectObservable(observable1).toBe('500ms a|'); + expectObservable(observable2, '^ 100ms !').toBe(''); + expectObservable(observable3).toBe('1001ms c|'); + }); + }); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/screenshots/semaphore.ts b/x-pack/plugins/screenshotting/server/screenshots/semaphore.ts new file mode 100644 index 0000000000000..cdf021da0f63e --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/semaphore.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Observable } from 'rxjs'; +import type { OperatorFunction } from 'rxjs'; +import { finalize } from 'rxjs/operators'; + +type Task = () => void; + +export class Semaphore { + private queue: Task[] = []; + + constructor(private capacity: number) { + this.release = this.release.bind(this); + } + + acquire(): OperatorFunction { + return (inner) => + new Observable((outer) => { + const task = () => { + /** + * outer.remove(cancel); + * + * @todo Uncomment the line above when RxJS is bumped to at least 6.6.3. + * @see https://github.com/ReactiveX/rxjs/pull/5659 + */ + + outer.add(inner.pipe(finalize(this.release)).subscribe(outer)); + }; + const cancel = this.cancel.bind(this, task); + + outer.add(cancel); + this.schedule(task); + }); + } + + protected release(): void { + this.capacity++; + this.next(); + } + + private next() { + if (this.capacity <= 0 || !this.queue.length) { + return; + } + + const task = this.queue.shift()!; + this.capacity--; + + task(); + } + + private schedule(task: Task) { + this.queue.push(task); + this.next(); + } + + private cancel(task: Task) { + const index = this.queue.indexOf(task); + if (index < 0) { + return; + } + + this.queue.splice(index, 1); + } +} From 9790a7e0087838a6c757192039e1f604ee64b765 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 9 Feb 2022 13:49:20 +0000 Subject: [PATCH 07/85] [ML] Adding category definition and category examples api tests (#124710) * [ML] Adding category definition api tests * adding test * adding category examples tests * updating test checks * changes based on review * removing test * correcting mistake where wrong test was removed * correctling text text * fixing test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ml/results/get_category_definition.ts | 174 +++++++++++++++++ .../apis/ml/results/get_category_examples.ts | 178 ++++++++++++++++++ .../api_integration/apis/ml/results/index.ts | 2 + x-pack/test/functional/services/ml/api.ts | 10 +- 4 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 x-pack/test/api_integration/apis/ml/results/get_category_definition.ts create mode 100644 x-pack/test/api_integration/apis/ml/results/get_category_examples.ts diff --git a/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts b/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts new file mode 100644 index 0000000000000..36f0f3946ac66 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; +import { Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + const spacesService = getService('spaces'); + + const jobIdSpace1 = `sample_logs_${Date.now()}`; + const idSpace1 = 'space1'; + const idSpace2 = 'space2'; + + const PARTITION_FIELD_NAME = 'event.dataset'; + const testJobConfig = { + job_id: jobIdSpace1, + groups: ['sample_logs', 'bootstrap', 'categorization'], + description: "count by mlcategory (message) on 'sample logs' dataset with 15m bucket span", + analysis_config: { + bucket_span: '15m', + categorization_field_name: 'message', + per_partition_categorization: { enabled: true, stop_on_warn: true }, + detectors: [ + { + function: 'count', + by_field_name: 'mlcategory', + partition_field_name: PARTITION_FIELD_NAME, + }, + ], + influencers: ['mlcategory'], + }, + analysis_limits: { model_memory_limit: '26MB' }, + data_description: { time_field: '@timestamp', time_format: 'epoch_ms' }, + model_plot_config: { enabled: false, annotations_enabled: true }, + model_snapshot_retention_days: 10, + daily_model_snapshot_retention_after_days: 1, + allow_lazy_open: false, + }; + // @ts-expect-error not full interface + const testDatafeedConfig: Datafeed = { + datafeed_id: `datafeed-${jobIdSpace1}`, + indices: ['ft_module_sample_logs'], + job_id: jobIdSpace1, + query: { bool: { must: [{ match_all: {} }] } }, + }; + + const expectedCategoryDefinition = { + categoryId: '1', + examplesLength: 4, + }; + + async function getCategoryDefinition( + jobId: string, + categoryId: string, + user: USER, + expectedStatusCode: number, + space?: string + ) { + const { body } = await supertest + .post(`${space ? `/s/${space}` : ''}/api/ml/results/category_definition`) + .auth(user, ml.securityCommon.getPasswordForUser(user)) + .set(COMMON_REQUEST_HEADERS) + .send({ jobId, categoryId }) + .expect(expectedStatusCode); + + return body; + } + + describe('get category_definition', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/module_sample_logs'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] }); + await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] }); + + await ml.api.createAndRunAnomalyDetectionLookbackJob( + // @ts-expect-error not full interface + testJobConfig, + testDatafeedConfig, + idSpace1 + ); + }); + + after(async () => { + await spacesService.delete(idSpace1); + await spacesService.delete(idSpace2); + await ml.api.cleanMlIndices(); + }); + + it('should produce the correct category for the job', async () => { + const resp = await getCategoryDefinition( + jobIdSpace1, + expectedCategoryDefinition.categoryId, + USER.ML_POWERUSER, + 200, + idSpace1 + ); + + expect(resp.categoryId).to.eql( + expectedCategoryDefinition.categoryId, + `categoryId should be ${expectedCategoryDefinition.categoryId} (got ${resp.categoryId})` + ); + expect(resp.examples.length).to.eql( + expectedCategoryDefinition.examplesLength, + `examples list length should be ${expectedCategoryDefinition.examplesLength} (got ${resp.examples.length})` + ); + expect(resp.terms.length).to.be.greaterThan( + 0, + `terms string length should be greater than 0 (got ${resp.terms.length})` + ); + expect(resp.regex.length).to.be.greaterThan( + 0, + `regex string length should be greater than 0 (got ${resp.regex.length})` + ); + }); + + it('should not produce the correct category for the job in the wrong space', async () => { + await getCategoryDefinition( + jobIdSpace1, + expectedCategoryDefinition.categoryId, + USER.ML_POWERUSER, + 404, + idSpace2 + ); + }); + + it('should produce the correct category for ml viewer user', async () => { + const resp = await getCategoryDefinition( + jobIdSpace1, + expectedCategoryDefinition.categoryId, + USER.ML_VIEWER, + 200, + idSpace1 + ); + + expect(resp.categoryId).to.eql( + expectedCategoryDefinition.categoryId, + `categoryId should be ${expectedCategoryDefinition.categoryId} (got ${resp.categoryId})` + ); + expect(resp.examples.length).to.eql( + expectedCategoryDefinition.examplesLength, + `examples list length should be ${expectedCategoryDefinition.examplesLength} (got ${resp.examples.length})` + ); + expect(resp.terms.length).to.be.greaterThan( + 0, + `terms string length should be greater than 0 (got ${resp.terms.length})` + ); + expect(resp.regex.length).to.be.greaterThan( + 0, + `regex string length should be greater than 0 (got ${resp.regex.length})` + ); + }); + + it('should not produce the correct category for ml unauthorized user', async () => { + await getCategoryDefinition( + jobIdSpace1, + expectedCategoryDefinition.categoryId, + USER.ML_UNAUTHORIZED, + 403, + idSpace1 + ); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts b/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts new file mode 100644 index 0000000000000..79586daaa4aac --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; +import { Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + const spacesService = getService('spaces'); + + const jobIdSpace1 = `sample_logs_${Date.now()}`; + const idSpace1 = 'space1'; + const idSpace2 = 'space2'; + + const PARTITION_FIELD_NAME = 'event.dataset'; + const testJobConfig = { + job_id: jobIdSpace1, + groups: ['sample_logs', 'bootstrap', 'categorization'], + description: "count by mlcategory (message) on 'sample logs' dataset with 15m bucket span", + analysis_config: { + bucket_span: '15m', + categorization_field_name: 'message', + per_partition_categorization: { enabled: true, stop_on_warn: true }, + detectors: [ + { + function: 'count', + by_field_name: 'mlcategory', + partition_field_name: PARTITION_FIELD_NAME, + }, + ], + influencers: ['mlcategory'], + }, + analysis_limits: { model_memory_limit: '26MB' }, + data_description: { time_field: '@timestamp', time_format: 'epoch_ms' }, + model_plot_config: { enabled: false, annotations_enabled: true }, + model_snapshot_retention_days: 10, + daily_model_snapshot_retention_after_days: 1, + allow_lazy_open: false, + }; + // @ts-expect-error not full interface + const testDatafeedConfig: Datafeed = { + datafeed_id: `datafeed-${jobIdSpace1}`, + indices: ['ft_module_sample_logs'], + job_id: jobIdSpace1, + query: { bool: { must: [{ match_all: {} }] } }, + }; + + const expectedCategoryExamples = { + categoryId: '1', + examplesLength: 3, + }; + + async function getCategoryExamples( + jobId: string, + categoryIds: string[], + maxExamples: number, + user: USER, + expectedStatusCode: number, + space?: string + ) { + const { body } = await supertest + .post(`${space ? `/s/${space}` : ''}/api/ml/results/category_examples`) + .auth(user, ml.securityCommon.getPasswordForUser(user)) + .set(COMMON_REQUEST_HEADERS) + .send({ jobId, categoryIds, maxExamples }) + .expect(expectedStatusCode); + + return body; + } + + describe('get category_examples', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/module_sample_logs'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] }); + await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] }); + + await ml.api.createAndRunAnomalyDetectionLookbackJob( + // @ts-expect-error not full interface + testJobConfig, + testDatafeedConfig, + idSpace1 + ); + }); + + after(async () => { + await spacesService.delete(idSpace1); + await spacesService.delete(idSpace2); + await ml.api.cleanMlIndices(); + }); + + it('should produce the correct 1 example for the job', async () => { + const maxExamples = 1; + const resp = await getCategoryExamples( + jobIdSpace1, + [expectedCategoryExamples.categoryId], + maxExamples, + USER.ML_POWERUSER, + 200, + idSpace1 + ); + + expect(resp[expectedCategoryExamples.categoryId].length).to.eql( + maxExamples, + `response examples length should be ${maxExamples} (got ${ + resp[expectedCategoryExamples.categoryId].length + })` + ); + }); + + it('should produce the correct 3 examples for the job', async () => { + const resp = await getCategoryExamples( + jobIdSpace1, + [expectedCategoryExamples.categoryId], + expectedCategoryExamples.examplesLength, + USER.ML_POWERUSER, + 200, + idSpace1 + ); + + expect(resp[expectedCategoryExamples.categoryId].length).to.eql( + expectedCategoryExamples.examplesLength, + `response examples length should be ${expectedCategoryExamples.examplesLength} (got ${ + resp[expectedCategoryExamples.categoryId].length + })` + ); + }); + + it('should not produce the correct examples for the job in the wrong space', async () => { + await getCategoryExamples( + jobIdSpace1, + [expectedCategoryExamples.categoryId], + expectedCategoryExamples.examplesLength, + USER.ML_POWERUSER, + 404, + idSpace2 + ); + }); + + it('should produce the correct example for the job for the ml viewer user', async () => { + const resp = await getCategoryExamples( + jobIdSpace1, + [expectedCategoryExamples.categoryId], + expectedCategoryExamples.examplesLength, + USER.ML_VIEWER, + 200, + idSpace1 + ); + + expect(resp[expectedCategoryExamples.categoryId].length).to.eql( + expectedCategoryExamples.examplesLength, + `response examples length should be ${expectedCategoryExamples.examplesLength} (got ${ + resp[expectedCategoryExamples.categoryId].length + })` + ); + }); + + it('should not produce the correct example for the job for the ml unauthorized user', async () => { + await getCategoryExamples( + jobIdSpace1, + [expectedCategoryExamples.categoryId], + expectedCategoryExamples.examplesLength, + USER.ML_UNAUTHORIZED, + 403, + idSpace1 + ); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/results/index.ts b/x-pack/test/api_integration/apis/ml/results/index.ts index 83338dcab57cd..575435fa3a720 100644 --- a/x-pack/test/api_integration/apis/ml/results/index.ts +++ b/x-pack/test/api_integration/apis/ml/results/index.ts @@ -12,5 +12,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get_anomalies_table_data')); loadTestFile(require.resolve('./get_categorizer_stats')); loadTestFile(require.resolve('./get_stopped_partitions')); + loadTestFile(require.resolve('./get_category_definition')); + loadTestFile(require.resolve('./get_category_examples')); }); } diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index ebe7f7e84d158..58f0b6d678cc2 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -681,9 +681,13 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> Datafeed stopped.'); }, - async createAndRunAnomalyDetectionLookbackJob(jobConfig: Job, datafeedConfig: Datafeed) { - await this.createAnomalyDetectionJob(jobConfig); - await this.createDatafeed(datafeedConfig); + async createAndRunAnomalyDetectionLookbackJob( + jobConfig: Job, + datafeedConfig: Datafeed, + space?: string + ) { + await this.createAnomalyDetectionJob(jobConfig, space); + await this.createDatafeed(datafeedConfig, space); await this.openAnomalyDetectionJob(jobConfig.job_id); await this.startDatafeed(datafeedConfig.datafeed_id, { start: '0', end: `${Date.now()}` }); await this.waitForDatafeedState(datafeedConfig.datafeed_id, DATAFEED_STATE.STOPPED); From 7dfc395423d100144ed9ca235ca0dfb973f92f93 Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Wed, 9 Feb 2022 06:11:21 -0800 Subject: [PATCH 08/85] Adjusting uptime readonly feature (#125031) * Adjusting uptime readonly feature * Missed another occurrence, good call Xavier --- x-pack/plugins/uptime/server/kibana.index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/server/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts index f5008e3f39da7..461c2ba16366a 100644 --- a/x-pack/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -60,7 +60,7 @@ export const initServerWithKibana = ( catalogue: ['uptime'], api: ['uptime-read', 'uptime-write', 'lists-all'], savedObject: { - all: [umDynamicSettings.name, 'alert', syntheticsMonitorType, syntheticsApiKeyObjectType], + all: [umDynamicSettings.name, syntheticsMonitorType, syntheticsApiKeyObjectType], read: [], }, alerting: { @@ -91,7 +91,7 @@ export const initServerWithKibana = ( catalogue: ['uptime'], api: ['uptime-read', 'lists-read'], savedObject: { - all: ['alert'], + all: [], read: [umDynamicSettings.name, syntheticsMonitorType, syntheticsApiKeyObjectType], }, alerting: { From 691315a27450ccf19b1f67ede5e1ac32451300c5 Mon Sep 17 00:00:00 2001 From: sphilipse <94373878+sphilipse@users.noreply.github.com> Date: Wed, 9 Feb 2022 15:44:40 +0100 Subject: [PATCH 09/85] Fix minor textual errors in Workplace Search (#121853) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../content_sources/components/add_source/save_custom.tsx | 2 +- .../workplace_search/views/content_sources/constants.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index 9dbbcc537fa31..c136f22d91d3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -174,7 +174,7 @@ export const SaveCustom: React.FC = ({

Date: Wed, 9 Feb 2022 08:54:30 -0600 Subject: [PATCH 10/85] [App Search] Add `EuiThemeProvider` to fix crashing bug (#124993) --- .../shared/log_stream/log_stream.test.tsx | 35 ++++++++++--------- .../shared/log_stream/log_stream.tsx | 15 ++++---- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx index a934afb3b0d29..d2dd41e82b2e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx @@ -9,18 +9,17 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { LogStream } from '../../../../../infra/public'; - import { EntSearchLogStream } from './'; describe('EntSearchLogStream', () => { const mockDateNow = jest.spyOn(global.Date, 'now').mockReturnValue(160000000); describe('renders with default props', () => { - const wrapper = shallow(); + /** As a result of the theme provider being added, we have to extract the child component to correctly assert */ + const wrapper = shallow(shallow().prop('children')); - it('renders a LogStream component', () => { - expect(wrapper.type()).toEqual(LogStream); + it('renders a LogStream (wrapped in React.Suspense) component', () => { + expect(wrapper.type()).toEqual(React.Suspense); }); it('renders with the enterprise search log source ID', () => { @@ -36,7 +35,9 @@ describe('EntSearchLogStream', () => { describe('renders custom props', () => { it('overrides the default props', () => { const wrapper = shallow( - + shallow().prop( + 'children' + ) ); expect(wrapper.prop('sourceId')).toEqual('test'); @@ -45,7 +46,7 @@ describe('EntSearchLogStream', () => { }); it('allows passing a custom hoursAgo that modifies the default start timestamp', () => { - const wrapper = shallow(); + const wrapper = shallow(shallow().prop('children')); expect(wrapper.prop('startTimestamp')).toEqual(156400000); expect(wrapper.prop('endTimestamp')).toEqual(160000000); @@ -53,15 +54,17 @@ describe('EntSearchLogStream', () => { it('allows passing any prop that the LogStream component takes', () => { const wrapper = shallow( - + shallow( + + ).prop('children') ); expect(wrapper.prop('height')).toEqual(500); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx index c1f4262881bd2..e826a559451f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx @@ -7,6 +7,7 @@ import React from 'react'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { LogStream, LogStreamProps } from '../../../../../infra/public'; import { LOGS_SOURCE_ID } from '../../../../common/constants'; @@ -37,11 +38,13 @@ export const EntSearchLogStream: React.FC = ({ if (!startTimestamp) startTimestamp = endTimestamp - hoursAgo * 60 * 60 * 1000; return ( - + + + ); }; From 440f5f0d51373b7fa79735879fb853fbda310ed9 Mon Sep 17 00:00:00 2001 From: Faisal Kanout Date: Wed, 9 Feb 2022 18:32:46 +0300 Subject: [PATCH 11/85] [RAC][Logs]Use symbols comparator in logs reason msg (#124727) * Use symbols comparator in logs reason msg * Update i18n keys * Codereview fixes - Use symbols without i18n Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../alerting/logs/log_threshold/types.ts | 31 +++++++++++++++++++ .../log_threshold/reason_formatters.ts | 18 +++++------ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts index dba94d2c8fd93..845f5d928df26 100644 --- a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts +++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts @@ -92,6 +92,37 @@ export const ComparatorToi18nMap = { ), }; +export const ComparatorToi18nSymbolsMap = { + [Comparator.GT]: '>', + [Comparator.GT_OR_EQ]: '≥', + [Comparator.LT]: '<', + [Comparator.LT_OR_EQ]: '≤', + [Comparator.EQ]: '=', + [Comparator.NOT_EQ]: '≠', + [`${Comparator.EQ}:number`]: '=', + [`${Comparator.NOT_EQ}:number`]: '≠', + + // TODO: We could need to update the next messages to use symbols. + [Comparator.MATCH]: i18n.translate('xpack.infra.logs.alerting.comparator.symbol.match', { + defaultMessage: 'matches', + }), + [Comparator.NOT_MATCH]: i18n.translate('xpack.infra.logs.alerting.comparator.symbol.notMatch', { + defaultMessage: 'does not match', + }), + [Comparator.MATCH_PHRASE]: i18n.translate( + 'xpack.infra.logs.alerting.comparator.symbol.matchPhrase', + { + defaultMessage: 'matches phrase', + } + ), + [Comparator.NOT_MATCH_PHRASE]: i18n.translate( + 'xpack.infra.logs.alerting.comparator.symbol.notMatchPhrase', + { + defaultMessage: 'does not match phrase', + } + ), +}; + // Alert parameters // export enum AlertStates { OK, diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts index 25f8ca50e995d..5f43ac01485ed 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { Comparator, - ComparatorToi18nMap, + ComparatorToi18nSymbolsMap, TimeUnit, } from '../../../../common/alerting/logs/log_threshold/types'; @@ -23,11 +23,11 @@ export const getReasonMessageForUngroupedCountAlert = ( ) => i18n.translate('xpack.infra.logs.alerting.threshold.ungroupedCountAlertReasonDescription', { defaultMessage: - '{actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries}} in the last {duration}. Alert when ({translatedComparator} {expectedCount}).', + '{actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries}} in the last {duration}. Alert when {comparator} {expectedCount}.', values: { actualCount, expectedCount, - translatedComparator: ComparatorToi18nMap[comparator], + comparator: ComparatorToi18nSymbolsMap[comparator], duration: formatDurationFromTimeUnitChar(timeSize, timeUnit as TimeUnitChar), }, }); @@ -42,12 +42,12 @@ export const getReasonMessageForGroupedCountAlert = ( ) => i18n.translate('xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription', { defaultMessage: - '{actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries}} in the last {duration} for {groupName}. Alert when ({translatedComparator} {expectedCount}).', + '{actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries}} in the last {duration} for {groupName}. Alert when {comparator} {expectedCount}.', values: { actualCount, expectedCount, groupName, - translatedComparator: ComparatorToi18nMap[comparator], + comparator: ComparatorToi18nSymbolsMap[comparator], duration: formatDurationFromTimeUnitChar(timeSize, timeUnit as TimeUnitChar), }, }); @@ -61,11 +61,11 @@ export const getReasonMessageForUngroupedRatioAlert = ( ) => i18n.translate('xpack.infra.logs.alerting.threshold.ungroupedRatioAlertReasonDescription', { defaultMessage: - 'The ratio of selected logs is {actualRatio} in the last {duration}. Alert when ({translatedComparator} {expectedRatio}).', + 'The ratio of selected logs is {actualRatio} in the last {duration}. Alert when {comparator} {expectedRatio}.', values: { actualRatio, expectedRatio, - translatedComparator: ComparatorToi18nMap[comparator], + comparator: ComparatorToi18nSymbolsMap[comparator], duration: formatDurationFromTimeUnitChar(timeSize, timeUnit as TimeUnitChar), }, }); @@ -80,12 +80,12 @@ export const getReasonMessageForGroupedRatioAlert = ( ) => i18n.translate('xpack.infra.logs.alerting.threshold.groupedRatioAlertReasonDescription', { defaultMessage: - 'The ratio of selected logs is {actualRatio} in the last {duration} for {groupName}. Alert when ({translatedComparator} {expectedRatio}).', + 'The ratio of selected logs is {actualRatio} in the last {duration} for {groupName}. Alert when {comparator} {expectedRatio}.', values: { actualRatio, expectedRatio, groupName, - translatedComparator: ComparatorToi18nMap[comparator], + comparator: ComparatorToi18nSymbolsMap[comparator], duration: formatDurationFromTimeUnitChar(timeSize, timeUnit as TimeUnitChar), }, }); From 79c861639929e8618590dbacbe4ce1f56fdc6aa2 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 9 Feb 2022 10:51:01 -0500 Subject: [PATCH 12/85] [Fleet] Get package info should not store the whole package (#123509) --- .../fleet/server/routes/epm/handlers.ts | 4 +- .../routes/package_policy/handlers.test.ts | 2 +- .../fleet/server/services/epm/packages/get.ts | 39 + .../server/services/epm/packages/index.ts | 1 + .../epm/__snapshots__/install_by_upload.snap | 712 ------------------ .../fleet_api_integration/apis/epm/get.ts | 8 +- .../apis/epm/install_by_upload.ts | 14 - 7 files changed, 47 insertions(+), 733 deletions(-) delete mode 100644 x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 4953cecbf211d..16f2d2e13e18c 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -43,7 +43,7 @@ import { getCategories, getPackages, getFile, - getPackageInfo, + getPackageInfoFromRegistry, isBulkInstallError, installPackage, removeInstallation, @@ -199,7 +199,7 @@ export const getInfoHandler: FleetRequestHandler< if (pkgVersion && !semverValid(pkgVersion)) { throw new IngestManagerError('Package version is not a valid semver'); } - const res = await getPackageInfo({ + const res = await getPackageInfoFromRegistry({ savedObjectsClient, pkgName, pkgVersion: pkgVersion || '', diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index c4cef2a4d8d3b..a178422e20ee5 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -90,7 +90,7 @@ jest.mock( jest.mock('../../services/epm/packages', () => { return { ensureInstalledPackage: jest.fn(() => Promise.resolve()), - getPackageInfo: jest.fn(() => Promise.resolve()), + getPackageInfoFromRegistry: jest.fn(() => Promise.resolve()), }; }); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 755fafd8fbc7a..a7cbea4d6462a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -98,6 +98,45 @@ export async function getPackageSavedObjects( export const getInstallations = getPackageSavedObjects; +export async function getPackageInfoFromRegistry(options: { + savedObjectsClient: SavedObjectsClientContract; + pkgName: string; + pkgVersion: string; +}): Promise { + const { savedObjectsClient, pkgName, pkgVersion } = options; + const [savedObject, latestPackage] = await Promise.all([ + getInstallationObject({ savedObjectsClient, pkgName }), + Registry.fetchFindLatestPackage(pkgName), + ]); + + // If no package version is provided, use the installed version in the response + let responsePkgVersion = pkgVersion || savedObject?.attributes.install_version; + // If no installed version of the given package exists, default to the latest version of the package + if (!responsePkgVersion) { + responsePkgVersion = latestPackage.version; + } + const packageInfo = await Registry.fetchInfo(pkgName, responsePkgVersion); + + // Fix the paths + const paths = + packageInfo?.assets?.map((path) => + path.replace(`/package/${pkgName}/${pkgVersion}`, `${pkgName}-${pkgVersion}`) + ) ?? []; + + // add properties that aren't (or aren't yet) on the package + const additions: EpmPackageAdditions = { + latestVersion: latestPackage.version, + title: packageInfo.title || nameAsTitle(packageInfo.name), + assets: Registry.groupPathsByService(paths || []), + removable: true, + notice: Registry.getNoticePath(paths || []), + keepPoliciesUpToDate: savedObject?.attributes.keep_policies_up_to_date ?? false, + }; + const updated = { ...packageInfo, ...additions }; + + return createInstallableFrom(updated, savedObject); +} + export async function getPackageInfo(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts index 2c79045c626d3..fa2e5781a209e 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/index.ts @@ -20,6 +20,7 @@ export { getInstallation, getInstallations, getPackageInfo, + getPackageInfoFromRegistry, getPackages, getLimitedPackages, } from './get'; diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap deleted file mode 100644 index 2e0014b998bdc..0000000000000 --- a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap +++ /dev/null @@ -1,712 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Fleet Endpoints EPM Endpoints installs packages from direct upload should install a zip archive correctly and package info should return correctly after validation 1`] = ` -Object { - "assets": Object { - "elasticsearch": Object { - "ingest_pipeline": Array [ - Object { - "dataset": "access", - "file": "default.yml", - "path": "apache-0.1.4/data_stream/access/elasticsearch/ingest_pipeline/default.yml", - "pkgkey": "apache-0.1.4", - "service": "elasticsearch", - "type": "ingest_pipeline", - }, - Object { - "dataset": "error", - "file": "default.yml", - "path": "apache-0.1.4/data_stream/error/elasticsearch/ingest_pipeline/default.yml", - "pkgkey": "apache-0.1.4", - "service": "elasticsearch", - "type": "ingest_pipeline", - }, - ], - }, - "kibana": Object { - "dashboard": Array [ - Object { - "file": "apache-Logs-Apache-Dashboard-ecs-new.json", - "path": "apache-0.1.4/kibana/dashboard/apache-Logs-Apache-Dashboard-ecs-new.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "dashboard", - }, - Object { - "file": "apache-Metrics-Apache-HTTPD-server-status-ecs.json", - "path": "apache-0.1.4/kibana/dashboard/apache-Metrics-Apache-HTTPD-server-status-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "dashboard", - }, - ], - "search": Array [ - Object { - "file": "Apache-access-logs-ecs.json", - "path": "apache-0.1.4/kibana/search/Apache-access-logs-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "search", - }, - Object { - "file": "Apache-errors-log-ecs.json", - "path": "apache-0.1.4/kibana/search/Apache-errors-log-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "search", - }, - Object { - "file": "Apache-HTTPD-ecs.json", - "path": "apache-0.1.4/kibana/search/Apache-HTTPD-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "search", - }, - ], - "visualization": Array [ - Object { - "file": "Apache-access-unique-IPs-map-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-access-unique-IPs-map-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-CPU-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-CPU-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-Load1-slash-5-slash-15-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-Load1-slash-5-slash-15-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-response-codes-over-time-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-response-codes-over-time-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-Workers-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-Workers-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-Hostname-list-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-Hostname-list-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-error-logs-over-time-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-error-logs-over-time-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-Scoreboard-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-Scoreboard-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-Uptime-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-Uptime-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-operating-systems-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-operating-systems-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-HTTPD-Total-accesses-and-kbytes-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-HTTPD-Total-accesses-and-kbytes-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-browsers-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-browsers-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - Object { - "file": "Apache-response-codes-of-top-URLs-ecs.json", - "path": "apache-0.1.4/kibana/visualization/Apache-response-codes-of-top-URLs-ecs.json", - "pkgkey": "apache-0.1.4", - "service": "kibana", - "type": "visualization", - }, - ], - }, - }, - "categories": Array [ - "web", - ], - "conditions": Object { - "kibana.version": "^7.9.0", - }, - "data_streams": Array [ - Object { - "dataset": "apache.access", - "package": "apache", - "path": "access", - "release": "experimental", - "streams": Array [ - Object { - "description": "Collect Apache access logs", - "input": "logfile", - "template_path": "log.yml.hbs", - "title": "Apache access logs", - "vars": Array [ - Object { - "default": Array [ - "/var/log/apache2/access.log*", - "/var/log/apache2/other_vhosts_access.log*", - "/var/log/httpd/access_log*", - ], - "multi": true, - "name": "paths", - "required": true, - "show_user": true, - "title": "Paths", - "type": "text", - }, - ], - }, - ], - "title": "Apache access logs", - "type": "logs", - }, - Object { - "dataset": "apache.status", - "package": "apache", - "path": "status", - "release": "experimental", - "streams": Array [ - Object { - "description": "Collect Apache status metrics", - "input": "apache/metrics", - "template_path": "stream.yml.hbs", - "title": "Apache status metrics", - "vars": Array [ - Object { - "default": "10s", - "multi": false, - "name": "period", - "required": true, - "show_user": true, - "title": "Period", - "type": "text", - }, - Object { - "default": "/server-status", - "multi": false, - "name": "server_status_path", - "required": true, - "show_user": false, - "title": "Server Status Path", - "type": "text", - }, - ], - }, - ], - "title": "Apache status metrics", - "type": "metrics", - }, - Object { - "dataset": "apache.error", - "package": "apache", - "path": "error", - "release": "experimental", - "streams": Array [ - Object { - "description": "Collect Apache error logs", - "input": "logfile", - "template_path": "log.yml.hbs", - "title": "Apache error logs", - "vars": Array [ - Object { - "default": Array [ - "/var/log/apache2/error.log*", - "/var/log/httpd/error_log*", - ], - "multi": true, - "name": "paths", - "required": true, - "show_user": true, - "title": "Paths", - "type": "text", - }, - ], - }, - ], - "title": "Apache error logs", - "type": "logs", - }, - ], - "description": "Apache Uploaded Test Integration", - "format_version": "1.0.0", - "icons": Array [ - Object { - "size": "32x32", - "src": "/img/logo_apache_test.svg", - "title": "Apache Logo", - "type": "image/svg+xml", - }, - ], - "keepPoliciesUpToDate": false, - "license": "basic", - "name": "apache", - "owner": Object { - "github": "elastic/integrations-services", - }, - "policy_templates": Array [ - Object { - "description": "Collect logs and metrics from Apache instances", - "inputs": Array [ - Object { - "description": "Collecting Apache access and error logs", - "title": "Collect logs from Apache instances", - "type": "logfile", - "vars": Array [], - }, - Object { - "description": "Collecting Apache status metrics", - "title": "Collect metrics from Apache instances", - "type": "apache/metrics", - "vars": Array [ - Object { - "default": Array [ - "http://127.0.0.1", - ], - "multi": true, - "name": "hosts", - "required": true, - "show_user": true, - "title": "Hosts", - "type": "text", - }, - ], - }, - ], - "multiple": true, - "name": "apache", - "title": "Apache logs and metrics", - }, - ], - "readme": "/package/apache/0.1.4/docs/README.md", - "release": "experimental", - "removable": true, - "savedObject": Object { - "attributes": Object { - "es_index_patterns": Object { - "access": "logs-apache.access-*", - "error": "logs-apache.error-*", - "status": "metrics-apache.status-*", - }, - "install_source": "upload", - "install_status": "installed", - "install_version": "0.1.4", - "installed_es": Array [ - Object { - "id": "logs-apache.access-0.1.4-default", - "type": "ingest_pipeline", - }, - Object { - "id": "logs-apache.error-0.1.4-default", - "type": "ingest_pipeline", - }, - Object { - "id": "logs-apache.access", - "type": "index_template", - }, - Object { - "id": "logs-apache.access@settings", - "type": "component_template", - }, - Object { - "id": "logs-apache.access@custom", - "type": "component_template", - }, - Object { - "id": "metrics-apache.status", - "type": "index_template", - }, - Object { - "id": "metrics-apache.status@settings", - "type": "component_template", - }, - Object { - "id": "metrics-apache.status@custom", - "type": "component_template", - }, - Object { - "id": "logs-apache.error", - "type": "index_template", - }, - Object { - "id": "logs-apache.error@settings", - "type": "component_template", - }, - Object { - "id": "logs-apache.error@custom", - "type": "component_template", - }, - ], - "installed_kibana": Array [ - Object { - "id": "apache-Logs-Apache-Dashboard-ecs", - "type": "dashboard", - }, - Object { - "id": "apache-Metrics-Apache-HTTPD-server-status-ecs", - "type": "dashboard", - }, - Object { - "id": "Apache-access-unique-IPs-map-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-CPU-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-Load1-slash-5-slash-15-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-response-codes-over-time-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-Workers-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-Hostname-list-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-error-logs-over-time-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-Scoreboard-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-Uptime-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-operating-systems-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-HTTPD-Total-accesses-and-kbytes-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-browsers-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-response-codes-of-top-URLs-ecs", - "type": "visualization", - }, - Object { - "id": "Apache-access-logs-ecs", - "type": "search", - }, - Object { - "id": "Apache-errors-log-ecs", - "type": "search", - }, - Object { - "id": "Apache-HTTPD-ecs", - "type": "search", - }, - ], - "installed_kibana_space_id": "default", - "name": "apache", - "package_assets": Array [ - Object { - "id": "2f1ab9c0-8cf6-5e83-afcd-0d12851c8108", - "type": "epm-packages-assets", - }, - Object { - "id": "841166f1-6db0-5f7a-a8d9-768e88ddf984", - "type": "epm-packages-assets", - }, - Object { - "id": "b12ae5e1-daf2-51a7-99d8-0888d1f13b5b", - "type": "epm-packages-assets", - }, - Object { - "id": "2f263b24-c36a-5ea8-a707-76d1f274c888", - "type": "epm-packages-assets", - }, - Object { - "id": "bd5ff9ad-ba4a-5215-b5af-cef58a3aa886", - "type": "epm-packages-assets", - }, - Object { - "id": "5fc59aa9-1d7e-50ae-8ce5-b875ab44cfc5", - "type": "epm-packages-assets", - }, - Object { - "id": "7c850453-346b-5010-a946-28b83fc69e48", - "type": "epm-packages-assets", - }, - Object { - "id": "f02f8adb-3e0c-5f2f-b4f2-a04dc645b713", - "type": "epm-packages-assets", - }, - Object { - "id": "889d88db-6214-5836-aeff-1a87f8513b27", - "type": "epm-packages-assets", - }, - Object { - "id": "06a6b940-a745-563c-abf4-83eb3335926b", - "type": "epm-packages-assets", - }, - Object { - "id": "e68fd7ac-302e-5b75-bbbb-d69b441c8848", - "type": "epm-packages-assets", - }, - Object { - "id": "2c57fe0f-3b1a-57da-a63b-28f9b9e82bce", - "type": "epm-packages-assets", - }, - Object { - "id": "13db43e8-f8f9-57f0-b131-a171c2f2070f", - "type": "epm-packages-assets", - }, - Object { - "id": "e8750081-1c0b-5c55-bcab-fa6d47f01a85", - "type": "epm-packages-assets", - }, - Object { - "id": "71af57fe-25c4-5935-9879-ca4a2fba730e", - "type": "epm-packages-assets", - }, - Object { - "id": "cc287718-9573-5c56-a9ed-6dfef6589506", - "type": "epm-packages-assets", - }, - Object { - "id": "8badd8ba-289a-5e60-a1c0-f3d39e15cda3", - "type": "epm-packages-assets", - }, - Object { - "id": "20300efc-10eb-5fac-ba90-f6aa9b467e84", - "type": "epm-packages-assets", - }, - Object { - "id": "047c89df-33c2-5d74-b0a4-8b441879761c", - "type": "epm-packages-assets", - }, - Object { - "id": "9838a13f-1b89-5c54-844e-978620d66a1d", - "type": "epm-packages-assets", - }, - Object { - "id": "e105414b-221d-5433-8b24-452625f59b7c", - "type": "epm-packages-assets", - }, - Object { - "id": "eb166c25-843b-5271-8d43-6fb005d2df5a", - "type": "epm-packages-assets", - }, - Object { - "id": "342dbf4d-d88d-53e8-b365-d3639ebbbb14", - "type": "epm-packages-assets", - }, - Object { - "id": "f98c44a3-eaea-505f-8598-3b7f1097ef59", - "type": "epm-packages-assets", - }, - Object { - "id": "12da8c6c-d0e3-589c-9244-88d857ea76b6", - "type": "epm-packages-assets", - }, - Object { - "id": "e2d151ed-709c-542d-b797-cb95f353b9b3", - "type": "epm-packages-assets", - }, - Object { - "id": "f434cffe-0b00-59de-a17f-c1e71bd4ab0f", - "type": "epm-packages-assets", - }, - Object { - "id": "5bd0c25f-04a5-5fd0-8298-ba9aa2f6fe5e", - "type": "epm-packages-assets", - }, - Object { - "id": "279da3a3-8e9b-589b-86e0-bd7364821bab", - "type": "epm-packages-assets", - }, - Object { - "id": "b8758fcb-08bf-50fa-89bd-24398955298a", - "type": "epm-packages-assets", - }, - Object { - "id": "96e4eb36-03c3-5856-af44-559fd5133f2b", - "type": "epm-packages-assets", - }, - Object { - "id": "a59a79c3-66bd-5cfc-91f5-ee84f7227855", - "type": "epm-packages-assets", - }, - Object { - "id": "395143f9-54bf-5b46-b1be-a7b2a6142ad9", - "type": "epm-packages-assets", - }, - Object { - "id": "3449b8d2-ffd5-5aec-bb32-4245f2fbcde4", - "type": "epm-packages-assets", - }, - Object { - "id": "ab44094e-6c9d-50b8-b5c4-2e518d89912e", - "type": "epm-packages-assets", - }, - Object { - "id": "b093bfc0-6e98-5a1b-a502-e838a36f6568", - "type": "epm-packages-assets", - }, - Object { - "id": "03d86823-b756-5b91-850d-7ad231d33546", - "type": "epm-packages-assets", - }, - Object { - "id": "a76af2f0-049b-5be1-8d20-e87c9d1c2709", - "type": "epm-packages-assets", - }, - Object { - "id": "bc2f0c1e-992e-5407-9435-fedb39ff74ea", - "type": "epm-packages-assets", - }, - Object { - "id": "84668ac1-d5ef-545b-88f3-1e49f8f1c8ad", - "type": "epm-packages-assets", - }, - Object { - "id": "69b41271-91a0-5a2e-a62c-60364d5a9c8f", - "type": "epm-packages-assets", - }, - Object { - "id": "8e4ec555-5fbf-55d3-bea3-3af12c9aca3f", - "type": "epm-packages-assets", - }, - Object { - "id": "aa18f3f9-f62a-5ab8-9b34-75696efa5c48", - "type": "epm-packages-assets", - }, - Object { - "id": "71c8c6b1-2116-5817-b65f-7a87ef5ef2b7", - "type": "epm-packages-assets", - }, - Object { - "id": "8f6d7a1f-1e7f-5a60-8fe7-ce19115ed460", - "type": "epm-packages-assets", - }, - Object { - "id": "c115dbbf-edad-59f2-b046-c65a0373a81c", - "type": "epm-packages-assets", - }, - Object { - "id": "b7d696c3-8106-585c-9ecc-94a75cf1e3da", - "type": "epm-packages-assets", - }, - Object { - "id": "639e6a78-59d8-5ce8-9687-64e8f9af7e71", - "type": "epm-packages-assets", - }, - Object { - "id": "ae60c853-7a90-58d2-ab6c-04d3be5f1847", - "type": "epm-packages-assets", - }, - Object { - "id": "0cd33163-2ae4-57eb-96f6-c50af6685cab", - "type": "epm-packages-assets", - }, - Object { - "id": "39e0f78f-1172-5e61-9446-65ef3c0cb46c", - "type": "epm-packages-assets", - }, - Object { - "id": "b08f10ee-6afd-5e89-b9b4-569064fbdd9f", - "type": "epm-packages-assets", - }, - Object { - "id": "efcbe1c6-b2d5-521c-b27a-2146f08a604d", - "type": "epm-packages-assets", - }, - Object { - "id": "f9422c02-d43f-5ebb-b7c5-9e32f9b77c21", - "type": "epm-packages-assets", - }, - Object { - "id": "c276e880-3ba8-58e7-a5d5-c07707dba6b7", - "type": "epm-packages-assets", - }, - Object { - "id": "561a3711-c386-541c-9a77-2d0fa256caf6", - "type": "epm-packages-assets", - }, - Object { - "id": "1378350d-2e2b-52dd-ab3a-d8b9a09df92f", - "type": "epm-packages-assets", - }, - Object { - "id": "94e40729-4aea-59c8-86ba-075137c000dc", - "type": "epm-packages-assets", - }, - ], - "removable": true, - "version": "0.1.4", - }, - "id": "apache", - "namespaces": Array [], - "references": Array [], - "type": "epm-packages", - }, - "screenshots": Array [ - Object { - "size": "1215x1199", - "src": "/img/kibana-apache-test.png", - "title": "Apache Integration", - "type": "image/png", - }, - Object { - "size": "1919x1079", - "src": "/img/apache_httpd_server_status.png", - "title": "Apache HTTPD Server Status", - "type": "image/png", - }, - ], - "status": "installed", - "title": "Apache", - "type": "integration", - "version": "0.1.4", -} -`; diff --git a/x-pack/test/fleet_api_integration/apis/epm/get.ts b/x-pack/test/fleet_api_integration/apis/epm/get.ts index df6cbf3c4fecb..c605adc44233a 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/get.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/get.ts @@ -66,10 +66,10 @@ export default function (providerContext: FtrProviderContext) { .get(`/api/fleet/epm/packages/${testPkgName}/${testPkgVersion}`) .expect(200); const packageInfo = res.body.item; - // the uploaded version will have this description - expect(packageInfo.description).to.equal('Apache Uploaded Test Integration'); - // download property should not exist on uploaded packages - expect(packageInfo.download).to.equal(undefined); + // Get package info always return data from the registry + expect(packageInfo.description).to.not.equal('Apache Uploaded Test Integration'); + // download property exist on uploaded packages + expect(packageInfo.download).to.not.equal(undefined); await uninstallPackage(testPkgName, testPkgVersion); }); it('returns correct package info from registry if a different version is installed by upload', async function () { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index 1ac5f2750fc98..83ff7611ded8e 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -87,20 +87,6 @@ export default function (providerContext: FtrProviderContext) { .send(buf) .expect(200); expect(res.body.items.length).to.be(27); - - const packageInfoRes = await supertest - .get(`/api/fleet/epm/packages/${testPkgName}/${testPkgVersion}`) - .set('kbn-xsrf', 'xxxx') - .expect(200); - - delete packageInfoRes.body.item.latestVersion; - delete packageInfoRes.body.item.savedObject.attributes.install_started_at; - delete packageInfoRes.body.item.savedObject.version; - delete packageInfoRes.body.item.savedObject.updated_at; - delete packageInfoRes.body.item.savedObject.coreMigrationVersion; - delete packageInfoRes.body.item.savedObject.migrationVersion; - - expectSnapshot(packageInfoRes.body.item).toMatch(); }); it('should throw an error if the archive is zip but content type is gzip', async function () { From 9ccef34fdd1df66b7009a0abf9710af9bfd6eddb Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Wed, 9 Feb 2022 10:59:41 -0500 Subject: [PATCH 13/85] Use sentence case in Index Management list titles (#125037) Updates some description list titles on the Index Management page to use sentence case rather than title case. --- .../home/index_list/detail_panel/summary/summary.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js index 3e78188ebbd24..d01ee49320070 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js @@ -36,16 +36,16 @@ const getHeaders = () => { defaultMessage: 'Replicas', }), documents: i18n.translate('xpack.idxMgmt.summary.headers.documentsHeader', { - defaultMessage: 'Docs Count', + defaultMessage: 'Docs count', }), documents_deleted: i18n.translate('xpack.idxMgmt.summary.headers.deletedDocumentsHeader', { - defaultMessage: 'Docs Deleted', + defaultMessage: 'Docs deleted', }), size: i18n.translate('xpack.idxMgmt.summary.headers.storageSizeHeader', { - defaultMessage: 'Storage Size', + defaultMessage: 'Storage size', }), primary_size: i18n.translate('xpack.idxMgmt.summary.headers.primaryStorageSizeHeader', { - defaultMessage: 'Primary Storage Size', + defaultMessage: 'Primary storage size', }), aliases: i18n.translate('xpack.idxMgmt.summary.headers.aliases', { defaultMessage: 'Aliases', From f2e9efe4570e529a5cb6ec2a181770147d3845f8 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Wed, 9 Feb 2022 10:15:07 -0600 Subject: [PATCH 14/85] [DOCS] Adds 8.0.0 release notes (#124399) * [DOCS] Adds 8.0.0 release notes * Update docs/CHANGELOG.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/CHANGELOG.asciidoc Co-authored-by: Brandon Morelli Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Brandon Morelli --- docs/CHANGELOG.asciidoc | 114 +++++++++++++++++- .../development-visualize-index.asciidoc | 2 +- docs/index.asciidoc | 2 - docs/setup/upgrade/upgrade-standard.asciidoc | 4 +- docs/user/whats-new.asciidoc | 2 +- 5 files changed, 113 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 446c305c03b95..3101b649b09a5 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.0.0 releases. +* <> * <> * <> * <> @@ -17,6 +18,113 @@ Review important information about the {kib} 8.0.0 releases. * <> -- +[[release-notes-8.0.0]] +== {kib} 8.0.0 + +coming::[8.0.0] + +Review the {kib} 8.0.0 changes, then use the {kibana-ref-all}/7.17/upgrade-assistant.html[Upgrade Assistant] to complete the upgrade. + +[float] +[[breaking-changes-8.0.0]] +=== Breaking change + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.0.0, review the breaking change, then mitigate the impact to your application. + +// tag::notable-breaking-changes[] + +[discrete] +[[breaking-123754]] +.Removes the `console.ssl` setting +[%collapsible] +==== +*Details* + +The `console.ssl` setting has been removed. For more information, refer to {kibana-pull}123754[#123754]. + +*Impact* + +Before you upgrade to 8.0.0, remove `console.ssl` from kibana.yml. +==== + +// end::notable-breaking-changes[] + + +To review the breaking changes in previous versions, refer to the following: + +<> | <> | <> | <> | +<> + +[float] +[[deprecations-8.0.0]] +=== Deprecation + +The following functionality is deprecated in 8.0.0, and will be removed in 9.0.0. +Deprecated functionality does not have an immediate impact on your application, but we strongly recommend +you make the necessary updates after you upgrade to 8.0.0. + +[discrete] +[[deprecation-123229]] +.Removes support for `monitoring.cluster_alerts.allowedSpaces` +[%collapsible] +==== +*Details* + +The `monitoring.cluster_alerts.allowedSpaces` setting, which {kib} uses to create Stack Monitoring alerts, has been removed. For more information, refer to {kibana-pull}123229[#123229]. + +*Impact* + +Before you upgrade to 8.0.0, remove `monitoring.cluster_alerts.allowedSpaces` from kibana.yml. +==== + +To review the deprecations in previous versions, refer to the following: + +<> | <> + +[float] +[[features-8.0.0]] +=== Features +For information about the features introduced in 8.0.0, refer to <>. + +Elastic Security:: +For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. + +To review the features in previous versions, refer to the following: + +<> | <> | <> | <> + +[[enhancements-and-bug-fixes-v8.0.0]] +=== Enhancements and bug fixes + +For detailed information about the 8.0.0 release, review the enhancements and bug fixes. + +[float] +[[enhancement-v8.0.0]] +==== Enhancements +Dashboard:: +Clone ReferenceOrValueEmbeddables by value {kibana-pull}122199[#122199] + +Elastic Security:: +For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. + +[float] +[[fixes-v8.0.0]] +==== Bug Fixes +APM:: +Restrict aggregated transaction metrics search to date range {kibana-pull}123445[#123445] + +Elastic Security:: +For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. + +Fleet:: +Allow empty strings for required text fields in package policies {kibana-pull}123610[#123610] + +Maps:: +Fixes Label border color is not removed from legend when disabled {kibana-pull}122705[#122705] + +Monitoring:: +Ensure logstash getNodes always contains a uuid {kibana-pull}124201[#124201] + +Security:: +Long-running requests no longer cause sporadic logouts in certain cases, even when user sessions are active {kibana-pull}122155[#122155] + [[release-notes-8.0.0-rc2]] == {kib} 8.0.0-rc2 @@ -29,8 +137,6 @@ For information about the {kib} 8.0.0-rc2 release, review the following informat Breaking changes can prevent your application from optimal operation and performance. Before you upgrade, review the breaking change, then mitigate the impact to your application. -// tag::notable-breaking-changes[] - [discrete] [[breaking-122722]] .Removes the ability to use `elasticsearch.username: elastic` in production @@ -42,8 +148,6 @@ In production, you are no longer able to use the `elastic` superuser to authenti *Impact* + When you configure `elasticsearch.username: elastic`, {kib} fails. ==== - -// end::notable-breaking-changes[] To review the breaking changes in previous versions, refer to the following: @@ -1304,7 +1408,7 @@ Use the `xpack.monitoring.clusterAlertsEmail` in kibana.yml. ==== [float] -[[enhancements-and-bug-fixes-v8.0.0]] +[[enhancements-and-bug-fixes-v8.0.0-alpha1]] === Bug fix The 8.0.0-alpha1 release includes the following bug fix. diff --git a/docs/developer/architecture/development-visualize-index.asciidoc b/docs/developer/architecture/development-visualize-index.asciidoc index d41ee32c1fb27..b941cdedf9df9 100644 --- a/docs/developer/architecture/development-visualize-index.asciidoc +++ b/docs/developer/architecture/development-visualize-index.asciidoc @@ -19,7 +19,7 @@ We would recommend waiting until later in `7.x` to upgrade your plugins if possi If you would like to keep up with progress on the visualizations plugin in the meantime, here are a few resources: -* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. +* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. * link:https://github.com/elastic/kibana/issues/44121[Meta issue] which is tracking the move of the plugin to the new {kib} platform * Our link:https://www.elastic.co/blog/join-our-elastic-stack-workspace-on-slack[Elastic Stack workspace on Slack]. * The {kib-repo}blob/{branch}/src/plugins/visualizations[source code], which will continue to be diff --git a/docs/index.asciidoc b/docs/index.asciidoc index ec1a99fa5bffc..668a6edcad3db 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -24,8 +24,6 @@ include::user/index.asciidoc[] include::accessibility.asciidoc[] -include::migration.asciidoc[] - include::CHANGELOG.asciidoc[] include::developer/index.asciidoc[] diff --git a/docs/setup/upgrade/upgrade-standard.asciidoc b/docs/setup/upgrade/upgrade-standard.asciidoc index b43da6aef9765..b01759b4f3511 100644 --- a/docs/setup/upgrade/upgrade-standard.asciidoc +++ b/docs/setup/upgrade/upgrade-standard.asciidoc @@ -37,7 +37,7 @@ from 4.x, you will need to copy the configurations from your old config (`/etc/kibana/kibana.yml`). Make sure you remove or update any configurations -that are indicated in the <> documentation +that are indicated in the <> documentation otherwise {kib} will fail to start. -- . Upgrade any plugins by removing the existing plugin and reinstalling the @@ -58,7 +58,7 @@ and becomes a new instance in the monitoring data. -- . Copy the files from the `config` directory from your old installation to your new installation. Make sure you remove or update any configurations that are - indicated in the <> documentation + indicated in the <> documentation otherwise {kib} will fail to start. . Copy the files from the `data` directory from your old installation to your new installation. diff --git a/docs/user/whats-new.asciidoc b/docs/user/whats-new.asciidoc index 587f4588bb442..aa16a98a27fdb 100644 --- a/docs/user/whats-new.asciidoc +++ b/docs/user/whats-new.asciidoc @@ -2,7 +2,7 @@ == What's new in 8.0 This section summarizes the most important changes in each release. For the -full list, see <> and <>. +full list, see <> and <>. coming[8.0.0] From 67cd496daa93537a37ab1ddafbcc163d4c64c44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 9 Feb 2022 17:17:30 +0100 Subject: [PATCH 15/85] [IM] Removed undefined data stream link (#124847) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../home/indices_tab.helpers.ts | 8 ++++++ .../home/indices_tab.test.ts | 25 +++++++++++++++++-- .../index_list/index_table/index_table.js | 2 +- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index 719758e18525a..7daa3cc9e2221 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -33,6 +33,7 @@ export interface IndicesTestBed extends TestBed { getIncludeHiddenIndicesToggleStatus: () => boolean; clickIncludeHiddenIndicesToggle: () => void; clickDataStreamAt: (index: number) => void; + dataStreamLinkExistsAt: (index: number) => boolean; clickManageContextMenuButton: () => void; clickContextMenuOption: (optionDataTestSubject: string) => void; clickModalConfirm: () => void; @@ -103,6 +104,12 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { table } = testBed; + const { rows } = table.getMetaData('indexTable'); + return findTestSubject(rows[index].reactWrapper, 'dataStreamLink').exists(); + }; + const clickModalConfirm = async () => { const { find, component } = testBed; @@ -129,6 +136,7 @@ export const setup = async (overridingDependencies: any = {}): Promise', () => { name: 'data-stream-index', data_stream: 'dataStream1', }, + { + health: '', + status: '', + primary: '', + replica: '', + documents: '', + documents_deleted: '', + size: '', + primary_size: '', + name: 'no-data-stream-index', + data_stream: null, + }, ]); // The detail panel should still appear even if there are no data streams. @@ -125,14 +137,23 @@ describe('', () => { const { findDataStreamDetailPanel, findDataStreamDetailPanelTitle, - actions: { clickDataStreamAt }, + actions: { clickDataStreamAt, dataStreamLinkExistsAt }, } = testBed; + expect(dataStreamLinkExistsAt(0)).toBeTruthy(); await clickDataStreamAt(0); expect(findDataStreamDetailPanel().length).toBe(1); expect(findDataStreamDetailPanelTitle()).toBe('dataStream1'); }); + + test(`doesn't show data stream link if the index doesn't have a data stream`, () => { + const { + actions: { dataStreamLinkExistsAt }, + } = testBed; + + expect(dataStreamLinkExistsAt(1)).toBeFalsy(); + }); }); describe('index detail panel with % character in index name', () => { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 63e4503180f23..70a7f48178192 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -285,7 +285,7 @@ export class IndexTable extends Component { {renderBadges(index, filterChanged, appServices.extensionsService)} ); - } else if (fieldName === 'data_stream') { + } else if (fieldName === 'data_stream' && value) { return ( Date: Wed, 9 Feb 2022 10:32:20 -0600 Subject: [PATCH 16/85] [data view management] Fix set default data view permissions check (#124897) * fix set default data view permissions * fix fields table * fix scripted field add button * fix jest tests * lint fixes * fix test * updte snapshot --- .../create_edit_field/create_edit_field.tsx | 6 +++++- .../edit_index_pattern/edit_index_pattern.tsx | 7 ++++--- .../index_header/index_header.tsx | 6 ++++-- .../indexed_fields_table.test.tsx.snap | 10 +++++++++ .../indexed_fields_table.test.tsx | 21 ++++++++++++++----- .../indexed_fields_table.tsx | 18 ++++------------ .../components/header/header.tsx | 4 ++-- .../scripted_field_table.test.tsx | 18 ++++++++++------ .../scripted_fields_table.tsx | 18 ++++------------ .../edit_index_pattern/tabs/tabs.tsx | 9 +++++--- .../mount_management_section.tsx | 3 +-- .../data_view_management/public/mocks.ts | 6 +++--- .../data_view_management/public/types.ts | 2 -- src/plugins/data_views/public/mocks.ts | 1 + 14 files changed, 72 insertions(+), 57 deletions(-) diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index 0f41c08fbc6fe..2d8469975430b 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -70,7 +70,11 @@ export const CreateEditField = withRouter( if (spec) { return ( <> - + { - const { application, uiSettings, overlays, chrome, dataViews } = + const { uiSettings, overlays, chrome, dataViews } = useKibana().services; const [fields, setFields] = useState(indexPattern.getNonScriptedFields()); const [conflictedFields, setConflictedFields] = useState( @@ -143,15 +143,16 @@ export const EditIndexPattern = withRouter( const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0)); const kibana = useKibana(); const docsUrl = kibana.services.docLinks!.links.elasticsearch.mapping; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; + const userEditPermission = dataViews.getCanSaveSync(); return (

{showTagsSection && ( diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx index b64aed5c0811c..e40ef6a7ddf2f 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/index_header/index_header.tsx @@ -16,6 +16,7 @@ interface IndexHeaderProps { defaultIndex?: string; setDefault?: () => void; deleteIndexPatternClick?: () => void; + canSave: boolean; } const setDefaultAriaLabel = i18n.translate('indexPatternManagement.editDataView.setDefaultAria', { @@ -40,12 +41,13 @@ export const IndexHeader: React.FC = ({ setDefault, deleteIndexPatternClick, children, + canSave, }) => { return ( {indexPattern.title}} rightSideItems={[ - defaultIndex !== indexPattern.id && setDefault && ( + defaultIndex !== indexPattern.id && setDefault && canSave && ( = ({ /> ), - deleteIndexPatternClick && ( + canSave && (
`; @@ -196,6 +198,8 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` }, ] } + openModal={[Function]} + theme={Object {}} /> `; @@ -233,6 +237,8 @@ exports[`IndexedFieldsTable should filter based on the schema filter 1`] = ` }, ] } + openModal={[Function]} + theme={Object {}} /> `; @@ -267,6 +273,8 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` }, ] } + openModal={[Function]} + theme={Object {}} /> `; @@ -366,6 +374,8 @@ exports[`IndexedFieldsTable should render normally 1`] = ` }, ] } + openModal={[Function]} + theme={Object {}} /> `; diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index e179050ca7fe2..c7b92c227a5d9 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -97,6 +97,12 @@ const fields = [ }, ].map(mockFieldToIndexPatternField); +const mockedServices = { + userEditPermission: false, + openModal: () => ({ onClose: new Promise(() => {}), close: async () => {} }), + theme: {} as any, +}; + describe('IndexedFieldsTable', () => { test('should render normally', async () => { const component: ShallowWrapper, React.Component<{}, {}, any>> = shallow( @@ -110,8 +116,9 @@ describe('IndexedFieldsTable', () => { indexedFieldTypeFilter={[]} schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.update(); @@ -131,8 +138,9 @@ describe('IndexedFieldsTable', () => { indexedFieldTypeFilter={[]} schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.setProps({ fieldFilter: 'Elast' }); @@ -153,8 +161,9 @@ describe('IndexedFieldsTable', () => { indexedFieldTypeFilter={[]} schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.setProps({ indexedFieldTypeFilter: ['date'] }); @@ -175,8 +184,9 @@ describe('IndexedFieldsTable', () => { indexedFieldTypeFilter={[]} schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.setProps({ schemaFieldTypeFilter: ['runtime'] }); @@ -198,8 +208,9 @@ describe('IndexedFieldsTable', () => { indexedFieldTypeFilter={[]} schemaFieldTypeFilter={[]} fieldFilter="" + {...mockedServices} /> - ).dive(); + ); await new Promise((resolve) => process.nextTick(resolve)); component.update(); diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 07a3bf50aab53..ad85499009db0 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -10,10 +10,8 @@ import React, { Component } from 'react'; import { createSelector } from 'reselect'; import { OverlayStart, ThemeServiceStart } from 'src/core/public'; import { DataViewField, DataView } from '../../../../../../plugins/data_views/public'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; import { Table } from './components/table'; import { IndexedFieldItem } from './types'; -import { IndexPatternManagmentContext } from '../../../types'; interface IndexedFieldsTableProps { fields: DataViewField[]; @@ -36,16 +34,10 @@ interface IndexedFieldsTableState { fields: IndexedFieldItem[]; } -const withHooks = (Comp: typeof Component) => { - return (props: any) => { - const { application } = useKibana().services; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; - - return ; - }; -}; - -class IndexedFields extends Component { +export class IndexedFieldsTable extends Component< + IndexedFieldsTableProps, + IndexedFieldsTableState +> { constructor(props: IndexedFieldsTableProps) { super(props); @@ -158,5 +150,3 @@ class IndexedFields extends Component { - const { application, docLinks } = useKibana().services; + const { dataViews, docLinks } = useKibana().services; const links = docLinks?.links; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; + const userEditPermission = dataViews.getCanSaveSync(); return ( diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index 169b3673001a1..4febfdf0e1219 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -68,9 +68,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -87,9 +88,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -119,9 +121,10 @@ describe('ScriptedFieldsTable', () => { painlessDocLink={'painlessDoc'} helpers={helpers} saveIndexPattern={async () => {}} + userEditPermission={false} scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -145,9 +148,10 @@ describe('ScriptedFieldsTable', () => { painlessDocLink={'painlessDoc'} helpers={helpers} saveIndexPattern={async () => {}} + userEditPermission={false} scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); // Allow the componentWillMount code to execute // https://github.com/airbnb/enzyme/issues/450 @@ -166,9 +170,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); await component.update(); // Fire `componentWillMount()` // @ts-expect-error lang is not valid @@ -194,9 +199,10 @@ describe('ScriptedFieldsTable', () => { helpers={helpers} painlessDocLink={'painlessDoc'} saveIndexPattern={async () => {}} + userEditPermission={false} scriptedFieldLanguageFilter={[]} /> - ).dive(); + ); await component.update(); // Fire `componentWillMount()` // @ts-expect-error diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 1b9d63e2d2c6a..540131c50b236 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -15,10 +15,8 @@ import { import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; import { ScriptedFieldItem } from './types'; -import { IndexPatternManagmentContext } from '../../../types'; import { DataView, DataViewsPublicPluginStart } from '../../../../../../plugins/data_views/public'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; interface ScriptedFieldsTableProps { indexPattern: DataView; @@ -41,16 +39,10 @@ interface ScriptedFieldsTableState { fields: ScriptedFieldItem[]; } -const withHooks = (Comp: typeof Component) => { - return (props: any) => { - const { application } = useKibana().services; - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; - - return ; - }; -}; - -class ScriptedFields extends Component { +export class ScriptedFieldsTable extends Component< + ScriptedFieldsTableProps, + ScriptedFieldsTableState +> { constructor(props: ScriptedFieldsTableProps) { super(props); @@ -168,5 +160,3 @@ class ScriptedFields extends Component().services; const [fieldFilter, setFieldFilter] = useState(''); const [syncingStateFunc, setSyncingStateFunc] = useState({ @@ -241,7 +241,7 @@ export function Tabs({ [uiSettings] ); - const userEditPermission = !!application?.capabilities?.indexPatterns?.save; + const userEditPermission = dataViews.getCanSaveSync(); const getFilterSection = useCallback( (type: string) => { return ( @@ -448,7 +448,8 @@ export function Tabs({ getFieldInfo, }} openModal={overlays.openModal} - theme={theme} + theme={theme!} + userEditPermission={dataViews.getCanSaveSync()} /> )} @@ -472,6 +473,7 @@ export function Tabs({ }} onRemoveField={refreshFilters} painlessDocLink={docLinks.links.scriptedFields.painless} + userEditPermission={dataViews.getCanSaveSync()} /> ); @@ -510,6 +512,7 @@ export function Tabs({ refreshFields, overlays, theme, + dataViews, ] ); diff --git a/src/plugins/data_view_management/public/management_app/mount_management_section.tsx b/src/plugins/data_view_management/public/management_app/mount_management_section.tsx index 1b876e34a42fb..e4978acbc9d17 100644 --- a/src/plugins/data_view_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/data_view_management/public/management_app/mount_management_section.tsx @@ -39,7 +39,7 @@ export async function mountManagementSection( params: ManagementAppMountParams ) { const [ - { chrome, application, uiSettings, notifications, overlays, http, docLinks, theme }, + { chrome, uiSettings, notifications, overlays, http, docLinks, theme }, { data, dataViewFieldEditor, dataViewEditor, dataViews, fieldFormats }, indexPatternManagementStart, ] = await getStartServices(); @@ -51,7 +51,6 @@ export async function mountManagementSection( const deps: IndexPatternManagmentContext = { chrome, - application, uiSettings, notifications, overlays, diff --git a/src/plugins/data_view_management/public/mocks.ts b/src/plugins/data_view_management/public/mocks.ts index 3404ca4912c88..54c1900d37f4c 100644 --- a/src/plugins/data_view_management/public/mocks.ts +++ b/src/plugins/data_view_management/public/mocks.ts @@ -13,6 +13,7 @@ import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; import { indexPatternFieldEditorPluginMock } from '../../data_view_field_editor/public/mocks'; import { indexPatternEditorPluginMock } from '../../data_view_editor/public/mocks'; +import { dataViewPluginMocks } from '../../data_views/public/mocks'; import { IndexPatternManagementSetup, IndexPatternManagementStart, @@ -54,15 +55,14 @@ const docLinks = { const createIndexPatternManagmentContext = (): { [key in keyof IndexPatternManagmentContext]: any; } => { - const { chrome, application, uiSettings, notifications, overlays } = coreMock.createStart(); + const { chrome, uiSettings, notifications, overlays } = coreMock.createStart(); const { http } = coreMock.createSetup(); const data = dataPluginMock.createStartContract(); const dataViewFieldEditor = indexPatternFieldEditorPluginMock.createStartContract(); - const dataViews = data.indexPatterns; + const dataViews = dataViewPluginMocks.createStartContract(); return { chrome, - application, uiSettings, notifications, overlays, diff --git a/src/plugins/data_view_management/public/types.ts b/src/plugins/data_view_management/public/types.ts index dc5e0198a64f1..f0a79416892ef 100644 --- a/src/plugins/data_view_management/public/types.ts +++ b/src/plugins/data_view_management/public/types.ts @@ -8,7 +8,6 @@ import { ChromeStart, - ApplicationStart, IUiSettingsClient, OverlayStart, NotificationsStart, @@ -26,7 +25,6 @@ import { FieldFormatsStart } from '../../field_formats/public'; export interface IndexPatternManagmentContext { chrome: ChromeStart; - application: ApplicationStart; uiSettings: IUiSettingsClient; notifications: NotificationsStart; overlays: OverlayStart; diff --git a/src/plugins/data_views/public/mocks.ts b/src/plugins/data_views/public/mocks.ts index c9aece61c4e02..61713c9406c23 100644 --- a/src/plugins/data_views/public/mocks.ts +++ b/src/plugins/data_views/public/mocks.ts @@ -27,6 +27,7 @@ const createStartContract = (): Start => { }), get: jest.fn().mockReturnValue(Promise.resolve({})), clearCache: jest.fn(), + getCanSaveSync: jest.fn(), } as unknown as jest.Mocked; }; From 7d57d6be11de7e70d52ead26769d392cf2fb213e Mon Sep 17 00:00:00 2001 From: Melissa Burpo Date: Wed, 9 Feb 2022 10:54:59 -0600 Subject: [PATCH 17/85] Update x-pack readme to fix broken link (#124874) Functional testing info is now available at https://www.elastic.co/guide/en/kibana/current/development-tests.html in the Kibana Developer Guide. --- x-pack/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/README.md b/x-pack/README.md index 852b713e78465..d104dffff3d28 100644 --- a/x-pack/README.md +++ b/x-pack/README.md @@ -16,7 +16,7 @@ By default, this will also set the password for native realm accounts to the pas # Testing -For information on testing, see [the Elastic functional test development guide](https://www.elastic.co/guide/en/kibana/current/development-functional-tests.html). +For information on testing, see [the Elastic functional test development guide](https://www.elastic.co/guide/en/kibana/current/development-tests.html). #### Running functional tests From 5fde0a07e3755c83fab0dfd5b2c22eb9f250c542 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Wed, 9 Feb 2022 09:02:48 -0800 Subject: [PATCH 18/85] [DOCS] Edits to upgrade docs (#125019) * [DOCS] Edits to upgrade docs * Apply suggestions from code review Co-authored-by: Kaarina Tungseth Co-authored-by: Kaarina Tungseth Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../setup/upgrade/upgrade-migrations.asciidoc | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index 7136011a4f8f8..53b89b38cc88d 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -23,7 +23,10 @@ Saved objects are stored in two indices: The index aliases `.kibana` and `.kibana_task_manager` will always point to the most up-to-date saved object indices. -The first time a newer {kib} starts, it will first perform an upgrade migration before starting plugins or serving HTTP traffic. To prevent losing acknowledged writes old nodes should be shutdown before starting the upgrade. To reduce the likelihood of old nodes losing acknowledged writes, {kib} 7.12.0 and later will add a write block to the outdated index. Table 1 lists the saved objects indices used by previous versions of {kib}. +When you start a new {kib} installation, an upgrade migration is performed before starting plugins or serving HTTP traffic. +Before you upgrade, shut down old nodes to prevent losing acknowledged writes. +To reduce the likelihood of old nodes losing acknowledged writes, {kib} 7.12.0 and later +adds a write block to the outdated index. Table 1 lists the saved objects indices used by previous versions of {kib}. .Saved object indices and aliases per {kib} version [options="header"] @@ -40,11 +43,15 @@ The first time a newer {kib} starts, it will first perform an upgrade migration |======================= ==== Upgrading multiple {kib} instances -When upgrading several {kib} instances connected to the same {es} cluster, ensure that all outdated instances are shutdown before starting the upgrade. +When upgrading several {kib} instances connected to the same {es} cluster, +ensure that all outdated instances are shut down before starting the upgrade. -Kibana does not support rolling upgrades. However, once outdated instances are shutdown, all upgraded instances can be started in parallel in which case all instances will participate in the upgrade migration in parallel. +{kib} does not support rolling upgrades. However, once outdated instances are shut down, +all upgraded instances can be started in parallel, in which case all instances will participate in the upgrade migration in parallel. -For large deployments with more than 10 {kib} instances and more than 10 000 saved objects, the upgrade downtime can be reduced by bringing up a single {kib} instance and waiting for it to complete the upgrade migration before bringing up the remaining instances. +For large deployments with more than 10 {kib} instances, and more than 10,000 saved objects, +you can reduce the upgrade downtime by bringing up a single {kib} instance and waiting for it to +complete the upgrade migration before bringing up the remaining instances. [float] [[preventing-migration-failures]] @@ -53,9 +60,9 @@ This section highlights common causes of {kib} upgrade failures and how to preve [float] ===== timeout_exception or receive_timeout_transport_exception -There is a known issue in v7.12.0 for users who tried the fleet beta. Upgrade migrations fail because of a large number of documents in the `.kibana` index. +There is a known issue in 7.12.0 for users who tried the {fleet} beta. +Upgrade migrations fail because of a large number of documents in the `.kibana` index, which causes {kib} to log errors such as: -This can cause Kibana to log errors like: [source,sh] -------------------------------------------- @@ -68,11 +75,12 @@ Instructions to work around this issue are in https://github.com/elastic/kibana/ [float] ===== Corrupt saved objects -We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. +We highly recommend testing your {kib} upgrade in a development cluster to find and remedy problems +caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Unable to migrate the corrupt Saved Object document ...`. -Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. +For a successful upgrade migration, you must fix or delete corrupt documents. For example, given the following error message: @@ -81,7 +89,7 @@ For example, given the following error message: Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. -------------------------------------------- -The following steps must be followed to delete the document that is causing the migration to fail: +To delete the documents that cause migrations to fail, take the following steps: . Remove the write block which the migration system has placed on the previous index: + @@ -104,15 +112,15 @@ DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab . Restart {kib}. + -In this example, the Dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **will no longer be available**. +In this example, the dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **is no longer available**. Be sure you have a snapshot before you delete the corrupt document. If restoring from a snapshot is not an option, it is recommended to also delete the `temp` and `target` indices the migration created before restarting {kib} and retrying. [float] ===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings -Matching index templates which specify `settings.refresh_interval` or `mappings` are known to interfere with {kib} upgrades. +Matching index templates that specify `settings.refresh_interval` or `mappings` are known to interfere with {kib} upgrades. -Prevention: narrow down the index patterns of any user-defined index templates to ensure that these won't apply to new `.kibana*` indices. +Prevention: Narrow down the {data-sources} of any user-defined index templates to ensure that these won't apply to new `.kibana*` indices. NOTE: {kib} < 6.5 creates it's own index template called `kibana_index_template:.kibana` and uses an index pattern of `.kibana`. This index template will not interfere and does not need to be changed or removed. @@ -127,19 +135,21 @@ Problems with your {es} cluster can prevent {kib} upgrades from succeeding. Ensu [float] ===== Different versions of {kib} connected to the same {es} index -When different versions of {kib} are attempting an upgrade migration in parallel this can lead to migration failures. Ensure that all {kib} instances are running the same version, configuration and plugins. +When you perform an upgrade migration of different {kib} versions, the migration can fail. +Ensure that all {kib} instances are running the same version, configuration, and plugins. [float] ===== Incompatible `xpack.tasks.index` configuration setting -For {kib} versions prior to 7.5.1, if the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, upgrade migrations will fail. {kib} 7.5.1 and later prevents this by refusing to start with an incompatible configuration setting. +For {kib} 7.5.0 and earlier, when the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, +upgrade migrations fail. In {kib} 7.5.1 and later, the incompatible configuration setting prevents upgrade migrations from starting. [float] [[resolve-migrations-failures]] ==== Resolving migration failures -If {kib} terminates unexpectedly while migrating a saved object index it will automatically attempt to -perform the migration again once the process has restarted. Do not delete any saved objects indices to -attempt to fix a failed migration. Unlike previous versions, {kib} version 7.12.0 and +If {kib} unexpectedly terminates while migrating a saved object index, {kib} automatically attempts to +perform the migration again when the process restarts. Do not delete any saved objects indices to +attempt to fix a failed migration. Unlike previous versions, {kib} 7.12.0 and later does not require deleting any indices to release a failed migration lock. If upgrade migrations fail repeatedly, follow the advice in @@ -154,41 +164,48 @@ If you're unable to resolve a failed migration following these steps, please con If you've followed the advice in <> and <> and -{kib} is still not able to upgrade successfully, you might choose to rollback {kib} until +If {kib} is still unable to upgrade successfully, rollback {kib} until you're able to identify and fix the root cause. -WARNING: Before rolling back {kib}, ensure that the version you wish to rollback to is compatible with -your {es} cluster. If the version you're rolling back to is not compatible, you will have to also rollback {es}. -Any changes made after an upgrade will be lost when rolling back to a previous version. +WARNING: Before rolling back {kib}, ensure that the version you want to rollback to is compatible with +your {es} cluster. If the version you're rolling back to is not compatible, you must also rollback {es}. +Any changes made after an upgrade are lost when rolling back to a previous version. -In order to rollback after a failed upgrade migration, the saved object indices have to be +To rollback after a failed upgrade migration, the saved object indices have to be rolled back to be compatible with the previous {kib} version. [float] -===== Rollback by restoring a backup snapshot: +===== Rollback by restoring a backup snapshot -1. Before proceeding, {ref}/snapshots-take-snapshot.html[take a snapshot] that contains the `kibana` feature state. +. Before proceeding, {ref}/snapshots-take-snapshot.html[take a snapshot] that contains the `kibana` feature state. Snapshots include this feature state by default. -2. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. -3. Delete all saved object indices with `DELETE /.kibana*` -4. {ref}/snapshots-restore-snapshot.html[Restore] the `kibana` feature state from the snapshot. -5. Start up all {kib} instances on the older version you wish to rollback to. +. To make sure no {kib} instances are performing an upgrade migration, shut down all {kib} instances. +. Delete all saved object indices with `DELETE /.kibana*`. +. {ref}/snapshots-restore-snapshot.html[Restore] the `kibana` feature state from the snapshot. +. Start all {kib} instances on the older version you want to rollback to. [float] -===== (Not recommended) Rollback without a backup snapshot: +===== (Not recommended) Rollback without a backup snapshot -1. Shutdown all {kib} instances to be 100% sure that there are no {kib} instances currently performing a migration. -2. {ref}/snapshots-take-snapshot.html[Take a snapshot] that includes the `kibana` feature state. Snapshots include this feature state by default. -3. Delete the version specific indices created by the failed upgrade migration. For example, if you wish to rollback from a failed upgrade to v7.12.0 `DELETE /.kibana_7.12.0_*,.kibana_task_manager_7.12.0_*` -4. Inspect the output of `GET /_cat/aliases`. -If either the `.kibana` and/or `.kibana_task_manager` alias is missing, these will have to be created manually. +. To make sure no {kib} instances are performing an upgrade migration, shut down all {kib} instances. +. {ref}/snapshots-take-snapshot.html[Take a snapshot] that includes the `kibana` feature state. By default, snapshots include the feature state. +. Delete the version-specific indices created by the failed upgrade migration. ++ +For example, to rollback from a failed upgrade +to v7.12.0: `DELETE /.kibana_7.12.0_*,.kibana_task_manager_7.12.0_*` +. Inspect the output of `GET /_cat/aliases`. ++ +If the `.kibana` or `.kibana_task_manager` aliases are missing, you must create them manually. Find the latest index from the output of `GET /_cat/indices` and create the missing alias to point to the latest index. -For example. if the `.kibana` alias was missing and the latest index is `.kibana_3` create a new alias with `POST /.kibana_3/_aliases/.kibana`. -5. Remove the write block from the rollback indices. `PUT /.kibana,.kibana_task_manager/_settings {"index.blocks.write": false}` -6. Start up {kib} on the older version you wish to rollback to. +For example, if the `.kibana` alias is missing, and the latest index is `.kibana_3`, create a new alias with `POST /.kibana_3/_aliases/.kibana`. +. To remove the write block from the rollback indices: +`PUT /.kibana,.kibana_task_manager/_settings {"index.blocks.write": false}` +. Start {kib} on the older version you want to rollback to. [float] [[upgrade-migrations-old-indices]] ==== Handling old `.kibana_N` indices -After migrations have completed, there will be multiple {kib} indices in {es}: (`.kibana_1`, `.kibana_2`, `.kibana_7.12.0` etc). {kib} only uses the index that the `.kibana` and `.kibana_task_manager` alias points to. The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. +After the migrations complete, multiple {kib} indices are created in {es}: (`.kibana_1`, `.kibana_2`, `.kibana_7.12.0` etc). +{kib} only uses the index that the `.kibana` and `.kibana_task_manager` aliases point to. +The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. From 8e8c4b8d96a6bc9a23b9d58996882fedef8147be Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Wed, 9 Feb 2022 10:11:10 -0700 Subject: [PATCH 19/85] [Canvas] Migrate by value embeddables (#123515) * Add migrations for by value embeddables Check for id in embeddable input Removed unused import Fixed tests Fix variable name Move migration into embeddable function definition Remove unused code * Cleanup * Fix embeddable test * Remove check for by-value embeddables in embeddable function migration * Removed unused import Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../functions/external/embeddable.test.ts | 11 +++++-- .../functions/external/embeddable.ts | 32 ++++++++++++++++++- .../functions/external/index.ts | 1 + .../canvas/canvas_plugin_src/plugin.ts | 1 + x-pack/plugins/canvas/server/plugin.ts | 1 + 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts index 001fb0e3f62e3..ad25b8a3081a1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.test.ts @@ -9,7 +9,6 @@ import { embeddableFunctionFactory } from './embeddable'; import { getQueryFilters } from '../../../common/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; import { encode } from '../../../common/lib/embeddable_dataurl'; -import { InitializeArguments } from '.'; const filterContext: ExpressionValueFilter = { type: 'filter', @@ -32,8 +31,16 @@ const filterContext: ExpressionValueFilter = { ], }; +const embeddablePersistableStateServiceMock = { + extract: jest.fn(), + inject: jest.fn(), + getAllMigrations: jest.fn(), +}; + describe('embeddable', () => { - const fn = embeddableFunctionFactory({} as InitializeArguments)().fn; + const fn = embeddableFunctionFactory({ + embeddablePersistableStateService: embeddablePersistableStateServiceMock, + })().fn; const config = { id: 'some-id', timerange: { from: '15m', to: 'now' }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts index 7ef8f0a09eb90..8ec299c60dae5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts @@ -5,7 +5,16 @@ * 2.0. */ -import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { mapValues } from 'lodash'; +import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; +import { + ExpressionFunctionDefinition, + ExpressionAstFunction, +} from 'src/plugins/expressions/common'; +import { + MigrateFunction, + MigrateFunctionsObject, +} from '../../../../../../src/plugins/kibana_utils/common'; import { ExpressionValueFilter, EmbeddableInput } from '../../../types'; import { EmbeddableExpressionType, EmbeddableExpression } from '../../expression_types'; import { getFunctionHelp } from '../../../i18n'; @@ -45,6 +54,22 @@ export function embeddableFunctionFactory({ return function embeddable(): EmbeddableFunction { const { help, args: argHelp } = getFunctionHelp().embeddable; + const migrateByValueEmbeddable = + ( + migrateFn: MigrateFunction + ): MigrateFunction => + (state: ExpressionAstFunction): ExpressionAstFunction => { + const embeddableInput = decode(state.arguments.config[0] as string); + + const embeddableType = state.arguments.type[0]; + const migratedInput = migrateFn({ ...embeddableInput, type: embeddableType }); + + state.arguments.config[0] = encode(migratedInput); + state.arguments.type[0] = migratedInput.type as string; + + return state; + }; + return { name: 'embeddable', help, @@ -140,6 +165,11 @@ export function embeddableFunctionFactory({ } return state; }, + + migrations: mapValues< + MigrateFunctionsObject, + MigrateFunction + >(embeddablePersistableStateService.getAllMigrations(), migrateByValueEmbeddable), }; }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts index 1d69e181b5fd9..29200d938b53a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/index.ts @@ -16,6 +16,7 @@ export interface InitializeArguments { embeddablePersistableStateService: { extract: EmbeddableStart['extract']; inject: EmbeddableStart['inject']; + getAllMigrations: EmbeddableStart['getAllMigrations']; }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts index 591795637aebe..6153d20b657f5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts @@ -46,6 +46,7 @@ export class CanvasSrcPlugin implements Plugin embeddablePersistableStateService: { extract: depsStart.embeddable.extract, inject: depsStart.embeddable.inject, + getAllMigrations: depsStart.embeddable.getAllMigrations, }, }); plugins.canvas.addFunctions(externalFunctions); diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts index 27b6186216b69..172d1e8dd8bf1 100644 --- a/x-pack/plugins/canvas/server/plugin.ts +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -58,6 +58,7 @@ export class CanvasPlugin implements Plugin { embeddablePersistableStateService: { extract: plugins.embeddable.extract, inject: plugins.embeddable.inject, + getAllMigrations: plugins.embeddable.getAllMigrations, }, }); From 153b7e135c6a8a24a6b29efece8cd7ca70251fcd Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 9 Feb 2022 12:22:08 -0500 Subject: [PATCH 20/85] [Fleet] Fix fleet server hosts client validation (#125085) --- .../use_fleet_server_host_form.test.tsx | 83 +++++++++++++++++++ .../use_fleet_server_host_form.tsx | 7 +- .../plugins/fleet/public/hooks/use_input.ts | 26 +++--- 3 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx new file mode 100644 index 0000000000000..151a3d5354c17 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-test-renderer'; + +import { createFleetTestRendererMock } from '../../../../../../mock'; + +import { useFleetServerHostsForm } from './use_fleet_server_host_form'; + +jest.mock('../../services/agent_and_policies_count', () => ({ + ...jest.requireActual('../../services/agent_and_policies_count'), + getAgentAndPolicyCount: () => ({ agentCount: 0, agentPolicyCount: 0 }), +})); +jest.mock('../../hooks/use_confirm_modal', () => ({ + ...jest.requireActual('../../hooks/use_confirm_modal'), + useConfirmModal: () => ({ confirm: () => true }), +})); + +describe('useFleetServerHostsForm', () => { + it('should not allow to submit an invalid form', async () => { + const testRenderer = createFleetTestRendererMock(); + const onSucess = jest.fn(); + const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); + + act(() => + result.current.fleetServerHostsInput.props.onChange(['http://test.fr', 'http://test.fr']) + ); + + await act(() => result.current.submit()); + + expect(result.current.fleetServerHostsInput.props.errors).toMatchInlineSnapshot(` + Array [ + Object { + "index": 0, + "message": "Duplicate URL", + }, + Object { + "index": 1, + "message": "Duplicate URL", + }, + ] + `); + expect(onSucess).not.toBeCalled(); + expect(result.current.isDisabled).toBeTruthy(); + }); + + it('should submit a valid form', async () => { + const testRenderer = createFleetTestRendererMock(); + const onSucess = jest.fn(); + testRenderer.startServices.http.post.mockResolvedValue({}); + const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); + + act(() => result.current.fleetServerHostsInput.props.onChange(['http://test.fr'])); + + await act(() => result.current.submit()); + expect(onSucess).toBeCalled(); + }); + + it('should allow the user to correct and submit a invalid form', async () => { + const testRenderer = createFleetTestRendererMock(); + const onSucess = jest.fn(); + testRenderer.startServices.http.post.mockResolvedValue({}); + const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); + + act(() => + result.current.fleetServerHostsInput.props.onChange(['http://test.fr', 'http://test.fr']) + ); + + await act(() => result.current.submit()); + expect(onSucess).not.toBeCalled(); + expect(result.current.isDisabled).toBeTruthy(); + + act(() => result.current.fleetServerHostsInput.props.onChange(['http://test.fr'])); + expect(result.current.isDisabled).toBeFalsy(); + + await act(() => result.current.submit()); + expect(onSucess).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx index 230985352da58..afe96713f065d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx @@ -142,7 +142,7 @@ export function useFleetServerHostsForm( const submit = useCallback(async () => { try { - if (!validate) { + if (!validate()) { return; } const { agentCount, agentPolicyCount } = await getAgentAndPolicyCount(); @@ -178,9 +178,12 @@ export function useFleetServerHostsForm( } }, [fleetServerHostsInput.value, validate, notifications, confirm, onSuccess]); + const isDisabled = + isLoading || !fleetServerHostsInput.hasChanged || fleetServerHostsInput.props.isInvalid; + return { isLoading, - isDisabled: isLoading || !fleetServerHostsInput.hasChanged, + isDisabled, submit, fleetServerHostsInput, }; diff --git a/x-pack/plugins/fleet/public/hooks/use_input.ts b/x-pack/plugins/fleet/public/hooks/use_input.ts index 1c89fb232a66e..435cfec95b028 100644 --- a/x-pack/plugins/fleet/public/hooks/use_input.ts +++ b/x-pack/plugins/fleet/public/hooks/use_input.ts @@ -125,11 +125,22 @@ export function useComboInput( const isInvalid = errors !== undefined; + const validateCallback = useCallback(() => { + if (validate) { + const newErrors = validate(value); + setErrors(newErrors); + + return newErrors === undefined; + } + + return true; + }, [validate, value]); + const onChange = useCallback( (newValues: string[]) => { setValue(newValues); - if (errors && validate && validate(newValues) === undefined) { - setErrors(undefined); + if (errors && validate) { + setErrors(validate(newValues)); } }, [validate, errors] @@ -149,16 +160,7 @@ export function useComboInput( setValue([]); }, setValue, - validate: () => { - if (validate) { - const newErrors = validate(value); - setErrors(newErrors); - - return newErrors === undefined; - } - - return true; - }, + validate: validateCallback, hasChanged, }; } From 809246721d966a084b1c748c2839528b8636303b Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Wed, 9 Feb 2022 10:28:39 -0700 Subject: [PATCH 21/85] [ResponseOps] Change the duration/percentile display format to mm:ss (#124647) * Change the duration/percentile display format to mm:ss * Addressed comments * Add time format to tooltip * Addressed comments, percentiles can show N/A * Fix flaky test * remove only * address comments, now tests for N/A Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/lib/monitoring_utils.test.ts | 23 +++++++--- .../application/lib/monitoring_utils.ts | 17 ++++++-- .../components/alerts_list.test.tsx | 42 ++++++++++++------- .../alerts_list/components/alerts_list.tsx | 23 ++++------ .../components/rule_duration_format.tsx | 39 +++++++++++++++++ .../alert_create_flyout.ts | 2 +- .../apps/triggers_actions_ui/alerts_list.ts | 27 ++++++++---- 7 files changed, 126 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_duration_format.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.test.ts index 151fd001f9fa1..2ac04ffd6eaaf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.test.ts @@ -4,7 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getFormattedSuccessRatio, getFormattedRuleExecutionPercentile } from './monitoring_utils'; +import { + getFormattedSuccessRatio, + getFormattedDuration, + getFormattedMilliseconds, +} from './monitoring_utils'; describe('monitoring_utils', () => { it('should return a decimal as a percent', () => { @@ -12,9 +16,18 @@ describe('monitoring_utils', () => { expect(getFormattedSuccessRatio(0.75345345345345)).toEqual('75%'); }); - it('should return percentiles as an integer', () => { - expect(getFormattedRuleExecutionPercentile(0)).toEqual('0ms'); - expect(getFormattedRuleExecutionPercentile(100.5555)).toEqual('101ms'); - expect(getFormattedRuleExecutionPercentile(99.1111)).toEqual('99ms'); + it('should return a formatted duration', () => { + expect(getFormattedDuration(0)).toEqual('00:00'); + expect(getFormattedDuration(100.111)).toEqual('00:00'); + expect(getFormattedDuration(50000)).toEqual('00:50'); + expect(getFormattedDuration(500000)).toEqual('08:20'); + expect(getFormattedDuration(5000000)).toEqual('83:20'); + expect(getFormattedDuration(50000000)).toEqual('833:20'); + }); + + it('should format a duration as an integer', () => { + expect(getFormattedMilliseconds(0)).toEqual('0 ms'); + expect(getFormattedMilliseconds(100.5555)).toEqual('101 ms'); + expect(getFormattedMilliseconds(99.1111)).toEqual('99 ms'); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.ts index 29c03f118436f..f5bec63056103 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/monitoring_utils.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import moment from 'moment'; import numeral from '@elastic/numeral'; export function getFormattedSuccessRatio(successRatio: number) { @@ -11,7 +12,17 @@ export function getFormattedSuccessRatio(successRatio: number) { return `${formatted}%`; } -export function getFormattedRuleExecutionPercentile(percentile: number) { - const formatted = numeral(percentile).format('0,0'); - return `${formatted}ms`; +export function getFormattedDuration(value: number) { + if (!value) { + return '00:00'; + } + const duration = moment.duration(value); + const minutes = Math.floor(duration.asMinutes()).toString().padStart(2, '0'); + const seconds = duration.seconds().toString().padStart(2, '0'); + return `${minutes}:${seconds}`; +} + +export function getFormattedMilliseconds(value: number) { + const formatted = numeral(value).format('0,0'); + return `${formatted} ms`; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 2ca32ec04cd70..28aa0b2097aba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -18,6 +18,8 @@ import { ALERTS_FEATURE_ID, parseDuration, } from '../../../../../../alerting/common'; +import { getFormattedDuration, getFormattedMilliseconds } from '../../../lib/monitoring_utils'; + import { useKibana } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); @@ -180,22 +182,22 @@ describe('alerts_list component with items', () => { history: [ { success: true, - duration: 100, + duration: 1000000, }, { success: true, - duration: 200, + duration: 200000, }, { success: false, - duration: 300, + duration: 300000, }, ], calculated_metrics: { success_ratio: 0.66, - p50: 200, - p95: 300, - p99: 300, + p50: 200000, + p95: 300000, + p99: 300000, }, }, }, @@ -227,18 +229,18 @@ describe('alerts_list component with items', () => { history: [ { success: true, - duration: 100, + duration: 100000, }, { success: true, - duration: 500, + duration: 500000, }, ], calculated_metrics: { success_ratio: 1, p50: 0, - p95: 100, - p99: 500, + p95: 100000, + p99: 500000, }, }, }, @@ -458,7 +460,7 @@ describe('alerts_list component with items', () => { wrapper.update(); expect(wrapper.find('.euiToolTipPopover').text()).toBe( - 'The length of time it took for the rule to run.' + 'The length of time it took for the rule to run (mm:ss).' ); // Status column @@ -508,14 +510,24 @@ describe('alerts_list component with items', () => { ).toBeTruthy(); let percentiles = wrapper.find( - `EuiTableRowCell[data-test-subj="alertsTableCell-ruleExecutionPercentile"] span[data-test-subj="${Percentiles.P50}Percentile"]` + `EuiTableRowCell[data-test-subj="alertsTableCell-ruleExecutionPercentile"] span[data-test-subj="rule-duration-format-value"]` ); mockedAlertsData.forEach((rule, index) => { if (typeof rule.monitoring?.execution.calculated_metrics.p50 === 'number') { + // Ensure the table cells are getting the correct values expect(percentiles.at(index).text()).toEqual( - `${rule.monitoring.execution.calculated_metrics.p50}ms` + getFormattedDuration(rule.monitoring.execution.calculated_metrics.p50) ); + // Ensure the tooltip is showing the correct content + expect( + wrapper + .find( + 'EuiTableRowCell[data-test-subj="alertsTableCell-ruleExecutionPercentile"] [data-test-subj="rule-duration-format-tooltip"]' + ) + .at(index) + .props().content + ).toEqual(getFormattedMilliseconds(rule.monitoring.execution.calculated_metrics.p50)); } else { expect(percentiles.at(index).text()).toEqual('N/A'); } @@ -581,13 +593,13 @@ describe('alerts_list component with items', () => { ).toBeTruthy(); percentiles = wrapper.find( - `EuiTableRowCell[data-test-subj="alertsTableCell-ruleExecutionPercentile"] span[data-test-subj="${Percentiles.P95}Percentile"]` + `EuiTableRowCell[data-test-subj="alertsTableCell-ruleExecutionPercentile"] span[data-test-subj="rule-duration-format-value"]` ); mockedAlertsData.forEach((rule, index) => { if (typeof rule.monitoring?.execution.calculated_metrics.p95 === 'number') { expect(percentiles.at(index).text()).toEqual( - `${rule.monitoring.execution.calculated_metrics.p95}ms` + getFormattedDuration(rule.monitoring.execution.calculated_metrics.p95) ); } else { expect(percentiles.at(index).text()).toEqual('N/A'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 40b5e981c181e..72228c285238d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -86,14 +86,9 @@ import { ManageLicenseModal } from './manage_license_modal'; import { checkAlertTypeEnabled } from '../../../lib/check_alert_type_enabled'; import { RuleEnabledSwitch } from './rule_enabled_switch'; import { PercentileSelectablePopover } from './percentile_selectable_popover'; -import { - formatMillisForDisplay, - shouldShowDurationWarning, -} from '../../../lib/execution_duration_utils'; -import { - getFormattedSuccessRatio, - getFormattedRuleExecutionPercentile, -} from '../../../lib/monitoring_utils'; +import { RuleDurationFormat } from './rule_duration_format'; +import { shouldShowDurationWarning } from '../../../lib/execution_duration_utils'; +import { getFormattedSuccessRatio } from '../../../lib/monitoring_utils'; const ENTER_KEY = 13; @@ -396,7 +391,7 @@ export const AlertsList: React.FunctionComponent = () => { content={i18n.translate( 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.ruleExecutionPercentileTooltip', { - defaultMessage: `{percentileOrdinal} percentile of this rule's past {sampleLimit} execution durations`, + defaultMessage: `{percentileOrdinal} percentile of this rule's past {sampleLimit} execution durations (mm:ss).`, values: { percentileOrdinal: percentileOrdinals[selectedPercentile!], sampleLimit: MONITORING_HISTORY_LIMIT, @@ -420,7 +415,7 @@ export const AlertsList: React.FunctionComponent = () => { const renderPercentileCellValue = (value: number) => { return ( - {typeof value === 'number' ? getFormattedRuleExecutionPercentile(value) : 'N/A'} + ); }; @@ -630,7 +625,7 @@ export const AlertsList: React.FunctionComponent = () => { content={i18n.translate( 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.durationTitle', { - defaultMessage: 'The length of time it took for the rule to run.', + defaultMessage: 'The length of time it took for the rule to run (mm:ss).', } )} > @@ -651,7 +646,7 @@ export const AlertsList: React.FunctionComponent = () => { return ( <> - {`${formatMillisForDisplay(value)}`} + {} {showDurationWarning && ( { ); }, }, + getPercentileColumn(), { field: 'monitoring.execution.calculated_metrics.success_ratio', width: '12%', @@ -680,7 +676,7 @@ export const AlertsList: React.FunctionComponent = () => { content={i18n.translate( 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.successRatioTitle', { - defaultMessage: 'How often this rule executes successfully', + defaultMessage: 'How often this rule executes successfully.', } )} > @@ -701,7 +697,6 @@ export const AlertsList: React.FunctionComponent = () => { ); }, }, - getPercentileColumn(), { field: 'executionStatus.status', name: i18n.translate( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_duration_format.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_duration_format.tsx new file mode 100644 index 0000000000000..b6512b93d8a94 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_duration_format.tsx @@ -0,0 +1,39 @@ +/* + * 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, { memo, useMemo } from 'react'; +import { EuiToolTip } from '@elastic/eui'; +import { getFormattedDuration, getFormattedMilliseconds } from '../../../lib/monitoring_utils'; + +interface Props { + duration: number; + allowZero?: boolean; +} + +export const RuleDurationFormat = memo((props: Props) => { + const { duration, allowZero = true } = props; + + const formattedDuration = useMemo(() => { + if (allowZero || typeof duration === 'number') { + return getFormattedDuration(duration); + } + return 'N/A'; + }, [duration, allowZero]); + + const formattedTooltip = useMemo(() => { + if (allowZero || typeof duration === 'number') { + return getFormattedMilliseconds(duration); + } + return 'N/A'; + }, [duration, allowZero]); + + return ( + + {formattedDuration} + + ); +}); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index eaf71c107edd9..95ff24fc8beef 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -168,7 +168,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { tags: '', interval: '1 min', }); - expect(searchResultAfterSave.duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(searchResultAfterSave.duration).to.match(/\d{2,}:\d{2}/); // clean up created alert const alertsToDelete = await getAlertsByName(alertName); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index 0edf65b4e3d42..2b45a12790107 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -81,7 +81,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(searchResults[0].name).to.equal(`${createdAlert.name}Test: Noop`); expect(searchResults[0].interval).to.equal('1 min'); expect(searchResults[0].tags).to.equal('2'); - expect(searchResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(searchResults[0].duration).to.match(/\d{2,}:\d{2}/); }); it('should update alert list on the search clear button click', async () => { @@ -103,7 +103,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(searchResults[0].name).to.equal('bTest: Noop'); expect(searchResults[0].interval).to.equal('1 min'); expect(searchResults[0].tags).to.equal('2'); - expect(searchResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(searchResults[0].duration).to.match(/\d{2,}:\d{2}/); const searchClearButton = await find.byCssSelector('.euiFormControlLayoutClearButton'); await searchClearButton.click(); @@ -115,11 +115,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(searchResultsAfterClear[0].name).to.equal('bTest: Noop'); expect(searchResultsAfterClear[0].interval).to.equal('1 min'); expect(searchResultsAfterClear[0].tags).to.equal('2'); - expect(searchResultsAfterClear[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(searchResultsAfterClear[0].duration).to.match(/\d{2,}:\d{2}/); expect(searchResultsAfterClear[1].name).to.equal('cTest: Noop'); expect(searchResultsAfterClear[1].interval).to.equal('1 min'); expect(searchResultsAfterClear[1].tags).to.equal(''); - expect(searchResultsAfterClear[1].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(searchResultsAfterClear[1].duration).to.match(/\d{2,}:\d{2}/); }); it('should search for tags', async () => { @@ -136,7 +136,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(searchResults[0].name).to.equal(`${createdAlert.name}Test: Noop`); expect(searchResults[0].interval).to.equal('1 min'); expect(searchResults[0].tags).to.equal('3'); - expect(searchResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(searchResults[0].duration).to.match(/\d{2,}:\d{2}/); }); it('should display an empty list when search did not return any alerts', async () => { @@ -369,11 +369,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.existOrFail('P50Percentile'); await retry.try(async () => { + const percentileCell = await find.byCssSelector( + '[data-test-subj="P50Percentile"]:nth-of-type(1)' + ); + const percentileCellText = await percentileCell.getVisibleText(); + expect(percentileCellText).to.match(/^N\/A|\d{2,}:\d{2}$/); + await testSubjects.click('percentileSelectablePopover-iconButton'); await testSubjects.existOrFail('percentileSelectablePopover-selectable'); const searchClearButton = await find.byCssSelector( '[data-test-subj="percentileSelectablePopover-selectable"] li:nth-child(2)' ); + const alertResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(alertResults[0].duration).to.match(/^N\/A|\d{2,}:\d{2}$/); + await searchClearButton.click(); await testSubjects.missingOrFail('percentileSelectablePopover-selectable'); await testSubjects.existOrFail('alertsTable-P95ColumnName'); @@ -427,7 +436,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(filterErrorOnlyResults[0].name).to.equal(`${failingAlert.name}Test: Failing`); expect(filterErrorOnlyResults[0].interval).to.equal('30 sec'); expect(filterErrorOnlyResults[0].status).to.equal('Error'); - expect(filterErrorOnlyResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(filterErrorOnlyResults[0].duration).to.match(/\d{2,}:\d{2}/); }); }); @@ -440,7 +449,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(refreshResults[0].name).to.equal(`${createdAlert.name}Test: Noop`); expect(refreshResults[0].interval).to.equal('1 min'); expect(refreshResults[0].status).to.equal('Ok'); - expect(refreshResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(refreshResults[0].duration).to.match(/\d{2,}:\d{2}/); }); const alertsErrorBannerWhenNoErrors = await find.allByCssSelector( @@ -484,7 +493,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(filterFailingAlertOnlyResults.length).to.equal(1); expect(filterFailingAlertOnlyResults[0].name).to.equal(`${failingAlert.name}Test: Failing`); expect(filterFailingAlertOnlyResults[0].interval).to.equal('30 sec'); - expect(filterFailingAlertOnlyResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(filterFailingAlertOnlyResults[0].duration).to.match(/\d{2,}:\d{2}/); }); }); @@ -518,7 +527,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { `${noopAlertWithAction.name}Test: Noop` ); expect(filterWithSlackOnlyResults[0].interval).to.equal('1 min'); - expect(filterWithSlackOnlyResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); + expect(filterWithSlackOnlyResults[0].duration).to.match(/\d{2,}:\d{2}/); }); await testSubjects.click('alertTypeFilterButton'); From 3c73b605aa32bbf1d90dcad48253e8cbfe142410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Casper=20H=C3=BCbertz?= Date: Wed, 9 Feb 2022 19:00:26 +0100 Subject: [PATCH 22/85] [Unified Observability] Overview style updates (#124702) * Big chunk of style updates * New layout and position for news and resources * Alerts updated * Rename headings and links * Removed unncessary prop * More fixes * Remove active status * Fixing tests * fix tests * fix checks Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ester Marti --- .../components/app/chart_container/index.tsx | 4 +- .../public/components/app/news_feed/index.tsx | 85 +++++++------- .../public/components/app/resources/index.tsx | 2 +- .../components/app/section/alerts/index.tsx | 108 ++++++++++-------- .../components/app/section/apm/index.test.tsx | 18 +-- .../components/app/section/apm/index.tsx | 4 +- .../public/components/app/section/index.tsx | 19 +-- .../components/app/section/logs/index.tsx | 6 +- .../components/app/section/metrics/index.tsx | 4 +- .../components/app/section/uptime/index.tsx | 6 +- .../components/app/section/ux/index.test.tsx | 6 +- .../components/app/section/ux/index.tsx | 2 +- .../public/pages/overview/data_sections.tsx | 2 +- .../pages/overview/old_overview_page.tsx | 42 ++++--- 14 files changed, 164 insertions(+), 144 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx index 6251afc1013a8..5164684e3490b 100644 --- a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx @@ -18,12 +18,12 @@ interface Props { children: React.ReactNode; } -const CHART_HEIGHT = 170; +const CHART_HEIGHT = 120; export function ChartContainer({ isInitialLoad, children, - iconSize = 'xl', + iconSize = 'l', height = CHART_HEIGHT, }: Props) { if (isInitialLoad) { diff --git a/x-pack/plugins/observability/public/components/app/news_feed/index.tsx b/x-pack/plugins/observability/public/components/app/news_feed/index.tsx index 6f1a5f33b9ba7..bdf82213aaa07 100644 --- a/x-pack/plugins/observability/public/components/app/news_feed/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news_feed/index.tsx @@ -9,7 +9,7 @@ import { EuiErrorBoundary, EuiFlexGroup, EuiFlexItem, - EuiHorizontalRule, + EuiPanel, EuiLink, EuiText, EuiTitle, @@ -56,48 +56,49 @@ function NewsItem({ item }: { item: INewsItem }) { const theme = useContext(ThemeContext); return ( - - - -

{item.title.en}

-
-
- - - - - - - {limitString(item.description.en, 128)} - - + + + + +

{item.title.en}

+
+
+ + + + + + + {limitString(item.description.en, 128)} + + + + + + {i18n.translate('xpack.observability.news.readFullStory', { + defaultMessage: 'Read full story', + })} + + + + + + {item.image_url?.en && ( - - - {i18n.translate('xpack.observability.news.readFullStory', { - defaultMessage: 'Read full story', - })} - - + {item.title.en} - - - {item.image_url?.en && ( - - {item.title.en} - - )} -
-
- -
+ )} +
+
+
+ ); } diff --git a/x-pack/plugins/observability/public/components/app/resources/index.tsx b/x-pack/plugins/observability/public/components/app/resources/index.tsx index 763b30860cd78..f2bcca594653e 100644 --- a/x-pack/plugins/observability/public/components/app/resources/index.tsx +++ b/x-pack/plugins/observability/public/components/app/resources/index.tsx @@ -42,7 +42,7 @@ const resources = [ export function Resources() { return ( - +

diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 328de71ac7874..77ea5eeea14ba 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -9,15 +9,14 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, - EuiHorizontalRule, - EuiIconTip, EuiLink, EuiText, EuiSpacer, EuiTitle, - EuiButton, + EuiButtonEmpty, EuiLoadingSpinner, EuiCallOut, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -68,7 +67,7 @@ export function AlertsSection() { return ( - + ); @@ -99,7 +98,12 @@ export function AlertsSection() { return (
- +

@@ -110,73 +114,77 @@ export function AlertsSection() { - + {i18n.translate('xpack.observability.overview.alert.appLink', { - defaultMessage: 'Manage alerts', + defaultMessage: 'Show all alerts', })} - + <> - + setFilter(e.target.value)} + prepend="Show" /> - + {alerts .filter((alert) => filter === ALL_TYPES || alert.consumer === filter) .map((alert, index) => { - const isLastElement = index === alerts.length - 1; return ( - - - - {alert.name} - - - - - - {alert.alertTypeId} - - {alert.tags.map((tag, idx) => { - return ( - - {tag} - - ); - })} - - - - - - - Updated {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago - - - {alert.muteAll && ( + + + + - + {alert.name} + + + + + + {alert.alertTypeId} + + {alert.tags.map((tag, idx) => { + return ( + + {tag} + + ); })} - /> + + + + + {alert.muteAll && ( + + + {i18n.translate('xpack.observability.overview.alerts.muted', { + defaultMessage: 'Muted', + })} + + + )} + + + Last updated{' '} + {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago + + + - )} - + + - {!isLastElement && } ); })} diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index 35835cd0bc8e6..5e45eda0d3176 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -84,12 +84,12 @@ describe('APMSection', () => { status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }); - const { getByText, queryAllByTestId } = render( + const { getByRole, getByText, queryAllByTestId } = render( ); - expect(getByText('APM')).toBeInTheDocument(); - expect(getByText('View in app')).toBeInTheDocument(); + expect(getByRole('heading')).toHaveTextContent('Services'); + expect(getByText('Show service inventory')).toBeInTheDocument(); expect(getByText('Services 11')).toBeInTheDocument(); expect(getByText('Throughput 900.0 tpm')).toBeInTheDocument(); expect(queryAllByTestId('loading')).toEqual([]); @@ -101,12 +101,12 @@ describe('APMSection', () => { status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }); - const { getByText, queryAllByTestId } = render( + const { getByRole, getByText, queryAllByTestId } = render( ); - expect(getByText('APM')).toBeInTheDocument(); - expect(getByText('View in app')).toBeInTheDocument(); + expect(getByRole('heading')).toHaveTextContent('Services'); + expect(getByText('Show service inventory')).toBeInTheDocument(); expect(getByText('Services 11')).toBeInTheDocument(); expect(getByText('Throughput 312.00k tpm')).toBeInTheDocument(); expect(queryAllByTestId('loading')).toEqual([]); @@ -117,13 +117,13 @@ describe('APMSection', () => { status: fetcherHook.FETCH_STATUS.LOADING, refetch: jest.fn(), }); - const { getByText, queryAllByText, getByTestId } = render( + const { getByRole, queryAllByText, getByTestId } = render( ); - expect(getByText('APM')).toBeInTheDocument(); + expect(getByRole('heading')).toHaveTextContent('Services'); expect(getByTestId('loading')).toBeInTheDocument(); - expect(queryAllByText('View in app')).toEqual([]); + expect(queryAllByText('Show service inventory')).toEqual([]); expect(queryAllByText('Services 11')).toEqual([]); expect(queryAllByText('Throughput 312.00k tpm')).toEqual([]); }); diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 11565cfb972e7..6c61ecb3f270e 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -93,12 +93,12 @@ export function APMSection({ bucketSize }: Props) { return ( + +
{title}
} extraAction={ appLink?.href && ( - + {appLink.label} - + ) } > <> - - {hasError ? : <>{children}} - + {hasError ? : <>{children}}
diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 0ff2c203c7707..78c23638a91bd 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -92,17 +92,17 @@ export function LogsSection({ bucketSize }: Props) { return ( - +

{i18n.translate('xpack.observability.overview.logs.subtitle', { defaultMessage: 'Logs rate per minute', diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 8cd49efe4787a..f7f35552fb686 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -202,12 +202,12 @@ export function MetricsSection({ bucketSize }: Props) { return ( { ); expect(getByText('User Experience')).toBeInTheDocument(); - expect(getByText('View in app')).toBeInTheDocument(); + expect(getByText('Show dashboard')).toBeInTheDocument(); expect(getByText('elastic-co-frontend')).toBeInTheDocument(); expect(getByText('Largest contentful paint')).toBeInTheDocument(); expect(getByText('1.94 s')).toBeInTheDocument(); @@ -113,7 +113,7 @@ describe('UXSection', () => { expect(getByText('User Experience')).toBeInTheDocument(); expect(getAllByText('--')).toHaveLength(3); - expect(queryAllByText('View in app')).toEqual([]); + expect(queryAllByText('Show dashboard')).toEqual([]); expect(getByText('elastic-co-frontend')).toBeInTheDocument(); }); it('shows empty state', () => { @@ -128,7 +128,7 @@ describe('UXSection', () => { expect(getByText('User Experience')).toBeInTheDocument(); expect(getAllByText('No data is available.')).toHaveLength(3); - expect(queryAllByText('View in app')).toEqual([]); + expect(queryAllByText('Show dashboard')).toEqual([]); expect(getByText('elastic-co-frontend')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx index 3092c7bf77f7a..6863916f9bb8c 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx @@ -57,7 +57,7 @@ export function UXSection({ bucketSize }: Props) { appLink={{ href: appLink, label: i18n.translate('xpack.observability.overview.ux.appLink', { - defaultMessage: 'View in app', + defaultMessage: 'Show dashboard', }), }} hasError={status === FETCH_STATUS.FAILURE} diff --git a/x-pack/plugins/observability/public/pages/overview/data_sections.tsx b/x-pack/plugins/observability/public/pages/overview/data_sections.tsx index 335f527560c7a..19827cd3eb459 100644 --- a/x-pack/plugins/observability/public/pages/overview/data_sections.tsx +++ b/x-pack/plugins/observability/public/pages/overview/data_sections.tsx @@ -23,7 +23,7 @@ interface Props { export function DataSections({ bucketSize }: Props) { return ( - + diff --git a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx index 7100a0552876d..a6af0d9182215 100644 --- a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel, EuiHorizontalRule } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { useTrackPageview } from '../..'; @@ -100,30 +100,38 @@ export function OverviewPage({ routeParams }: Props) { {hasData && ( <> - - - {/* Data sections */} - {hasAnyData && } - - - - - {/* Resources / What's New sections */} - - - - {!!newsFeed?.items?.length && } - - + + + {hasDataMap?.alert?.hasData && ( - + )} + + {/* Data sections */} + {hasAnyData && } + + + + + + + + {/* Resources / What's New sections */} + + + {!!newsFeed?.items?.length && } + + + + + + )} From a208ae974f4a2784fe7c050f7921da33258732a7 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Wed, 9 Feb 2022 13:26:04 -0500 Subject: [PATCH 23/85] Change index-pattern text to data view (#125108) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/canvas/i18n/functions/dict/escount.ts | 2 +- x-pack/plugins/canvas/i18n/functions/dict/esdocs.ts | 2 +- x-pack/plugins/canvas/i18n/ui.ts | 2 +- .../canvas/server/routes/es_fields/es_fields.test.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/canvas/i18n/functions/dict/escount.ts b/x-pack/plugins/canvas/i18n/functions/dict/escount.ts index af1337360ba6d..c831213e1f923 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/escount.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/escount.ts @@ -26,7 +26,7 @@ export const help: FunctionHelp> = { }, }), index: i18n.translate('xpack.canvas.functions.escount.args.indexHelpText', { - defaultMessage: 'An index or index pattern. For example, {example}.', + defaultMessage: 'An index or data view. For example, {example}.', values: { example: '`"logstash-*"`', }, diff --git a/x-pack/plugins/canvas/i18n/functions/dict/esdocs.ts b/x-pack/plugins/canvas/i18n/functions/dict/esdocs.ts index 6be5acdb8bc90..99979b566f529 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/esdocs.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/esdocs.ts @@ -35,7 +35,7 @@ export const help: FunctionHelp> = { defaultMessage: 'A comma-separated list of fields. For better performance, use fewer fields.', }), index: i18n.translate('xpack.canvas.functions.esdocs.args.indexHelpText', { - defaultMessage: 'An index or index pattern. For example, {example}.', + defaultMessage: 'An index or data view. For example, {example}.', values: { example: '`"logstash-*"`', }, diff --git a/x-pack/plugins/canvas/i18n/ui.ts b/x-pack/plugins/canvas/i18n/ui.ts index dcbaf92abbe4e..2448db2d99904 100644 --- a/x-pack/plugins/canvas/i18n/ui.ts +++ b/x-pack/plugins/canvas/i18n/ui.ts @@ -405,7 +405,7 @@ export const DataSourceStrings = { }), getIndexLabel: () => i18n.translate('xpack.canvas.uis.dataSources.esdocs.indexLabel', { - defaultMessage: 'Enter an index name or select an index pattern', + defaultMessage: 'Enter an index name or select a data view', }), getQueryTitle: () => i18n.translate('xpack.canvas.uis.dataSources.esdocs.queryTitle', { diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index 977aaacdc2669..21b3357703866 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -30,7 +30,7 @@ describe('Retrieve ES Fields', () => { routeHandler = routerDeps.router.get.mock.calls[0][1]; }); - it(`returns 200 with fields from existing index/index pattern`, async () => { + it(`returns 200 with fields from existing index/data view`, async () => { const index = 'test'; const mockResults = { body: { @@ -85,7 +85,7 @@ describe('Retrieve ES Fields', () => { `); }); - it(`returns 200 with empty object when index/index pattern has no fields`, async () => { + it(`returns 200 with empty object when index/data view has no fields`, async () => { const index = 'test'; const mockResults = { body: { indices: [index], fields: {} } }; const request = httpServerMock.createKibanaRequest({ @@ -107,7 +107,7 @@ describe('Retrieve ES Fields', () => { expect(response.payload).toMatchInlineSnapshot('Object {}'); }); - it(`returns 200 with empty object when index/index pattern does not have specified field(s)`, async () => { + it(`returns 200 with empty object when index/data view does not have specified field(s)`, async () => { const index = 'test'; const mockResults = { From a1a273238e676947898ea582ef8e3faebb637e5c Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Wed, 9 Feb 2022 14:39:17 -0600 Subject: [PATCH 24/85] [DOCS] Updates the 8.0.0 upgrade docs (#125128) * [DOCS] Updates the 8.0.0 upgrade docs * Update docs/setup/upgrade/upgrade-migrations.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/setup/upgrade/upgrade-migrations.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/setup/upgrade/upgrade-migrations.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/setup/upgrade/upgrade-migrations.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/setup/upgrade/upgrade-migrations.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/setup/upgrade/upgrade-migrations.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/setup/upgrade.asciidoc | 23 ++--- .../logging-configuration-changes.asciidoc | 2 +- .../setup/upgrade/upgrade-migrations.asciidoc | 93 +++++++++---------- 3 files changed, 57 insertions(+), 61 deletions(-) diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index 4eabfa0c07714..98713e75d24f6 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -1,33 +1,30 @@ [[upgrade]] == Upgrade {kib} -To upgrade from 7.16 or earlier to {version}, -**you must first upgrade to {prev-major-last}**. -This enables you to use the Upgrade Assistant to -{stack-ref}/upgrading-elastic-stack.html#prepare-to-upgrade[prepare to upgrade]. -You must resolve all critical issues identified by the Upgrade Assistant -before proceeding with the upgrade. +To upgrade from 7.16.0 or earlier to {version}, +**you must first upgrade to {prev-major-last}**, which enables you to use the *Upgrade Assistant* to +{stack-ref}/upgrading-elastic-stack.html#prepare-to-upgrade[prepare for the upgrade]. +Before you upgrade, you must resolve all critical issues identified by the *Upgrade Assistant*. -{kib} does not support rolling upgrades. -You must shut down all {kib} instances, install the new software, and restart {kib}. +Rolling upgrades are unsupported in {kib}. To upgrade, +you must shut down all {kib} instances, install the new software, and restart {kib}. Upgrading while older {kib} instances are running can cause data loss or upgrade failures. [WARNING] ==== -{kib} automatically runs <> -when required. +When required, {kib} automatically migrates <>. In case of an upgrade failure, you can roll back to an earlier version of {kib}. To roll back, you **must** have a {ref}/snapshot-restore.html[backup snapshot] that includes the `kibana` feature -state. Snapshots include this feature state by default. +state. By default, snapshots include the `kibana` feature state. ==== For more information about upgrading, refer to {stack-ref}/upgrading-elastic-stack.html[Upgrading to Elastic {version}.] IMPORTANT: You can upgrade to pre-release versions for testing, -but upgrading from a pre-release to the General Available version is not supported. -Pre-releases should only be used for testing in a temporary environment. +but upgrading from a pre-release to the General Available version is unsupported. +You should use pre-release versions only for testing in a temporary environment. include::upgrade/upgrade-migrations.asciidoc[leveloffset=-1] diff --git a/docs/setup/upgrade/logging-configuration-changes.asciidoc b/docs/setup/upgrade/logging-configuration-changes.asciidoc index 4d5f5f732536e..4a9d03d3b5312 100644 --- a/docs/setup/upgrade/logging-configuration-changes.asciidoc +++ b/docs/setup/upgrade/logging-configuration-changes.asciidoc @@ -2,7 +2,7 @@ [[logging-config-changes]] === Logging configuration changes -WARNING: {kib} 8.0 and later uses a new logging system. Be sure to read the documentation for your version of {kib} before proceeding. +WARNING: {kib} 8.0.0 and later uses a new logging system. Before you upgrade, read the documentation for your {kib} version. [[logging-pattern-format-old-and-new-example]] [options="header"] diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index 53b89b38cc88d..059ae47d2e476 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -2,14 +2,14 @@ [[saved-object-migrations]] === Saved object migrations -Every time {kib} is upgraded it will perform an upgrade migration to ensure that all <> are compatible with the new version. +Each time you upgrade {kib}, an upgrade migration is performed to ensure that all <> are compatible with the new version. -NOTE: 6.7 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[Upgrade Assistant] -to help you prepare for your upgrade to 7.0. To access the assistant, go to *Management > 7.0 Upgrade Assistant*. +NOTE: To help you prepare for the upgrade to 7.0.0, 6.7.0 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[*Upgrade Assistant*]. +To access the assistant, go to *Management > 7.0 Upgrade Assistant*. -WARNING: {kib} 7.12.0 and later uses a new migration process and index naming scheme. Be sure to read the documentation for your version of {kib} before proceeding. +WARNING: {kib} 7.12.0 and later uses a new migration process and index naming scheme. Before you upgrade, read the documentation for your version of {kib}. -WARNING: The following instructions assumes {kib} is using the default index names. If the `kibana.index` or `xpack.tasks.index` configuration settings were changed these instructions will have to be adapted accordingly. +WARNING: The following instructions assumes {kib} is using the default index names. If the `kibana.index` or `xpack.tasks.index` configuration settings are different from the default, adapt the instructions accordingly. [float] [[upgrade-migrations-process]] @@ -17,16 +17,16 @@ WARNING: The following instructions assumes {kib} is using the default index nam Saved objects are stored in two indices: -* `.kibana_{kibana_version}_001`, e.g. for Kibana v7.12.0 `.kibana_7.12.0_001`. -* `.kibana_task_manager_{kibana_version}_001`, e.g. for Kibana v7.12.0 `.kibana_task_manager_7.12.0_001`. +* `.kibana_{kibana_version}_001`, e.g. for {kib} 7.12.0 `.kibana_7.12.0_001`. +* `.kibana_task_manager_{kibana_version}_001`, e.g. for {kib} 7.12.0 `.kibana_task_manager_7.12.0_001`. -The index aliases `.kibana` and `.kibana_task_manager` will always point to +The index aliases `.kibana` and `.kibana_task_manager` always point to the most up-to-date saved object indices. When you start a new {kib} installation, an upgrade migration is performed before starting plugins or serving HTTP traffic. Before you upgrade, shut down old nodes to prevent losing acknowledged writes. To reduce the likelihood of old nodes losing acknowledged writes, {kib} 7.12.0 and later -adds a write block to the outdated index. Table 1 lists the saved objects indices used by previous versions of {kib}. +adds a write block to the outdated index. Table 1 lists the saved objects indices used by previous {kib} versions. .Saved object indices and aliases per {kib} version [options="header"] @@ -46,8 +46,8 @@ adds a write block to the outdated index. Table 1 lists the saved objects indice When upgrading several {kib} instances connected to the same {es} cluster, ensure that all outdated instances are shut down before starting the upgrade. -{kib} does not support rolling upgrades. However, once outdated instances are shut down, -all upgraded instances can be started in parallel, in which case all instances will participate in the upgrade migration in parallel. +Rolling upgrades are unsupported in {kib}. However, when outdated instances are shut down, you can start all upgraded instances in parallel, +which allows all instances to participate in the upgrade migration in parallel. For large deployments with more than 10 {kib} instances, and more than 10,000 saved objects, you can reduce the upgrade downtime by bringing up a single {kib} instance and waiting for it to @@ -56,7 +56,7 @@ complete the upgrade migration before bringing up the remaining instances. [float] [[preventing-migration-failures]] ==== Preventing migration failures -This section highlights common causes of {kib} upgrade failures and how to prevent them. +Review the common causes of {kib} upgrade failures and how to prevent them. [float] ===== timeout_exception or receive_timeout_transport_exception @@ -71,18 +71,18 @@ Error: Unable to complete saved object migrations for the [.kibana] index. Pleas Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] -------------------------------------------- -Instructions to work around this issue are in https://github.com/elastic/kibana/issues/95321[this GitHub issue]. +For instructions on how to mitigate the known issue, refer to https://github.com/elastic/kibana/issues/95321[the GitHub issue]. [float] ===== Corrupt saved objects -We highly recommend testing your {kib} upgrade in a development cluster to find and remedy problems -caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. +To find and remedy problems caused by corrupt documents, we highly recommend testing your {kib} upgrade in a development cluster, +especially when there are custom integrations that create saved objects in your environment. -Saved objects that were corrupted through manual editing or integrations will cause migration -failures with a log message like `Unable to migrate the corrupt Saved Object document ...`. +Saved objects that are corrupted through manual editing or integrations cause migration +failures with a log message, such as `Unable to migrate the corrupt Saved Object document ...`. For a successful upgrade migration, you must fix or delete corrupt documents. -For example, given the following error message: +For example, you receive the following error message: [source,sh] -------------------------------------------- @@ -112,18 +112,18 @@ DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab . Restart {kib}. + -In this example, the dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **is no longer available**. +The dashboard with the `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` ID that belongs to the `marketing_space` space **is no longer available**. -Be sure you have a snapshot before you delete the corrupt document. If restoring from a snapshot is not an option, it is recommended to also delete the `temp` and `target` indices the migration created before restarting {kib} and retrying. +Be sure you have a snapshot before you delete the corrupt document. If you are unable to restore from a snapshot, it is recommended to also delete the `temp` and `target` indices the migration creates before you restart {kib} and retry the snapshot restore. [float] -===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings +===== User defined index templates that cause new `.kibana*` indices to have incompatible settings or mappings Matching index templates that specify `settings.refresh_interval` or `mappings` are known to interfere with {kib} upgrades. -Prevention: Narrow down the {data-sources} of any user-defined index templates to ensure that these won't apply to new `.kibana*` indices. +To make sure the index templates won't apply to new `.kibana*` indices, narrow down the {data-sources} of any user-defined index templates. -NOTE: {kib} < 6.5 creates it's own index template called `kibana_index_template:.kibana` -and uses an index pattern of `.kibana`. This index template will not interfere and does not need to be changed or removed. +NOTE: In {kib} 6.5.0 and earlier, {kib} creates a `kibana_index_template:.kibana` index template +and uses a `.kibana` index pattern. You do not need to change or remove the index template. [float] ===== An unhealthy {es} cluster @@ -140,7 +140,7 @@ Ensure that all {kib} instances are running the same version, configuration, and [float] ===== Incompatible `xpack.tasks.index` configuration setting -For {kib} 7.5.0 and earlier, when the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, +In {kib} 7.5.0 and earlier, when the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, upgrade migrations fail. In {kib} 7.5.1 and later, the incompatible configuration setting prevents upgrade migrations from starting. [float] @@ -149,56 +149,55 @@ upgrade migrations fail. In {kib} 7.5.1 and later, the incompatible configuratio If {kib} unexpectedly terminates while migrating a saved object index, {kib} automatically attempts to perform the migration again when the process restarts. Do not delete any saved objects indices to -attempt to fix a failed migration. Unlike previous versions, {kib} 7.12.0 and -later does not require deleting any indices to release a failed migration lock. +to fix a failed migration. Unlike previous versions, {kib} 7.12.0 and +later does not require deleting indices to release a failed migration lock. -If upgrade migrations fail repeatedly, follow the advice in +If upgrade migrations fail repeatedly, refer to <>. -Once the root cause for the migration failure has been addressed, -{kib} will automatically retry the migration without any further intervention. -If you're unable to resolve a failed migration following these steps, please contact support. +When you address the root cause for the migration failure, +{kib} automatically retries the migration. +If you're unable to resolve a failed migration, contact Support. [float] [[upgrade-migrations-rolling-back]] ==== Rolling back to a previous version of {kib} -If you've followed the advice in <> -and <> and -If {kib} is still unable to upgrade successfully, rollback {kib} until +If you've followed <> +and <>, and +{kib} is still unable to successfully upgrade, rollback {kib} until you're able to identify and fix the root cause. -WARNING: Before rolling back {kib}, ensure that the version you want to rollback to is compatible with -your {es} cluster. If the version you're rolling back to is not compatible, you must also rollback {es}. -Any changes made after an upgrade are lost when rolling back to a previous version. +WARNING: Before you roll back {kib}, ensure that the version you want to roll back to is compatible with +your {es} cluster. If the version you want to roll back to is not compatible, you must also rollback {es}. +Any changes made after an upgrade are lost when you roll back to a previous version. -To rollback after a failed upgrade migration, the saved object indices have to be -rolled back to be compatible with the previous {kib} version. +To roll back after a failed upgrade migration, you must also rollback the saved object indices to be compatible with the previous {kib} version. [float] -===== Rollback by restoring a backup snapshot +===== Roll back by restoring a backup snapshot . Before proceeding, {ref}/snapshots-take-snapshot.html[take a snapshot] that contains the `kibana` feature state. - Snapshots include this feature state by default. + By default, snapshots include the `kibana` feature state. . To make sure no {kib} instances are performing an upgrade migration, shut down all {kib} instances. -. Delete all saved object indices with `DELETE /.kibana*`. +. To delete all saved object indices, use `DELETE /.kibana*`. . {ref}/snapshots-restore-snapshot.html[Restore] the `kibana` feature state from the snapshot. . Start all {kib} instances on the older version you want to rollback to. [float] -===== (Not recommended) Rollback without a backup snapshot +===== (Not recommended) Roll back without a backup snapshot . To make sure no {kib} instances are performing an upgrade migration, shut down all {kib} instances. -. {ref}/snapshots-take-snapshot.html[Take a snapshot] that includes the `kibana` feature state. By default, snapshots include the feature state. +. {ref}/snapshots-take-snapshot.html[Take a snapshot] that includes the `kibana` feature state. By default, snapshots include the `kibana` feature state. . Delete the version-specific indices created by the failed upgrade migration. + For example, to rollback from a failed upgrade -to v7.12.0: `DELETE /.kibana_7.12.0_*,.kibana_task_manager_7.12.0_*` +to v7.12.0, use `DELETE /.kibana_7.12.0_*,.kibana_task_manager_7.12.0_*`. . Inspect the output of `GET /_cat/aliases`. + If the `.kibana` or `.kibana_task_manager` aliases are missing, you must create them manually. Find the latest index from the output of `GET /_cat/indices` and create the missing alias to point to the latest index. -For example, if the `.kibana` alias is missing, and the latest index is `.kibana_3`, create a new alias with `POST /.kibana_3/_aliases/.kibana`. -. To remove the write block from the rollback indices: +For example, if the `.kibana` alias is missing, and the latest index is `.kibana_3`, create a new alias using `POST /.kibana_3/_aliases/.kibana`. +. To remove the write block from the roll back indices, use `PUT /.kibana,.kibana_task_manager/_settings {"index.blocks.write": false}` . Start {kib} on the older version you want to rollback to. From c78a4f355cc5546753db03ab5ccedb60bbf8b9ed Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 9 Feb 2022 20:43:57 +0000 Subject: [PATCH 25/85] chore(NA): upgrade bazelisk into v1.11.0 (#125070) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .bazeliskversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazeliskversion b/.bazeliskversion index 4dae2985b58cc..1cac385c6cb86 100644 --- a/.bazeliskversion +++ b/.bazeliskversion @@ -1 +1 @@ -1.10.1 +1.11.0 From 855c0148c7d889b78f929d008c13ca654870989c Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Wed, 9 Feb 2022 14:52:39 -0600 Subject: [PATCH 26/85] [DOCS] Reformats the Security settings tables into definition lists (#123965) * [DOCS] Reformats the Security settings tables into definition lists * Review comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/settings/security-settings.asciidoc | 430 +++++++++-------------- 1 file changed, 157 insertions(+), 273 deletions(-) diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 56d08ee24efe1..787efa64f0775 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -8,10 +8,6 @@ You do not need to configure any additional settings to use the {security-features} in {kib}. They are enabled by default. -[float] -[[general-security-settings]] -==== General security settings - [float] [[authentication-security-settings]] ==== Authentication security settings @@ -46,123 +42,80 @@ xpack.security.authc: <3> Specifies the settings for the SAML authentication provider with a `saml1` name. <4> Specifies the settings for the SAML authentication provider with a `saml2` name. -The valid settings in the `xpack.security.authc.providers` namespace vary depending on the authentication provider type. For more information, refer to <>. - [float] [[authentication-provider-settings]] -===== Valid settings for all authentication providers - -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`..enabled` {ess-icon} -| Determines if the authentication provider should be enabled. By default, {kib} enables the provider as soon as you configure any of its properties. - -| `xpack.security.authc.providers.` -`..order` {ess-icon} -| Order of the provider in the authentication chain and on the Login Selector UI. - -| `xpack.security.authc.providers.` -`..description` {ess-icon} -| Custom description of the provider entry displayed on the Login Selector UI. - -| `xpack.security.authc.providers.` -`..hint` {ess-icon} -| Custom hint for the provider entry displayed on the Login Selector UI. - -| `xpack.security.authc.providers.` -`..icon` {ess-icon} -| Custom icon for the provider entry displayed on the Login Selector UI. - -| `xpack.security.authc.providers..` -`.showInSelector` {ess-icon} -| Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. - -2+a| -[TIP] -[NOTE] -============ -You are unable to set this setting to `false` for `basic` and `token` authentication providers. -============ - -| `xpack.security.authc.providers..` -`.accessAgreement.message` {ess-icon} -| Access agreement text in Markdown format. For more information, refer to <>. - -| [[xpack-security-provider-session-idleTimeout]] `xpack.security.authc.providers..` -`.session.idleTimeout` {ess-icon} -| Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>. - -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ - -| [[xpack-security-provider-session-lifespan]] `xpack.security.authc.providers..` -`.session.lifespan` {ess-icon} -| Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If -this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>. +==== Valid settings for all authentication providers + +The valid settings in the `xpack.security.authc.providers` namespace vary depending on the authentication provider type. For more information, refer to <>. + +xpack.security.authc.providers...enabled {ess-icon}:: +Determines if the authentication provider should be enabled. By default, {kib} enables the provider as soon as you configure any of its properties. + +xpack.security.authc.providers...order {ess-icon}:: +Order of the provider in the authentication chain and on the Login Selector UI. + +xpack.security.authc.providers...description {ess-icon}:: +Custom description of the provider entry displayed on the Login Selector UI. -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ +xpack.security.authc.providers...hint {ess-icon}:: +Custom hint for the provider entry displayed on the Login Selector UI. -|=== +xpack.security.authc.providers...icon {ess-icon}:: +Custom icon for the provider entry displayed on the Login Selector UI. + +xpack.security.authc.providers...showInSelector {ess-icon}:: +Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. ++ +NOTE: You are unable to set this setting to `false` for `basic` and `token` authentication providers. + +xpack.security.authc.providers...accessAgreement.message {ess-icon}:: +Access agreement text in Markdown format. For more information, refer to <>. + +[[xpack-security-provider-session-idleTimeout]] xpack.security.authc.providers...session.idleTimeout {ess-icon}:: +Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>. ++ +NOTE: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). + +[[xpack-security-provider-session-lifespan]] xpack.security.authc.providers...session.lifespan {ess-icon}:: +Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If +this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>. ++ +NOTE: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). [float] [[saml-authentication-provider-settings]] -===== SAML authentication provider settings +==== SAML authentication provider settings In addition to <>, you can specify the following settings: -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`saml..realm` {ess-icon} -| SAML realm in {es} that provider should use. +xpack.security.authc.providers.saml..realm {ess-icon}:: +SAML realm in {es} that provider should use. -| `xpack.security.authc.providers.` -`saml..useRelayStateDeepLink` {ess-icon} -| Determines if the provider should treat the `RelayState` parameter as a deep link in {kib} during Identity Provider initiated log in. By default, this setting is set to `false`. The link specified in `RelayState` should be a relative, URL-encoded {kib} URL. For example, the `/app/dashboards#/list` link in `RelayState` parameter would look like this: `RelayState=%2Fapp%2Fdashboards%23%2Flist`. - -|=== +xpack.security.authc.providers.saml..useRelayStateDeepLink {ess-icon}:: +Determines if the provider should treat the `RelayState` parameter as a deep link in {kib} during Identity Provider initiated log in. By default, this setting is set to `false`. The link specified in `RelayState` should be a relative, URL-encoded {kib} URL. For example, the `/app/dashboards#/list` link in `RelayState` parameter would look like this: `RelayState=%2Fapp%2Fdashboards%23%2Flist`. [float] [[oidc-authentication-provider-settings]] -===== OpenID Connect authentication provider settings +==== OpenID Connect authentication provider settings In addition to <>, you can specify the following settings: -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`oidc..realm` {ess-icon} -| OpenID Connect realm in {es} that the provider should use. - -|=== +xpack.security.authc.providers.oidc..realm {ess-icon}:: +OpenID Connect realm in {es} that the provider should use. [float] [[anonymous-authentication-provider-settings]] -===== Anonymous authentication provider settings +==== Anonymous authentication provider settings In addition to <>, you can specify the following settings: -[NOTE] -============ -You can configure only one anonymous provider per {kib} instance. -============ - -[cols="2*<"] -|=== -| `xpack.security.authc.providers.` -`anonymous..credentials` {ess-icon} -| Credentials that {kib} should use internally to authenticate anonymous requests to {es}. Possible values are: username and password, API key, or the constant `elasticsearch_anonymous_user` if you want to leverage {ref}/anonymous-access.html[{es} anonymous access]. - -2+a| For example: +NOTE: You can configure only one anonymous provider per {kib} instance. +xpack.security.authc.providers.anonymous..credentials {ess-icon}:: +Credentials that {kib} should use internally to authenticate anonymous requests to {es}. Possible values are: username and password, API key, or the constant `elasticsearch_anonymous_user` if you want to leverage {ref}/anonymous-access.html[{es} anonymous access]. ++ +For example: ++ [source,yaml] ---------------------------------------- # Username and password credentials @@ -187,45 +140,35 @@ xpack.security.authc.providers.anonymous.anonymous1: credentials: "elasticsearch_anonymous_user" ---------------------------------------- -|=== - [float] [[http-authentication-settings]] -===== HTTP authentication settings +==== HTTP authentication settings There is a very limited set of cases when you'd want to change these settings. For more information, refer to <>. -[cols="2*<"] -|=== -| `xpack.security.authc.http.enabled` -| Determines if HTTP authentication should be enabled. By default, this setting is set to `true`. - -| `xpack.security.authc.http.autoSchemesEnabled` -| Determines if HTTP authentication schemes used by the enabled authentication providers should be automatically supported during HTTP authentication. By default, this setting is set to `true`. +xpack.security.authc.http.enabled:: +Determines if HTTP authentication should be enabled. By default, this setting is set to `true`. -| `xpack.security.authc.http.schemes[]` -| List of HTTP authentication schemes that {kib} HTTP authentication should support. By default, this setting is set to `['apikey', 'bearer']` to support HTTP authentication with the <> and <> schemes. +xpack.security.authc.http.autoSchemesEnabled:: +Determines if HTTP authentication schemes used by the enabled authentication providers should be automatically supported during HTTP authentication. By default, this setting is set to `true`. -|=== +xpack.security.authc.http.schemes[]:: +List of HTTP authentication schemes that {kib} HTTP authentication should support. By default, this setting is set to `['apikey', 'bearer']` to support HTTP authentication with the <> and <> schemes. [float] [[login-ui-settings]] -===== Login user interface settings +==== Login user interface settings You can configure the following settings in the `kibana.yml` file. -[cols="2*<"] -|=== -| `xpack.security.loginAssistanceMessage` {ess-icon} -| Adds a message to the login UI. Useful for displaying information about maintenance windows, links to corporate sign up pages, and so on. +xpack.security.loginAssistanceMessage {ess-icon}:: +Adds a message to the login UI. Useful for displaying information about maintenance windows, links to corporate sign up pages, and so on. -| `xpack.security.loginHelp` {ess-icon} -| Adds a message accessible at the login UI with additional help information for the login process. +xpack.security.loginHelp {ess-icon}:: +Adds a message accessible at the login UI with additional help information for the login process. -| `xpack.security.authc.selector.enabled` {ess-icon} -| Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured. - -|=== +xpack.security.authc.selector.enabled {ess-icon}:: +Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured. [float] [[security-session-and-cookie-settings]] @@ -233,81 +176,49 @@ You can configure the following settings in the `kibana.yml` file. You can configure the following settings in the `kibana.yml` file. -[cols="2*<"] -|=== -| `xpack.security.cookieName` - | Sets the name of the cookie used for the session. The default value is `"sid"`. - -|[[xpack-security-encryptionKey]] `xpack.security.encryptionKey` - | An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By - default, a value is automatically generated in memory. If you use that default - behavior, all sessions are invalidated when {kib} restarts. - In addition, high-availability deployments of {kib} will behave unexpectedly - if this setting isn't the same for all instances of {kib}. - -|[[xpack-security-secureCookies]] `xpack.security.secureCookies` - | Sets the `secure` flag of the session cookie. The default value is `false`. It - is automatically set to `true` if <> is set to `true`. Set - this to `true` if SSL is configured outside of {kib} (for example, you are - routing requests through a load balancer or proxy). - -| [[xpack-security-sameSiteCookies]] `xpack.security.sameSiteCookies` {ess-icon} - | Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. - Valid values are `Strict`, `Lax`, `None`. - This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting <>: `true`. - -|[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} - | Ensures that user sessions will expire after a period of inactivity. This and <> are both -highly recommended. You can also specify this setting for <>. If this is set to `0`, then sessions will never expire due to inactivity. By default, this value is 8 hours. - -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ - -|[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} - | Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If -this is set to `0`, user sessions could stay active indefinitely. This and <> are both highly -recommended. You can also specify this setting for <>. By default, this value is 30 days. +xpack.security.cookieName:: +Sets the name of the cookie used for the session. The default value is `"sid"`. -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ +[[xpack-security-encryptionKey]] xpack.security.encryptionKey:: +An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By default, a value is automatically generated in memory. If you use that default behavior, all sessions are invalidated when {kib} restarts. In addition, high-availability deployments of {kib} will behave unexpectedly if this setting isn't the same for all instances of {kib}. -| `xpack.security.session.cleanupInterval` {ess-icon} -| Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds. +[[xpack-security-secureCookies]] xpack.security.secureCookies:: +Sets the `secure` flag of the session cookie. The default value is `false`. It +is automatically set to `true` if <> is set to `true`. Set this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). -2+a| -[TIP] -============ -Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). -============ +[[xpack-security-sameSiteCookies]] xpack.security.sameSiteCookies {ess-icon}:: +Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. +Valid values are `Strict`, `Lax`, `None`. +This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting <>: `true`. -|=== +[[xpack-session-idleTimeout]] xpack.security.session.idleTimeout {ess-icon}:: +Ensures that user sessions will expire after a period of inactivity. This and <> are both highly recommended. You can also specify this setting for <>. If this is set to `0`, then sessions will never expire due to inactivity. By default, this value is 8 hours. ++ +NOTE: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). + +[[xpack-session-lifespan]] xpack.security.session.lifespan {ess-icon}:: +Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If this is set to `0`, user sessions could stay active indefinitely. This and <> are both highly +recommended. You can also specify this setting for <>. By default, this value is 30 days. ++ +TIP: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). + +xpack.security.session.cleanupInterval {ess-icon}:: +Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds. ++ +TIP: Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). [[security-encrypted-saved-objects-settings]] ==== Encrypted saved objects settings These settings control the encryption of saved objects with sensitive data. For more details, refer to <>. -[IMPORTANT] -============ -In high-availability deployments, make sure you use the same encryption and decryption keys for all instances of {kib}. Although the keys can be specified in clear text in `kibana.yml`, it's recommended to store them securely in the <>. -============ +IMPORTANT: In high-availability deployments, make sure you use the same encryption and decryption keys for all instances of {kib}. Although the keys can be specified in clear text in `kibana.yml`, it's recommended to store them securely in the <>. -[cols="2*<"] -|=== -| [[xpack-encryptedSavedObjects-encryptionKey]] `xpack.encryptedSavedObjects.` -`encryptionKey` -| An arbitrary string of at least 32 characters that is used to encrypt sensitive properties of saved objects before they're stored in {es}. If not set, {kib} will generate a random key on startup, but certain features won't be available until you set the encryption key explicitly. +[[xpack-encryptedSavedObjects-encryptionKey]] xpack.encryptedSavedObjects.encryptionKey:: +An arbitrary string of at least 32 characters that is used to encrypt sensitive properties of saved objects before they're stored in {es}. If not set, {kib} will generate a random key on startup, but certain features won't be available until you set the encryption key explicitly. -| [[xpack-encryptedSavedObjects-keyRotation-decryptionOnlyKeys]] `xpack.encryptedSavedObjects.` -`keyRotation.decryptionOnlyKeys` -| An optional list of previously used encryption keys. Like <>, these must be at least 32 characters in length. {kib} doesn't use these keys for encryption, but may still require them to decrypt some existing saved objects. Use this setting if you wish to change your encryption key, but don't want to lose access to saved objects that were previously encrypted with a different key. -|=== +[[xpack-encryptedSavedObjects-keyRotation-decryptionOnlyKeys]] xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys:: +An optional list of previously used encryption keys. Like <>, these must be at least 32 characters in length. {kib} doesn't use these keys for encryption, but may still require them to decrypt some existing saved objects. Use this setting if you wish to change your encryption key, but don't want to lose access to saved objects that were previously encrypted with a different key. [float] [[audit-logging-settings]] @@ -315,18 +226,17 @@ In high-availability deployments, make sure you use the same encryption and decr You can enable audit logging to support compliance, accountability, and security. When enabled, {kib} will capture: -- Who performed an action -- What action was performed -- When the action occurred +* Who performed an action +* What action was performed +* When the action occurred For more details and a reference of audit events, refer to <>. -[cols="2*<"] -|====== -| `xpack.security.audit.enabled` {ess-icon} -| Set to `true` to enable audit logging`. *Default:* `false` - -2+a| For example: +xpack.security.audit.enabled {ess-icon}:: +Set to `true` to enable audit logging`. *Default:* `false` ++ +For example: ++ [source,yaml] ---------------------------------------- xpack.security.audit.enabled: true @@ -346,128 +256,103 @@ xpack.security.audit.appender: <1> <2> Rotates log files every 24 hours. <3> Keeps maximum of 10 log files before deleting older ones. -| `xpack.security.audit.appender` -| Optional. Specifies where audit logs should be written to and how they should be formatted. If no appender is specified, a default appender will be used (see above). - -| `xpack.security.audit.appender.type` -| Required. Specifies where audit logs should be written to. Allowed values are `console`, `file`, or `rolling-file`. +xpack.security.audit.appender:: +Optional. Specifies where audit logs should be written to and how they should be formatted. If no appender is specified, a default appender will be used (see above). +xpack.security.audit.appender.type:: +Required. Specifies where audit logs should be written to. Allowed values are `console`, `file`, or `rolling-file`. ++ Refer to <> and <> for appender specific settings. -| `xpack.security.audit.appender.layout.type` -| Required. Specifies how audit logs should be formatted. Allowed values are `json` or `pattern`. - +xpack.security.audit.appender.layout.type:: +Required. Specifies how audit logs should be formatted. Allowed values are `json` or `pattern`. ++ Refer to <> for layout specific settings. - -2+a| -[TIP] -============ -We recommend using `json` format to allow ingesting {kib} audit logs into {es} using Filebeat. -============ - -|====== ++ +TIP: We recommend using `json` format to allow ingesting {kib} audit logs into {es} using Filebeat. [float] [[audit-logging-file-appender,file appender]] -===== File appender +==== File appender The `file` appender writes to a file and can be configured using the following settings: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.fileName` -| Required. Full file path the log file should be written to. -|====== +xpack.security.audit.appender.fileName:: +Required. Full file path the log file should be written to. [float] [[audit-logging-rolling-file-appender, rolling file appender]] -===== Rolling file appender +==== Rolling file appender The `rolling-file` appender writes to a file and rotates it using a rolling strategy, when a particular policy is triggered: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.fileName` -| Required. Full file path the log file should be written to. - -| `xpack.security.audit.appender.policy.type` -| Specifies when a rollover should occur. Allowed values are `size-limit` and `time-interval`. *Default:* `time-interval`. +xpack.security.audit.appender.fileName:: +Required. Full file path the log file should be written to. +xpack.security.audit.appender.policy.type:: +Specifies when a rollover should occur. Allowed values are `size-limit` and `time-interval`. *Default:* `time-interval`. ++ Refer to <> and <> for policy specific settings. -| `xpack.security.audit.appender.strategy.type` -| Specifies how the rollover should occur. Only allowed value is currently `numeric`. *Default:* `numeric` +xpack.security.audit.appender.strategy.type:: +Specifies how the rollover should occur. Only allowed value is currently `numeric`. *Default:* `numeric` ++ Refer to <> for strategy specific settings. -|====== [float] [[audit-logging-size-limit-policy, size limit policy]] -===== Size limit triggering policy +==== Size limit triggering policy The `size-limit` triggering policy will rotate the file when it reaches a certain size: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.policy.size` -| Maximum size the log file should reach before a rollover should be performed. *Default:* `100mb` -|====== +xpack.security.audit.appender.policy.size:: +Maximum size the log file should reach before a rollover should be performed. *Default:* `100mb` [float] [[audit-logging-time-interval-policy, time interval policy]] -===== Time interval triggering policy +==== Time interval triggering policy The `time-interval` triggering policy will rotate the file every given interval of time: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.policy.interval` -| How often a rollover should occur. *Default:* `24h` +xpack.security.audit.appender.policy.interval:: +How often a rollover should occur. *Default:* `24h` -| `xpack.security.audit.appender.policy.modulate` -| Whether the interval should be adjusted to cause the next rollover to occur on the interval boundary. *Default:* `true` -|====== +xpack.security.audit.appender.policy.modulate:: +Whether the interval should be adjusted to cause the next rollover to occur on the interval boundary. *Default:* `true` [float] [[audit-logging-numeric-strategy, numeric strategy]] -===== Numeric rolling strategy +==== Numeric rolling strategy The `numeric` rolling strategy will suffix the log file with a given pattern when rolling over, and will retain a fixed number of rolled files: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.strategy.pattern` -| Suffix to append to the file name when rolling over. Must include `%i`. *Default:* `-%i` +xpack.security.audit.appender.strategy.pattern:: +Suffix to append to the file name when rolling over. Must include `%i`. *Default:* `-%i` -| `xpack.security.audit.appender.strategy.max` -| Maximum number of files to keep. Once this number is reached, oldest files will be deleted. *Default:* `7` -|====== +xpack.security.audit.appender.strategy.max:: +Maximum number of files to keep. Once this number is reached, oldest files will be deleted. *Default:* `7` [float] [[audit-logging-pattern-layout, pattern layout]] -===== Pattern layout +==== Pattern layout The `pattern` layout outputs a string, formatted using a pattern with special placeholders, which will be replaced with data from the actual log message: -[cols="2*<"] -|====== -| `xpack.security.audit.appender.layout.pattern` -| Optional. Specifies how the log line should be formatted. *Default:* `[%date][%level][%logger]%meta %message` +xpack.security.audit.appender.layout.pattern:: +Optional. Specifies how the log line should be formatted. *Default:* `[%date][%level][%logger]%meta %message` -| `xpack.security.audit.appender.layout.highlight` -| Optional. Set to `true` to enable highlighting log messages with colors. -|====== +xpack.security.audit.appender.layout.highlight:: +Optional. Set to `true` to enable highlighting log messages with colors. [float] [[audit-logging-ignore-filters]] -===== Ignore filters - -[cols="2*<"] -|====== -| `xpack.security.audit.ignore_filters[]` {ess-icon} -| List of filters that determine which events should be excluded from the audit log. An event will get filtered out if at least one of the provided filters matches. - -2+a| For example: +==== Ignore filters +xpack.security.audit.ignore_filters[] {ess-icon}:: +List of filters that determine which events should be excluded from the audit log. An event will get filtered out if at least one of the provided filters matches. ++ +For example: ++ [source,yaml] ---------------------------------------- xpack.security.audit.ignore_filters: @@ -478,15 +363,14 @@ xpack.security.audit.ignore_filters: <1> Filters out HTTP request events <2> Filters out any data write events -| `xpack.security.audit.ignore_filters[].actions[]` {ess-icon} -| List of values matched against the `event.action` field of an audit event. Refer to <> for a list of available events. +xpack.security.audit.ignore_filters[].actions[] {ess-icon}:: +List of values matched against the `event.action` field of an audit event. Refer to <> for a list of available events. -| `xpack.security.audit.ignore_filters[].categories[]` {ess-icon} -| List of values matched against the `event.category` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-category.html[ECS categorization field] for allowed values. +xpack.security.audit.ignore_filters[].categories[] {ess-icon}:: +List of values matched against the `event.category` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-category.html[ECS categorization field] for allowed values. -| `xpack.security.audit.ignore_filters[].types[]` {ess-icon} -| List of values matched against the `event.type` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-type.html[ECS type field] for allowed values. +xpack.security.audit.ignore_filters[].types[] {ess-icon}:: +List of values matched against the `event.type` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-type.html[ECS type field] for allowed values. -| `xpack.security.audit.ignore_filters[].outcomes[]` {ess-icon} -| List of values matched against the `event.outcome` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-outcome.html[ECS outcome field] for allowed values. -|====== +xpack.security.audit.ignore_filters[].outcomes[] {ess-icon}:: +List of values matched against the `event.outcome` field of an audit event. Refer to https://www.elastic.co/guide/en/ecs/1.5/ecs-allowed-values-event-outcome.html[ECS outcome field] for allowed values. \ No newline at end of file From fcd564152008bdf8f4bbccab359eb6a48145ce35 Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Wed, 9 Feb 2022 22:14:41 +0100 Subject: [PATCH 27/85] Migrate the pre-7.11 tasks that has no schedule field. (#124304) * Migrate the pre-7.11 tasks that has no schedule field. --- .../server/saved_objects/migrations.test.ts | 62 +++++++++++++++++++ .../server/saved_objects/migrations.ts | 53 +++++++++++----- x-pack/plugins/task_manager/server/task.ts | 7 ++- .../es_archives/task_manager_tasks/data.json | 33 +++++++++- .../test_suites/task_manager/migrations.ts | 30 ++++++++- 5 files changed, 167 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts b/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts index 73141479d9081..e912eda258090 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts @@ -108,6 +108,68 @@ describe('successful migrations', () => { }); }); }); + + describe('8.2.0', () => { + test('resets attempts and status of a "failed" alerting tasks without schedule interval', () => { + const migration820 = getMigrations()['8.2.0']; + const taskInstance = getMockData({ + taskType: 'alerting:123', + status: 'failed', + schedule: undefined, + }); + + expect(migration820(taskInstance, migrationContext)).toEqual({ + ...taskInstance, + attributes: { + ...taskInstance.attributes, + attempts: 0, + status: 'idle', + }, + }); + }); + + test('resets attempts and status of a "running" alerting tasks without schedule interval', () => { + const migration820 = getMigrations()['8.2.0']; + const taskInstance = getMockData({ + taskType: 'alerting:123', + status: 'running', + schedule: undefined, + }); + + expect(migration820(taskInstance, migrationContext)).toEqual({ + ...taskInstance, + attributes: { + ...taskInstance.attributes, + attempts: 0, + status: 'idle', + }, + }); + }); + + test('does not update the tasks that are not "failed"', () => { + const migration820 = getMigrations()['8.2.0']; + const taskInstance = getMockData({ + taskType: 'alerting:123', + status: 'idle', + attempts: 3, + schedule: undefined, + }); + + expect(migration820(taskInstance, migrationContext)).toEqual(taskInstance); + }); + + test('does not update the tasks that are not "failed" and has a schedule', () => { + const migration820 = getMigrations()['8.2.0']; + const taskInstance = getMockData({ + taskType: 'alerting:123', + status: 'idle', + attempts: 3, + schedule: { interval: '1000' }, + }); + + expect(migration820(taskInstance, migrationContext)).toEqual(taskInstance); + }); + }); }); describe('handles errors during migrations', () => { diff --git a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts index 89bbb3d783881..f50b3d6a927ad 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts @@ -13,15 +13,15 @@ import { SavedObjectsUtils, SavedObjectUnsanitizedDoc, } from '../../../../../src/core/server'; -import { TaskInstance, TaskInstanceWithDeprecatedFields } from '../task'; +import { ConcreteTaskInstance, TaskStatus } from '../task'; interface TaskInstanceLogMeta extends LogMeta { - migrations: { taskInstanceDocument: SavedObjectUnsanitizedDoc }; + migrations: { taskInstanceDocument: SavedObjectUnsanitizedDoc }; } type TaskInstanceMigration = ( - doc: SavedObjectUnsanitizedDoc -) => SavedObjectUnsanitizedDoc; + doc: SavedObjectUnsanitizedDoc +) => SavedObjectUnsanitizedDoc; export function getMigrations(): SavedObjectMigrationMap { return { @@ -37,18 +37,19 @@ export function getMigrations(): SavedObjectMigrationMap { pipeMigrations(alertingTaskLegacyIdToSavedObjectIds, actionsTasksLegacyIdToSavedObjectIds), '8.0.0' ), + '8.2.0': executeMigrationWithErrorHandling( + pipeMigrations(resetAttemptsAndStatusForTheTasksWithoutSchedule), + '8.2.0' + ), }; } function executeMigrationWithErrorHandling( - migrationFunc: SavedObjectMigrationFn< - TaskInstanceWithDeprecatedFields, - TaskInstanceWithDeprecatedFields - >, + migrationFunc: SavedObjectMigrationFn, version: string ) { return ( - doc: SavedObjectUnsanitizedDoc, + doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext ) => { try { @@ -68,8 +69,8 @@ function executeMigrationWithErrorHandling( } function alertingTaskLegacyIdToSavedObjectIds( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { if (doc.attributes.taskType.startsWith('alerting:')) { let params: { spaceId?: string; alertId?: string } = {}; params = JSON.parse(doc.attributes.params as unknown as string); @@ -94,8 +95,8 @@ function alertingTaskLegacyIdToSavedObjectIds( } function actionsTasksLegacyIdToSavedObjectIds( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { if (doc.attributes.taskType.startsWith('actions:')) { let params: { spaceId?: string; actionTaskParamsId?: string } = {}; params = JSON.parse(doc.attributes.params as unknown as string); @@ -126,7 +127,7 @@ function actionsTasksLegacyIdToSavedObjectIds( function moveIntervalIntoSchedule({ attributes: { interval, ...attributes }, ...doc -}: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc { +}: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc { return { ...doc, attributes: { @@ -143,6 +144,28 @@ function moveIntervalIntoSchedule({ } function pipeMigrations(...migrations: TaskInstanceMigration[]): TaskInstanceMigration { - return (doc: SavedObjectUnsanitizedDoc) => + return (doc: SavedObjectUnsanitizedDoc) => migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc); } + +function resetAttemptsAndStatusForTheTasksWithoutSchedule( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + if (doc.attributes.taskType.startsWith('alerting:')) { + if ( + !doc.attributes.schedule?.interval && + (doc.attributes.status === TaskStatus.Failed || doc.attributes.status === TaskStatus.Running) + ) { + return { + ...doc, + attributes: { + ...doc.attributes, + attempts: 0, + status: TaskStatus.Idle, + }, + }; + } + } + + return doc; +} diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index 2452e3e6f4920..6d12a3f5984ca 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -307,7 +307,12 @@ export interface ConcreteTaskInstance extends TaskInstance { id: string; /** - * The saved object version from the Elaticsearch document. + * @deprecated This field has been moved under schedule (deprecated) with version 7.6.0 + */ + interval?: string; + + /** + * The saved object version from the Elasticsearch document. */ version?: string; diff --git a/x-pack/test/functional/es_archives/task_manager_tasks/data.json b/x-pack/test/functional/es_archives/task_manager_tasks/data.json index b59abd341a7af..3431419dda17e 100644 --- a/x-pack/test/functional/es_archives/task_manager_tasks/data.json +++ b/x-pack/test/functional/es_archives/task_manager_tasks/data.json @@ -58,4 +58,35 @@ "updated_at": "2020-11-30T15:43:08.277Z" } } -} \ No newline at end of file +} + +{ + "type": "doc", + "value": { + "id": "task:d33d7590-8377-11ec-8c11-2dfe94229b95", + "index": ".kibana_task_manager_1", + "source": { + "migrationVersion": { + "task": "7.6.0" + }, + "task": { + "taskType": "alerting:xpack.uptime.alerts.monitorStatus", + "retryAt": null, + "runAt": "2222-02-01T15:59:42.908Z", + "scope": [ + "alerting" + ], + "startedAt": null, + "state": "{}", + "params": "{\"alertId\":\"c9a10a61-7dcc-4297-991a-6c52c10eb7d2\",\"spaceId\":\"default\"}", + "ownerId": null, + "scheduledAt": "2022-02-01T15:58:41.000Z", + "attempts": 3, + "status": "failed" + }, + "references": [], + "updated_at": "2022-02-01T15:58:44.109Z", + "type": "task" + } + } +} diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts index 329aee7e74b98..1e6bb11c13583 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts @@ -8,7 +8,11 @@ import expect from '@kbn/expect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { TransportResult } from '@elastic/elasticsearch'; -import { TaskInstanceWithDeprecatedFields } from '../../../../plugins/task_manager/server/task'; +import { + ConcreteTaskInstance, + TaskInstanceWithDeprecatedFields, + TaskStatus, +} from '../../../../plugins/task_manager/server/task'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { SavedObjectsUtils } from '../../../../../src/core/server/saved_objects'; @@ -76,5 +80,29 @@ export default function createGetTests({ getService }: FtrProviderContext) { )}"}` ); }); + + it('8.2.0 migrates alerting tasks that has no schedule.interval', async () => { + const searchResult: TransportResult< + estypes.SearchResponse<{ task: ConcreteTaskInstance }>, + unknown + > = await es.search( + { + index: '.kibana_task_manager', + body: { + query: { + term: { + _id: 'task:d33d7590-8377-11ec-8c11-2dfe94229b95', + }, + }, + }, + }, + { meta: true } + ); + expect(searchResult.statusCode).to.equal(200); + expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1); + const hit = searchResult.body.hits.hits[0]; + expect(hit!._source!.task.attempts).to.be(0); + expect(hit!._source!.task.status).to.be(TaskStatus.Idle); + }); }); } From 46f679878b53c7c8d566379b5b38fdaa28d7bf31 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Wed, 9 Feb 2022 13:30:49 -0800 Subject: [PATCH 28/85] [DOCS] Removes Homebrew docs (#125155) --- docs/redirects.asciidoc | 5 ++ docs/setup/install.asciidoc | 8 --- docs/setup/install/brew-running.asciidoc | 9 ---- docs/setup/install/brew.asciidoc | 65 ------------------------ docs/setup/start-stop.asciidoc | 8 --- 5 files changed, 5 insertions(+), 90 deletions(-) delete mode 100644 docs/setup/install/brew-running.asciidoc delete mode 100644 docs/setup/install/brew.asciidoc diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 0ca518c3a8788..6a34076f10988 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -400,3 +400,8 @@ This content has moved. Refer to <>. == Upgrade Assistant This content has moved. Refer to {kibana-ref-all}/7.17/upgrade-assistant.html[Upgrade Assistant]. + +[role="exclude",id="brew"] +== Install {kib} on macOS with Homebrew + +This page has been deleted. Refer to <>. diff --git a/docs/setup/install.asciidoc b/docs/setup/install.asciidoc index 8b64bdf5fe2a2..ac49946d877bc 100644 --- a/docs/setup/install.asciidoc +++ b/docs/setup/install.asciidoc @@ -46,12 +46,6 @@ downloaded from the Elastic Docker Registry. + <> -`brew`:: - -Formulae are available from the Elastic Homebrew tap for installing {kib} on macOS with the Homebrew package manager. -+ -<> - IMPORTANT: If your Elasticsearch installation is protected by {ref}/elasticsearch-security.html[{stack-security-features}] see {kibana-ref}/using-kibana-with-security.html[Configuring security in {kib}] for @@ -66,5 +60,3 @@ include::install/deb.asciidoc[] include::install/rpm.asciidoc[] include::{kib-repo-dir}/setup/docker.asciidoc[] - -include::install/brew.asciidoc[] diff --git a/docs/setup/install/brew-running.asciidoc b/docs/setup/install/brew-running.asciidoc deleted file mode 100644 index d73102b098ec1..0000000000000 --- a/docs/setup/install/brew-running.asciidoc +++ /dev/null @@ -1,9 +0,0 @@ -==== Run {kib} with `brew services` - -With Homebrew, Kibana can be started and stopped as follows: - -[source,sh] --------------------------------------------------- -brew services start elastic/tap/kibana-full -brew services stop elastic/tap/kibana-full --------------------------------------------------- diff --git a/docs/setup/install/brew.asciidoc b/docs/setup/install/brew.asciidoc deleted file mode 100644 index eeba869a259d4..0000000000000 --- a/docs/setup/install/brew.asciidoc +++ /dev/null @@ -1,65 +0,0 @@ -[[brew]] -=== Install {kib} on macOS with Homebrew -++++ -Install on macOS with Homebrew -++++ - -Elastic publishes Homebrew formulae so you can install {kib} with the https://brew.sh/[Homebrew] package manager. - -To install with Homebrew, you first need to tap the Elastic Homebrew repository: - -[source,sh] -------------------------- -brew tap elastic/tap -------------------------- - -Once you've tapped the Elastic Homebrew repo, you can use `brew install` to -install the **latest version** of {kib}: - -[source,sh] -------------------------- -brew install elastic/tap/kibana-full -------------------------- - -[[brew-layout]] -==== Directory layout for Homebrew installs - -When you install {kib} with `brew install`, the config files, logs, -and data directory are stored in the following locations. - -[cols=" Date: Wed, 9 Feb 2022 16:43:04 -0700 Subject: [PATCH 29/85] [Security Solution] Bugfix for inspect index pattern not aligned with data view index pattern (#125007) --- .../common/components/inspect/index.test.tsx | 141 ++++++++++++-- .../common/components/inspect/index.tsx | 41 ++-- .../common/components/inspect/modal.test.tsx | 180 +++++++----------- .../common/components/inspect/modal.tsx | 59 ++++-- .../common/components/inspect/translations.ts | 15 ++ 5 files changed, 283 insertions(+), 153 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx index d73b4cb7d98d6..b3dbbb86ace68 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx @@ -21,6 +21,10 @@ import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers'; import { InspectButton } from '.'; import { cloneDeep } from 'lodash/fp'; +jest.mock('./modal', () => ({ + ModalInspectQuery: jest.fn(() =>
), +})); + describe('Inspect Button', () => { const refetch = jest.fn(); const state: State = mockGlobalState; @@ -103,6 +107,54 @@ describe('Inspect Button', () => { ); expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true); }); + + test('Button disabled when inspect == null', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = null; + myState.inputs = upsertQuery(myQuery); + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + + ); + expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true); + }); + + test('Button disabled when inspect.dsl.length == 0', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = { + dsl: [], + response: ['my response'], + }; + myState.inputs = upsertQuery(myQuery); + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + + ); + expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true); + }); + + test('Button disabled when inspect.response.length == 0', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = { + dsl: ['my dsl'], + response: [], + }; + myState.inputs = upsertQuery(myQuery); + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + + ); + expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true); + }); }); describe('Modal Inspect - happy path', () => { @@ -127,46 +179,103 @@ describe('Inspect Button', () => { wrapper.update(); expect(store.getState().inputs.global.queries[0].isInspected).toBe(true); - expect(wrapper.find('button[data-test-subj="modal-inspect-close"]').first().exists()).toBe( - true - ); + expect(wrapper.find('[data-test-subj="mocker-modal"]').first().exists()).toBe(true); }); - test('Close Inspect Modal', () => { + test('Do not Open Inspect Modal if it is loading', () => { const wrapper = mount( ); + expect(store.getState().inputs.global.queries[0].isInspected).toBe(false); + store.getState().inputs.global.queries[0].loading = true; wrapper.find('button[data-test-subj="inspect-icon-button"]').first().simulate('click'); wrapper.update(); - wrapper.find('button[data-test-subj="modal-inspect-close"]').first().simulate('click'); - - wrapper.update(); - - expect(store.getState().inputs.global.queries[0].isInspected).toBe(false); + expect(store.getState().inputs.global.queries[0].isInspected).toBe(true); expect(wrapper.find('button[data-test-subj="modal-inspect-close"]').first().exists()).toBe( false ); }); + }); - test('Do not Open Inspect Modal if it is loading', () => { + describe('Modal Inspect - show or hide', () => { + test('shows when request/response are complete and isInspected=true', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = { + dsl: ['a length'], + response: ['my response'], + }; + myState.inputs = upsertQuery(myQuery); + myState.inputs.global.queries[0].isInspected = true; + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const wrapper = mount( ); - store.getState().inputs.global.queries[0].loading = true; - wrapper.find('button[data-test-subj="inspect-icon-button"]').first().simulate('click'); - wrapper.update(); + expect(wrapper.find('[data-test-subj="mocker-modal"]').first().exists()).toEqual(true); + }); - expect(store.getState().inputs.global.queries[0].isInspected).toBe(true); - expect(wrapper.find('button[data-test-subj="modal-inspect-close"]').first().exists()).toBe( - false + test('hides when request/response are complete and isInspected=false', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = { + dsl: ['a length'], + response: ['my response'], + }; + myState.inputs = upsertQuery(myQuery); + myState.inputs.global.queries[0].isInspected = false; + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="mocker-modal"]').first().exists()).toEqual(false); + }); + + test('hides when request is empty and isInspected=true', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = { + dsl: [], + response: ['my response'], + }; + myState.inputs = upsertQuery(myQuery); + myState.inputs.global.queries[0].isInspected = true; + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="mocker-modal"]').first().exists()).toEqual(false); + }); + + test('hides when response is empty and isInspected=true', () => { + const myState = cloneDeep(state); + const myQuery = cloneDeep(newQuery); + myQuery.inspect = { + dsl: ['my dsl'], + response: [], + }; + myState.inputs = upsertQuery(myQuery); + myState.inputs.global.queries[0].isInspected = true; + store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const wrapper = mount( + + + ); + + expect(wrapper.find('[data-test-subj="mocker-modal"]').first().exists()).toEqual(false); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx index 4f52703620b5f..defb90b9054f1 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/index.tsx @@ -7,7 +7,7 @@ import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; import { omit } from 'lodash/fp'; -import React, { useCallback } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { inputsSelectors, State } from '../../store'; @@ -52,10 +52,10 @@ const InspectButtonComponent: React.FC = ({ compact = false, inputId = 'global', inspect, + inspectIndex = 0, isDisabled, isInspected, loading, - inspectIndex = 0, multiple = false, // If multiple = true we ignore the inspectIndex and pass all requests and responses to the inspect modal onCloseInspect, queryId = '', @@ -63,7 +63,6 @@ const InspectButtonComponent: React.FC = ({ setIsInspected, title = '', }) => { - const isShowingModal = !loading && selectedInspectIndex === inspectIndex && isInspected; const handleClick = useCallback(() => { setIsInspected({ id: queryId, @@ -105,6 +104,16 @@ const InspectButtonComponent: React.FC = ({ } } + const isShowingModal = useMemo( + () => !loading && selectedInspectIndex === inspectIndex && isInspected, + [inspectIndex, isInspected, loading, selectedInspectIndex] + ); + + const isButtonDisabled = useMemo( + () => loading || isDisabled || request == null || response == null, + [isDisabled, loading, request, response] + ); + return ( <> {inputId === 'timeline' && !compact && ( @@ -115,7 +124,7 @@ const InspectButtonComponent: React.FC = ({ color="text" iconSide="left" iconType="inspect" - isDisabled={loading || isDisabled || false} + isDisabled={isButtonDisabled} isLoading={loading} onClick={handleClick} > @@ -129,21 +138,23 @@ const InspectButtonComponent: React.FC = ({ data-test-subj="inspect-icon-button" iconSize="m" iconType="inspect" - isDisabled={loading || isDisabled || false} + isDisabled={isButtonDisabled} title={i18n.INSPECT} onClick={handleClick} /> )} - + {isShowingModal && request !== null && response !== null && ( + + )} ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx index 572513180025f..7a9c36a986afd 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx @@ -7,103 +7,50 @@ import { mount } from 'enzyme'; import React from 'react'; -import { ThemeProvider } from 'styled-components'; +import { TestProviders } from '../../mock'; import { NO_ALERT_INDEX } from '../../../../common/constants'; import { ModalInspectQuery, formatIndexPatternRequested } from './modal'; -import { getMockTheme } from '../../lib/kibana/kibana_react.mock'; +import { InputsModelId } from '../../store/inputs/constants'; +import { EXCLUDE_ELASTIC_CLOUD_INDEX } from '../../containers/sourcerer'; -const mockTheme = getMockTheme({ - eui: { - euiBreakpoints: { - l: '1200px', - }, - }, +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + + return { + ...original, + useLocation: jest.fn().mockReturnValue([{ pathname: '/overview' }]), + }; }); -const request = - '{"index": ["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"allowNoIndices": true, "ignoreUnavailable": true, "body": { "aggregations": {"hosts": {"cardinality": {"field": "host.name" } }, "hosts_histogram": {"auto_date_histogram": {"field": "@timestamp","buckets": "6"},"aggs": { "count": {"cardinality": {"field": "host.name" }}}}}, "query": {"bool": {"filter": [{"range": { "@timestamp": {"gte": 1562290224506,"lte": 1562376624506 }}}]}}, "size": 0, "track_total_hits": false}}'; +const getRequest = ( + indices: string[] = ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'] +) => + `{"index": ${JSON.stringify( + indices + )},"allowNoIndices": true, "ignoreUnavailable": true, "body": { "aggregations": {"hosts": {"cardinality": {"field": "host.name" } }, "hosts_histogram": {"auto_date_histogram": {"field": "@timestamp","buckets": "6"},"aggs": { "count": {"cardinality": {"field": "host.name" }}}}}, "query": {"bool": {"filter": [{"range": { "@timestamp": {"gte": 1562290224506,"lte": 1562376624506 }}}]}}, "size": 0, "track_total_hits": false}}`; + +const request = getRequest(); + const response = '{"took": 880,"timed_out": false,"_shards": {"total": 26,"successful": 26,"skipped": 0,"failed": 0},"hits": {"max_score": null,"hits": []},"aggregations": {"hosts": {"value": 541},"hosts_histogram": {"buckets": [{"key_as_string": "2019 - 07 - 05T01: 00: 00.000Z", "key": 1562288400000, "doc_count": 1492321, "count": { "value": 105 }}, {"key_as_string": "2019 - 07 - 05T13: 00: 00.000Z", "key": 1562331600000, "doc_count": 2412761, "count": { "value": 453}},{"key_as_string": "2019 - 07 - 06T01: 00: 00.000Z", "key": 1562374800000, "doc_count": 111658, "count": { "value": 15}}],"interval": "12h"}},"status": 200}'; describe('Modal Inspect', () => { const closeModal = jest.fn(); - - describe('rendering', () => { - test('when isShowing is positive and request and response are not null', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="modal-inspect-euiModal"]').first().exists()).toBe(true); - expect(wrapper.find('.euiModalHeader__title').first().text()).toBe('Inspect My title'); - }); - - test('when isShowing is negative and request and response are not null', () => { - const wrapper = mount( - - ); - expect(wrapper.find('[data-test-subj="modal-inspect-euiModal"]').first().exists()).toBe( - false - ); - }); - - test('when isShowing is positive and request is null and response is not null', () => { - const wrapper = mount( - - ); - expect(wrapper.find('[data-test-subj="modal-inspect-euiModal"]').first().exists()).toBe( - false - ); - }); - - test('when isShowing is positive and request is not null and response is null', () => { - const wrapper = mount( - - ); - expect(wrapper.find('[data-test-subj="modal-inspect-euiModal"]').first().exists()).toBe( - false - ); - }); - }); + const defaultProps = { + closeModal, + inputId: 'timeline' as InputsModelId, + request, + response, + title: 'My title', + }; describe('functionality from tab statistics/request/response', () => { test('Click on statistic Tab', () => { const wrapper = mount( - - - + + + ); wrapper.find('.euiTab').first().simulate('click'); @@ -134,15 +81,9 @@ describe('Modal Inspect', () => { test('Click on request Tab', () => { const wrapper = mount( - - - + + + ); wrapper.find('.euiTab').at(2).simulate('click'); @@ -201,15 +142,9 @@ describe('Modal Inspect', () => { test('Click on response Tab', () => { const wrapper = mount( - - - + + + ); wrapper.find('.euiTab').at(1).simulate('click'); @@ -237,15 +172,9 @@ describe('Modal Inspect', () => { describe('events', () => { test('Make sure that toggle function has been called when you click on the close button', () => { const wrapper = mount( - - - + + + ); wrapper.find('button[data-test-subj="modal-inspect-close"]').simulate('click'); @@ -280,4 +209,37 @@ describe('Modal Inspect', () => { expect(expected).toEqual('Sorry about that, something went wrong.'); }); }); + + describe('index pattern messaging', () => { + test('no messaging when all patterns are in sourcerer selection', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('i[data-test-subj="not-sourcerer-msg"]').first().exists()).toEqual(false); + expect(wrapper.find('i[data-test-subj="exclude-logs-msg"]').first().exists()).toEqual(false); + }); + test('not-sourcerer-msg when not all patterns are in sourcerer selection', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('i[data-test-subj="not-sourcerer-msg"]').first().exists()).toEqual(true); + expect(wrapper.find('i[data-test-subj="exclude-logs-msg"]').first().exists()).toEqual(false); + }); + test('exclude-logs-msg when EXCLUDE_ELASTIC_CLOUD_INDEX is present in patterns', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('i[data-test-subj="not-sourcerer-msg"]').first().exists()).toEqual(false); + expect(wrapper.find('i[data-test-subj="exclude-logs-msg"]').first().exists()).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index 78a3d744e46bb..45fcf1e746b87 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -19,11 +19,19 @@ import { EuiTabbedContent, } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import React, { Fragment, ReactNode } from 'react'; +import React, { useMemo, Fragment, ReactNode } from 'react'; import styled from 'styled-components'; +import { useLocation } from 'react-router-dom'; import { NO_ALERT_INDEX } from '../../../../common/constants'; import * as i18n from './translations'; +import { + EXCLUDE_ELASTIC_CLOUD_INDEX, + getScopeFromPath, + useSourcererDataView, +} from '../../containers/sourcerer'; +import { InputsModelId } from '../../store/inputs/constants'; +import { SourcererScopeName } from '../../store/sourcerer/model'; const DescriptionListStyled = styled(EuiDescriptionList)` @media only screen and (min-width: ${(props) => props.theme.eui.euiBreakpoints.s}) { @@ -40,12 +48,12 @@ const DescriptionListStyled = styled(EuiDescriptionList)` DescriptionListStyled.displayName = 'DescriptionListStyled'; interface ModalInspectProps { - closeModal: () => void; - isShowing: boolean; - request: string | null; - response: string | null; additionalRequests?: string[] | null; additionalResponses?: string[] | null; + closeModal: () => void; + inputId?: InputsModelId; + request: string; + response: string; title: string | React.ReactElement | React.ReactNode; } @@ -101,18 +109,18 @@ export const formatIndexPatternRequested = (indices: string[] = []) => { }; export const ModalInspectQuery = ({ + additionalRequests, + additionalResponses, closeModal, - isShowing = false, + inputId, request, response, - additionalRequests, - additionalResponses, title, }: ModalInspectProps) => { - if (!isShowing || request == null || response == null) { - return null; - } - + const { pathname } = useLocation(); + const { selectedPatterns } = useSourcererDataView( + inputId === 'timeline' ? SourcererScopeName.timeline : getScopeFromPath(pathname) + ); const requests: string[] = [request, ...(additionalRequests != null ? additionalRequests : [])]; const responses: string[] = [ response, @@ -122,6 +130,16 @@ export const ModalInspectQuery = ({ const inspectRequests: Request[] = parseInspectStrings(requests); const inspectResponses: Response[] = parseInspectStrings(responses); + const isSourcererPattern = useMemo( + () => (inspectRequests[0]?.index ?? []).every((pattern) => selectedPatterns.includes(pattern)), + [inspectRequests, selectedPatterns] + ); + + const isLogsExclude = useMemo( + () => (inspectRequests[0]?.index ?? []).includes(EXCLUDE_ELASTIC_CLOUD_INDEX), + [inspectRequests] + ); + const statistics: Array<{ title: NonNullable; description: NonNullable; @@ -135,7 +153,22 @@ export const ModalInspectQuery = ({ ), description: ( - {formatIndexPatternRequested(inspectRequests[0]?.index ?? [])} +

{formatIndexPatternRequested(inspectRequests[0]?.index ?? [])}

+ + {!isSourcererPattern && ( +

+ + {i18n.INSPECT_PATTERN_DIFFERENT} + +

+ )} + {isLogsExclude && ( +

+ + {i18n.LOGS_EXCLUDE_MESSAGE} + +

+ )}
), }, diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts b/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts index 28561aadf8d7e..732432c659d4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/inspect/translations.ts @@ -36,6 +36,21 @@ export const INDEX_PATTERN_DESC = i18n.translate( } ); +export const INSPECT_PATTERN_DIFFERENT = i18n.translate( + 'xpack.securitySolution.inspectPatternDifferent', + { + defaultMessage: 'This element has a unique index pattern separate from the data view setting.', + } +); + +export const LOGS_EXCLUDE_MESSAGE = i18n.translate( + 'xpack.securitySolution.inspectPatternExcludeLogs', + { + defaultMessage: + 'When the logs-* index pattern is selected, Elastic cloud logs are excluded from the search.', + } +); + export const QUERY_TIME = i18n.translate('xpack.securitySolution.inspect.modal.queryTimeLabel', { defaultMessage: 'Query time', }); From dba7207787654c54e2bdf1abf399378f3f9c94f8 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 9 Feb 2022 16:02:25 -0800 Subject: [PATCH 30/85] [Security Solution][Lists] - Update exception item viewer overflow (#125145) ### Summary Addresses #119012 - updates exception item viewer UI --- .../exception_lists/new/exception_list.json | 4 +- .../exception_item/exception_details.tsx | 16 +- .../exception_item/exception_entries.tsx | 3 +- .../exceptions/viewer/helpers.test.tsx | 267 +++++++++++------- .../components/exceptions/viewer/helpers.tsx | 35 ++- 5 files changed, 209 insertions(+), 116 deletions(-) diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json index 73271514269da..68bbcc6288df2 100644 --- a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json @@ -2,6 +2,6 @@ "list_id": "simple_list", "tags": ["user added string for a tag", "malware"], "type": "detection", - "description": "This is a sample endpoint type exception", - "name": "Sample Endpoint Exception List" + "description": "This is a sample detection type exception", + "name": "Sample Detection Exception List" } diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx index 3354637b9f745..429f9672aece5 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx @@ -11,7 +11,6 @@ import { EuiDescriptionList, EuiButtonEmpty, EuiDescriptionListTitle, - EuiDescriptionListDescription, EuiToolTip, } from '@elastic/eui'; import React, { useMemo, Fragment } from 'react'; @@ -92,7 +91,11 @@ const ExceptionDetailsComponent = ({ - + {descriptionListItems.map((item) => ( @@ -100,14 +103,7 @@ const ExceptionDetailsComponent = ({ {item.title} - - - {item.description} - - + {item.description} ))} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx index 10210463e9a1e..4db00bea5c932 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx @@ -150,8 +150,7 @@ const ExceptionEntriesComponent = ({ }, }, ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [entries] + [] ); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx index d67f526fa9bdc..25e260e67855a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.test.tsx @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { getFormattedEntries, formatEntry, getDescriptionListContent } from './helpers'; -import { FormattedEntry, DescriptionListItem } from '../types'; +import { FormattedEntry } from '../types'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntriesArrayMock } from '../../../../../../lists/common/schemas/types/entries.mock'; import { getEntryMatchMock } from '../../../../../../lists/common/schemas/types/entry_match.mock'; @@ -157,103 +157,171 @@ describe('Exception viewer helpers', () => { const payload = getExceptionListItemSchemaMock({ os_types: ['linux'] }); payload.description = ''; const result = getDescriptionListContent(payload); - const expected: DescriptionListItem[] = [ - { - description: 'Linux', - title: 'OS', - }, - { - description: 'April 20th 2020 @ 15:25:31', - title: 'Date created', - }, - { - description: 'some user', - title: 'Created by', - }, - ]; + const os = result.find(({ title }) => title === 'OS'); - expect(result).toEqual(expected); + expect(os).toMatchInlineSnapshot(` + Object { + "description": + + Linux + + , + "title": "OS", + } + `); }); test('it returns formatted description list with a description if one specified', () => { const payload = getExceptionListItemSchemaMock({ os_types: ['linux'] }); payload.description = 'Im a description'; const result = getDescriptionListContent(payload); - const expected: DescriptionListItem[] = [ - { - description: 'Linux', - title: 'OS', - }, - { - description: 'April 20th 2020 @ 15:25:31', - title: 'Date created', - }, - { - description: 'some user', - title: 'Created by', - }, - { - description: 'Im a description', - title: 'Description', - }, - ]; + const description = result.find(({ title }) => title === 'Description'); - expect(result).toEqual(expected); + expect(description).toMatchInlineSnapshot(` + Object { + "description": + + Im a description + + , + "title": "Description", + } + `); }); - test('it returns just user and date created if no other fields specified', () => { + test('it returns scrolling element when description is longer than 75 charachters', () => { const payload = getExceptionListItemSchemaMock({ os_types: ['linux'] }); - payload.description = ''; + payload.description = + 'Puppy kitty ipsum dolor sit good dog foot stick canary. Teeth Mittens grooming vaccine walk swimming nest good boy furry tongue heel furry treats fish. Cage run fast kitten dinnertime ball run foot park fleas throw house train licks stick dinnertime window. Yawn litter fish yawn toy pet gate throw Buddy kitty wag tail ball groom crate ferret heel wet nose Rover toys pet supplies. Bird Food treats tongue lick teeth ferret litter box slobbery litter box crate bird small animals yawn small animals shake slobber gimme five toys polydactyl meow. '; const result = getDescriptionListContent(payload); - const expected: DescriptionListItem[] = [ - { - description: 'Linux', - title: 'OS', - }, - { - description: 'April 20th 2020 @ 15:25:31', - title: 'Date created', - }, - { - description: 'some user', - title: 'Created by', - }, - ]; + const description = result.find(({ title }) => title === 'Description'); - expect(result).toEqual(expected); + expect(description).toMatchInlineSnapshot(` + Object { + "description": + + Puppy kitty ipsum dolor sit good dog foot stick canary. Teeth Mittens grooming vaccine walk swimming nest good boy furry tongue heel furry treats fish. Cage run fast kitten dinnertime ball run foot park fleas throw house train licks stick dinnertime window. Yawn litter fish yawn toy pet gate throw Buddy kitty wag tail ball groom crate ferret heel wet nose Rover toys pet supplies. Bird Food treats tongue lick teeth ferret litter box slobbery litter box crate bird small animals yawn small animals shake slobber gimme five toys polydactyl meow. + + , + "title": "Description", + } + `); }); - test('it returns Modified By/On info. when `includeModified` is true', () => { + test('it returns just user and date created if no other fields specified', () => { + const payload = getExceptionListItemSchemaMock(); + payload.description = ''; + const result = getDescriptionListContent(payload); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "description": + + April 20th 2020 @ 15:25:31 + + , + "title": "Date created", + }, + Object { + "description": + + some user + + , + "title": "Created by", + }, + ] + `); + }); + + test('it returns Modified By/On info when `includeModified` is true', () => { const result = getDescriptionListContent( getExceptionListItemSchemaMock({ os_types: ['linux'] }), true ); - expect(result).toEqual([ - { - description: 'Linux', - title: 'OS', - }, - { - description: 'April 20th 2020 @ 15:25:31', - title: 'Date created', - }, - { - description: 'some user', - title: 'Created by', - }, - { - description: 'April 20th 2020 @ 15:25:31', - title: 'Date modified', - }, - { - description: 'some user', - title: 'Modified by', - }, - { - description: 'some description', - title: 'Description', - }, - ]); + const dateModified = result.find(({ title }) => title === 'Date modified'); + const modifiedBy = result.find(({ title }) => title === 'Modified by'); + expect(modifiedBy).toMatchInlineSnapshot(` + Object { + "description": + + some user + + , + "title": "Modified by", + } + `); + expect(dateModified).toMatchInlineSnapshot(` + Object { + "description": + + April 20th 2020 @ 15:25:31 + + , + "title": "Date modified", + } + `); }); test('it returns Name when `includeName` is true', () => { @@ -262,28 +330,25 @@ describe('Exception viewer helpers', () => { false, true ); - expect(result).toEqual([ - { - description: 'some name', - title: 'Name', - }, - { - description: 'Linux', - title: 'OS', - }, - { - description: 'April 20th 2020 @ 15:25:31', - title: 'Date created', - }, - { - description: 'some user', - title: 'Created by', - }, - { - description: 'some description', - title: 'Description', - }, - ]); + const name = result.find(({ title }) => title === 'Name'); + expect(name).toMatchInlineSnapshot(` + Object { + "description": + + some name + + , + "title": "Name", + } + `); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.tsx index 597e8a6fed52f..37bfeb6166405 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/helpers.tsx @@ -14,6 +14,8 @@ import { BuilderEntry, } from '@kbn/securitysolution-list-utils'; +import React from 'react'; +import { EuiDescriptionListDescription, EuiText, EuiToolTip } from '@elastic/eui'; import { formatOperatingSystems } from '../helpers'; import type { FormattedEntry, DescriptionListItem } from '../types'; import * as i18n from '../translations'; @@ -125,7 +127,38 @@ export const getDescriptionListContent = ( return details.reduce((acc, { value, title }) => { if (value != null && value.trim() !== '') { - return [...acc, { title, description: value }]; + const valueElement = ( + + + {value} + + + ); + if (title === i18n.DESCRIPTION) { + return [ + ...acc, + { + title, + description: + value.length > 75 ? ( + + + {value} + + + ) : ( + valueElement + ), + }, + ]; + } + return [...acc, { title, description: valueElement }]; } else { return acc; } From 7407c245ef0a261b9be5a520129a25fbf023f312 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 10 Feb 2022 02:38:35 +0000 Subject: [PATCH 31/85] skip flaky suite (#118432) --- test/functional/apps/discover/_search_on_page_load.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_search_on_page_load.ts b/test/functional/apps/discover/_search_on_page_load.ts index 0198881e981b8..3df6ce1c13c43 100644 --- a/test/functional/apps/discover/_search_on_page_load.ts +++ b/test/functional/apps/discover/_search_on_page_load.ts @@ -58,7 +58,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); }); - describe(`when it's false`, () => { + // FLAKY: https://github.com/elastic/kibana/issues/118432 + describe.skip(`when it's false`, () => { beforeEach(async () => await initSearchOnPageLoad(false)); it('should not fetch data from ES initially', async function () { From 560ca276b301cf639f9987f5aeca6f6b0e622473 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 10 Feb 2022 02:43:29 +0000 Subject: [PATCH 32/85] skip flaky suite (#124663) --- .../functional/apps/management/_index_pattern_create_delete.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js index 62612ad5a9080..a07141a073d64 100644 --- a/test/functional/apps/management/_index_pattern_create_delete.js +++ b/test/functional/apps/management/_index_pattern_create_delete.js @@ -35,7 +35,8 @@ export default function ({ getService, getPageObjects }) { }); }); - describe('validation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/124663 + describe.skip('validation', function () { it('can display errors', async function () { await PageObjects.settings.clickAddNewIndexPatternButton(); await PageObjects.settings.setIndexPatternField('log-fake*'); From 87eaa7525cd7ad3b56b51b1321e6b2e3dbfe5cd0 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 9 Feb 2022 20:25:43 -0700 Subject: [PATCH 33/85] [Reporting] Switch from EventLog integration to ECS logging (#124762) * [Reporting] Remove EventLog Dependency * calculate duration * use LogMeta interface of core logger * fix ts * rename the debug log tag * clean up return types for testing * remove reporting fields from the event log mappings * unwrap code from iife * add class for log adapter * remove useless factory fn * remove eventLog * user field was meant to be ECS field * duration is nanoseconds * fix nanoseconds application and test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/event_log/generated/mappings.json | 13 -- x-pack/plugins/event_log/generated/schemas.ts | 7 -- x-pack/plugins/event_log/scripts/mappings.js | 14 --- x-pack/plugins/reporting/kibana.json | 1 - x-pack/plugins/reporting/server/core.ts | 4 +- .../server/lib/event_logger/adapter.test.ts | 67 ++++++++++ .../server/lib/event_logger/adapter.ts | 57 +++++++++ .../server/lib/event_logger/index.ts | 7 +- .../server/lib/event_logger/logger.test.ts | 51 +++----- .../server/lib/event_logger/logger.ts | 116 +++++++++--------- .../server/lib/event_logger/types.ts | 63 +++------- .../reporting/server/lib/level_logger.ts | 9 +- x-pack/plugins/reporting/server/plugin.ts | 3 - .../create_mock_reportingplugin.ts | 4 - x-pack/plugins/reporting/server/types.ts | 2 - x-pack/plugins/reporting/tsconfig.json | 1 - .../__snapshots__/event_log.snap | 11 -- .../reporting_and_security/event_log.ts | 88 ------------- .../reporting_and_security/index.ts | 1 - 19 files changed, 220 insertions(+), 299 deletions(-) create mode 100644 x-pack/plugins/reporting/server/lib/event_logger/adapter.test.ts create mode 100644 x-pack/plugins/reporting/server/lib/event_logger/adapter.ts delete mode 100644 x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/event_log.snap delete mode 100644 x-pack/test/reporting_api_integration/reporting_and_security/event_log.ts diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index 5e6a9d660f82a..e9f030ffbc886 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -309,19 +309,6 @@ } } }, - "reporting": { - "properties": { - "id": { - "type": "keyword" - }, - "jobType": { - "type": "keyword" - }, - "byteSize": { - "type": "long" - } - } - }, "saved_objects": { "type": "nested", "properties": { diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index 4607495b85c4e..d61689d6238e4 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -140,13 +140,6 @@ export const EventSchema = schema.maybe( ), }) ), - reporting: schema.maybe( - schema.object({ - id: ecsString(), - jobType: ecsString(), - byteSize: ecsNumber(), - }) - ), saved_objects: schema.maybe( schema.arrayOf( schema.object({ diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 22d36d7f20d4c..091b50eceea6c 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -91,20 +91,6 @@ exports.EcsCustomPropertyMappings = { }, }, }, - // reporting specific fields - reporting: { - properties: { - id: { - type: 'keyword', - }, - jobType: { - type: 'keyword', - }, - byteSize: { - type: 'long', - }, - }, - }, // array of saved object references, for "linking" via search saved_objects: { type: 'nested', diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json index e7162d0974de6..8f75a462ed8f6 100644 --- a/x-pack/plugins/reporting/kibana.json +++ b/x-pack/plugins/reporting/kibana.json @@ -18,7 +18,6 @@ "licensing", "uiActions", "taskManager", - "eventLog", "embeddable", "screenshotting", "screenshotMode", diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 5c00089afc381..745542c358a69 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -21,7 +21,6 @@ import type { import type { PluginStart as DataPluginStart } from 'src/plugins/data/server'; import type { FieldFormatsStart } from 'src/plugins/field_formats/server'; import { KibanaRequest, ServiceStatusLevels } from '../../../../src/core/server'; -import type { IEventLogService } from '../../event_log/server'; import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import type { LicensingPluginStart } from '../../licensing/server'; import type { ScreenshotResult, ScreenshottingStart } from '../../screenshotting/server'; @@ -40,7 +39,6 @@ import { ExecuteReportTask, MonitorReportsTask, ReportTaskParams } from './lib/t import type { ReportingPluginRouter, ScreenshotOptions } from './types'; export interface ReportingInternalSetup { - eventLog: IEventLogService; basePath: Pick; router: ReportingPluginRouter; features: FeaturesPluginSetup; @@ -390,7 +388,7 @@ export class ReportingCore { } public getEventLogger(report: IReport, task?: { id: string }) { - const ReportingEventLogger = reportingEventLoggerFactory(this.pluginSetupDeps!.eventLog); + const ReportingEventLogger = reportingEventLoggerFactory(this.logger); return new ReportingEventLogger(report, task); } } diff --git a/x-pack/plugins/reporting/server/lib/event_logger/adapter.test.ts b/x-pack/plugins/reporting/server/lib/event_logger/adapter.test.ts new file mode 100644 index 0000000000000..aef569a49e357 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/event_logger/adapter.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { LogMeta } from 'kibana/server'; +import { createMockLevelLogger } from '../../test_helpers'; +import { EcsLogAdapter } from './adapter'; + +describe('EcsLogAdapter', () => { + const logger = createMockLevelLogger(); + beforeAll(() => { + jest + .spyOn(global.Date, 'now') + .mockImplementationOnce(() => new Date('2021-04-12T16:00:00.000Z').valueOf()) + .mockImplementationOnce(() => new Date('2021-04-12T16:02:00.000Z').valueOf()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('captures a log event', () => { + const eventLogger = new EcsLogAdapter(logger, { event: { provider: 'test-adapting' } }); + + const event = { kibana: { reporting: { wins: 5000 } } } as object & LogMeta; // an object that extends LogMeta + eventLogger.logEvent('hello world', event); + + expect(logger.debug).toBeCalledWith('hello world', ['events'], { + event: { + duration: undefined, + end: undefined, + provider: 'test-adapting', + start: undefined, + }, + kibana: { + reporting: { + wins: 5000, + }, + }, + }); + }); + + it('captures timings between start and complete', () => { + const eventLogger = new EcsLogAdapter(logger, { event: { provider: 'test-adapting' } }); + eventLogger.startTiming(); + + const event = { kibana: { reporting: { wins: 9000 } } } as object & LogMeta; // an object that extends LogMeta + eventLogger.logEvent('hello duration', event); + + expect(logger.debug).toBeCalledWith('hello duration', ['events'], { + event: { + duration: 120000000000, + end: '2021-04-12T16:02:00.000Z', + provider: 'test-adapting', + start: '2021-04-12T16:00:00.000Z', + }, + kibana: { + reporting: { + wins: 9000, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/event_logger/adapter.ts b/x-pack/plugins/reporting/server/lib/event_logger/adapter.ts new file mode 100644 index 0000000000000..c9487a79d9e70 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/event_logger/adapter.ts @@ -0,0 +1,57 @@ +/* + * 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 deepMerge from 'deepmerge'; +import { LogMeta } from 'src/core/server'; +import { LevelLogger } from '../level_logger'; +import { IReportingEventLogger } from './logger'; + +/** @internal */ +export class EcsLogAdapter implements IReportingEventLogger { + start?: Date; + end?: Date; + + /** + * This class provides a logging system to Reporting code, using a shape similar to the EventLog service. + * The logging action causes ECS data with Reporting metrics sent to DEBUG logs. + * + * @param {LevelLogger} logger - Reporting's wrapper of the core logger + * @param {Partial} properties - initial ECS data with template for Reporting metrics + */ + constructor(private logger: LevelLogger, private properties: Partial) {} + + logEvent(message: string, properties: LogMeta) { + if (this.start && !this.end) { + this.end = new Date(Date.now()); + } + + let duration: number | undefined; + if (this.end && this.start) { + duration = (this.end.valueOf() - this.start.valueOf()) * 1000000; // nanoseconds + } + + // add the derived properties for timing between "start" and "complete" logging calls + const newProperties: LogMeta = deepMerge(this.properties, { + event: { + duration, + start: this.start?.toISOString(), + end: this.end?.toISOString(), + }, + }); + + // sends an ECS object with Reporting metrics to the DEBUG logs + this.logger.debug(message, ['events'], deepMerge(newProperties, properties)); + } + + startTiming() { + this.start = new Date(Date.now()); + } + + stopTiming() { + this.end = new Date(Date.now()); + } +} diff --git a/x-pack/plugins/reporting/server/lib/event_logger/index.ts b/x-pack/plugins/reporting/server/lib/event_logger/index.ts index 566f0a21e2b05..f9c4c5574b3e1 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/index.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/index.ts @@ -5,9 +5,6 @@ * 2.0. */ -import { IEventLogService } from '../../../../event_log/server'; -import { PLUGIN_ID } from '../../../common/constants'; - export enum ActionType { SCHEDULE_TASK = 'schedule-task', CLAIM_TASK = 'claim-task', @@ -16,7 +13,5 @@ export enum ActionType { SAVE_REPORT = 'save-report', RETRY = 'retry', FAIL_REPORT = 'fail-report', -} -export function registerEventLogProviderActions(eventLog: IEventLogService) { - eventLog.registerProviderActions(PLUGIN_ID, Object.values(ActionType)); + EXECUTE_ERROR = 'execute-error', } diff --git a/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts b/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts index 21c4ee2d5e4cf..9a1c282a01a59 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts @@ -6,7 +6,7 @@ */ import { ConcreteTaskInstance } from '../../../../task_manager/server'; -import { eventLogServiceMock } from '../../../../event_log/server/mocks'; +import { createMockLevelLogger } from '../../test_helpers'; import { BasePayload } from '../../types'; import { Report } from '../store'; import { ReportingEventLogger, reportingEventLoggerFactory } from './logger'; @@ -21,7 +21,7 @@ describe('Event Logger', () => { let factory: ReportingEventLogger; beforeEach(() => { - factory = reportingEventLoggerFactory(eventLogServiceMock.create()); + factory = reportingEventLoggerFactory(createMockLevelLogger()); }); it(`should construct with an internal seed object`, () => { @@ -29,7 +29,6 @@ describe('Event Logger', () => { expect(logger.eventObj).toMatchInlineSnapshot(` Object { "event": Object { - "provider": "reporting", "timezone": "UTC", }, "kibana": Object { @@ -38,9 +37,6 @@ describe('Event Logger', () => { "jobType": "csv", }, }, - "log": Object { - "logger": "reporting", - }, "user": undefined, } `); @@ -51,7 +47,6 @@ describe('Event Logger', () => { expect(logger.eventObj).toMatchInlineSnapshot(` Object { "event": Object { - "provider": "reporting", "timezone": "UTC", }, "kibana": Object { @@ -60,9 +55,6 @@ describe('Event Logger', () => { "jobType": "csv", }, }, - "log": Object { - "logger": "reporting", - }, "user": Object { "name": "thundercat", }, @@ -77,7 +69,6 @@ describe('Event Logger', () => { expect(logger.eventObj).toMatchInlineSnapshot(` Object { "event": Object { - "provider": "reporting", "timezone": "UTC", }, "kibana": Object { @@ -89,9 +80,6 @@ describe('Event Logger', () => { "id": "some-task-id-123", }, }, - "log": Object { - "logger": "reporting", - }, "user": Object { "name": "thundercat", }, @@ -101,16 +89,16 @@ describe('Event Logger', () => { it(`logExecutionStart`, () => { const logger = new factory(mockReport); + jest.spyOn(logger.completionLogger, 'startTiming'); + jest.spyOn(logger.completionLogger, 'stopTiming'); const result = logger.logExecutionStart(); expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "execute-start", - "kind": "event", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "execute-start", "id": "12348", "jobType": "csv", }, @@ -119,23 +107,23 @@ describe('Event Logger', () => { `); expect(result.message).toMatchInlineSnapshot(`"starting csv execution"`); expect(logger.completionLogger.startTiming).toBeCalled(); + expect(logger.completionLogger.stopTiming).not.toBeCalled(); }); it(`logExecutionComplete`, () => { const logger = new factory(mockReport); + jest.spyOn(logger.completionLogger, 'startTiming'); + jest.spyOn(logger.completionLogger, 'stopTiming'); logger.logExecutionStart(); const result = logger.logExecutionComplete({ byteSize: 444 }); expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "execute-complete", - "kind": "metrics", - "outcome": "success", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "execute-complete", "byteSize": 444, "id": "12348", "jobType": "csv", @@ -154,13 +142,10 @@ describe('Event Logger', () => { expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "execute-complete", - "kind": "error", - "outcome": "failure", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "execute-error", "id": "12348", "jobType": "csv", }, @@ -176,12 +161,10 @@ describe('Event Logger', () => { expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "claim-task", - "kind": "event", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "claim-task", "id": "12348", "jobType": "csv", }, @@ -196,12 +179,10 @@ describe('Event Logger', () => { expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "fail-report", - "kind": "event", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "fail-report", "id": "12348", "jobType": "csv", }, @@ -215,12 +196,10 @@ describe('Event Logger', () => { expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "save-report", - "kind": "event", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "save-report", "id": "12348", "jobType": "csv", }, @@ -234,12 +213,10 @@ describe('Event Logger', () => { expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "action": "retry", - "kind": "event", - "provider": "reporting", "timezone": "UTC", }, Object { + "actionType": "retry", "id": "12348", "jobType": "csv", }, diff --git a/x-pack/plugins/reporting/server/lib/event_logger/logger.ts b/x-pack/plugins/reporting/server/lib/event_logger/logger.ts index 0ec864e36620b..ccdee24d1879e 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/logger.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/logger.ts @@ -6,16 +6,19 @@ */ import deepMerge from 'deepmerge'; -import { IEventLogger, IEventLogService } from '../../../../event_log/server'; +import { LogMeta } from 'src/core/server'; +import { LevelLogger } from '../'; import { PLUGIN_ID } from '../../../common/constants'; import { IReport } from '../store'; import { ActionType } from './'; +import { EcsLogAdapter } from './adapter'; import { ClaimedTask, CompletedExecution, ErrorAction, ExecuteError, FailedReport, + ReportingAction, SavedReport, ScheduledRetry, ScheduledTask, @@ -27,171 +30,162 @@ export interface ExecutionCompleteMetrics { byteSize: number; } +export interface IReportingEventLogger { + logEvent(message: string, properties: LogMeta): void; + startTiming(): void; + stopTiming(): void; +} + /** @internal */ -export function reportingEventLoggerFactory(eventLog: IEventLogService) { - const genericLogger = eventLog.getLogger({ event: { provider: PLUGIN_ID } }); +export function reportingEventLoggerFactory(logger: LevelLogger) { + const genericLogger = new EcsLogAdapter(logger, { event: { provider: PLUGIN_ID } }); return class ReportingEventLogger { readonly eventObj: { event: { timezone: string; - provider: 'reporting'; }; - kibana: { reporting: StartedExecution['kibana']['reporting']; task?: { id: string } }; - log: { logger: 'reporting' }; + kibana: { + reporting: ReportingAction['kibana']['reporting']; + task?: { id: string }; + }; user?: { name: string }; }; readonly report: IReport; readonly task?: { id: string }; - completionLogger: IEventLogger; + completionLogger: IReportingEventLogger; constructor(report: IReport, task?: { id: string }) { this.report = report; this.task = task; this.eventObj = { - event: { timezone: report.payload.browserTimezone, provider: 'reporting' }, + event: { timezone: report.payload.browserTimezone }, kibana: { reporting: { id: report._id, jobType: report.jobtype }, ...(task?.id ? { task: { id: task.id } } : undefined), }, - log: { logger: 'reporting' }, user: report.created_by ? { name: report.created_by } : undefined, }; // create a "complete" logger that will use EventLog helpers to calculate timings - this.completionLogger = eventLog.getLogger({ event: { provider: PLUGIN_ID } }); + this.completionLogger = new EcsLogAdapter(logger, { event: { provider: PLUGIN_ID } }); } logScheduleTask(): ScheduledTask { + const message = `queued report ${this.report._id}`; const event = deepMerge( { - message: `queued report ${this.report._id}`, - event: { kind: 'event', action: ActionType.SCHEDULE_TASK }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.SCHEDULE_TASK } }, } as Partial, this.eventObj ); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } logExecutionStart(): StartedExecution { - this.completionLogger.startTiming(this.eventObj); + const message = `starting ${this.report.jobtype} execution`; + this.completionLogger.startTiming(); const event = deepMerge( { - message: `starting ${this.report.jobtype} execution`, - event: { kind: 'event', action: ActionType.EXECUTE_START }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.EXECUTE_START } }, } as Partial, this.eventObj ); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } logExecutionComplete({ byteSize }: ExecutionCompleteMetrics): CompletedExecution { - this.completionLogger.stopTiming(this.eventObj); + const message = `completed ${this.report.jobtype} execution`; + this.completionLogger.stopTiming(); const event = deepMerge( { - message: `completed ${this.report.jobtype} execution`, - event: { - kind: 'metrics', - outcome: 'success', - action: ActionType.EXECUTE_COMPLETE, - }, - kibana: { reporting: { byteSize } }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.EXECUTE_COMPLETE, byteSize } }, } as Partial, this.eventObj ); - this.completionLogger.logEvent(event); + this.completionLogger.logEvent(message, event); return event; } logError(error: ErrorAction): ExecuteError { - interface LoggedErrorMessage { - message: string; - error: ExecuteError['error']; - event: Omit; - log: Omit; - } - const logErrorMessage: LoggedErrorMessage = { - message: error.message, + const message = `an error occurred`; + const logErrorMessage = { + message, + kibana: { reporting: { actionType: ActionType.EXECUTE_ERROR } }, error: { message: error.message, code: error.code, stack_trace: error.stack_trace, type: error.type, }, - event: { - kind: 'error', - outcome: 'failure', - action: ActionType.EXECUTE_COMPLETE, - }, - log: { level: 'error' }, - }; + } as Partial; const event = deepMerge(logErrorMessage, this.eventObj); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } logClaimTask(): ClaimedTask { + const message = `claimed report ${this.report._id}`; const event = deepMerge( { - message: `claimed report ${this.report._id}`, - event: { kind: 'event', action: ActionType.CLAIM_TASK }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.CLAIM_TASK } }, } as Partial, this.eventObj ); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } logReportFailure(): FailedReport { + const message = `report ${this.report._id} has failed`; const event = deepMerge( { - message: `report ${this.report._id} has failed`, - event: { kind: 'event', action: ActionType.FAIL_REPORT }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.FAIL_REPORT } }, } as Partial, this.eventObj ); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } logReportSaved(): SavedReport { + const message = `saved report ${this.report._id}`; const event = deepMerge( { - message: `saved report ${this.report._id}`, - event: { kind: 'event', action: ActionType.SAVE_REPORT }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.SAVE_REPORT } }, } as Partial, this.eventObj ); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } logRetry(): ScheduledRetry { + const message = `scheduled retry for report ${this.report._id}`; const event = deepMerge( { - message: `scheduled retry for report ${this.report._id}`, - event: { kind: 'event', action: ActionType.RETRY }, - log: { level: 'info' }, + message, + kibana: { reporting: { actionType: ActionType.RETRY } }, } as Partial, this.eventObj ); - genericLogger.logEvent(event); + genericLogger.logEvent(message, event); return event; } }; diff --git a/x-pack/plugins/reporting/server/lib/event_logger/types.ts b/x-pack/plugins/reporting/server/lib/event_logger/types.ts index 1c31292d03e44..3ae06dfdb4775 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/types.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/types.ts @@ -5,31 +5,23 @@ * 2.0. */ +import { LogMeta } from 'src/core/server'; import { ActionType } from './'; -type ActionKind = 'event' | 'error' | 'metrics'; -type ActionOutcome = 'success' | 'failure'; - -interface ActionBase< - A extends ActionType, - K extends ActionKind, - O extends ActionOutcome, - EventProvider -> { +interface ActionBase { event: { - action: A; - kind: K; - outcome?: O; - provider: 'reporting'; timezone: string; }; - kibana: EventProvider & { task?: { id?: string } }; - user?: { name: string }; - log: { - logger: 'reporting'; - level: K extends 'error' ? 'error' : 'info'; - }; message: string; + kibana: { + reporting: { + actionType?: A; + id?: string; // "immediate download" exports have no ID + jobType: string; + byteSize?: number; + }; + } & { task?: { id?: string } }; + user?: { name: string }; } export interface ErrorAction { @@ -39,30 +31,15 @@ export interface ErrorAction { type?: string; } -type ReportingAction< - A extends ActionType, - K extends ActionKind, - O extends ActionOutcome = 'success' -> = ActionBase< - A, - K, - O, - { - reporting: { - id?: string; // "immediate download" exports have no ID - jobType: string; - byteSize?: number; - }; - } ->; +export type ReportingAction = ActionBase & LogMeta; -export type ScheduledTask = ReportingAction; -export type StartedExecution = ReportingAction; -export type CompletedExecution = ReportingAction; -export type SavedReport = ReportingAction; -export type ClaimedTask = ReportingAction; -export type ScheduledRetry = ReportingAction; -export type FailedReport = ReportingAction; -export type ExecuteError = ReportingAction & { +export type ScheduledTask = ReportingAction; +export type StartedExecution = ReportingAction; +export type CompletedExecution = ReportingAction; +export type SavedReport = ReportingAction; +export type ClaimedTask = ReportingAction; +export type ScheduledRetry = ReportingAction; +export type FailedReport = ReportingAction; +export type ExecuteError = ReportingAction & { error: ErrorAction; }; diff --git a/x-pack/plugins/reporting/server/lib/level_logger.ts b/x-pack/plugins/reporting/server/lib/level_logger.ts index 4985ae2d681d0..91cf6757dbee2 100644 --- a/x-pack/plugins/reporting/server/lib/level_logger.ts +++ b/x-pack/plugins/reporting/server/lib/level_logger.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { LoggerFactory } from 'src/core/server'; +import { LoggerFactory, LogMeta } from 'src/core/server'; const trimStr = (toTrim: string) => { return typeof toTrim === 'string' ? toTrim.trim() : toTrim; }; export interface GenericLevelLogger { - debug: (msg: string) => void; + debug: (msg: string, tags: string[], meta: T) => void; info: (msg: string) => void; warning: (msg: string) => void; error: (msg: Error) => void; @@ -46,8 +46,9 @@ export class LevelLogger implements GenericLevelLogger { this.getLogger(tags).warn(msg); } - public debug(msg: string, tags: string[] = []) { - this.getLogger(tags).debug(msg); + // only "debug" logging supports the LogMeta for now... + public debug(msg: string, tags: string[] = [], meta?: T) { + this.getLogger(tags).debug(msg, meta); } public trace(msg: string, tags: string[] = []) { diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 3b25fedd0d5fb..a0d4bfed7c7e0 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -11,7 +11,6 @@ import { ReportingCore } from './'; import { buildConfig, registerUiSettings, ReportingConfigType } from './config'; import { registerDeprecations } from './deprecations'; import { LevelLogger, ReportingStore } from './lib'; -import { registerEventLogProviderActions } from './lib/event_logger'; import { registerRoutes } from './routes'; import { setFieldFormats } from './services'; import type { @@ -38,7 +37,6 @@ export class ReportingPlugin public setup(core: CoreSetup, plugins: ReportingSetupDeps) { const { http, status } = core; - const reportingCore = new ReportingCore(this.logger, this.initContext); // prevent throwing errors in route handlers about async deps not being initialized @@ -60,7 +58,6 @@ export class ReportingPlugin ...plugins, }); - registerEventLogProviderActions(plugins.eventLog); registerUiSettings(core); registerDeprecations({ core, reportingCore }); registerReportingUsageCollector(reportingCore, plugins.usageCollection); diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index aa065e7be52c7..49d92a0fe4448 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -40,10 +40,6 @@ export const createMockPluginSetup = ( taskManager: taskManagerMock.createSetup(), logger: createMockLevelLogger(), status: statusServiceMock.createSetupContract(), - eventLog: { - registerProviderActions: jest.fn(), - getLogger: jest.fn(() => ({ logEvent: jest.fn() })), - }, ...setupMock, }; }; diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index c695df6d0a410..cd28972f5941a 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -12,7 +12,6 @@ import { FieldFormatsStart } from 'src/plugins/field_formats/server'; import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { Writable } from 'stream'; -import { IEventLogService } from '../../event_log/server'; import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import type { LicensingPluginStart } from '../../licensing/server'; import type { @@ -96,7 +95,6 @@ export interface ExportTypeDefinition< * @internal */ export interface ReportingSetupDeps { - eventLog: IEventLogService; features: FeaturesPluginSetup; screenshotMode: ScreenshotModePluginSetup; security?: SecurityPluginSetup; diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json index 24db825856627..cb22a7d9e719a 100644 --- a/x-pack/plugins/reporting/tsconfig.json +++ b/x-pack/plugins/reporting/tsconfig.json @@ -20,7 +20,6 @@ { "path": "../../../src/plugins/ui_actions/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../../../src/plugins/field_formats/tsconfig.json" }, - { "path": "../event_log/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, { "path": "../screenshotting/tsconfig.json" }, diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/event_log.snap b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/event_log.snap deleted file mode 100644 index 603ab78db8c13..0000000000000 --- a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/event_log.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Reporting APIs Report generation event logging creates a completed action for a PDF report 1`] = ` -"\\"_id\\",\\"_index\\",\\"_score\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user -zQMtOW0BH63Xcmy432DJ,ecommerce,1,\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Eddie,Eddie,\\"Eddie Underwood\\",\\"Eddie Underwood\\",MALE,38,Underwood,Underwood,\\"(empty)\\",Monday,0,\\"eddie@underwood-family.zzz\\",Cairo,Africa,EG,\\"POINT (31.3 30.1)\\",\\"Cairo Governorate\\",\\"Elitelligence, Oceanavigations\\",\\"Elitelligence, Oceanavigations\\",\\"Jul 7, 2019 @ 00:00:00.000\\",584677,\\"sold_product_584677_6283, sold_product_584677_19400\\",\\"sold_product_584677_6283, sold_product_584677_19400\\",\\"11.992, 24.984\\",\\"11.992, 24.984\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 26, 2016 @ 00:00:00.000, Dec 26, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Oceanavigations\\",\\"Elitelligence, Oceanavigations\\",\\"6.352, 11.75\\",\\"11.992, 24.984\\",\\"6,283, 19,400\\",\\"Basic T-shirt - dark blue/white, Sweatshirt - grey multicolor\\",\\"Basic T-shirt - dark blue/white, Sweatshirt - grey multicolor\\",\\"1, 1\\",\\"ZO0549605496, ZO0299602996\\",\\"0, 0\\",\\"11.992, 24.984\\",\\"11.992, 24.984\\",\\"0, 0\\",\\"ZO0549605496, ZO0299602996\\",\\"36.969\\",\\"36.969\\",2,2,order,eddie -zgMtOW0BH63Xcmy432DJ,ecommerce,1,\\"Women's Clothing\\",\\"Women's Clothing\\",EUR,Mary,Mary,\\"Mary Bailey\\",\\"Mary Bailey\\",FEMALE,20,Bailey,Bailey,\\"(empty)\\",Sunday,6,\\"mary@bailey-family.zzz\\",Dubai,Asia,AE,\\"POINT (55.3 25.3)\\",Dubai,\\"Champion Arts, Pyramidustries\\",\\"Champion Arts, Pyramidustries\\",\\"Jul 6, 2019 @ 00:00:00.000\\",584021,\\"sold_product_584021_11238, sold_product_584021_20149\\",\\"sold_product_584021_11238, sold_product_584021_20149\\",\\"24.984, 28.984\\",\\"24.984, 28.984\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 25, 2016 @ 00:00:00.000, Dec 25, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Champion Arts, Pyramidustries\\",\\"Champion Arts, Pyramidustries\\",\\"11.75, 15.648\\",\\"24.984, 28.984\\",\\"11,238, 20,149\\",\\"Denim dress - black denim, Shorts - black\\",\\"Denim dress - black denim, Shorts - black\\",\\"1, 1\\",\\"ZO0489604896, ZO0185501855\\",\\"0, 0\\",\\"24.984, 28.984\\",\\"24.984, 28.984\\",\\"0, 0\\",\\"ZO0489604896, ZO0185501855\\",\\"53.969\\",\\"53.969\\",2,2,order,mary -zwMtOW0BH63Xcmy432DJ,ecommerce,1,\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,Gwen,Gwen,\\"Gwen Butler\\",\\"Gwen Butler\\",FEMALE,26,Butler,Butler,\\"(empty)\\",Sunday,6,\\"gwen@butler-family.zzz\\",\\"Los Angeles\\",\\"North America\\",US,\\"POINT (-118.2 34.1)\\",California,\\"Low Tide Media, Oceanavigations\\",\\"Low Tide Media, Oceanavigations\\",\\"Jul 6, 2019 @ 00:00:00.000\\",584058,\\"sold_product_584058_22794, sold_product_584058_23386\\",\\"sold_product_584058_22794, sold_product_584058_23386\\",\\"100, 100\\",\\"100, 100\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 25, 2016 @ 00:00:00.000, Dec 25, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Low Tide Media, Oceanavigations\\",\\"Low Tide Media, Oceanavigations\\",\\"46, 54\\",\\"100, 100\\",\\"22,794, 23,386\\",\\"Boots - Midnight Blue, Short coat - white/black\\",\\"Boots - Midnight Blue, Short coat - white/black\\",\\"1, 1\\",\\"ZO0374603746, ZO0272202722\\",\\"0, 0\\",\\"100, 100\\",\\"100, 100\\",\\"0, 0\\",\\"ZO0374603746, ZO0272202722\\",200,200,2,2,order,gwen -0AMtOW0BH63Xcmy432DJ,ecommerce,1,\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,Diane,Diane,\\"Diane Chandler\\",\\"Diane Chandler\\",FEMALE,22,Chandler,Chandler,\\"(empty)\\",Sunday,6,\\"diane@chandler-family.zzz\\",\\"-\\",Europe,GB,\\"POINT (-0.1 51.5)\\",\\"-\\",\\"Primemaster, Oceanavigations\\",\\"Primemaster, Oceanavigations\\",\\"Jul 6, 2019 @ 00:00:00.000\\",584093,\\"sold_product_584093_12304, sold_product_584093_19587\\",\\"sold_product_584093_12304, sold_product_584093_19587\\",\\"75, 100\\",\\"75, 100\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 25, 2016 @ 00:00:00.000, Dec 25, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Primemaster, Oceanavigations\\",\\"Primemaster, Oceanavigations\\",\\"34.5, 47\\",\\"75, 100\\",\\"12,304, 19,587\\",\\"High heeled sandals - argento, Classic coat - black\\",\\"High heeled sandals - argento, Classic coat - black\\",\\"1, 1\\",\\"ZO0360303603, ZO0272002720\\",\\"0, 0\\",\\"75, 100\\",\\"75, 100\\",\\"0, 0\\",\\"ZO0360303603, ZO0272002720\\",175,175,2,2,order,diane -0QMtOW0BH63Xcmy432DJ,ecommerce,1,\\"Men's Clothing, Men's Accessories\\",\\"Men's Clothing, Men's Accessories\\",EUR,Eddie,Eddie,\\"Eddie Weber\\",\\"Eddie Weber\\",MALE,38,Weber,Weber,\\"(empty)\\",Monday,0,\\"eddie@weber-family.zzz\\",Cairo,Africa,EG,\\"POINT (31.3 30.1)\\",\\"Cairo Governorate\\",Elitelligence,Elitelligence,\\"Jun 30, 2019 @ 00:00:00.000\\",574916,\\"sold_product_574916_11262, sold_product_574916_15713\\",\\"sold_product_574916_11262, sold_product_574916_15713\\",\\"60, 20.984\\",\\"60, 20.984\\",\\"Men's Clothing, Men's Accessories\\",\\"Men's Clothing, Men's Accessories\\",\\"Dec 19, 2016 @ 00:00:00.000, Dec 19, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Elitelligence, Elitelligence\\",\\"Elitelligence, Elitelligence\\",\\"28.203, 10.703\\",\\"60, 20.984\\",\\"11,262, 15,713\\",\\"Winter jacket - black, Watch - green\\",\\"Winter jacket - black, Watch - green\\",\\"1, 1\\",\\"ZO0542505425, ZO0601306013\\",\\"0, 0\\",\\"60, 20.984\\",\\"60, 20.984\\",\\"0, 0\\",\\"ZO0542505425, ZO0601306013\\",81,81,2,2,order,eddie -" -`; diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/event_log.ts b/x-pack/test/reporting_api_integration/reporting_and_security/event_log.ts deleted file mode 100644 index 63317e7644021..0000000000000 --- a/x-pack/test/reporting_api_integration/reporting_and_security/event_log.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const reportingAPI = getService('reportingAPI'); - const es = getService('es'); - - // FLAKY: https://github.com/elastic/kibana/issues/124649 - describe.skip('Report generation event logging', () => { - before(async () => { - await reportingAPI.initEcommerce(); - }); - - after(async () => { - await reportingAPI.teardownEcommerce(); - }); - - it('creates a completed action for a PDF report', async () => { - const res = await reportingAPI.generateCsv({ - browserTimezone: 'UTC', - title: 'Test-PDF', - objectType: 'search', - searchSource: { - version: true, - fields: [{ field: '*', include_unmapped: 'true' }], - index: '5193f870-d861-11e9-a311-0fa548c5f953', - }, - columns: [], - version: '7.16.0', - }); - expect(res.status).to.eql(200); - expect(res.body.path).to.match(/download/); - - const { path } = res.body; - - // wait for the the pending job to complete - await reportingAPI.waitForJobToFinish(path); - - const csvFile = await reportingAPI.getCompletedJobOutput(path); - expectSnapshot(csvFile).toMatch(); - - // search for the raw event log data - const events = await es.search<{ event: any; kibana: { reporting: any } }>({ - index: '.kibana-event-log*', - filter_path: 'hits.hits._source.event,hits.hits._source.kibana', - query: { - bool: { - filter: [ - { - bool: { - must: [ - { term: { 'event.provider': 'reporting' } }, - { term: { 'event.action': 'execute-complete' } }, - ], - }, - }, - ], - }, - }, - sort: [{ '@timestamp': { order: 'desc' } }] as unknown as string[], - size: 1, - }); - - // validate the log has the expected fields with expected values - const logSource = events.hits.hits[0]._source; - expect(omit(logSource?.kibana.reporting, 'id')).to.eql({ - byteSize: 5943, - jobType: 'csv_searchsource', - }); - expect(omit(logSource?.event, ['duration', 'start', 'end'])).to.eql({ - action: 'execute-complete', - kind: 'metrics', - outcome: 'success', - provider: 'reporting', - timezone: 'UTC', - }); - }); - }); -} diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts index e2f70eed3a508..02a2915fffd60 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts @@ -24,7 +24,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./bwc_existing_indexes')); loadTestFile(require.resolve('./security_roles_privileges')); loadTestFile(require.resolve('./download_csv_dashboard')); - loadTestFile(require.resolve('./event_log')); loadTestFile(require.resolve('./generate_csv_discover')); loadTestFile(require.resolve('./network_policy')); loadTestFile(require.resolve('./spaces')); From 109a3be015aee0ba005e9881fb95993dc436ee5f Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Thu, 10 Feb 2022 08:11:54 +0000 Subject: [PATCH 34/85] Remove transaction type filter in errors table (#124933) * delete transaxtion type filter from errors queries * Remove transaction type filter on errors tableat service overview * Fix api tests --- .../app/error_group_overview/index.tsx | 25 +++---------------- .../service_overview_errors_table/index.tsx | 9 ++----- .../errors/__snapshots__/queries.test.ts.snap | 10 -------- .../get_error_group_detailed_statistics.ts | 7 ------ .../get_error_group_main_statistics.ts | 5 ---- .../apm/server/routes/errors/queries.test.ts | 2 -- .../plugins/apm/server/routes/errors/route.ts | 18 ++----------- .../tests/errors/error_group_list.spec.ts | 1 - .../tests/feature_controls.spec.ts | 2 +- .../error_groups_detailed_statistics.spec.ts | 1 - .../error_groups_main_statistics.spec.ts | 1 - .../error_groups/get_error_group_ids.ts | 1 - 12 files changed, 9 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index a6ba3febeb01d..46b963d13e510 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -46,7 +46,7 @@ const INITIAL_STATE_DETAILED_STATISTICS: ErrorGroupDetailedStatistics = { }; export function ErrorGroupOverview() { - const { serviceName, transactionType } = useApmServiceContext(); + const { serviceName } = useApmServiceContext(); const { query: { @@ -82,7 +82,7 @@ export function ErrorGroupOverview() { const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; - if (start && end && transactionType) { + if (start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics', { @@ -92,7 +92,6 @@ export function ErrorGroupOverview() { }, query: { environment, - transactionType, kuery, start, end, @@ -110,16 +109,7 @@ export function ErrorGroupOverview() { }); } }, - [ - environment, - kuery, - serviceName, - transactionType, - start, - end, - sortField, - sortDirection, - ] + [environment, kuery, serviceName, start, end, sortField, sortDirection] ); const { requestId, errorGroupMainStatistics } = errorGroupListData; @@ -128,13 +118,7 @@ export function ErrorGroupOverview() { data: errorGroupDetailedStatistics = INITIAL_STATE_DETAILED_STATISTICS, } = useFetcher( (callApmApi) => { - if ( - requestId && - errorGroupMainStatistics.length && - start && - end && - transactionType - ) { + if (requestId && errorGroupMainStatistics.length && start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics', { @@ -146,7 +130,6 @@ export function ErrorGroupOverview() { start, end, numBuckets: 20, - transactionType, groupIds: JSON.stringify( errorGroupMainStatistics.map(({ groupId }) => groupId).sort() ), diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index 84f65d4e5feb6..cffc5563d75cd 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -15,7 +15,6 @@ import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; import React, { useState } from 'react'; import uuid from 'uuid'; -import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; @@ -62,7 +61,6 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { urlParams: { comparisonType, comparisonEnabled }, } = useLegacyUrlParams(); - const { transactionType } = useApmServiceContext(); const [tableOptions, setTableOptions] = useState<{ pageIndex: number; sort: { @@ -92,7 +90,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { data = INITIAL_STATE_MAIN_STATISTICS, status } = useFetcher( (callApmApi) => { - if (!start || !end || !transactionType) { + if (!start || !end) { return; } return callApmApi( @@ -105,7 +103,6 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { kuery, start, end, - transactionType, }, }, } @@ -131,7 +128,6 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { start, end, serviceName, - transactionType, pageIndex, direction, field, @@ -148,7 +144,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { data: errorGroupDetailedStatistics = INITIAL_STATE_DETAILED_STATISTICS, } = useFetcher( (callApmApi) => { - if (requestId && items.length && start && end && transactionType) { + if (requestId && items.length && start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics', { @@ -160,7 +156,6 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { start, end, numBuckets: 20, - transactionType, groupIds: JSON.stringify( items.map(({ groupId: groupId }) => groupId).sort() ), diff --git a/x-pack/plugins/apm/server/routes/errors/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/routes/errors/__snapshots__/queries.test.ts.snap index 0317009b01f59..dd28f6d1389ee 100644 --- a/x-pack/plugins/apm/server/routes/errors/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/routes/errors/__snapshots__/queries.test.ts.snap @@ -103,11 +103,6 @@ Object { "service.name": "serviceName", }, }, - Object { - "term": Object { - "transaction.type": "request", - }, - }, Object { "range": Object { "@timestamp": Object { @@ -176,11 +171,6 @@ Object { "service.name": "serviceName", }, }, - Object { - "term": Object { - "transaction.type": "request", - }, - }, Object { "range": Object { "@timestamp": Object { diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts index 870c307a3f769..9eda5769e7fb8 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts @@ -10,7 +10,6 @@ import { Coordinate } from '../../../../typings/timeseries'; import { ERROR_GROUP_ID, SERVICE_NAME, - TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { rangeQuery, kqlQuery } from '../../../../../observability/server'; @@ -23,7 +22,6 @@ export async function getErrorGroupDetailedStatistics({ serviceName, setup, numBuckets, - transactionType, groupIds, environment, start, @@ -33,7 +31,6 @@ export async function getErrorGroupDetailedStatistics({ serviceName: string; setup: Setup; numBuckets: number; - transactionType: string; groupIds: string[]; environment: string; start: number; @@ -56,7 +53,6 @@ export async function getErrorGroupDetailedStatistics({ filter: [ { terms: { [ERROR_GROUP_ID]: groupIds } }, { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), @@ -111,7 +107,6 @@ export async function getErrorGroupPeriods({ serviceName, setup, numBuckets, - transactionType, groupIds, environment, comparisonStart, @@ -123,7 +118,6 @@ export async function getErrorGroupPeriods({ serviceName: string; setup: Setup; numBuckets: number; - transactionType: string; groupIds: string[]; environment: string; comparisonStart?: number; @@ -137,7 +131,6 @@ export async function getErrorGroupPeriods({ serviceName, setup, numBuckets, - transactionType, groupIds, }; diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts index e460991029915..d6b58b197914d 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts @@ -15,7 +15,6 @@ import { ERROR_GROUP_ID, ERROR_LOG_MESSAGE, SERVICE_NAME, - TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { environmentQuery } from '../../../../common/utils/environment_query'; @@ -27,7 +26,6 @@ export async function getErrorGroupMainStatistics({ serviceName, setup, environment, - transactionType, sortField, sortDirection = 'desc', start, @@ -37,7 +35,6 @@ export async function getErrorGroupMainStatistics({ serviceName: string; setup: Setup; environment: string; - transactionType: string; sortField?: string; sortDirection?: 'asc' | 'desc'; start: number; @@ -66,7 +63,6 @@ export async function getErrorGroupMainStatistics({ bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), @@ -82,7 +78,6 @@ export async function getErrorGroupMainStatistics({ }, aggs: { sample: { - // change to top_metrics top_hits: { size: 1, _source: [ diff --git a/x-pack/plugins/apm/server/routes/errors/queries.test.ts b/x-pack/plugins/apm/server/routes/errors/queries.test.ts index af4a4aef694fc..7cb84db0d7862 100644 --- a/x-pack/plugins/apm/server/routes/errors/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/queries.test.ts @@ -42,7 +42,6 @@ describe('error queries', () => { sortDirection: 'asc', sortField: 'foo', serviceName: 'serviceName', - transactionType: 'request', setup, environment: ENVIRONMENT_ALL.value, kuery: '', @@ -60,7 +59,6 @@ describe('error queries', () => { sortDirection: 'asc', sortField: 'lastSeen', serviceName: 'serviceName', - transactionType: 'request', setup, environment: ENVIRONMENT_ALL.value, kuery: '', diff --git a/x-pack/plugins/apm/server/routes/errors/route.ts b/x-pack/plugins/apm/server/routes/errors/route.ts index f1d1c79b24f18..52a72a70b7d67 100644 --- a/x-pack/plugins/apm/server/routes/errors/route.ts +++ b/x-pack/plugins/apm/server/routes/errors/route.ts @@ -35,9 +35,6 @@ const errorsMainStatisticsRoute = createApmServerRoute({ environmentRt, kueryRt, rangeRt, - t.type({ - transactionType: t.string, - }), ]), }), options: { tags: ['access:apm'] }, @@ -57,21 +54,13 @@ const errorsMainStatisticsRoute = createApmServerRoute({ const { params } = resources; const setup = await setupRequest(resources); const { serviceName } = params.path; - const { - environment, - transactionType, - kuery, - sortField, - sortDirection, - start, - end, - } = params.query; + const { environment, kuery, sortField, sortDirection, start, end } = + params.query; const errorGroups = await getErrorGroupMainStatistics({ environment, kuery, serviceName, - transactionType, sortField, sortDirection, setup, @@ -97,7 +86,6 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ comparisonRangeRt, t.type({ numBuckets: toNumberRt, - transactionType: t.string, groupIds: jsonRt.pipe(t.array(t.string)), }), ]), @@ -127,7 +115,6 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ environment, kuery, numBuckets, - transactionType, groupIds, comparisonStart, comparisonEnd, @@ -142,7 +129,6 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ serviceName, setup, numBuckets, - transactionType, groupIds, comparisonStart, comparisonEnd, diff --git a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts index 4820eda1870a0..7ec0380429cb9 100644 --- a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts @@ -38,7 +38,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { start: new Date(start).toISOString(), end: new Date(end).toISOString(), environment: 'ENVIRONMENT_ALL', - transactionType: 'request', kuery: '', ...overrides?.query, }, diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.spec.ts b/x-pack/test/apm_api_integration/tests/feature_controls.spec.ts index 64e9ce248b455..77b8faf781eb9 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.spec.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.spec.ts @@ -44,7 +44,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) { // this doubles as a smoke test for the _inspect query parameter req: { - url: `/internal/apm/services/foo/errors/groups/main_statistics?start=${start}&end=${end}&_inspect=true&environment=ENVIRONMENT_ALL&transactionType=bar&kuery=`, + url: `/internal/apm/services/foo/errors/groups/main_statistics?start=${start}&end=${end}&_inspect=true&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts index 0d5d61ee57297..9d09e7046519b 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.spec.ts @@ -43,7 +43,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { start: new Date(start).toISOString(), end: new Date(end).toISOString(), numBuckets: 20, - transactionType: 'request', groupIds: JSON.stringify(['foo']), environment: 'ENVIRONMENT_ALL', kuery: '', diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts index d81c3106e4e2e..43df9a5c4ec5e 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.spec.ts @@ -38,7 +38,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { query: { start: new Date(start).toISOString(), end: new Date(end).toISOString(), - transactionType: 'request', environment: 'ENVIRONMENT_ALL', kuery: '', ...overrides?.query, diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts index ae0f552819920..914f6962d9bce 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts @@ -27,7 +27,6 @@ export async function getErrorGroupIds({ query: { start: new Date(start).toISOString(), end: new Date(end).toISOString(), - transactionType: 'request', environment: 'ENVIRONMENT_ALL', kuery: '', }, From 8b5293bd6a7f79cca36b05506b9d793a5b426c5b Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Thu, 10 Feb 2022 03:40:01 -0500 Subject: [PATCH 35/85] [Response Ops][Cases] Fixing Metrics User Actions access (#125024) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../cases/server/authorization/index.ts | 5 +- .../tests/common/metrics/get_case_metrics.ts | 118 +++++++++++++++++- 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index 8347a456f0811..bf37e3530a710 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -58,6 +58,7 @@ const EVENT_TYPES: Record = { const DELETE_COMMENT_OPERATION = 'deleteComment'; const ACCESS_COMMENT_OPERATION = 'getComment'; const ACCESS_CASE_OPERATION = 'getCase'; +const ACCESS_USER_ACTION_OPERATION = 'getUserActions'; /** * Database constant for ECS category for use for audit logging. @@ -293,7 +294,7 @@ export const Operations: Record { const supertest = getService('supertest'); const es = getService('es'); const kibanaServer = getService('kibanaServer'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('case metrics', () => { describe('closed case from kbn archive', () => { @@ -95,6 +108,109 @@ export default ({ getService }: FtrProviderContext): void => { }); }); }); + + describe('rbac', () => { + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should retrieve the metrics without getting an authorization error', async () => { + const newCase = await createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolutionFixture' }), + 200, + { + user: superUser, + space: 'space1', + } + ); + + for (const user of [globalRead, superUser, secOnly, secOnlyRead, obsSec, obsSecRead]) { + const metrics = await getCaseMetrics({ + supertest: supertestWithoutAuth, + caseId: newCase.id, + features: [ + 'lifespan', + 'alerts.hosts', + 'alerts.users', + 'alerts.count', + 'connectors', + 'actions.isolateHost', + ], + auth: { user, space: 'space1' }, + }); + + expect(metrics.alerts).to.eql({ + count: 0, + hosts: { total: 0, values: [] }, + users: { total: 0, values: [] }, + }); + expect(metrics.connectors).to.eql({ + total: 0, + }); + expect(metrics.actions).to.eql({ + isolateHost: { isolate: { total: 0 }, unisolate: { total: 0 } }, + }); + expect(metrics.lifespan).to.not.eql(undefined); + } + }); + + it('should receive a 403 when attempting to retrieve the metrics when the user does not have access to the owner', async () => { + const newCase = await createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolutionFixture' }), + 200, + { + user: superUser, + space: 'space1', + } + ); + + for (const user of [noKibanaPrivileges, obsOnly, obsOnlyRead]) { + await getCaseMetrics({ + supertest: supertestWithoutAuth, + caseId: newCase.id, + features: [ + 'lifespan', + 'alerts.hosts', + 'alerts.users', + 'alerts.count', + 'connectors', + 'actions.isolateHost', + ], + expectedHttpCode: 403, + auth: { user, space: 'space1' }, + }); + } + }); + + it('should receive a 403 when attempting to retrieve the metrics when the user does not have permissions in the space', async () => { + const newCase = await createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolutionFixture' }), + 200, + { + user: superUser, + space: 'space2', + } + ); + + await getCaseMetrics({ + supertest: supertestWithoutAuth, + caseId: newCase.id, + features: [ + 'lifespan', + 'alerts.hosts', + 'alerts.users', + 'alerts.count', + 'connectors', + 'actions.isolateHost', + ], + expectedHttpCode: 403, + auth: { user: secOnly, space: 'space2' }, + }); + }); + }); }); }; From 227e4d1c6390e216c19fbd3d4845a6eecf06b0af Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 10 Feb 2022 09:48:22 +0100 Subject: [PATCH 36/85] make locator async (#125112) --- src/plugins/visualizations/common/locator.ts | 62 ++--------------- .../visualizations/common/locator_location.ts | 68 +++++++++++++++++++ 2 files changed, 73 insertions(+), 57 deletions(-) create mode 100644 src/plugins/visualizations/common/locator_location.ts diff --git a/src/plugins/visualizations/common/locator.ts b/src/plugins/visualizations/common/locator.ts index a1d15ee5188d3..8e562eedb765a 100644 --- a/src/plugins/visualizations/common/locator.ts +++ b/src/plugins/visualizations/common/locator.ts @@ -6,21 +6,12 @@ * Side Public License, v 1. */ -import type { SerializableRecord, Serializable } from '@kbn/utility-types'; -import { omitBy } from 'lodash'; -import type { ParsedQuery } from 'query-string'; -import { stringify } from 'query-string'; -import rison from 'rison-node'; -import { Filter, isFilterPinned } from '@kbn/es-query'; +import type { SerializableRecord } from '@kbn/utility-types'; +import { Filter } from '@kbn/es-query'; import type { Query, RefreshInterval, TimeRange } from 'src/plugins/data/common'; import type { LocatorDefinition, LocatorPublic } from 'src/plugins/share/common'; -import { url } from '../../kibana_utils/common'; -import { GLOBAL_STATE_STORAGE_KEY, STATE_STORAGE_KEY, VisualizeConstants } from './constants'; import type { SavedVisState } from './types'; -const removeEmptyKeys = (o: Record): Record => - omitBy(o, (v) => v == null); - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type VisualizeLocatorParams = { /** @@ -83,51 +74,8 @@ export const VISUALIZE_APP_LOCATOR = 'VISUALIZE_APP_LOCATOR'; export class VisualizeLocatorDefinition implements LocatorDefinition { id = VISUALIZE_APP_LOCATOR; - public async getLocation({ - visId, - timeRange, - filters, - refreshInterval, - linked, - uiState, - query, - vis, - savedSearchId, - indexPattern, - }: VisualizeLocatorParams) { - let path = visId - ? `#${VisualizeConstants.EDIT_PATH}/${visId}` - : `#${VisualizeConstants.CREATE_PATH}`; - - const urlState: ParsedQuery = { - [GLOBAL_STATE_STORAGE_KEY]: rison.encode( - removeEmptyKeys({ - time: timeRange, - filters: filters?.filter((f) => isFilterPinned(f)), - refreshInterval, - }) - ), - [STATE_STORAGE_KEY]: rison.encode( - removeEmptyKeys({ - linked, - filters: filters?.filter((f) => !isFilterPinned(f)), - uiState, - query, - vis, - }) - ), - }; - - path += `?${stringify(url.encodeQuery(urlState), { encode: false, sort: false })}`; - - const otherParams = stringify({ type: vis?.type, savedSearchId, indexPattern }); - - if (otherParams) path += `&${otherParams}`; - - return { - app: VisualizeConstants.APP_ID, - path, - state: {}, - }; + public async getLocation(params: VisualizeLocatorParams) { + const { getLocation } = await import('./locator_location'); + return getLocation(params); } } diff --git a/src/plugins/visualizations/common/locator_location.ts b/src/plugins/visualizations/common/locator_location.ts new file mode 100644 index 0000000000000..1e6ee3d362d68 --- /dev/null +++ b/src/plugins/visualizations/common/locator_location.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Serializable } from '@kbn/utility-types'; +import { omitBy } from 'lodash'; +import type { ParsedQuery } from 'query-string'; +import { stringify } from 'query-string'; +import rison from 'rison-node'; +import { isFilterPinned } from '@kbn/es-query'; +import { url } from '../../kibana_utils/common'; +import { GLOBAL_STATE_STORAGE_KEY, STATE_STORAGE_KEY, VisualizeConstants } from './constants'; +import type { VisualizeLocatorParams } from './locator'; + +const removeEmptyKeys = (o: Record): Record => + omitBy(o, (v) => v == null); + +export async function getLocation({ + visId, + timeRange, + filters, + refreshInterval, + linked, + uiState, + query, + vis, + savedSearchId, + indexPattern, +}: VisualizeLocatorParams) { + let path = visId + ? `#${VisualizeConstants.EDIT_PATH}/${visId}` + : `#${VisualizeConstants.CREATE_PATH}`; + + const urlState: ParsedQuery = { + [GLOBAL_STATE_STORAGE_KEY]: rison.encode( + removeEmptyKeys({ + time: timeRange, + filters: filters?.filter((f) => isFilterPinned(f)), + refreshInterval, + }) + ), + [STATE_STORAGE_KEY]: rison.encode( + removeEmptyKeys({ + linked, + filters: filters?.filter((f) => !isFilterPinned(f)), + uiState, + query, + vis, + }) + ), + }; + + path += `?${stringify(url.encodeQuery(urlState), { encode: false, sort: false })}`; + + const otherParams = stringify({ type: vis?.type, savedSearchId, indexPattern }); + + if (otherParams) path += `&${otherParams}`; + + return { + app: VisualizeConstants.APP_ID, + path, + state: {}, + }; +} From 157f8e1bbb833bda906a155d1ba12922d2c12274 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 10 Feb 2022 09:49:37 +0100 Subject: [PATCH 37/85] remove legacy import (#125119) --- .../vis_types/vega/public/vega_view/vega_base_view.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js index a87c8318e319c..23856950e3405 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js @@ -15,8 +15,8 @@ import { version as vegaLiteVersion } from 'vega-lite'; import { Utils } from '../data_model/utils'; import { euiPaletteColorBlind } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { buildQueryFilter, compareFilters } from '@kbn/es-query'; import { TooltipHandler } from './vega_tooltip'; -import { esFilters } from '../../../../data/public'; import { getEnableExternalUrls, getData } from '../services'; import { extractIndexPatternsFromSpec } from '../lib/extract_index_pattern'; @@ -344,7 +344,7 @@ export class VegaBaseView { */ async addFilterHandler(query, index, alias) { const indexId = await this.findIndex(index); - const filter = esFilters.buildQueryFilter(query, indexId, alias); + const filter = buildQueryFilter(query, indexId, alias); this._fireEvent({ name: 'applyFilter', data: { filters: [filter] } }); } @@ -355,12 +355,10 @@ export class VegaBaseView { */ async removeFilterHandler(query, index) { const indexId = await this.findIndex(index); - const filterToRemove = esFilters.buildQueryFilter(query, indexId); + const filterToRemove = buildQueryFilter(query, indexId); const currentFilters = this._filterManager.getFilters(); - const existingFilter = currentFilters.find((filter) => - esFilters.compareFilters(filter, filterToRemove) - ); + const existingFilter = currentFilters.find((filter) => compareFilters(filter, filterToRemove)); if (!existingFilter) return; From e93a651c1d7f7c0923a01ed41be87d6daf5abd4e Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 10 Feb 2022 09:54:10 +0100 Subject: [PATCH 38/85] [Uptime monitor management] Add full screen/copy button ability in browser inline script editing (#124500) --- .../__snapshots__/code_editor.test.tsx.snap | 76 +++---- .../public/code_editor/code_editor.tsx | 204 +++++++++++++++--- .../public/code_editor/editor.scss | 17 ++ .../components/fleet_package/code_editor.tsx | 2 + 4 files changed, 228 insertions(+), 71 deletions(-) diff --git a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap index b05abbcece0b9..9a4511f8b03f5 100644 --- a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap +++ b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap @@ -126,6 +126,7 @@ exports[` is rendered 1`] = ` >
is rendered 1`] = ` /> - + -
-
-