From 656b6cd88b3dc2782e627b8ab954d832d6158edb Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 11 Apr 2022 19:26:47 -0400 Subject: [PATCH 01/32] Fix partial QA-ed process tree styles (#129957) --- .../public/components/process_tree_alerts/styles.ts | 3 +-- .../public/components/process_tree_node/styles.ts | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/process_tree_alerts/styles.ts b/x-pack/plugins/session_view/public/components/process_tree_alerts/styles.ts index 4c6f011ec870b..bd95d87258178 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alerts/styles.ts +++ b/x-pack/plugins/session_view/public/components/process_tree_alerts/styles.ts @@ -16,14 +16,13 @@ export const useStyles = () => { const { size, colors, border } = euiTheme; const container: CSSObject = { - margin: `${size.xs} ${size.s} 0 ${size.xs}`, + margin: `${size.xs} ${size.base} 0 ${size.xs}`, color: colors.text, padding: `${size.s} 0`, borderStyle: 'solid', borderColor: colors.lightShade, borderWidth: border.width.thin, borderRadius: border.radius.medium, - maxWidth: 800, maxHeight: 378, overflowY: 'auto', backgroundColor: colors.emptyShade, diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts b/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts index c3122294e44fd..cd514c2087bdc 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts +++ b/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts @@ -23,6 +23,7 @@ export const useStyles = ({ depth, hasAlerts, hasInvestigatedAlert, isSelected } const { colors, border, size, font } = euiTheme; const TREE_INDENT = `calc(${size.l} + ${size.xxs})`; + const PROCESS_TREE_LEFT_PADDING = size.s; const darkText: CSSObject = { color: colors.text, @@ -79,10 +80,10 @@ export const useStyles = ({ depth, hasAlerts, hasInvestigatedAlert, isSelected } height: '100%', pointerEvents: 'none', content: `''`, - marginLeft: `calc(-${depth} * ${TREE_INDENT})`, + marginLeft: `calc(-${depth} * ${TREE_INDENT} - ${PROCESS_TREE_LEFT_PADDING})`, borderLeft: `${size.xs} solid ${borderColor}`, backgroundColor: bgColor, - width: `calc(100% + ${depth} * ${TREE_INDENT})`, + width: `calc(100% + ${depth} * ${TREE_INDENT} + ${PROCESS_TREE_LEFT_PADDING})`, transform: `translateY(-${size.xs})`, }, }; @@ -99,8 +100,8 @@ export const useStyles = ({ depth, hasAlerts, hasInvestigatedAlert, isSelected } verticalAlign: 'middle', color: colors.mediumShade, wordBreak: 'break-all', - minHeight: size.l, - lineHeight: size.l, + minHeight: `calc(${size.l} - ${size.xxs})`, + lineHeight: `calc(${size.l} - ${size.xxs})`, }; const workingDir: CSSObject = { From 35593dda6c402201261a64110c6c6198e013bd26 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 12 Apr 2022 02:43:00 +0100 Subject: [PATCH 02/32] chore(NA): upgrades bazel to v5.1.1 (#129943) --- .bazelversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index 0062ac971805f..ac14c3dfaa865 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.0.0 +5.1.1 From 36e4ddd79f81cfc12ccbae99b5645d73cfc881e9 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:31:55 +0500 Subject: [PATCH 03/32] [Discover] Cancel long running request after navigating out from Discover (#129444) * [Discover] cancel search on navigating out * [Discover] fix abortController reference Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/main/utils/use_saved_search.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plugins/discover/public/application/main/utils/use_saved_search.ts b/src/plugins/discover/public/application/main/utils/use_saved_search.ts index c8a59d2eb3167..8197979e103eb 100644 --- a/src/plugins/discover/public/application/main/utils/use_saved_search.ts +++ b/src/plugins/discover/public/application/main/utils/use_saved_search.ts @@ -147,7 +147,6 @@ export const useSavedSearch = ({ * Values that shouldn't trigger re-rendering when changed */ const refs = useRef<{ - abortController?: AbortController; autoRefreshDone?: AutoRefreshDoneFn; }>({}); @@ -172,6 +171,7 @@ export const useSavedSearch = ({ searchSource, initialFetchStatus, }); + let abortController: AbortController; const subscription = fetch$.subscribe(async (val) => { if (!validateTimeRange(timefilter.getTime(), services.toastNotifications)) { @@ -179,12 +179,12 @@ export const useSavedSearch = ({ } inspectorAdapters.requests.reset(); - refs.current.abortController?.abort(); - refs.current.abortController = new AbortController(); + abortController?.abort(); + abortController = new AbortController(); const autoRefreshDone = refs.current.autoRefreshDone; await fetchAll(dataSubjects, searchSource, val === 'reset', { - abortController: refs.current.abortController, + abortController, appStateContainer: stateContainer.appStateContainer, data, initialFetchStatus, @@ -205,7 +205,10 @@ export const useSavedSearch = ({ } }); - return () => subscription.unsubscribe(); + return () => { + abortController?.abort(); + subscription.unsubscribe(); + }; }, [ data, data.query.queryString, From 67ef4f73f77ef35b58432d6eff4dd0e3a84ea7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Tue, 12 Apr 2022 08:26:25 +0200 Subject: [PATCH 04/32] [DOCS] Adds GET reporters to cases API docs (#129089) --- docs/api/cases.asciidoc | 3 +- .../cases/cases-api-get-reporters.asciidoc | 60 +++++++++++++++++++ docs/api/cases/cases-api-get-tags.asciidoc | 9 +-- 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 docs/api/cases/cases-api-get-reporters.asciidoc diff --git a/docs/api/cases.asciidoc b/docs/api/cases.asciidoc index 6342d3c4b8d2f..3b5bfaeceaff4 100644 --- a/docs/api/cases.asciidoc +++ b/docs/api/cases.asciidoc @@ -17,7 +17,7 @@ these APIs: * <> * <> * {security-guide}/cases-get-connector.html[Get current connector] -* {security-guide}/cases-api-get-reporters.html[Get reporters] +* <> * <> * {security-guide}/cases-api-push.html[Push case] * {security-guide}/assign-connector.html[Set default Elastic Security UI connector] @@ -40,6 +40,7 @@ include::cases/cases-api-get-case-activity.asciidoc[leveloffset=+1] include::cases/cases-api-get-case.asciidoc[leveloffset=+1] include::cases/cases-api-get-status.asciidoc[leveloffset=+1] include::cases/cases-api-get-comments.asciidoc[leveloffset=+1] +include::cases/cases-api-get-reporters.asciidoc[leveloffset=+1] include::cases/cases-api-get-tags.asciidoc[leveloffset=+1] //UPDATE include::cases/cases-api-update.asciidoc[leveloffset=+1] diff --git a/docs/api/cases/cases-api-get-reporters.asciidoc b/docs/api/cases/cases-api-get-reporters.asciidoc new file mode 100644 index 0000000000000..eca8d3e45173f --- /dev/null +++ b/docs/api/cases/cases-api-get-reporters.asciidoc @@ -0,0 +1,60 @@ +[[cases-api-get-reporters]] +== Get reporters API +++++ +Get reporters +++++ + +Returns information about the users who opened cases. + +=== Request + +`GET :/api/cases/reporters` + +`GET :/s/api/cases/reporters` + +=== Prerequisite + +You must have `read` privileges for the *Cases* feature in the *Management*, +*{observability}*, or *Security* section of the +<>, depending on the +`owner` of the cases you're seeking. + +=== Query parameters + +`owner`:: +(Optional, string or array of strings) A filter to limit the retrieved reporters +to a specific set of applications. If this parameter is omitted, the response +will contain all reporters from cases that the user has access to read. + +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Returns all case reporters: + +[source,sh] +-------------------------------------------------- +GET api/cases/reporters +-------------------------------------------------- +// KIBANA + +The API returns a JSON object with the retrieved reporters. For example: + +[source,json] +-------------------------------------------------- +[ + { + "full_name": "Alan Hunley", + "email": "ahunley@imf.usa.gov", + "username": "ahunley" + }, + { + "full_name": "Rat Hustler", + "email": "jrhustler@aol.com", + "username": "rhustler" + } +] +-------------------------------------------------- diff --git a/docs/api/cases/cases-api-get-tags.asciidoc b/docs/api/cases/cases-api-get-tags.asciidoc index b97fa23df06e8..426a7e91a0f47 100644 --- a/docs/api/cases/cases-api-get-tags.asciidoc +++ b/docs/api/cases/cases-api-get-tags.asciidoc @@ -32,9 +32,9 @@ default space is used. === Query parameters `owner`:: -(Optional, string) Specifies the set of applications to limit the retrieved -tags. If not specified, the response contains all tags that the user has access -to read. +(Optional, string or array of strings) Specifies the set of applications to +limit the retrieved tags. If not specified, the response contains all tags from +cases that the user has access to read. ==== Response code @@ -51,7 +51,8 @@ GET api/cases/tags -------------------------------------------------- // KIBANA -The API returns a JSON object with all tags for all cases. For example: +The API returns a JSON object with the names and email addresses of users who +opened cases. For example: [source,json] -------------------------------------------------- From cbf0ceacb82b58b61c4ebe05438552e551b61418 Mon Sep 17 00:00:00 2001 From: Baturalp Gurdin <9674241+suchcodemuchwow@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:32:54 +0300 Subject: [PATCH 05/32] Performance Test service and config refactor (#128258) * add build id, job id and execution id to playwright config * change apm configs (serverUrl, secretToken) * refactor single user journeys to break "test" concept around them * Authentication service and Login Journey * move test steps to runUserJourney as argument Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../functional/performance_playwright.sh | 3 - x-pack/test/performance/config.playwright.ts | 7 +- x-pack/test/performance/services/auth.ts | 62 ++++++ x-pack/test/performance/services/index.ts | 2 + .../test/performance/services/performance.ts | 193 ++++++++---------- .../tests/playwright/ecommerce_dashboard.ts | 93 +++++---- .../tests/playwright/flight_dashboard.ts | 121 ++++++----- .../performance/tests/playwright/index.ts | 9 +- .../performance/tests/playwright/login.ts | 42 ++++ .../promotion_tracking_dashboard.ts | 93 +++++---- .../tests/playwright/web_logs_dashboard.ts | 91 +++++---- 11 files changed, 423 insertions(+), 293 deletions(-) create mode 100644 x-pack/test/performance/services/auth.ts create mode 100644 x-pack/test/performance/tests/playwright/login.ts diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index 596304d156cf0..9a4301e94f7fe 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -20,9 +20,6 @@ sleep 120 cd "$XPACK_DIR" -jobId=$(npx uuid) -export TEST_JOB_ID="$jobId" - journeys=("ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard") for i in "${journeys[@]}"; do diff --git a/x-pack/test/performance/config.playwright.ts b/x-pack/test/performance/config.playwright.ts index cc290ee19d2fa..9077c58a30e15 100644 --- a/x-pack/test/performance/config.playwright.ts +++ b/x-pack/test/performance/config.playwright.ts @@ -19,8 +19,11 @@ export default async function ({ readConfigFile, log }: FtrConfigProviderContext const testFiles = [require.resolve('./tests/playwright')]; - const testJobId = process.env.TEST_JOB_ID ?? uuid(); - log.info(`👷 JOB ID ${testJobId}👷`); + const testBuildId = process.env.BUILDKITE_BUILD_ID ?? `local-${uuid()}`; + const testJobId = process.env.BUILDKITE_JOB_ID ?? `local-${uuid()}`; + const executionId = uuid(); + + log.info(` 👷‍♀️ BUILD ID ${testBuildId}\n 👷 JOB ID ${testJobId}\n 👷‍♂️ EXECUTION ID:${executionId}`); return { testFiles, diff --git a/x-pack/test/performance/services/auth.ts b/x-pack/test/performance/services/auth.ts new file mode 100644 index 0000000000000..90e20c36e59d9 --- /dev/null +++ b/x-pack/test/performance/services/auth.ts @@ -0,0 +1,62 @@ +/* + * 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 axios, { AxiosResponse } from 'axios'; +import Url from 'url'; +import { FtrService, FtrProviderContext } from '../ftr_provider_context'; + +export interface Credentials { + username: string; + password: string; +} + +function extractCookieValue(authResponse: AxiosResponse) { + return authResponse.headers['set-cookie'][0].toString().split(';')[0].split('sid=')[1] as string; +} +export class AuthService extends FtrService { + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + private readonly config = this.ctx.getService('config'); + + constructor(ctx: FtrProviderContext) { + super(ctx); + } + + public async login({ username, password }: Credentials) { + const headers = { + 'content-type': 'application/json', + 'kbn-version': await this.kibanaServer.version.get(), + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + }; + + const baseUrl = Url.format({ + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: this.config.get('servers.kibana.port'), + }); + + const loginUrl = baseUrl + '/internal/security/login'; + const provider = baseUrl.includes('localhost') ? 'basic' : 'cloud-basic'; + + const authBody = { + providerType: 'basic', + providerName: provider, + currentURL: `${baseUrl}/login?next=%2F`, + params: { username, password }, + }; + + const authResponse = await axios.post(loginUrl, authBody, { headers }); + + return { + name: 'sid', + value: extractCookieValue(authResponse), + url: baseUrl, + }; + } +} + +export const AuthProvider = (ctx: FtrProviderContext) => new AuthService(ctx); diff --git a/x-pack/test/performance/services/index.ts b/x-pack/test/performance/services/index.ts index 99cc10bc14bdc..d8cd6075c91c2 100644 --- a/x-pack/test/performance/services/index.ts +++ b/x-pack/test/performance/services/index.ts @@ -8,6 +8,7 @@ import { services as functionalServices } from '../../functional/services'; import { PerformanceTestingService } from './performance'; import { InputDelaysProvider } from './input_delays'; +import { AuthProvider } from './auth'; export const services = { es: functionalServices.es, @@ -16,4 +17,5 @@ export const services = { retry: functionalServices.retry, performance: PerformanceTestingService, inputDelays: InputDelaysProvider, + auth: AuthProvider, }; diff --git a/x-pack/test/performance/services/performance.ts b/x-pack/test/performance/services/performance.ts index 3905a61bf7b33..ffe7211c63153 100644 --- a/x-pack/test/performance/services/performance.ts +++ b/x-pack/test/performance/services/performance.ts @@ -10,52 +10,46 @@ import Url from 'url'; import { inspect } from 'util'; import apm, { Span, Transaction } from 'elastic-apm-node'; -import { setTimeout } from 'timers/promises'; -import playwright, { ChromiumBrowser, Page, BrowserContext } from 'playwright'; +import playwright, { ChromiumBrowser, Page, BrowserContext, CDPSession } from 'playwright'; import { FtrService, FtrProviderContext } from '../ftr_provider_context'; -type StorageState = Awaited>; - apm.start({ - secretToken: 'Q5q5rWQEw6tKeirBpw', - serverUrl: 'https://2fad4006bf784bb8a54e52f4a5862609.apm.us-west1.gcp.cloud.es.io:443', serviceName: 'functional test runner', + serverUrl: 'https://kibana-ops-e2e-perf.apm.us-central1.gcp.cloud.es.io:443', + secretToken: 'CTs9y3cvcfq13bQqsB', }); -interface StepCtx { +export interface StepCtx { page: Page; + kibanaUrl: string; } + type StepFn = (ctx: StepCtx) => Promise; -type Steps = Array<{ name: string; fn: StepFn }>; +export type Steps = Array<{ name: string; handler: StepFn }>; export class PerformanceTestingService extends FtrService { + private readonly auth = this.ctx.getService('auth'); private readonly config = this.ctx.getService('config'); - private readonly lifecycle = this.ctx.getService('lifecycle'); - private readonly inputDelays = this.ctx.getService('inputDelays'); + private browser: ChromiumBrowser | undefined; - private storageState: StorageState | undefined; private currentSpanStack: Array = []; - private currentTransaction: Transaction | undefined | null; + private currentTransaction: Transaction | undefined | null = undefined; constructor(ctx: FtrProviderContext) { super(ctx); + } - this.lifecycle.beforeTests.add(async () => { - await this.withTransaction('Journey setup', async () => { - await this.getStorageState(); - }); - }); - - this.lifecycle.cleanup.add(async () => { - apm.flush(); - await setTimeout(5000); - await this.browser?.close(); + private getKibanaUrl() { + return Url.format({ + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: this.config.get('servers.kibana.port'), }); } private async withTransaction(name: string, block: () => Promise) { try { - if (this.currentTransaction !== undefined) { + if (this.currentTransaction) { throw new Error( `Transaction already started, make sure you end transaction ${this.currentTransaction?.name}` ); @@ -105,42 +99,6 @@ export class PerformanceTestingService extends FtrService { ?.traceparent; } - private async getStorageState() { - if (this.storageState) { - return this.storageState; - } - - await this.withSpan('initial login', undefined, async () => { - const kibanaUrl = Url.format({ - protocol: this.config.get('servers.kibana.protocol'), - hostname: this.config.get('servers.kibana.hostname'), - port: this.config.get('servers.kibana.port'), - }); - - const browser = await this.getBrowserInstance(); - const context = await browser.newContext(); - const page = await context.newPage(); - await this.interceptBrowserRequests(page); - await page.goto(`${kibanaUrl}`); - - const usernameLocator = page.locator('[data-test-subj=loginUsername]'); - const passwordLocator = page.locator('[data-test-subj=loginPassword]'); - const submitButtonLocator = page.locator('[data-test-subj=loginSubmit]'); - - await usernameLocator?.type('elastic', { delay: this.inputDelays.TYPING }); - await passwordLocator?.type('changeme', { delay: this.inputDelays.TYPING }); - await submitButtonLocator?.click({ delay: this.inputDelays.MOUSE_CLICK }); - - await page.waitForSelector('#headerUserMenu'); - - this.storageState = await page.context().storageState(); - await page.close(); - await context.close(); - }); - - return this.storageState; - } - private async getBrowserInstance() { if (this.browser) { return this.browser; @@ -179,70 +137,79 @@ export class PerformanceTestingService extends FtrService { }); } - public makePage(journeyName: string) { - const steps: Steps = []; - - it(journeyName, async () => { - await this.withTransaction(`Journey ${journeyName}`, async () => { - const browser = await this.getBrowserInstance(); - const context = await browser.newContext({ - viewport: { width: 1600, height: 1200 }, - storageState: await this.getStorageState(), - }); - - const page = await context.newPage(); - page.on('console', (message) => { - (async () => { - try { - const args = await Promise.all( - message.args().map(async (handle) => handle.jsonValue()) - ); + public runUserJourney( + journeyName: string, + steps: Steps, + { requireAuth }: { requireAuth: boolean } + ) { + return this.withTransaction(`Journey ${journeyName}`, async () => { + const browser = await this.getBrowserInstance(); + const viewport = { width: 1600, height: 1200 }; + const context = await browser.newContext({ viewport }); - const { url, lineNumber, columnNumber } = message.location(); + if (!requireAuth) { + const cookie = await this.auth.login({ username: 'elastic', password: 'changeme' }); + await context.addCookies([cookie]); + } - const location = `${url},${lineNumber},${columnNumber}`; + const page = await context.newPage(); + if (!process.env.NO_BROWSER_LOG) { + page.on('console', this.onConsoleEvent()); + } + const client = await this.sendCDPCommands(context, page); - const text = args.length - ? args.map((arg) => (typeof arg === 'string' ? arg : inspect(arg))).join(' ') - : message.text(); + await this.interceptBrowserRequests(page); + await this.handleSteps(steps, page); + await this.tearDown(page, client, context); + }); + } - console.log(`[console.${message.type()}]`, text); - console.log(' ', location); - } catch (e) { - console.error('Failed to evaluate console.log line', e); - } - })(); - }); - const client = await this.sendCDPCommands(context, page); + private async tearDown(page: Page, client: CDPSession, context: BrowserContext) { + if (page) { + apm.flush(); + await client.detach(); + await page.close(); + await context.close(); + } + } - await this.interceptBrowserRequests(page); + public async shutdownBrowser() { + if (this.browser) { + await (await this.getBrowserInstance()).close(); + } + } + private async handleSteps(steps: Array<{ name: string; handler: StepFn }>, page: Page) { + for (const step of steps) { + await this.withSpan(`step: ${step.name}`, 'step', async () => { try { - for (const step of steps) { - await this.withSpan(`step: ${step.name}`, 'step', async () => { - try { - await step.fn({ page }); - } catch (e) { - const error = new Error(`Step [${step.name}] failed: ${e.message}`); - error.stack = e.stack; - throw error; - } - }); - } - } finally { - if (page) { - await client.detach(); - await page.close(); - await context.close(); - } + await step.handler({ page, kibanaUrl: this.getKibanaUrl() }); + } catch (e) { + const error = new Error(`Step [${step.name}] failed: ${e.message}`); + error.stack = e.stack; } }); - }); + } + } + + private onConsoleEvent() { + return async (message: playwright.ConsoleMessage) => { + try { + const args = await Promise.all(message.args().map(async (handle) => handle.jsonValue())); + + const { url, lineNumber, columnNumber } = message.location(); - return { - step: (name: string, fn: StepFn) => { - steps.push({ name, fn }); - }, + const location = `${url},${lineNumber},${columnNumber}`; + + const text = args.length + ? args.map((arg) => (typeof arg === 'string' ? arg : inspect(arg, false, null))).join(' ') + : message.text(); + + console.log(`[console.${message.type()}]`, text); + console.log(' ', location); + } catch (e) { + console.error('Failed to evaluate console.log line', e); + } }; } } diff --git a/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts b/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts index 1f75d39a3cd62..143ca97c6d0b0 100644 --- a/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts @@ -4,53 +4,64 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; +import { Page } from 'playwright'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function ecommerceDashboard({ getService }: FtrProviderContext) { describe('ecommerce_dashboard', () => { - const config = getService('config'); - const performance = getService('performance'); - const logger = getService('log'); + it('ecommerce_dashboard', async () => { + const performance = getService('performance'); + const logger = getService('log'); - const { step } = performance.makePage('ecommerce_dashboard'); + await performance.runUserJourney( + 'ecommerce_dashboard', + [ + { + name: 'Go to Sample Data Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); + await page.waitForSelector('text="More ways to add data"'); + }, + }, + { + name: 'Add Ecommerce Sample Data', + handler: async ({ page }: { page: Page }) => { + const removeButton = page.locator('[data-test-subj=removeSampleDataSetecommerce]'); + try { + await removeButton.click({ timeout: 1_000 }); + } catch (e) { + logger.info('Ecommerce data does not exist'); + } + const addDataButton = page.locator('[data-test-subj=addSampleDataSetecommerce]'); + if (addDataButton) { + await addDataButton.click(); + } + }, + }, + { + name: 'Go to Ecommerce Dashboard', + handler: async ({ page }: { page: Page }) => { + await page.click('[data-test-subj=launchSampleDataSetecommerce]'); + await page.click('[data-test-subj=viewSampleDataSetecommerce-dashboard]'); - step('Go to Sample Data Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); - - await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); - await page.waitForSelector('text="More ways to add data"'); - }); - - step('Add Ecommerce Sample Data', async ({ page }) => { - const removeButton = page.locator('[data-test-subj=removeSampleDataSetecommerce]'); - try { - await removeButton.click({ timeout: 1_000 }); - } catch (e) { - logger.info('Ecommerce data does not exist'); - } - const addDataButton = page.locator('[data-test-subj=addSampleDataSetecommerce]'); - if (addDataButton) { - await addDataButton.click(); - } - }); - - step('Go to Ecommerce Dashboard', async ({ page }) => { - await page.click('[data-test-subj=launchSampleDataSetecommerce]'); - await page.click('[data-test-subj=viewSampleDataSetecommerce-dashboard]'); - - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + ], + { + requireAuth: false, + } + ); }); }); } diff --git a/x-pack/test/performance/tests/playwright/flight_dashboard.ts b/x-pack/test/performance/tests/playwright/flight_dashboard.ts index 04ea397d95e34..4844265018a05 100644 --- a/x-pack/test/performance/tests/playwright/flight_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/flight_dashboard.ts @@ -4,69 +4,84 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function flightDashboard({ getService }: FtrProviderContext) { - describe('flights_dashboard', () => { - const config = getService('config'); - const performance = getService('performance'); - const logger = getService('log'); - const { step } = performance.makePage('flights_dashboard'); + describe('flight_dashboard', () => { + it('flight_dashboard', async () => { + const performance = getService('performance'); + const logger = getService('log'); - step('Go to Sample Data Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); + await performance.runUserJourney( + 'flight_dashboard', + [ + { + name: 'Go to Sample Data Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); + await page.waitForSelector('[data-test-subj=sampleDataSetCardflights]'); + }, + }, + { + name: 'Add Flights Sample Data', + handler: async ({ page }) => { + const removeButton = page.locator('[data-test-subj=removeSampleDataSetflights]'); + try { + await removeButton.click({ timeout: 1_000 }); + } catch (e) { + logger.info('Flights data does not exist'); + } - await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); - await page.waitForSelector('[data-test-subj=sampleDataSetCardflights]'); - }); - - step('Add Flights Sample Data', async ({ page }) => { - const removeButton = page.locator('[data-test-subj=removeSampleDataSetflights]'); - try { - await removeButton.click({ timeout: 1_000 }); - } catch (e) { - logger.info('Flights data does not exist'); - } - - const addDataButton = page.locator('[data-test-subj=addSampleDataSetflights]'); - if (addDataButton) { - await addDataButton.click(); - } - }); - - step('Go to Flights Dashboard', async ({ page }) => { - await page.click('[data-test-subj=launchSampleDataSetflights]'); - await page.click('[data-test-subj=viewSampleDataSetflights-dashboard]'); + const addDataButton = page.locator('[data-test-subj=addSampleDataSetflights]'); + if (addDataButton) { + await addDataButton.click(); + } + }, + }, + { + name: 'Go to Flights Dashboard', + handler: async ({ page }) => { + await page.click('[data-test-subj=launchSampleDataSetflights]'); + await page.click('[data-test-subj=viewSampleDataSetflights-dashboard]'); - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); - }); - // embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport) - step('Go to Airport Connections Visualizations Edit', async ({ page }) => { - await page.click('[data-test-subj="dashboardEditMode"]'); + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + { + name: 'Go to Airport Connections Visualizations Edit', + handler: async ({ page }) => { + await page.click('[data-test-subj="dashboardEditMode"]'); - const flightsPanelHeadingSelector = `[data-test-subj="embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport)"]`; - const panelToggleMenuIconSelector = `[data-test-subj="embeddablePanelToggleMenuIcon"]`; + const flightsPanelHeadingSelector = `[data-test-subj="embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport)"]`; + const panelToggleMenuIconSelector = `[data-test-subj="embeddablePanelToggleMenuIcon"]`; - await page.click(`${flightsPanelHeadingSelector} ${panelToggleMenuIconSelector}`); + await page.click(`${flightsPanelHeadingSelector} ${panelToggleMenuIconSelector}`); - await page.click('[data-test-subj="embeddablePanelAction-editPanel"]'); + await page.click('[data-test-subj="embeddablePanelAction-editPanel"]'); - await page.waitForFunction(() => { - const visualization = document.querySelector('[data-rendering-count]'); - return visualization && visualization?.getAttribute('data-render-complete') === 'true'; - }); + await page.waitForFunction(function renderCompleted() { + const visualization = document.querySelector('[data-rendering-count]'); + return ( + visualization && visualization?.getAttribute('data-render-complete') === 'true' + ); + }); + }, + }, + ], + { + requireAuth: false, + } + ); }); }); } diff --git a/x-pack/test/performance/tests/playwright/index.ts b/x-pack/test/performance/tests/playwright/index.ts index 7f426ee047d3e..05dfe4cdfbc81 100644 --- a/x-pack/test/performance/tests/playwright/index.ts +++ b/x-pack/test/performance/tests/playwright/index.ts @@ -6,11 +6,18 @@ */ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const performance = getService('performance'); + describe('Performance tests', () => { + loadTestFile(require.resolve('./login')); loadTestFile(require.resolve('./ecommerce_dashboard')); loadTestFile(require.resolve('./flight_dashboard')); loadTestFile(require.resolve('./web_logs_dashboard')); loadTestFile(require.resolve('./promotion_tracking_dashboard')); + + after(async () => { + await performance.shutdownBrowser(); + }); }); } diff --git a/x-pack/test/performance/tests/playwright/login.ts b/x-pack/test/performance/tests/playwright/login.ts new file mode 100644 index 0000000000000..74baabc049f86 --- /dev/null +++ b/x-pack/test/performance/tests/playwright/login.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 { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; + +export default function ecommerceDashboard({ getService }: FtrProviderContext) { + describe('login', () => { + it('login', async () => { + const inputDelays = getService('inputDelays'); + const performance = getService('performance'); + + await performance.runUserJourney( + 'login', + [ + { + name: 'Login', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}`); + + const usernameLocator = page.locator('[data-test-subj=loginUsername]'); + const passwordLocator = page.locator('[data-test-subj=loginPassword]'); + const submitButtonLocator = page.locator('[data-test-subj=loginSubmit]'); + + await usernameLocator?.type('elastic', { delay: inputDelays.TYPING }); + await passwordLocator?.type('changeme', { delay: inputDelays.TYPING }); + await submitButtonLocator?.click({ delay: inputDelays.MOUSE_CLICK }); + + await page.waitForSelector('#headerUserMenu'); + }, + }, + ], + { + requireAuth: true, + } + ); + }); + }); +} diff --git a/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts b/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts index 4f36378eb1dc3..cb0fb09aafefa 100644 --- a/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts @@ -4,16 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function promotionTrackingDashboard({ getService }: FtrProviderContext) { describe('promotion_tracking_dashboard', () => { - const config = getService('config'); const performance = getService('performance'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const { step } = performance.makePage('promotion_tracking_dashboard'); before(async () => { await kibanaServer.importExport.load( @@ -29,45 +27,60 @@ export default function promotionTrackingDashboard({ getService }: FtrProviderCo await esArchiver.unload('x-pack/test/performance/es_archives/ecommerce_sample_data'); }); - step('Go to Dashboards Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); + it('promotion_tracking_dashboard', async () => { + await performance.runUserJourney( + 'promotion_tracking_dashboard', + [ + { + name: 'Go to Dashboards Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/dashboards`); + await page.waitForSelector('#dashboardListingHeading'); + }, + }, + { + name: 'Go to Promotion Tracking Dashboard', + handler: async ({ page }) => { + const promotionDashboardButton = page.locator( + '[data-test-subj="dashboardListingTitleLink-Promotion-Dashboard"]' + ); + await promotionDashboardButton.click(); + }, + }, + { + name: 'Change time range', + handler: async ({ page }) => { + const beginningTimeRangeButton = page.locator( + '[data-test-subj="superDatePickerToggleQuickMenuButton"]' + ); + await beginningTimeRangeButton.click(); - await page.goto(`${kibanaUrl}/app/dashboards`); - await page.waitForSelector('#dashboardListingHeading'); - }); - - step('Go to Promotion Tracking Dashboard', async ({ page }) => { - const promotionDashboardButton = page.locator( - '[data-test-subj="dashboardListingTitleLink-Promotion-Dashboard"]' - ); - await promotionDashboardButton.click(); - }); - - step('Change time range', async ({ page }) => { - const beginningTimeRangeButton = page.locator( - '[data-test-subj="superDatePickerToggleQuickMenuButton"]' + const lastYearButton = page.locator( + '[data-test-subj="superDatePickerCommonlyUsed_Last_30 days"]' + ); + await lastYearButton.click(); + }, + }, + { + name: 'Wait for visualization animations to finish', + handler: async ({ page }) => { + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + ], + { + requireAuth: false, + } ); - await beginningTimeRangeButton.click(); - - const lastYearButton = page.locator( - '[data-test-subj="superDatePickerCommonlyUsed_Last_30 days"]' - ); - await lastYearButton.click(); - }); - - step('Wait for visualization animations to finish', async ({ page }) => { - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); }); }); } diff --git a/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts b/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts index 6da99a6662c4e..6ecee7f1244f7 100644 --- a/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts @@ -4,53 +4,64 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function weblogDashboard({ getService }: FtrProviderContext) { describe('weblogs_dashboard', () => { - const config = getService('config'); - const performance = getService('performance'); - const logger = getService('log'); - const { step } = performance.makePage('weblogs_dashboard'); + it('weblogs_dashboard', async () => { + const performance = getService('performance'); + const logger = getService('log'); - step('Go to Sample Data Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); + await performance.runUserJourney( + 'weblogs_dashboard', + [ + { + name: 'Go to Sample Data Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); + await page.waitForSelector('text="More ways to add data"'); + }, + }, + { + name: 'Add Web Logs Sample Data', + handler: async ({ page }) => { + const removeButton = page.locator('[data-test-subj=removeSampleDataSetlogs]'); + try { + await removeButton.click({ timeout: 1_000 }); + } catch (e) { + logger.info('Weblogs data does not exist'); + } - await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); - await page.waitForSelector('text="More ways to add data"'); - }); - - step('Add Web Logs Sample Data', async ({ page }) => { - const removeButton = page.locator('[data-test-subj=removeSampleDataSetlogs]'); - try { - await removeButton.click({ timeout: 1_000 }); - } catch (e) { - logger.info('Weblogs data does not exist'); - } - - const addDataButton = page.locator('[data-test-subj=addSampleDataSetlogs]'); - if (addDataButton) { - await addDataButton.click(); - } - }); - - step('Go to Web Logs Dashboard', async ({ page }) => { - await page.click('[data-test-subj=launchSampleDataSetlogs]'); - await page.click('[data-test-subj=viewSampleDataSetlogs-dashboard]'); + const addDataButton = page.locator('[data-test-subj=addSampleDataSetlogs]'); + if (addDataButton) { + await addDataButton.click(); + } + }, + }, + { + name: 'Go to Web Logs Dashboard', + handler: async ({ page }) => { + await page.click('[data-test-subj=launchSampleDataSetlogs]'); + await page.click('[data-test-subj=viewSampleDataSetlogs-dashboard]'); - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + ], + { + requireAuth: false, + } + ); }); }); } From 485f2d169c718004a4eeb33d8bbe6490bff541c1 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Tue, 12 Apr 2022 08:36:13 +0200 Subject: [PATCH 06/32] fixed enrollment token modal for managed policy (#129870) --- .../components/new_enrollment_key_modal.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/new_enrollment_key_modal.tsx b/x-pack/plugins/fleet/public/components/new_enrollment_key_modal.tsx index ffda9bdcb16ad..95e1e2563c231 100644 --- a/x-pack/plugins/fleet/public/components/new_enrollment_key_modal.tsx +++ b/x-pack/plugins/fleet/public/components/new_enrollment_key_modal.tsx @@ -76,8 +76,18 @@ export const NewEnrollmentTokenModal: React.FunctionComponent = ({ agentPolicies = [], }) => { const { notifications } = useStartServices(); + + const selectPolicyOptions = useMemo(() => { + return agentPolicies + .filter((agentPolicy) => !agentPolicy.is_managed) + .map((agentPolicy) => ({ + value: agentPolicy.id, + text: agentPolicy.name, + })); + }, [agentPolicies]); + const form = useCreateApiKeyForm( - agentPolicies.length > 0 ? agentPolicies[0].id : undefined, + selectPolicyOptions.length > 0 ? selectPolicyOptions[0].value : undefined, (key: EnrollmentAPIKey) => { onClose(key); notifications.toasts.addSuccess( @@ -93,15 +103,6 @@ export const NewEnrollmentTokenModal: React.FunctionComponent = ({ } ); - const selectPolicyOptions = useMemo(() => { - return agentPolicies - .filter((agentPolicy) => !agentPolicy.is_managed) - .map((agentPolicy) => ({ - value: agentPolicy.id, - text: agentPolicy.name, - })); - }, [agentPolicies]); - const body = (
From d1e3ce25485214165fa42e03059f33590d30a60b Mon Sep 17 00:00:00 2001 From: Karl Godard Date: Tue, 12 Apr 2022 00:33:05 -0700 Subject: [PATCH 07/32] [Session view] Search will now only search through verbose stuff if verbose mode is on. (#129959) * session view search will now only search through verbose stuff if verbose mode is on. * lint fix * revised approach to auto expand when searching. * any process with alerts now is auto expanded, regardless of jumpTo * removed hacky selectionArea code (it was a premature optimization). also fixed ordering of children and search results. * another couple fixes. duplicated events in ProcessImpl, as well as tweaks to scrollIntoView * fix to nested jumpTo not working on initial load Co-authored-by: mitodrummer --- .../constants/session_view_process.mock.ts | 22 +++++ .../common/types/process_tree/index.ts | 3 + .../components/process_tree/helpers.test.ts | 4 +- .../public/components/process_tree/helpers.ts | 21 ++++- .../public/components/process_tree/hooks.ts | 73 +++++++++++---- .../components/process_tree/index.test.tsx | 43 +-------- .../public/components/process_tree/index.tsx | 84 ++--------------- .../process_tree_node/index.test.tsx | 10 +- .../components/process_tree_node/index.tsx | 53 +++++++---- .../components/process_tree_node/styles.ts | 9 +- .../process_tree_node/use_button_styles.ts | 5 - .../components/session_view/index.test.tsx | 4 - .../public/components/session_view/index.tsx | 12 ++- .../session_view_display_options/index.tsx | 93 ++++++++++++++----- .../session_view_search_bar/index.tsx | 10 +- .../session_view_search_bar/styles.ts | 14 +++ 16 files changed, 257 insertions(+), 203 deletions(-) diff --git a/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts b/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts index 581134eae1782..6def7c7bcbd47 100644 --- a/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts +++ b/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts @@ -160,6 +160,7 @@ export const mockEvents: ProcessEvent[] = [ action: EventAction.fork, category: 'process', kind: EventKind.event, + id: '1', }, host: { architecture: 'x86_64', @@ -317,6 +318,7 @@ export const mockEvents: ProcessEvent[] = [ action: EventAction.exec, category: 'process', kind: EventKind.event, + id: '2', }, }, { @@ -459,6 +461,7 @@ export const mockEvents: ProcessEvent[] = [ action: EventAction.end, category: 'process', kind: EventKind.event, + id: '3', }, host: { architecture: 'x86_64', @@ -621,6 +624,7 @@ export const mockEvents: ProcessEvent[] = [ action: EventAction.end, category: 'process', kind: EventKind.event, + id: '4', }, host: { architecture: 'x86_64', @@ -809,6 +813,7 @@ export const mockAlerts: ProcessEvent[] = [ action: EventAction.exec, category: 'process', kind: EventKind.signal, + id: '5', }, host: { architecture: 'x86_64', @@ -995,6 +1000,7 @@ export const mockAlerts: ProcessEvent[] = [ action: EventAction.end, category: 'process', kind: EventKind.signal, + id: '6', }, host: { architecture: 'x86_64', @@ -1264,6 +1270,7 @@ export const childProcessMock: Process = { getAlerts: () => [], updateAlertsStatus: (_) => undefined, hasExec: () => false, + isVerbose: () => true, getOutput: () => '', getDetails: () => ({ @@ -1272,6 +1279,7 @@ export const childProcessMock: Process = { kind: EventKind.event, category: 'process', action: EventAction.exec, + id: '1', }, host: { architecture: 'x86_64', @@ -1326,6 +1334,7 @@ export const childProcessMock: Process = { isUserEntered: () => false, getMaxAlertLevel: () => null, getEndTime: () => '', + isDescendantOf: () => false, }; export const processMock: Process = { @@ -1346,6 +1355,7 @@ export const processMock: Process = { getAlerts: () => [], updateAlertsStatus: (_) => undefined, hasExec: () => false, + isVerbose: () => true, getOutput: () => '', getDetails: () => ({ @@ -1354,6 +1364,7 @@ export const processMock: Process = { kind: EventKind.event, category: 'process', action: EventAction.exec, + id: '2', }, host: { architecture: 'x86_64', @@ -1390,6 +1401,14 @@ export const processMock: Process = { working_directory: '/home/vagrant', start: '2021-11-23T15:25:04.210Z', pid: 1, + user: { + id: '1000', + name: 'vagrant', + }, + group: { + id: '1000', + name: 'vagrant', + }, parent: { pid: 2442, user: { @@ -1499,6 +1518,7 @@ export const processMock: Process = { isUserEntered: () => false, getMaxAlertLevel: () => null, getEndTime: () => '', + isDescendantOf: () => false, }; export const sessionViewBasicProcessMock: Process = { @@ -1544,7 +1564,9 @@ export const mockProcessMap = mockEvents.reduce( getDetails: () => event, isUserEntered: () => false, getMaxAlertLevel: () => null, + isVerbose: () => true, getEndTime: () => '', + isDescendantOf: () => false, }; return processMap; }, diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index c4cb85a81dd0c..11f5aeb2ffac2 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -140,6 +140,7 @@ export interface ProcessEvent { kind?: EventKind; category?: string; action?: EventAction; + id?: string; }; user?: User; group?: Group; @@ -178,7 +179,9 @@ export interface Process { isUserEntered(): boolean; getMaxAlertLevel(): number | null; getChildren(verboseMode: boolean): Process[]; + isVerbose(): boolean; getEndTime(): string; + isDescendantOf(process: Process): boolean; } export type ProcessMap = { diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts index dcea2249ac198..9f7ce962dec3f 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts @@ -61,7 +61,7 @@ describe('process tree hook helpers tests', () => { }); it('searchProcessTree works', () => { - const searchResults = searchProcessTree(mockProcessMap, SEARCH_QUERY); + const searchResults = searchProcessTree(mockProcessMap, SEARCH_QUERY, true); // search returns the process with search query in its event args expect(searchResults[0].id).toBe(SEARCH_RESULT_PROCESS_ID); @@ -76,7 +76,7 @@ describe('process tree hook helpers tests', () => { processMap[SESSION_ENTITY_ID].children = childProcesses; expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeFalsy(); - processMap = autoExpandProcessTree(processMap); + processMap = autoExpandProcessTree(processMap, SEARCH_RESULT_PROCESS_ID); // session leader should have autoExpand to be true expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeTruthy(); }); diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts index 2f383271f498d..ee2670dad47d2 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { sortProcesses } from '../../../common/utils/sort_processes'; import { AlertStatusEventEntityIdMap, EventKind, @@ -137,13 +138,25 @@ export const buildProcessTree = ( // this funtion also returns a list of process results which is used by session_view_search_bar to drive // result navigation UX // FYI: this function mutates properties of models contained in processMap -export const searchProcessTree = (processMap: ProcessMap, searchQuery: string | undefined) => { +export const searchProcessTree = ( + processMap: ProcessMap, + searchQuery: string | undefined, + verboseMode: boolean +) => { const results = []; for (const processId of Object.keys(processMap)) { const process = processMap[processId]; if (searchQuery) { + const details = process.getDetails(); + const entryLeader = details?.process?.entry_leader; + + // if this is the entry leader process OR verbose mode is OFF and is a verbose process, don't match. + if (entryLeader?.entity_id === process.id || (!verboseMode && process.isVerbose())) { + continue; + } + const event = process.getDetails(); const { working_directory: workingDirectory, args } = event.process || {}; @@ -162,19 +175,19 @@ export const searchProcessTree = (processMap: ProcessMap, searchQuery: string | } } - return results; + return results.sort(sortProcesses); }; // Iterate over all processes in processMap, and mark each process (and it's ancestors) for auto expansion if: // a) the process was "user entered" (aka an interactive group leader) -// b) matches the plain text search above +// b) we are jumping to a specific process // Returns the processMap with it's processes autoExpand bool set to true or false // process.autoExpand is read by process_tree_node to determine whether to auto expand it's child processes. export const autoExpandProcessTree = (processMap: ProcessMap, jumpToEntityId?: string) => { for (const processId of Object.keys(processMap)) { const process = processMap[processId]; - if (process.searchMatched || process.isUserEntered() || jumpToEntityId === process.id) { + if (process.isUserEntered() || jumpToEntityId === process.id || process.hasAlerts()) { let { parent } = process; const parentIdSet = new Set(); diff --git a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts index 1725abdcdb5b9..7054bdade9546 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts @@ -31,6 +31,7 @@ interface UseProcessTreeDeps { alerts: ProcessEvent[]; searchQuery?: string; updatedAlertsStatus: AlertStatusEventEntityIdMap; + verboseMode: boolean; jumpToEntityId?: string; } @@ -54,10 +55,16 @@ export class ProcessImpl implements Process { this.searchMatched = null; } - addEvent(event: ProcessEvent) { + addEvent(newEvent: ProcessEvent) { // rather than push new events on the array, we return a new one // this helps the below memoizeOne functions to behave correctly. - this.events = this.events.concat(event); + const exists = this.events.find((event) => { + return event.event?.id === newEvent.event?.id; + }); + + if (!exists) { + this.events = this.events.concat(newEvent); + } } addAlert(alert: ProcessEvent) { @@ -66,7 +73,6 @@ export class ProcessImpl implements Process { clearSearch() { this.searchMatched = null; - this.autoExpand = false; } getChildren(verboseMode: boolean) { @@ -74,28 +80,22 @@ export class ProcessImpl implements Process { // if there are orphans, we just render them inline with the other child processes (currently only session leader does this) if (this.orphans.length) { - children = [...children, ...this.orphans].sort(sortProcesses); + children = [...children, ...this.orphans]; } // When verboseMode is false, we filter out noise via a few techniques. // This option is driven by the "verbose mode" toggle in SessionView/index.tsx if (!verboseMode) { - return children.filter((child) => { + children = children.filter((child) => { if (child.events.length === 0) { return false; } - const { group_leader: groupLeader, session_leader: sessionLeader } = - child.getDetails().process ?? {}; - - // search matches or processes with alerts will never be filtered out - if (child.autoExpand || child.searchMatched || child.hasAlerts()) { + // processes with alerts will never be filtered out + if (child.autoExpand || child.hasAlerts()) { return true; } - // Hide processes that have their session leader as their process group leader. - // This accounts for a lot of noise from bash and other shells forking, running auto completion processes and - // other shell startup activities (e.g bashrc .profile etc) - if (!groupLeader || !sessionLeader || groupLeader.pid === sessionLeader.pid) { + if (child.isVerbose()) { return false; } @@ -103,7 +103,27 @@ export class ProcessImpl implements Process { }); } - return children; + return children.sort(sortProcesses); + } + + isVerbose() { + const { + group_leader: groupLeader, + session_leader: sessionLeader, + entry_leader: entryLeader, + } = this.getDetails().process ?? {}; + + // Processes that have their session leader as their process group leader are considered "verbose" + // This accounts for a lot of noise from bash and other shells forking, running auto completion processes and + // other shell startup activities (e.g bashrc .profile etc) + if ( + this.id !== entryLeader?.entity_id && + (!groupLeader || !sessionLeader || groupLeader.pid === sessionLeader.pid) + ) { + return true; + } + + return false; } hasOutput() { @@ -221,6 +241,20 @@ export class ProcessImpl implements Process { // If a process has an 'end' event will always be returned (since it is last and includes details like exit_code and end time) return filtered[filtered.length - 1] ?? {}; }); + + isDescendantOf(process: Process) { + let parent = this.parent; + + while (parent) { + if (parent === process) { + return true; + } + + parent = parent.parent; + } + + return false; + } } export const useProcessTree = ({ @@ -229,6 +263,7 @@ export const useProcessTree = ({ alerts, searchQuery, updatedAlertsStatus, + verboseMode, jumpToEntityId, }: UseProcessTreeDeps) => { // initialize map, as well as a placeholder for session leader process @@ -289,8 +324,9 @@ export const useProcessTree = ({ setProcessMap({ ...updatedProcessMap }); setProcessedPages([...processedPages, ...newProcessedPages]); setOrphans(newOrphans); + autoExpandProcessTree(updatedProcessMap, jumpToEntityId); } - }, [data, processMap, orphans, processedPages, sessionEntityId]); + }, [data, processMap, orphans, processedPages, sessionEntityId, jumpToEntityId]); useEffect(() => { // currently we are loading a single page of alerts, with no pagination @@ -303,9 +339,8 @@ export const useProcessTree = ({ }, [processMap, alerts, alertsProcessed]); useEffect(() => { - setSearchResults(searchProcessTree(processMap, searchQuery)); - autoExpandProcessTree(processMap, jumpToEntityId); - }, [searchQuery, processMap, jumpToEntityId]); + setSearchResults(searchProcessTree(processMap, searchQuery, verboseMode)); + }, [searchQuery, processMap, verboseMode]); // set new orphans array on the session leader const sessionLeader = processMap[sessionEntityId]; diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx index 4dfb00025f35a..3201d6dfa7e1b 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx @@ -14,7 +14,6 @@ import { } from '../../../common/mocks/constants/session_view_process.mock'; import { Process } from '../../../common/types/process_tree'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; -import { ProcessImpl } from './hooks'; import { ProcessTreeDeps, ProcessTree } from './index'; describe('ProcessTree component', () => { @@ -22,7 +21,6 @@ describe('ProcessTree component', () => { let renderResult: ReturnType; let mockedContext: AppContextTestRender; const sessionLeader = mockData[0].events![0]; - const sessionLeaderVerboseTest = mockData[0].events![3]; const props: ProcessTreeDeps = { sessionEntityId: sessionLeader.process!.entity_id!, data: mockData, @@ -104,50 +102,13 @@ describe('ProcessTree component', () => { }); it('When Verbose mode is OFF, it should not show all childrens', () => { - renderResult = mockedContext.render(); + renderResult = mockedContext.render(); expect(renderResult.queryByText('cat')).toBeFalsy(); - - const selectionArea = renderResult.queryAllByTestId('sessionView:processTreeNode'); - const result = selectionArea.map((a) => a?.getAttribute('data-id')); - - expect(result.includes(sessionLeader.process!.entity_id!)).toBeTruthy(); - expect(result.includes(sessionLeaderVerboseTest.process!.entity_id!)).toBeFalsy(); }); it('When Verbose mode is ON, it should show all childrens', () => { - renderResult = mockedContext.render(); + renderResult = mockedContext.render(); expect(renderResult.queryByText('cat')).toBeTruthy(); - - const selectionArea = renderResult.queryAllByTestId('sessionView:processTreeNode'); - const result = selectionArea.map((a) => a?.getAttribute('data-id')); - - expect(result.includes(sessionLeader.process!.entity_id!)).toBeTruthy(); - expect(result.includes(sessionLeaderVerboseTest.process!.entity_id!)).toBeTruthy(); - }); - - it('should insert a DOM element used to highlight a process when selectedProcess is set', () => { - const mockSelectedProcess = new ProcessImpl(mockData[0].events![0].process!.entity_id!); - - renderResult = mockedContext.render( - - ); - - expect( - renderResult - .queryByTestId('sessionView:processTreeSelectionArea') - ?.parentElement?.getAttribute('data-id') - ).toEqual(mockSelectedProcess.id); - - // change the selected process - const mockSelectedProcess2 = new ProcessImpl(mockData[0].events![1].process!.entity_id!); - - renderResult.rerender(); - - expect( - renderResult - .queryByTestId('sessionView:processTreeSelectionArea') - ?.parentElement?.getAttribute('data-id') - ).toEqual(mockSelectedProcess2.id); }); }); }); diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.tsx index 158e5b8faa24a..f2b5fef85002c 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useRef, useEffect, useLayoutEffect, useCallback, useMemo } from 'react'; +import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { ProcessTreeNode } from '../process_tree_node'; import { BackToInvestigatedAlert } from '../back_to_investigated_alert'; @@ -62,8 +62,8 @@ export interface ProcessTreeDeps { // a map for alerts with updated status and process.entity_id updatedAlertsStatus: AlertStatusEventEntityIdMap; onShowAlertDetails: (alertUuid: string) => void; - timeStampOn?: boolean; - verboseModeOn?: boolean; + showTimestamp?: boolean; + verboseMode?: boolean; } export const ProcessTree = ({ @@ -83,8 +83,8 @@ export const ProcessTree = ({ setSearchResults, updatedAlertsStatus, onShowAlertDetails, - timeStampOn, - verboseModeOn, + showTimestamp = true, + verboseMode = false, }: ProcessTreeDeps) => { const [isInvestigatedEventVisible, setIsInvestigatedEventVisible] = useState(true); const [isInvestigatedEventAbove, setIsInvestigatedEventAbove] = useState(false); @@ -96,6 +96,7 @@ export const ProcessTree = ({ alerts, searchQuery, updatedAlertsStatus, + verboseMode, jumpToEntityId, }); @@ -109,7 +110,6 @@ export const ProcessTree = ({ }, [data]); const scrollerRef = useRef(null); - const selectionAreaRef = useRef(null); const onChangeJumpToEventVisibility = useCallback( (isVisible: boolean, isAbove: boolean) => { @@ -143,58 +143,6 @@ export const ProcessTree = ({ }, }); - /** - * highlights a process in the tree - * we do it this way to avoid state changes on potentially thousands of components - */ - const selectProcess = useCallback( - (process: Process) => { - if (!selectionAreaRef?.current || !scrollerRef?.current) { - return; - } - - const selectionAreaEl = selectionAreaRef.current; - selectionAreaEl.style.display = 'block'; - - // TODO: concept of alert level unknown wrt to elastic security - const alertLevel = process.getMaxAlertLevel(); - - if (alertLevel && alertLevel >= 0) { - selectionAreaEl.style.backgroundColor = - alertLevel > 0 ? styles.alertSelected : styles.defaultSelected; - } else { - selectionAreaEl.style.backgroundColor = ''; - } - - // find the DOM element for the command which is selected by id - const processEl = scrollerRef.current.querySelector(`[data-id="${process.id}"]`); - - if (processEl) { - processEl.prepend(selectionAreaEl); - - const { height: elHeight, y: elTop } = processEl.getBoundingClientRect(); - const { y: viewPortElTop, height: viewPortElHeight } = - scrollerRef.current.getBoundingClientRect(); - - const viewPortElBottom = viewPortElTop + viewPortElHeight; - const elBottom = elTop + elHeight; - const isVisible = elBottom >= viewPortElTop && elTop <= viewPortElBottom; - - // jest will die when calling scrollIntoView (perhaps not part of the DOM it executes under) - if (!isVisible && processEl.scrollIntoView) { - processEl.scrollIntoView({ block: 'center' }); - } - } - }, - [styles.alertSelected, styles.defaultSelected] - ); - - useLayoutEffect(() => { - if (selectedProcess) { - selectProcess(selectedProcess); - } - }, [selectedProcess, selectProcess]); - useEffect(() => { if (jumpToEntityId) { const process = processMap[jumpToEntityId]; @@ -206,14 +154,7 @@ export const ProcessTree = ({ } else if (!selectedProcess) { onProcessSelected(sessionLeader); } - }, [ - jumpToEntityId, - processMap, - onProcessSelected, - selectProcess, - selectedProcess, - sessionLeader, - ]); + }, [jumpToEntityId, processMap, onProcessSelected, selectedProcess, sessionLeader]); return ( <> @@ -229,12 +170,12 @@ export const ProcessTree = ({ onProcessSelected={onProcessSelected} jumpToEntityId={jumpToEntityId} investigatedAlertId={investigatedAlertId} - selectedProcessId={selectedProcess?.id} + selectedProcess={selectedProcess} scrollerRef={scrollerRef} onChangeJumpToEventVisibility={onChangeJumpToEventVisibility} onShowAlertDetails={onShowAlertDetails} - timeStampOn={timeStampOn} - verboseModeOn={verboseModeOn} + showTimestamp={showTimestamp} + verboseMode={verboseMode} searchResults={searchResults} loadPreviousButton={ hasPreviousPage ? ( @@ -260,11 +201,6 @@ export const ProcessTree = ({ } /> )} -
{!isInvestigatedEventVisible && ( { } as unknown as RefObject, onChangeJumpToEventVisibility: jest.fn(), onShowAlertDetails: jest.fn(), + showTimestamp: true, + verboseMode: false, }; beforeEach(() => { @@ -123,6 +125,10 @@ describe('ProcessTreeNode component', () => { }, process: { ...processMock.getDetails().process, + user: { + id: '-1', + name: 'root', + }, parent: { ...processMock.getDetails().process!.parent, user: { @@ -176,7 +182,7 @@ describe('ProcessTreeNode component', () => { it('When Timestamp is ON, it shows Timestamp', async () => { // set a mock where Timestamp is turned ON renderResult = mockedContext.render( - + ); expect(renderResult.getByTestId('sessionView:processTreeNodeTimestamp')).toBeTruthy(); @@ -185,7 +191,7 @@ describe('ProcessTreeNode component', () => { it('When Timestamp is OFF, it doesnt show Timestamp', async () => { // set a mock where Timestamp is turned OFF renderResult = mockedContext.render( - + ); expect(renderResult.queryByTestId('sessionView:processTreeNodeTimestamp')).toBeFalsy(); diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx index 208a0c6fbfd5b..d1b0fded615f6 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx @@ -41,9 +41,9 @@ export interface ProcessDeps { onProcessSelected?: (process: Process) => void; jumpToEntityId?: string; investigatedAlertId?: string; - selectedProcessId?: string; - timeStampOn?: boolean; - verboseModeOn?: boolean; + selectedProcess?: Process | null; + showTimestamp: boolean; + verboseMode: boolean; searchResults?: Process[]; scrollerRef: RefObject; onChangeJumpToEventVisibility: (isVisible: boolean, isAbove: boolean) => void; @@ -62,9 +62,9 @@ export function ProcessTreeNode({ onProcessSelected, jumpToEntityId, investigatedAlertId, - selectedProcessId, - timeStampOn = true, - verboseModeOn = true, + selectedProcess, + showTimestamp, + verboseMode, searchResults, scrollerRef, onChangeJumpToEventVisibility, @@ -78,9 +78,18 @@ export function ProcessTreeNode({ const [alertsExpanded, setAlertsExpanded] = useState(false); const { searchMatched } = process; + // forces nodes to expand if the selected process is a descendant useEffect(() => { - setChildrenExpanded(isSessionLeader || process.autoExpand); - }, [isSessionLeader, process.autoExpand]); + if (!childrenExpanded && selectedProcess) { + if (selectedProcess.isDescendantOf(process)) { + setChildrenExpanded(true); + } + } + }, [selectedProcess, process, childrenExpanded]); + + useEffect(() => { + setChildrenExpanded(process.autoExpand); + }, [process.autoExpand]); const alerts = process.getAlerts(); const hasAlerts = useMemo(() => !!alerts.length, [alerts]); @@ -94,7 +103,7 @@ export function ProcessTreeNode({ ), [hasAlerts, alerts, investigatedAlertId] ); - const isSelected = selectedProcessId === process.id; + const isSelected = selectedProcess?.id === process.id; const styles = useStyles({ depth, hasAlerts, hasInvestigatedAlert, isSelected }); const buttonStyles = useButtonStyles({}); @@ -109,6 +118,12 @@ export function ProcessTreeNode({ shouldAddListener: hasInvestigatedAlert, }); + useEffect(() => { + if (process.id === selectedProcess?.id && nodeRef.current?.scrollIntoView) { + nodeRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + }, [selectedProcess, process, nodeRef]); + // Automatically expand alerts list when investigating an alert useEffect(() => { if (hasInvestigatedAlert) { @@ -194,15 +209,14 @@ export function ProcessTreeNode({ // hidden processes. } - return process.getChildren(verboseModeOn); - }, [process, verboseModeOn, searchResults]); + return process.getChildren(verboseMode); + }, [process, verboseMode, searchResults]); if (!processDetails?.process) { return null; } const id = process.id; - const { user } = processDetails; const { args, name, @@ -210,12 +224,13 @@ export function ProcessTreeNode({ parent, working_directory: workingDirectory, start, + user, } = processDetails.process; const shouldRenderChildren = childrenExpanded && children?.length > 0; const childrenTreeDepth = depth + 1; - const showUserEscalation = !isSessionLeader && !!user?.id && user.id !== parent?.user?.id; + const showUserEscalation = !isSessionLeader && !!user?.name && user.name !== parent?.user?.name; const interactiveSession = !!tty; const sessionIcon = interactiveSession ? 'desktop' : 'gear'; const iconTestSubj = hasExec @@ -248,7 +263,7 @@ export function ProcessTreeNode({ ) : ( - {timeStampOn && ( + {showTimestamp && ( {timeStampsNormal} @@ -273,7 +288,7 @@ export function ProcessTreeNode({ id="xpack.sessionView.execUserChange" defaultMessage="Exec user change: " /> - {user.name} + {user.name} )} {!isSessionLeader && children.length > 0 && ( @@ -293,7 +308,7 @@ export function ProcessTreeNode({ @@ -311,9 +326,9 @@ export function ProcessTreeNode({ onProcessSelected={onProcessSelected} jumpToEntityId={jumpToEntityId} investigatedAlertId={investigatedAlertId} - selectedProcessId={selectedProcessId} - timeStampOn={timeStampOn} - verboseModeOn={verboseModeOn} + selectedProcess={selectedProcess} + showTimestamp={showTimestamp} + verboseMode={verboseMode} searchResults={searchResults} scrollerRef={scrollerRef} onChangeJumpToEventVisibility={onChangeJumpToEventVisibility} diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts b/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts index cd514c2087bdc..6503f373240ad 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts +++ b/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts @@ -53,12 +53,13 @@ export const useStyles = ({ depth, hasAlerts, hasInvestigatedAlert, isSelected } borderColor = colors.danger; } - if (hasInvestigatedAlert) { - bgColor = transparentize(colors.danger, 0.04); - } - if (isSelected) { searchResColor = colors.warning; + bgColor = `${transparentize(colors.primary, 0.1)}!important`; + } + + if (hasInvestigatedAlert) { + bgColor = `${transparentize(colors.danger, 0.04)}!important`; } return { bgColor, borderColor, hoverColor, searchResColor }; diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/use_button_styles.ts b/x-pack/plugins/session_view/public/components/process_tree_node/use_button_styles.ts index 4c713b42a2d7b..67883b12e2bba 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/use_button_styles.ts +++ b/x-pack/plugins/session_view/public/components/process_tree_node/use_button_styles.ts @@ -75,10 +75,6 @@ export const useButtonStyles = ({ isExpanded }: ButtonStylesDeps) => { border: `${border.width.thin} solid ${transparentize(theme.euiColorVis3, 0.48)}`, }; - const userChangedButtonUsername: CSSObject = { - textTransform: 'capitalize', - }; - const buttonSize: CSSObject = { padding: `0px ${euiTheme.size.xs}`, }; @@ -91,7 +87,6 @@ export const useButtonStyles = ({ isExpanded }: ButtonStylesDeps) => { alertButton, alertsCountNumber, userChangedButton, - userChangedButtonUsername, buttonSize, expandedIcon, }; diff --git a/x-pack/plugins/session_view/public/components/session_view/index.test.tsx b/x-pack/plugins/session_view/public/components/session_view/index.test.tsx index e75c09e393911..41865f1adad43 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.test.tsx @@ -84,10 +84,6 @@ describe('SessionView component', () => { await waitForApiCall(); expect(renderResult.getAllByTestId('sessionView:processTreeNode')).toBeTruthy(); - - const selectionArea = renderResult.queryByTestId('sessionView:processTreeSelectionArea'); - - expect(selectionArea?.parentElement?.getAttribute('data-id')).toEqual('test-entity-id'); }); it('should toggle detail panel visibilty when detail button clicked', async () => { diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index 1e41878814697..fee4a67c746fc 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState, useCallback, useEffect, useMemo } from 'react'; import { EuiEmptyPrompt, EuiButton, @@ -72,6 +72,11 @@ export const SessionView = ({ const styles = useStyles({ height, isFullScreen }); + // to give an indication to the user that there may be more search results if they turn on verbose mode. + const showVerboseSearchTooltip = useMemo(() => { + return !!(!displayOptions?.verboseMode && searchQuery && searchResults?.length === 0); + }, [displayOptions?.verboseMode, searchResults, searchQuery]); + const onProcessSelected = useCallback((process: Process | null) => { setSelectedProcess(process); }, []); @@ -194,6 +199,7 @@ export const SessionView = ({ @@ -273,8 +279,8 @@ export const SessionView = ({ setSearchResults={setSearchResults} updatedAlertsStatus={updatedAlertsStatus} onShowAlertDetails={onShowAlertDetails} - timeStampOn={displayOptions?.timestamp} - verboseModeOn={displayOptions?.verboseMode} + showTimestamp={displayOptions?.timestamp} + verboseMode={displayOptions?.verboseMode} /> )} diff --git a/x-pack/plugins/session_view/public/components/session_view_display_options/index.tsx b/x-pack/plugins/session_view/public/components/session_view_display_options/index.tsx index 7970a3b523e8b..ab381ca757f9f 100644 --- a/x-pack/plugins/session_view/public/components/session_view_display_options/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view_display_options/index.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import { + EuiToolTip, EuiPopover, EuiSelectable, EuiPopoverTitle, @@ -22,17 +23,50 @@ import { useStyles } from './styles'; const TIMESTAMP_OPTION_KEY = 'Timestamp'; const VERBOSE_MODE_OPTION_KEY = 'Verbose mode'; +const TOOLTIP_SHOW_DELAY = 3000; +const TOOLTIP_HIDE_DELAY = 5000; + +const VERBOSE_TOOLTIP_TITLE = i18n.translate( + 'xpack.sessionView.sessionViewToggle.sessionViewVerboseTipTitle', + { + defaultMessage: 'Some results may be hidden', + } +); + +const VERBOSE_TOOLTIP_CONTENT = i18n.translate( + 'xpack.sessionView.sessionViewToggle.sessionViewVerboseTipContent', + { + defaultMessage: 'For a complete set of results, turn on Verbose mode.', + } +); export const SessionViewDisplayOptions = ({ onChange, displayOptions, + showVerboseSearchTooltip, }: { onChange: (vars: DisplayOptionsState) => void; displayOptions: DisplayOptionsState; + showVerboseSearchTooltip: boolean; }) => { const [isOptionDropdownOpen, setOptionDropdownOpen] = useState(false); - const styles = useStyles(); + const tooltipRef = useRef(null); + + useEffect(() => { + if (tooltipRef.current) { + setTimeout(() => { + if (tooltipRef.current) { + (tooltipRef.current as EuiToolTip).onFocus(); + setTimeout(() => { + if (tooltipRef.current) { + (tooltipRef.current as EuiToolTip).onBlur(); + } + }, TOOLTIP_HIDE_DELAY); + } + }, TOOLTIP_SHOW_DELAY); + } + }, [showVerboseSearchTooltip]); const optionsList: EuiSelectableOption[] = useMemo( () => [ @@ -106,27 +140,38 @@ export const SessionViewDisplayOptions = ({ onChange(updateOptionState); }; - return ( - <> - - - {(list) => ( -
- - - - {list} -
- )} -
-
- + const popOver = ( + + + {(list) => ( +
+ + + + {list} +
+ )} +
+
+ ); + + return !isOptionDropdownOpen && showVerboseSearchTooltip ? ( + + {popOver} + + ) : ( + popOver ); }; diff --git a/x-pack/plugins/session_view/public/components/session_view_search_bar/index.tsx b/x-pack/plugins/session_view/public/components/session_view_search_bar/index.tsx index 05154fca40769..9c24f6b94199c 100644 --- a/x-pack/plugins/session_view/public/components/session_view_search_bar/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view_search_bar/index.tsx @@ -24,6 +24,10 @@ const translatePlaceholder = { }), }; +const NO_RESULTS = i18n.translate('xpack.sessionView.searchBar.searchBarNoResults', { + defaultMessage: 'No results', +}); + /** * The main wrapper component for the session view. */ @@ -33,7 +37,8 @@ export const SessionViewSearchBar = ({ onProcessSelected, searchResults, }: SessionViewSearchBarDeps) => { - const showPagination = !!searchResults?.length; + const showPagination = !!searchQuery && searchResults?.length !== 0; + const noResults = !!searchQuery && searchResults?.length === 0; const styles = useStyles({ hasSearchResults: showPagination }); @@ -62,11 +67,12 @@ export const SessionViewSearchBar = ({ return (
+ {noResults && {NO_RESULTS}} {showPagination && ( { position: 'absolute', top: euiTheme.size.s, right: euiTheme.size.xxl, + 'button[data-test-subj="pagination-button-last"]': { + display: 'none', + }, + 'button[data-test-subj="pagination-button-first"]': { + display: 'none', + }, + }; + + const noResults: CSSObject = { + position: 'absolute', + color: euiTheme.colors.subdued, + top: euiTheme.size.m, + right: euiTheme.size.xxl, }; const searchBarWithResult: CSSObject = { @@ -33,6 +46,7 @@ export const useStyles = ({ hasSearchResults }: StylesDeps) => { return { pagination, searchBarWithResult, + noResults, }; }, [euiTheme, hasSearchResults]); From d638fa419f23beeaff8265ad24953126aebeca89 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Tue, 12 Apr 2022 10:40:15 +0200 Subject: [PATCH 08/32] [SecuritySolution] Remove `empty row` (#129909) * fix: remove horizontal rule The horizontal rule in this component in combination with the bottom border of the above table made it look like there is an empty row in the table. The divider is now replaced by a spacer to visually separate the Investigation guide and the highlighted fields. * chore: remove unused import --- .../components/event_details/investigation_guide_view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx index b4abd253b4a96..dfec4ab07db16 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiSpacer, EuiHorizontalRule, EuiTitle, EuiText } from '@elastic/eui'; +import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import React, { useMemo } from 'react'; @@ -39,7 +39,7 @@ const InvestigationGuideViewComponent: React.FC<{ return ( <> - +
{i18n.INVESTIGATION_GUIDE}
From 6366f3738dbc8adf0aef6c93b9a1449189297c5e Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:41:45 +0100 Subject: [PATCH 09/32] [SecuritySolution] update sourcer messages (#129329) * update wording for temporary sourcerer * update sourcer messages * add message utils * fix FormattedMessage * remove unused i18n keys Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/sourcerer/index.test.tsx | 40 ++++ .../common/components/sourcerer/temporary.tsx | 63 ++---- .../common/components/sourcerer/utils.tsx | 201 ++++++++++++++++++ .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 5 files changed, 258 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/sourcerer/utils.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index 05b24600ff9af..f7a4bd9b9e621 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -1000,6 +1000,14 @@ describe('Update available', () => { expect(wrapper.find(`[data-test-subj="sourcerer-deprecated-callout"]`).first().text()).toEqual( 'This timeline uses a legacy data view selector' ); + + expect( + wrapper.find(`[data-test-subj="sourcerer-current-patterns-message"]`).first().text() + ).toEqual('The active index patterns in this timeline are: myFakebeat-*'); + + expect(wrapper.find(`[data-test-subj="sourcerer-deprecated-message"]`).first().text()).toEqual( + "We have preserved your timeline by creating a temporary data view. If you'd like to modify your data, we can recreate your temporary data view with the new data view selector. You can also manually select a data view here." + ); }); test('Show Add index pattern in UpdateDefaultDataViewModal', () => { @@ -1103,6 +1111,10 @@ describe('Update available for timeline template', () => { expect(wrapper.find(`[data-test-subj="sourcerer-deprecated-callout"]`).first().text()).toEqual( 'This timeline template uses a legacy data view selector' ); + + expect(wrapper.find(`[data-test-subj="sourcerer-deprecated-message"]`).first().text()).toEqual( + "We have preserved your timeline template by creating a temporary data view. If you'd like to modify your data, we can recreate your temporary data view with the new data view selector. You can also manually select a data view here." + ); }); }); @@ -1179,6 +1191,20 @@ describe('Missing index patterns', () => { expect(wrapper.find(`[data-test-subj="sourcerer-deprecated-callout"]`).first().text()).toEqual( 'This timeline is out of date with the Security Data View' ); + + expect( + wrapper.find(`[data-test-subj="sourcerer-current-patterns-message"]`).first().text() + ).toEqual('The active index patterns in this timeline are: myFakebeat-*'); + + expect( + wrapper.find(`[data-test-subj="sourcerer-missing-patterns-callout"]`).first().text() + ).toEqual('Security Data View is missing the following index patterns: myFakebeat-*'); + + expect( + wrapper.find(`[data-test-subj="sourcerer-missing-patterns-message"]`).first().text() + ).toEqual( + "We have preserved your timeline by creating a temporary data view. If you'd like to modify your data, we can add the missing index patterns to the Security Data View. You can also manually select a data view here." + ); }); test('Show UpdateDefaultDataViewModal CallOut for timeline template', () => { @@ -1201,5 +1227,19 @@ describe('Missing index patterns', () => { expect(wrapper.find(`[data-test-subj="sourcerer-deprecated-callout"]`).first().text()).toEqual( 'This timeline template is out of date with the Security Data View' ); + + expect( + wrapper.find(`[data-test-subj="sourcerer-current-patterns-message"]`).first().text() + ).toEqual('The active index patterns in this timeline template are: myFakebeat-*'); + + expect( + wrapper.find(`[data-test-subj="sourcerer-missing-patterns-callout"]`).first().text() + ).toEqual('Security Data View is missing the following index patterns: myFakebeat-*'); + + expect( + wrapper.find(`[data-test-subj="sourcerer-missing-patterns-message"]`).first().text() + ).toEqual( + "We have preserved your timeline template by creating a temporary data view. If you'd like to modify your data, we can add the missing index patterns to the Security Data View. You can also manually select a data view here." + ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/temporary.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/temporary.tsx index 156d08df79d06..e4cfaea78b48d 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/temporary.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/temporary.tsx @@ -8,7 +8,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCallOut, - EuiLink, EuiText, EuiTextColor, EuiSpacer, @@ -16,7 +15,6 @@ import { EuiFlexItem, EuiButton, EuiToolTip, - EuiIcon, } from '@elastic/eui'; import React, { useMemo } from 'react'; import * as i18n from './translations'; @@ -26,6 +24,12 @@ import { TimelineId, TimelineType } from '../../../../common/types'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { + BadCurrentPatternsMessage, + CurrentPatternsMessage, + DeprecatedMessage, + MissingPatternsMessage, +} from './utils'; interface Props { activePatterns?: string[]; @@ -116,66 +120,33 @@ export const TemporarySourcererComp = React.memo(

{activePatterns && activePatterns.length > 0 ? ( - 0 ? ( - !activePatterns.includes(p)) - .join(', '), - }} - /> - } - > - - - ) : null, - callout:

{activePatterns.join(', ')}
, - }} + ) : ( - {selectedPatterns.join(', ')}, - }} + )} {isModified === 'deprecated' && ( - {i18n.TOGGLE_TO_NEW_SOURCERER}, - }} - /> + )} {isModified === 'missingPatterns' && ( <> {missingPatterns.join(', ')}, }} /> - {i18n.TOGGLE_TO_NEW_SOURCERER}, - }} - /> + )}

diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/utils.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/utils.tsx new file mode 100644 index 0000000000000..18fba2648c25e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/utils.tsx @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; +import { TimelineType } from '../../../../common/types'; +import { Blockquote } from './helpers'; +import * as i18n from './translations'; + +export const CurrentPatternsMessage = ({ + activePatterns, + deadPatterns, + selectedPatterns, + timelineType, +}: { + activePatterns: string[]; + deadPatterns: string[]; + selectedPatterns: string[]; + timelineType: TimelineType; +}) => { + const tooltip = useMemo( + () => + deadPatterns.length > 0 ? ( + + } + > + + + ) : null, + [activePatterns, deadPatterns.length, selectedPatterns, timelineType] + ); + + if (timelineType === TimelineType.template) { + return ( + {activePatterns.join(', ')}, + }} + /> + ); + } + + return ( + {activePatterns.join(', ')}, + }} + /> + ); +}; + +export const NoMatchDataMessage = ({ + activePatterns, + selectedPatterns, + timelineType, +}: { + activePatterns: string[]; + selectedPatterns: string[]; + timelineType: TimelineType; +}) => { + const aliases = useMemo( + () => selectedPatterns.filter((p) => !activePatterns.includes(p)).join(', '), + [activePatterns, selectedPatterns] + ); + if (timelineType === TimelineType.template) { + return ( + + ); + } + + return ( + + ); +}; + +export const BadCurrentPatternsMessage = ({ + timelineType, + selectedPatterns, +}: { + timelineType: TimelineType; + selectedPatterns: string[]; +}) => { + const callout = useMemo( + () =>
{selectedPatterns.join(', ')}
, + [selectedPatterns] + ); + + if (timelineType === TimelineType.template) { + return ( + + ); + } + return ( + + ); +}; + +export const DeprecatedMessage = ({ + onReset, + timelineType, +}: { + onReset: () => void; + timelineType: TimelineType; +}) => { + if (timelineType === TimelineType.template) { + return ( + {i18n.TOGGLE_TO_NEW_SOURCERER}, + }} + /> + ); + } + return ( + {i18n.TOGGLE_TO_NEW_SOURCERER}, + }} + /> + ); +}; + +export const MissingPatternsMessage = ({ + onReset, + timelineType, +}: { + timelineType: TimelineType; + onReset: () => void; +}) => { + if (timelineType === TimelineType.template) { + return ( + {i18n.TOGGLE_TO_NEW_SOURCERER}, + }} + /> + ); + } + return ( + {i18n.TOGGLE_TO_NEW_SOURCERER}, + }} + /> + ); +}; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d58da0f2ed657..72aa6e24034d6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -24620,8 +24620,6 @@ "xpack.securitySolution.indexPatterns.chooseDataViewLabel": "データビューを選択", "xpack.securitySolution.indexPatterns.closeButton": "閉じる", "xpack.securitySolution.indexPatterns.continue": "追加せずに続行", - "xpack.securitySolution.indexPatterns.currentPatterns": "このタイムラインのアクティブなインデックスパターンは{tooltip}です:{callout}", - "xpack.securitySolution.indexPatterns.currentPatternsBad": "このタイムラインの現在のインデックスパターン:{callout}", "xpack.securitySolution.indexPatterns.dataViewLabel": "データビュー", "xpack.securitySolution.indexPatterns.descriptionsLabel": "これらは現在選択されているインデックスパターンです。データビューからインデックスパターンを除外すると、全体的なパフォーマンスを改善できます。", "xpack.securitySolution.indexPatterns.disabled": "このページでは無効なインデックスパターンが推奨されますが、最初にKibanaインデックスパターン設定で構成する必要があります。", @@ -24631,10 +24629,8 @@ "xpack.securitySolution.indexPatterns.indexPatternsLabel": "インデックスパターン", "xpack.securitySolution.indexPatterns.missingPatterns": "以前のタイムラインのデータビューを再作成するには、セキュリティデータビューに次のインデックスパターンがありません:{callout}", "xpack.securitySolution.indexPatterns.missingPatterns.callout": "セキュリティデータビューには次のインデックスパターンがありません:{callout}", - "xpack.securitySolution.indexPatterns.missingPatterns.description": "一時データビューを作成することで、タイムラインを保持しています。データを修正する場合は、見つからないインデックスパターンをセキュリティデータビューに追加できます。手動でデータビュー{link}を選択することもできます。", "xpack.securitySolution.indexPatterns.modifiedBadgeTitle": "変更済み", "xpack.securitySolution.indexPatterns.noData": "このタイムラインのインデックスパターンはデータストリーム、インデックス、またはインデックスエイリアスと一致しません。", - "xpack.securitySolution.indexPatterns.noMatchData": "次のインデックスパターンはこのタイムラインに保存されますが、データストリーム、インデックス、またはインデックスエイリアスと一致しません:{aliases}", "xpack.securitySolution.indexPatterns.onlyDetectionAlertsLabel": "検出アラートのみを表示", "xpack.securitySolution.indexPatterns.pickIndexPatternsCombo": "インデックスパターンを選択", "xpack.securitySolution.indexPatterns.reloadPageTitle": "ページを再読み込み", @@ -24644,7 +24640,6 @@ "xpack.securitySolution.indexPatterns.securityDefaultDataViewLabel": "セキュリティデフォルトデータビュー", "xpack.securitySolution.indexPatterns.selectDataView": "データビュー選択", "xpack.securitySolution.indexPatterns.successToastTitle": "設定を有効にするためにページの再読み込みが必要です", - "xpack.securitySolution.indexPatterns.toggleToNewSourcerer": "一時データビューを作成することで、タイムラインを保持しています。データを修正する場合は、新しいデータビューセレクターを使用して、一時データビューを再作成できます。手動でデータビュー{link}を選択することもできます。", "xpack.securitySolution.indexPatterns.toggleToNewSourcerer.link": "こちら", "xpack.securitySolution.indexPatterns.update": "データビューを更新して再作成", "xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle": "更新が利用可能です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1f8bcf7b4fb74..41e2dab0f29ec 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -24648,8 +24648,6 @@ "xpack.securitySolution.indexPatterns.chooseDataViewLabel": "选择数据视图", "xpack.securitySolution.indexPatterns.closeButton": "关闭", "xpack.securitySolution.indexPatterns.continue": "继续,而不添加", - "xpack.securitySolution.indexPatterns.currentPatterns": "此时间线中的活动索引模式为 {tooltip}:{callout}", - "xpack.securitySolution.indexPatterns.currentPatternsBad": "此时间线中的当前索引模式为:{callout}", "xpack.securitySolution.indexPatterns.dataViewLabel": "数据视图", "xpack.securitySolution.indexPatterns.descriptionsLabel": "这些是当前选择的索引模式。从您的数据视图中筛除索引模式可帮助提高整体性能。", "xpack.securitySolution.indexPatterns.disabled": "在此页面上建议使用已禁用的索引模式,但是首先需要在 Kibana 索引模式设置中配置这些模式", @@ -24659,10 +24657,8 @@ "xpack.securitySolution.indexPatterns.indexPatternsLabel": "索引模式", "xpack.securitySolution.indexPatterns.missingPatterns": "要重新创建上一时间线的数据视图,安全数据视图缺少以下索引模式:{callout}", "xpack.securitySolution.indexPatterns.missingPatterns.callout": "安全数据视图缺少以下索引模式:{callout}", - "xpack.securitySolution.indexPatterns.missingPatterns.description": "我们已通过创建临时数据视图来保留您的时间线。如果您要修改数据,我们可以将缺失的索引模式添加到安全数据视图。您还可以手动选择数据视图 {link}。", "xpack.securitySolution.indexPatterns.modifiedBadgeTitle": "已修改", "xpack.securitySolution.indexPatterns.noData": "此时间线上的索引模式不匹配任何数据流、索引或索引别名。", - "xpack.securitySolution.indexPatterns.noMatchData": "以下索引模式已保存到此时间线,但不匹配任何数据流、索引或索引别名:{aliases}", "xpack.securitySolution.indexPatterns.onlyDetectionAlertsLabel": "仅显示检测告警", "xpack.securitySolution.indexPatterns.pickIndexPatternsCombo": "选取索引模式", "xpack.securitySolution.indexPatterns.reloadPageTitle": "重新加载页面", @@ -24672,7 +24668,6 @@ "xpack.securitySolution.indexPatterns.securityDefaultDataViewLabel": "安全默认数据视图", "xpack.securitySolution.indexPatterns.selectDataView": "数据视图选择", "xpack.securitySolution.indexPatterns.successToastTitle": "一个或多个设置需要您重新加载页面才能生效", - "xpack.securitySolution.indexPatterns.toggleToNewSourcerer": "我们已通过创建临时数据视图来保留您的时间线。如果您要修改数据,我们可以使用新的数据视图选择器重新创建临时数据视图。您还可以手动选择数据视图 {link}。", "xpack.securitySolution.indexPatterns.toggleToNewSourcerer.link": "此处", "xpack.securitySolution.indexPatterns.update": "更新并重新创建数据视图", "xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle": "有可用更新", From 7849d9ebb3073c8a7c135c08142a4a8df61f8f2c Mon Sep 17 00:00:00 2001 From: Gerard Soldevila Date: Tue, 12 Apr 2022 12:39:45 +0200 Subject: [PATCH 10/32] Hide welcome page privacy statement on cloud instances (#129198) * Add new hidePrivacyStatement flag to Telemetry * Remove hidePrivacyStatement from asciidocs --- .../resources/base/bin/kibana-docker | 1 + src/plugins/telemetry/public/plugin.test.ts | 60 +++++++++++++++++++ src/plugins/telemetry/public/plugin.ts | 4 +- .../public/services/telemetry_service.test.ts | 13 ++++ .../public/services/telemetry_service.ts | 4 +- src/plugins/telemetry/server/config/config.ts | 2 + .../test_suites/core_plugins/rendering.ts | 1 + 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/plugins/telemetry/public/plugin.test.ts diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 191f53208df72..7ce7459d6eefd 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -170,6 +170,7 @@ kibana_vars=( status.v6ApiFormat telemetry.allowChangingOptInStatus telemetry.enabled + telemetry.hidePrivacyStatement telemetry.optIn telemetry.sendUsageTo telemetry.sendUsageFrom diff --git a/src/plugins/telemetry/public/plugin.test.ts b/src/plugins/telemetry/public/plugin.test.ts new file mode 100644 index 0000000000000..4473e41572fef --- /dev/null +++ b/src/plugins/telemetry/public/plugin.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { TelemetryPlugin } from './plugin'; +import { coreMock } from '../../../core/public/mocks'; +import { homePluginMock } from '../../home/public/mocks'; +import { screenshotModePluginMock } from '../../screenshot_mode/public/mocks'; +import { HomePublicPluginSetup } from '../../home/public'; +import { ScreenshotModePluginSetup } from '../../screenshot_mode/public'; + +let screenshotMode: ScreenshotModePluginSetup; +let home: HomePublicPluginSetup; + +describe('TelemetryPlugin', () => { + beforeEach(() => { + screenshotMode = screenshotModePluginMock.createSetupContract(); + home = homePluginMock.createSetupContract(); + }); + + describe('setup', () => { + describe('when home is provided', () => { + describe('and hidePrivacyStatement is false (default)', () => { + it('registers the telemetry notice renderer and onRendered handlers', () => { + const initializerContext = coreMock.createPluginInitializerContext(); + + new TelemetryPlugin(initializerContext).setup(coreMock.createSetup(), { + screenshotMode, + home, + }); + + expect(home.welcomeScreen.registerTelemetryNoticeRenderer).toHaveBeenCalledWith( + expect.any(Function) + ); + expect(home.welcomeScreen.registerOnRendered).toHaveBeenCalledWith(expect.any(Function)); + }); + }); + + describe('and hidePrivacyStatement is true', () => { + it('does not register the telemetry notice renderer and onRendered handlers', () => { + const initializerContext = coreMock.createPluginInitializerContext({ + hidePrivacyStatement: true, + }); + + new TelemetryPlugin(initializerContext).setup(coreMock.createSetup(), { + screenshotMode, + home, + }); + + expect(home.welcomeScreen.registerTelemetryNoticeRenderer).not.toBeCalled(); + expect(home.welcomeScreen.registerOnRendered).not.toBeCalled(); + }); + }); + }); + }); +}); diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index a758715e62ba8..b227a0b751e03 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -109,6 +109,8 @@ export interface TelemetryPluginConfig { telemetryNotifyUserAboutOptInDefault?: boolean; /** Does the user have enough privileges to change the settings? **/ userCanChangeSettings?: boolean; + /** Should we hide the privacy statement notice? Useful on some environments, e.g. Cloud */ + hidePrivacyStatement?: boolean; } function getTelemetryConstants(docLinks: DocLinksStart): TelemetryConstants { @@ -155,7 +157,7 @@ export class TelemetryPlugin implements Plugin { if (this.telemetryService?.userCanChangeSettings) { this.telemetryNotifications?.setOptedInNoticeSeen(); diff --git a/src/plugins/telemetry/public/services/telemetry_service.test.ts b/src/plugins/telemetry/public/services/telemetry_service.test.ts index 778dd2fbfe0b0..03564f5b6f632 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.test.ts @@ -219,6 +219,19 @@ describe('TelemetryService', () => { }); describe('getUserShouldSeeOptInNotice', () => { + it('should return false if the telemetry notice is hidden by config', () => { + const telemetryService = mockTelemetryService({ + config: { + userCanChangeSettings: true, + telemetryNotifyUserAboutOptInDefault: true, + hidePrivacyStatement: true, + }, + }); + expect(telemetryService.config.userCanChangeSettings).toBe(true); + expect(telemetryService.userCanChangeSettings).toBe(true); + expect(telemetryService.getUserShouldSeeOptInNotice()).toBe(false); + }); + it('returns whether the user can update the telemetry config (has SavedObjects access)', () => { const telemetryService = mockTelemetryService({ config: { userCanChangeSettings: undefined }, diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 55dc623a8ccf8..7499a403bcd95 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -113,7 +113,9 @@ export class TelemetryService { */ public getUserShouldSeeOptInNotice(): boolean { return ( - (this.config.telemetryNotifyUserAboutOptInDefault && this.config.userCanChangeSettings) ?? + (!this.config.hidePrivacyStatement && + this.config.telemetryNotifyUserAboutOptInDefault && + this.config.userCanChangeSettings) ?? false ); } diff --git a/src/plugins/telemetry/server/config/config.ts b/src/plugins/telemetry/server/config/config.ts index 166598371fe36..020a01f3b41b8 100644 --- a/src/plugins/telemetry/server/config/config.ts +++ b/src/plugins/telemetry/server/config/config.ts @@ -18,6 +18,7 @@ const clusterEnvSchema: [Type<'prod'>, Type<'staging'>] = [ const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), allowChangingOptInStatus: schema.boolean({ defaultValue: true }), + hidePrivacyStatement: schema.boolean({ defaultValue: false }), optIn: schema.conditional( schema.siblingRef('allowChangingOptInStatus'), schema.literal(false), @@ -50,5 +51,6 @@ export const config: PluginConfigDescriptor = { optIn: true, sendUsageFrom: true, sendUsageTo: true, + hidePrivacyStatement: true, }, }; diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index c18e38cc1a4d6..d7789b5e62a3b 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -129,6 +129,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'telemetry.allowChangingOptInStatus (boolean)', 'telemetry.banner (boolean)', 'telemetry.enabled (boolean)', + 'telemetry.hidePrivacyStatement (boolean)', 'telemetry.optIn (any)', 'telemetry.sendUsageFrom (alternatives)', 'telemetry.sendUsageTo (any)', From 623070bbde6bccab9e07b00579f4fd485e716701 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 12 Apr 2022 12:46:20 +0200 Subject: [PATCH 11/32] Codeowners observability change (#129980) * remove observability-ui from Observability Shared * observability rules page owned by actionable observability --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b846bc0801870..a06ddaf49822f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -88,7 +88,6 @@ ### Observability Plugins # Observability Shared -/x-pack/plugins/observability/ @elastic/observability-ui /x-pack/plugins/observability/public/components/shared/date_picker/ @elastic/uptime # Unified Observability @@ -103,6 +102,7 @@ /x-pack/plugins/observability/public/rules @elastic/actionable-observability /x-pack/plugins/observability/public/pages/alerts @elastic/actionable-observability /x-pack/plugins/observability/public/pages/cases @elastic/actionable-observability +/x-pack/plugins/observability/public/pages/rules @elastic/actionable-observability # Infra Monitoring /x-pack/plugins/infra/ @elastic/infra-monitoring-ui From a8977474a7c75e72c3aa0f66bccc6ab95fb2b7eb Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Tue, 12 Apr 2022 13:06:27 +0200 Subject: [PATCH 12/32] Deprecate outdated detection rules Bulk APIs (#129448) --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../security_solution/common/constants.ts | 6 +++ .../routes/__mocks__/request_responses.ts | 17 +++--- .../rules/create_rules_bulk_route.test.ts | 16 +++--- .../routes/rules/create_rules_bulk_route.ts | 25 +++++++-- .../rules/delete_rules_bulk_route.test.ts | 12 +++-- .../routes/rules/delete_rules_bulk_route.ts | 26 ++++++--- .../rules/patch_rules_bulk_route.test.ts | 23 ++++---- .../routes/rules/patch_rules_bulk_route.ts | 25 +++++++-- .../rules/update_rules_bulk_route.test.ts | 18 ++++--- .../routes/rules/update_rules_bulk_route.ts | 25 +++++++-- .../routes/rules/utils/deprecation.ts | 42 +++++++++++++++ .../security_solution/server/routes/index.ts | 8 +-- .../basic/tests/create_rules_bulk.ts | 12 ++--- .../basic/tests/delete_rules_bulk.ts | 26 ++++----- .../basic/tests/patch_rules_bulk.ts | 28 +++++----- .../basic/tests/update_rules_bulk.ts | 29 +++++----- .../tests/create_rules_bulk.ts | 32 ++++++++--- .../tests/delete_rules_bulk.ts | 48 +++++++++++------ .../tests/patch_rules_bulk.ts | 50 +++++++++++------ .../tests/update_rules_bulk.ts | 54 +++++++++++++------ .../detection_engine_api_integration/utils.ts | 14 ++--- 23 files changed, 367 insertions(+), 171 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.ts diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 690765f3d0c4d..c52407a864c31 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -328,6 +328,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { detectionsReq: `${SECURITY_SOLUTION_DOCS}detections-permissions-section.html`, networkMap: `${SECURITY_SOLUTION_DOCS}conf-map-ui.html`, troubleshootGaps: `${SECURITY_SOLUTION_DOCS}alerts-ui-monitor.html#troubleshoot-gaps`, + ruleApiOverview: `${SECURITY_SOLUTION_DOCS}rule-api-overview.html`, }, securitySolution: { trustedApps: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/trusted-apps-ov.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 5e85d01c22ce2..f017c2ec4be00 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -233,6 +233,7 @@ export interface DocLinks { readonly detectionsReq: string; readonly networkMap: string; readonly troubleshootGaps: string; + readonly ruleApiOverview: string; }; readonly securitySolution: { readonly trustedApps: string; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 3b820b16ef8a4..74f9bff078b89 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -235,6 +235,12 @@ export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = export const DETECTION_ENGINE_RULES_BULK_ACTION = `${DETECTION_ENGINE_RULES_URL}/_bulk_action` as const; export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/preview` as const; +export const DETECTION_ENGINE_RULES_BULK_DELETE = + `${DETECTION_ENGINE_RULES_URL}/_bulk_delete` as const; +export const DETECTION_ENGINE_RULES_BULK_CREATE = + `${DETECTION_ENGINE_RULES_URL}/_bulk_create` as const; +export const DETECTION_ENGINE_RULES_BULK_UPDATE = + `${DETECTION_ENGINE_RULES_URL}/_bulk_update` as const; /** * Internal detection engine routes diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 94c4de459a2ea..e38df7657f512 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -24,6 +24,9 @@ import { DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL, DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULE_EXECUTION_EVENTS_URL, + DETECTION_ENGINE_RULES_BULK_UPDATE, + DETECTION_ENGINE_RULES_BULK_DELETE, + DETECTION_ENGINE_RULES_BULK_CREATE, } from '../../../../../common/constants'; import { GetAggregateRuleExecutionEventsResponse } from '../../../../../common/detection_engine/schemas/response'; import { RuleAlertType, HapiReadableStream } from '../../rules/types'; @@ -110,21 +113,21 @@ export const getFindRequest = () => export const getReadBulkRequest = () => requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, body: [getCreateRulesSchemaMock()], }); export const getUpdateBulkRequest = () => requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [getCreateRulesSchemaMock()], }); export const getPatchBulkRequest = () => requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [getCreateRulesSchemaMock()], }); @@ -145,28 +148,28 @@ export const getBulkActionEditRequest = () => export const getDeleteBulkRequest = () => requestMock.create({ method: 'delete', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, body: [{ rule_id: 'rule-1' }], }); export const getDeleteBulkRequestById = () => requestMock.create({ method: 'delete', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, body: [{ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }], }); export const getDeleteAsPostBulkRequestById = () => requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, body: [{ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }], }); export const getDeleteAsPostBulkRequest = () => requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, body: [{ rule_id: 'rule-1' }], }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 68a3ec0733b60..1d63a977e2480 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../common/constants'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { @@ -23,6 +23,7 @@ import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -38,6 +39,7 @@ describe.each([ server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); + const logger = loggingSystemMock.createLogger(); clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no existing rules clients.rulesClient.create.mockResolvedValue( @@ -47,7 +49,7 @@ describe.each([ context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); + createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled, logger); }); describe('status codes', () => { @@ -137,7 +139,7 @@ describe.each([ test('returns an error object if duplicate rule_ids found in request payload', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, body: [getCreateRulesSchemaMock(), getCreateRulesSchemaMock()], }); const response = await server.inject(request, context); @@ -158,7 +160,7 @@ describe.each([ test('allows rule type of query', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, body: [{ ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -169,7 +171,7 @@ describe.each([ test('allows rule type of query and custom from and interval', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock() }], }); const result = server.validate(request); @@ -180,7 +182,7 @@ describe.each([ test('disallows unknown rule type', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, body: [{ ...getCreateRulesSchemaMock(), type: 'unexpected_type' }], }); const result = server.validate(request); @@ -191,7 +193,7 @@ describe.each([ test('disallows invalid "from" param on rule', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, body: [ { from: 'now-3755555555555555.67s', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 74f777b29ca01..8a350e7e12f46 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -12,7 +12,7 @@ import { createRulesBulkSchema } from '../../../../../common/detection_engine/sc import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { - DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_RULES_BULK_CREATE, NOTIFICATION_THROTTLE_NO_ACTIONS, } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -25,15 +25,21 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils'; import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +import { Logger } from '../../../../../../../../src/core/server'; +/** + * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead + */ export const createRulesBulkRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], - isRuleRegistryEnabled: boolean + isRuleRegistryEnabled: boolean, + logger: Logger ) => { router.post( { - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + path: DETECTION_ENGINE_RULES_BULK_CREATE, validate: { body: buildRouteValidation(createRulesBulkSchema), }, @@ -42,6 +48,8 @@ export const createRulesBulkRoute = ( }, }, async (context, request, response) => { + logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_CREATE); + const siemResponse = buildSiemResponse(response); const rulesClient = context.alerting.getRulesClient(); const esClient = context.core.elasticsearch.client; @@ -138,9 +146,16 @@ export const createRulesBulkRoute = ( ]; const [validated, errors] = validate(rulesBulk, rulesBulkSchema); if (errors != null) { - return siemResponse.error({ statusCode: 500, body: errors }); + return siemResponse.error({ + statusCode: 500, + body: errors, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_CREATE), + }); } else { - return response.ok({ body: validated ?? {} }); + return response.ok({ + body: validated ?? {}, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_CREATE), + }); } } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts index 4ac4822c412fa..9d46ebabb7c8a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../common/constants'; import { getEmptyFindResult, getFindResultWithSingleHit, @@ -17,6 +17,7 @@ import { } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { deleteRulesBulkRoute } from './delete_rules_bulk_route'; +import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; describe.each([ ['Legacy', false], @@ -28,12 +29,13 @@ describe.each([ beforeEach(() => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); + const logger = loggingSystemMock.createLogger(); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists clients.rulesClient.delete.mockResolvedValue({}); // successful deletion clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request - deleteRulesBulkRoute(server.router, isRuleRegistryEnabled); + deleteRulesBulkRoute(server.router, isRuleRegistryEnabled, logger); }); describe('status codes with actionClient and alertClient', () => { @@ -42,7 +44,7 @@ describe.each([ expect(response.status).toEqual(200); }); - test('resturns 200 when deleting a single rule and related rule status', async () => { + test('returns 200 when deleting a single rule and related rule status', async () => { const response = await server.inject(getDeleteBulkRequest(), context); expect(response.status).toEqual(200); }); @@ -88,7 +90,7 @@ describe.each([ test('rejects requests without IDs', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, body: [{}], }); const response = await server.inject(request, context); @@ -104,7 +106,7 @@ describe.each([ test('rejects requests with both id and rule_id', async () => { const request = requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, body: [{ id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f', rule_id: 'rule_1' }], }); const response = await server.inject(request, context); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 442b707532ea6..cd3c219675ccb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -14,18 +14,19 @@ import { QueryRulesBulkSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/query_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import type { RouteConfig, RequestHandler } from '../../../../../../../../src/core/server'; +import type { RouteConfig, RequestHandler, Logger } from '../../../../../../../../src/core/server'; import type { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../common/constants'; import { getIdBulkError } from './utils'; import { transformValidateBulkError } from './validate'; import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils'; import { deleteRules } from '../../rules/delete_rules'; import { readRules } from '../../rules/read_rules'; import { legacyMigrate } from '../../rules/utils'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; type Config = RouteConfig; type Handler = RequestHandler< @@ -36,9 +37,13 @@ type Handler = RequestHandler< 'delete' | 'post' >; +/** + * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead + */ export const deleteRulesBulkRoute = ( router: SecuritySolutionPluginRouter, - isRuleRegistryEnabled: boolean + isRuleRegistryEnabled: boolean, + logger: Logger ) => { const config: Config = { validate: { @@ -46,12 +51,14 @@ export const deleteRulesBulkRoute = ( queryRulesBulkSchema ), }, - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + path: DETECTION_ENGINE_RULES_BULK_DELETE, options: { tags: ['access:securitySolution'], }, }; const handler: Handler = async (context, request, response) => { + logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_DELETE); + const siemResponse = buildSiemResponse(response); const rulesClient = context.alerting.getRulesClient(); const ruleExecutionLog = context.securitySolution.getRuleExecutionLog(); @@ -102,9 +109,16 @@ export const deleteRulesBulkRoute = ( ); const [validated, errors] = validate(rules, rulesBulkSchema); if (errors != null) { - return siemResponse.error({ statusCode: 500, body: errors }); + return siemResponse.error({ + statusCode: 500, + body: errors, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_DELETE), + }); } else { - return response.ok({ body: validated ?? {} }); + return response.ok({ + body: validated ?? {}, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_DELETE), + }); } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts index 6b3fa7ad83c68..9539ad2cb9c7b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { + DETECTION_ENGINE_RULES_BULK_UPDATE, + DETECTION_ENGINE_RULES_URL, +} from '../../../../../common/constants'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { @@ -19,6 +22,7 @@ import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { patchRulesBulkRoute } from './patch_rules_bulk_route'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -34,13 +38,14 @@ describe.each([ server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); + const logger = loggingSystemMock.createLogger(); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists clients.rulesClient.update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); // update succeeds - patchRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); + patchRulesBulkRoute(server.router, ml, isRuleRegistryEnabled, logger); }); describe('status codes', () => { @@ -96,7 +101,7 @@ describe.each([ }); const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [typicalMlRulePayload()], }); const response = await server.inject(request, context); @@ -122,7 +127,7 @@ describe.each([ const { type, ...payloadWithoutType } = typicalMlRulePayload(); const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [payloadWithoutType], }); const response = await server.inject(request, context); @@ -144,7 +149,7 @@ describe.each([ test('rejects payloads with no ID', async () => { const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ ...getCreateRulesSchemaMock(), rule_id: undefined }], }); const response = await server.inject(request, context); @@ -164,7 +169,7 @@ describe.each([ test('allows query rule type', async () => { const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -175,7 +180,7 @@ describe.each([ test('rejects unknown rule type', async () => { const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ ...getCreateRulesSchemaMock(), type: 'unknown_type' }], }); const result = server.validate(request); @@ -188,7 +193,7 @@ describe.each([ test('allows rule type of query and custom from and interval', async () => { const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock() }], }); const result = server.validate(request); @@ -199,7 +204,7 @@ describe.each([ test('disallows invalid "from" param on rule', async () => { const request = requestMock.create({ method: 'patch', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [ { from: 'now-3755555555555555.67s', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 58d364cb34b5c..aedb78a248c34 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -14,7 +14,7 @@ import { import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwAuthzError } from '../../../machine_learning/validation'; @@ -25,15 +25,21 @@ import { patchRules } from '../../rules/patch_rules'; import { readRules } from '../../rules/read_rules'; import { PartialFilter } from '../../types'; import { legacyMigrate } from '../../rules/utils'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +import { Logger } from '../../../../../../../../src/core/server'; +/** + * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead + */ export const patchRulesBulkRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], - isRuleRegistryEnabled: boolean + isRuleRegistryEnabled: boolean, + logger: Logger ) => { router.patch( { - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { body: buildRouteValidation( patchRulesBulkSchema @@ -44,6 +50,8 @@ export const patchRulesBulkRoute = ( }, }, async (context, request, response) => { + logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_UPDATE); + const siemResponse = buildSiemResponse(response); const rulesClient = context.alerting.getRulesClient(); @@ -207,9 +215,16 @@ export const patchRulesBulkRoute = ( const [validated, errors] = validate(rules, rulesBulkSchema); if (errors != null) { - return siemResponse.error({ statusCode: 500, body: errors }); + return siemResponse.error({ + statusCode: 500, + body: errors, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_UPDATE), + }); } else { - return response.ok({ body: validated ?? {} }); + return response.ok({ + body: validated ?? {}, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_UPDATE), + }); } } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts index 88c15f99ed6f7..12b7968b9793a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { @@ -20,6 +20,7 @@ import { updateRulesBulkRoute } from './update_rules_bulk_route'; import { BulkError } from '../utils'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -35,6 +36,7 @@ describe.each([ server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); ml = mlServicesMock.createSetupContract(); + const logger = loggingSystemMock.createLogger(); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); clients.rulesClient.update.mockResolvedValue( @@ -43,7 +45,7 @@ describe.each([ clients.appClient.getSignalsIndex.mockReturnValue('.siem-signals-test-index'); - updateRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); + updateRulesBulkRoute(server.router, ml, isRuleRegistryEnabled, logger); }); describe('status codes', () => { @@ -90,7 +92,7 @@ describe.each([ }); const request = requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [typicalMlRulePayload()], }); @@ -112,7 +114,7 @@ describe.each([ test('rejects payloads with no ID', async () => { const noIdRequest = requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ ...getCreateRulesSchemaMock(), rule_id: undefined }], }); const response = await server.inject(noIdRequest, context); @@ -127,7 +129,7 @@ describe.each([ test('allows query rule type', async () => { const request = requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -138,7 +140,7 @@ describe.each([ test('rejects unknown rule type', async () => { const request = requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ ...getCreateRulesSchemaMock(), type: 'unknown_type' }], }); const result = server.validate(request); @@ -149,7 +151,7 @@ describe.each([ test('allows rule type of query and custom from and interval', async () => { const request = requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -160,7 +162,7 @@ describe.each([ test('disallows invalid "from" param on rule', async () => { const request = requestMock.create({ method: 'put', - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, body: [ { from: 'now-3755555555555555.67s', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index d1df5713914df..646fab5077dec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -11,7 +11,7 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v import { updateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/update_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwAuthzError } from '../../../machine_learning/validation'; @@ -21,15 +21,21 @@ import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '.. import { updateRules } from '../../rules/update_rules'; import { legacyMigrate } from '../../rules/utils'; import { readRules } from '../../rules/read_rules'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +import { Logger } from '../../../../../../../../src/core/server'; +/** + * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead + */ export const updateRulesBulkRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], - isRuleRegistryEnabled: boolean + isRuleRegistryEnabled: boolean, + logger: Logger ) => { router.put( { - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { body: buildRouteValidation(updateRulesBulkSchema), }, @@ -38,6 +44,8 @@ export const updateRulesBulkRoute = ( }, }, async (context, request, response) => { + logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_UPDATE); + const siemResponse = buildSiemResponse(response); const rulesClient = context.alerting.getRulesClient(); @@ -105,9 +113,16 @@ export const updateRulesBulkRoute = ( const [validated, errors] = validate(rules, rulesBulkSchema); if (errors != null) { - return siemResponse.error({ statusCode: 500, body: errors }); + return siemResponse.error({ + statusCode: 500, + body: errors, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_UPDATE), + }); } else { - return response.ok({ body: validated ?? {} }); + return response.ok({ + body: validated ?? {}, + headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_UPDATE), + }); } } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.ts new file mode 100644 index 0000000000000..18f77bfa85bc3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.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 { getDocLinks } from '@kbn/doc-links'; +import { Logger } from 'src/core/server'; +import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../../common/constants'; + +/** + * Helper method for building deprecation messages + * + * @param path Deprecated endpoint path + * @returns string + */ +export const buildDeprecatedBulkEndpointMessage = (path: string) => { + const docsLink = getDocLinks({ kibanaBranch: 'main' }).siem.ruleApiOverview; + return `Deprecated endpoint: ${path} API is deprecated since v8.2. Please use the ${DETECTION_ENGINE_RULES_BULK_ACTION} API instead. See ${docsLink} for more detail.`; +}; + +/** + * Logs usages of a deprecated bulk endpoint + * + * @param logger System logger + * @param path Deprecated endpoint path + */ +export const logDeprecatedBulkEndpoint = (logger: Logger, path: string) => { + logger.warn(buildDeprecatedBulkEndpointMessage(path), { tags: ['deprecation'] }); +}; + +/** + * Creates a warning header with a message formatted according to RFC7234. + * We follow the same formatting as Elasticsearch + * https://github.com/elastic/elasticsearch/blob/5baabff6670a8ed49297488ca8cac8ec12a2078d/server/src/main/java/org/elasticsearch/common/logging/HeaderWarning.java#L55 + * + * @param path Deprecated endpoint path + */ +export const getDeprecatedBulkEndpointHeader = (path: string) => ({ + warning: `299 Kibana "${buildDeprecatedBulkEndpointMessage(path)}"`, +}); diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 2efb132c96ff6..8646cb54088c5 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -112,10 +112,10 @@ export const initRoutes = ( addPrepackedRulesRoute(router); getPrepackagedRulesStatusRoute(router, config, security, isRuleRegistryEnabled); - createRulesBulkRoute(router, ml, isRuleRegistryEnabled); - updateRulesBulkRoute(router, ml, isRuleRegistryEnabled); - patchRulesBulkRoute(router, ml, isRuleRegistryEnabled); - deleteRulesBulkRoute(router, isRuleRegistryEnabled); + createRulesBulkRoute(router, ml, isRuleRegistryEnabled, logger); + updateRulesBulkRoute(router, ml, isRuleRegistryEnabled, logger); + patchRulesBulkRoute(router, ml, isRuleRegistryEnabled, logger); + deleteRulesBulkRoute(router, isRuleRegistryEnabled, logger); performBulkActionRoute(router, ml, logger, isRuleRegistryEnabled); getRuleExecutionEventsRoute(router); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts index b54e1432f5463..bb450d8f0efdc 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -48,7 +48,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should create a single rule with a rule_id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRule()]) .expect(200); @@ -59,7 +59,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should create a single rule without a rule_id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRuleWithoutRuleId()]) .expect(200); @@ -70,7 +70,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id twice', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRule(), getSimpleRule()]) .expect(200); @@ -88,13 +88,13 @@ export default ({ getService }: FtrProviderContext): void => { it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id that already exists', async () => { await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRule()]) .expect(200); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'foo') .send([getSimpleRule()]) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts index b7517697ad2a9..09c2f5960fae5 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/delete_rules_bulk.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, @@ -43,7 +43,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete the rule in bulk const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1' }]) .expect(200); @@ -57,7 +57,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its rule_id const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: bodyWithCreatedRule.rule_id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -71,7 +71,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its id const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: 'fake_id' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -100,7 +100,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the id does not exist when trying to delete an id', async () => { const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -120,7 +120,7 @@ export default ({ getService }: FtrProviderContext): void => { const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -155,7 +155,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete the rule in bulk const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1' }]) .expect(200); @@ -169,7 +169,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its rule_id const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: bodyWithCreatedRule.rule_id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -183,7 +183,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its id const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -194,7 +194,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: 'fake_id' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -212,7 +212,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the id does not exist when trying to delete an id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -232,7 +232,7 @@ export default ({ getService }: FtrProviderContext): void => { const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts index 0be23c1d8a289..dd1e36e4fb624 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/patch_rules_bulk.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -42,7 +42,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', name: 'some other name' }]) .expect(200); @@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { // patch both rule names const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { rule_id: 'rule-1', name: 'some other name' }, @@ -87,7 +87,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ id: createRuleBody.id, name: 'some other name' }]) .expect(200); @@ -105,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { // patch both rule names const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { id: createRule1.id, name: 'some other name' }, @@ -132,7 +132,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ id: createdBody.id, name: 'some other name' }]) .expect(200); @@ -149,7 +149,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's enabled to false const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', enabled: false }]) .expect(200); @@ -166,7 +166,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's enabled to false and another property const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', severity: 'low', enabled: false }]) .expect(200); @@ -185,14 +185,14 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's timeline_title await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', timeline_title: 'some title', timeline_id: 'some id' }]) .expect(200); // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', name: 'some other name' }]) .expect(200); @@ -209,7 +209,7 @@ export default ({ getService }: FtrProviderContext) => { it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ id: '5096dec6-b6b9-4d8d-8f93-6c2602079d9d', name: 'some other name' }]) .expect(200); @@ -227,7 +227,7 @@ export default ({ getService }: FtrProviderContext) => { it('should return a 200 but give a 404 in the message if it is given a fake rule_id', async () => { const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'fake_id', name: 'some other name' }]) .expect(200); @@ -245,7 +245,7 @@ export default ({ getService }: FtrProviderContext) => { // patch one rule name and give a fake id for the second const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { rule_id: 'rule-1', name: 'some other name' }, @@ -275,7 +275,7 @@ export default ({ getService }: FtrProviderContext) => { // patch one rule name and give a fake id for the second const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { id: createdBody.id, name: 'some other name' }, diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts index 46e34869a8e03..a2043c49cdbc2 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts @@ -7,7 +7,10 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { + DETECTION_ENGINE_RULES_BULK_UPDATE, + DETECTION_ENGINE_RULES_URL, +} from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -46,7 +49,7 @@ export default ({ getService }: FtrProviderContext) => { // update a simple rule's name const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule]) .expect(200); @@ -76,7 +79,7 @@ export default ({ getService }: FtrProviderContext) => { // update both rule names const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); @@ -105,7 +108,7 @@ export default ({ getService }: FtrProviderContext) => { delete updatedRule1.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1]) .expect(200); @@ -133,7 +136,7 @@ export default ({ getService }: FtrProviderContext) => { delete updatedRule2.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); @@ -162,7 +165,7 @@ export default ({ getService }: FtrProviderContext) => { delete updatedRule1.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1]) .expect(200); @@ -183,7 +186,7 @@ export default ({ getService }: FtrProviderContext) => { updatedRule1.enabled = false; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1]) .expect(200); @@ -206,7 +209,7 @@ export default ({ getService }: FtrProviderContext) => { ruleUpdate.timeline_id = 'some id'; await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate]) .expect(200); @@ -216,7 +219,7 @@ export default ({ getService }: FtrProviderContext) => { ruleUpdate2.name = 'some other name'; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate2]) .expect(200); @@ -235,7 +238,7 @@ export default ({ getService }: FtrProviderContext) => { delete ruleUpdate.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate]) .expect(200); @@ -257,7 +260,7 @@ export default ({ getService }: FtrProviderContext) => { delete ruleUpdate.id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate]) .expect(200); @@ -283,7 +286,7 @@ export default ({ getService }: FtrProviderContext) => { // update one rule name and give a fake id for the second const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate, ruleUpdate2]) .expect(200); @@ -320,7 +323,7 @@ export default ({ getService }: FtrProviderContext) => { rule2.name = 'some other name'; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([rule1, rule2]) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 7ea9e4cdb5f84..d2181d779e4e1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -30,6 +30,24 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); describe('create_rules_bulk', () => { + describe('deprecations', () => { + afterEach(async () => { + await deleteAllAlerts(supertest, log); + }); + + it('should return a warning header', async () => { + const { header } = await supertest + .post(DETECTION_ENGINE_RULES_BULK_CREATE) + .set('kbn-xsrf', 'true') + .send([getSimpleRule()]) + .expect(200); + + expect(header.warning).to.be( + '299 Kibana "Deprecated endpoint: /api/detection_engine/rules/_bulk_create API is deprecated since v8.2. Please use the /api/detection_engine/rules/_bulk_action API instead. See https://www.elastic.co/guide/en/security/master/rule-api-overview.html for more detail."' + ); + }); + }); + describe('creating rules in bulk', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); @@ -50,7 +68,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should create a single rule with a rule_id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRule()]) .expect(200); @@ -81,7 +99,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should create a single rule with a rule_id and validate it ran successfully', async () => { const simpleRule = getRuleForSignalTesting(['auditbeat-*']); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([simpleRule]) .expect(200); @@ -91,7 +109,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should create a single rule without a rule_id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRuleWithoutRuleId()]) .expect(200); @@ -102,7 +120,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id twice', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRule(), getSimpleRule()]) .expect(200); @@ -120,13 +138,13 @@ export default ({ getService }: FtrProviderContext): void => { it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id that already exists', async () => { await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'true') .send([getSimpleRule()]) .expect(200); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_create`) + .post(DETECTION_ENGINE_RULES_BULK_CREATE) .set('kbn-xsrf', 'foo') .send([getSimpleRule()]) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts index 69be1f2eb0aff..a2c20f8496049 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { BASE_ALERTING_API_PATH } from '../../../../plugins/alerting/common'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createLegacyRuleAction, @@ -31,6 +31,22 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); describe('delete_rules_bulk', () => { + describe('deprecations', () => { + it('should return a warning header', async () => { + await createRule(supertest, log, getSimpleRule()); + + const { header } = await supertest + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) + .set('kbn-xsrf', 'true') + .send([{ rule_id: 'rule-1' }]) + .expect(200); + + expect(header.warning).to.be( + '299 Kibana "Deprecated endpoint: /api/detection_engine/rules/_bulk_delete API is deprecated since v8.2. Please use the /api/detection_engine/rules/_bulk_action API instead. See https://www.elastic.co/guide/en/security/master/rule-api-overview.html for more detail."' + ); + }); + }); + describe('deleting rules bulk using DELETE', () => { beforeEach(async () => { await createSignalsIndex(supertest, log); @@ -46,7 +62,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete the rule in bulk const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1' }]) .expect(200); @@ -60,7 +76,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its rule_id const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: bodyWithCreatedRule.rule_id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -74,7 +90,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its id const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -85,7 +101,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: 'fake_id' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -103,7 +119,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the id does not exist when trying to delete an id', async () => { const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -123,7 +139,7 @@ export default ({ getService }: FtrProviderContext): void => { const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -158,7 +174,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete the rule in bulk const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1' }]) .expect(200); @@ -172,7 +188,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its rule_id const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: bodyWithCreatedRule.rule_id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -186,7 +202,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete that rule by its id const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -197,7 +213,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ rule_id: 'fake_id' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -215,7 +231,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return an error if the id does not exist when trying to delete an id', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -235,7 +251,7 @@ export default ({ getService }: FtrProviderContext): void => { const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .post(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: bodyWithCreatedRule.id }, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612' }]) .set('kbn-xsrf', 'true') .expect(200); @@ -272,7 +288,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete the rule with the legacy action const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: createRuleBody.id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -320,7 +336,7 @@ export default ({ getService }: FtrProviderContext): void => { // delete 2 rules where both have legacy actions const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: createRuleBody1.id }, { id: createRuleBody2.id }]) .set('kbn-xsrf', 'true') .expect(200); @@ -372,7 +388,7 @@ export default ({ getService }: FtrProviderContext): void => { // bulk delete the rule await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) .send([{ id: createRuleBody.id }]) .set('kbn-xsrf', 'true') .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts index 51cf1a334a2c7..e860c097c9964 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/patch_rules_bulk.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -28,6 +28,26 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); describe('patch_rules_bulk', () => { + describe('deprecations', () => { + afterEach(async () => { + await deleteAllAlerts(supertest, log); + }); + + it('should return a warning header', async () => { + await createRule(supertest, log, getSimpleRule('rule-1')); + + const { header } = await supertest + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) + .set('kbn-xsrf', 'true') + .send([{ rule_id: 'rule-1', name: 'some other name' }]) + .expect(200); + + expect(header.warning).to.be( + '299 Kibana "Deprecated endpoint: /api/detection_engine/rules/_bulk_update API is deprecated since v8.2. Please use the /api/detection_engine/rules/_bulk_action API instead. See https://www.elastic.co/guide/en/security/master/rule-api-overview.html for more detail."' + ); + }); + }); + describe('patch rules bulk', () => { beforeEach(async () => { await createSignalsIndex(supertest, log); @@ -43,7 +63,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', name: 'some other name' }]) .expect(200); @@ -61,7 +81,7 @@ export default ({ getService }: FtrProviderContext) => { // patch both rule names const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { rule_id: 'rule-1', name: 'some other name' }, @@ -88,7 +108,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ id: createRuleBody.id, name: 'some other name' }]) .expect(200); @@ -106,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => { // patch both rule names const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { id: createRule1.id, name: 'some other name' }, @@ -149,7 +169,7 @@ export default ({ getService }: FtrProviderContext) => { ]); // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { id: rule1.id, enabled: false }, @@ -182,7 +202,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ id: createdBody.id, name: 'some other name' }]) .expect(200); @@ -199,7 +219,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's enabled to false const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', enabled: false }]) .expect(200); @@ -216,7 +236,7 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's enabled to false and another property const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', severity: 'low', enabled: false }]) .expect(200); @@ -235,14 +255,14 @@ export default ({ getService }: FtrProviderContext) => { // patch a simple rule's timeline_title await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', timeline_title: 'some title', timeline_id: 'some id' }]) .expect(200); // patch a simple rule's name const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1', name: 'some other name' }]) .expect(200); @@ -259,7 +279,7 @@ export default ({ getService }: FtrProviderContext) => { it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ id: '5096dec6-b6b9-4d8d-8f93-6c2602079d9d', name: 'some other name' }]) .expect(200); @@ -277,7 +297,7 @@ export default ({ getService }: FtrProviderContext) => { it('should return a 200 but give a 404 in the message if it is given a fake rule_id', async () => { const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([{ rule_id: 'fake_id', name: 'some other name' }]) .expect(200); @@ -295,7 +315,7 @@ export default ({ getService }: FtrProviderContext) => { // patch one rule name and give a fake id for the second const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { rule_id: 'rule-1', name: 'some other name' }, @@ -325,7 +345,7 @@ export default ({ getService }: FtrProviderContext) => { // patch one rule name and give a fake id for the second const { body } = await supertest - .patch(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .patch(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ { id: createdBody.id, name: 'some other name' }, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts index b165258237b41..e754cb2c02080 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts @@ -8,7 +8,10 @@ import expect from '@kbn/expect'; import { FullResponseSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { + DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_RULES_BULK_UPDATE, +} from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -30,6 +33,27 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); describe('update_rules_bulk', () => { + describe('deprecations', () => { + afterEach(async () => { + await deleteAllAlerts(supertest, log); + }); + + it('should return a warning header', async () => { + await createRule(supertest, log, getSimpleRule('rule-1')); + const updatedRule = getSimpleRuleUpdate('rule-1'); + + const { header } = await supertest + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) + .set('kbn-xsrf', 'true') + .send([updatedRule]) + .expect(200); + + expect(header.warning).to.be( + '299 Kibana "Deprecated endpoint: /api/detection_engine/rules/_bulk_update API is deprecated since v8.2. Please use the /api/detection_engine/rules/_bulk_action API instead. See https://www.elastic.co/guide/en/security/master/rule-api-overview.html for more detail."' + ); + }); + }); + describe('update rules bulk', () => { beforeEach(async () => { await createSignalsIndex(supertest, log); @@ -48,7 +72,7 @@ export default ({ getService }: FtrProviderContext) => { // update a simple rule's name const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule]) .expect(200); @@ -78,7 +102,7 @@ export default ({ getService }: FtrProviderContext) => { // update both rule names const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); @@ -137,7 +161,7 @@ export default ({ getService }: FtrProviderContext) => { // update both rule names const { body }: { body: FullResponseSchema[] } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); @@ -199,7 +223,7 @@ export default ({ getService }: FtrProviderContext) => { // update both rule names const { body }: { body: FullResponseSchema[] } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); @@ -225,7 +249,7 @@ export default ({ getService }: FtrProviderContext) => { delete updatedRule1.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1]) .expect(200); @@ -253,7 +277,7 @@ export default ({ getService }: FtrProviderContext) => { delete updatedRule2.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); @@ -282,7 +306,7 @@ export default ({ getService }: FtrProviderContext) => { delete updatedRule1.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1]) .expect(200); @@ -303,7 +327,7 @@ export default ({ getService }: FtrProviderContext) => { updatedRule1.enabled = false; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1]) .expect(200); @@ -326,7 +350,7 @@ export default ({ getService }: FtrProviderContext) => { ruleUpdate.timeline_id = 'some id'; await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate]) .expect(200); @@ -336,7 +360,7 @@ export default ({ getService }: FtrProviderContext) => { ruleUpdate2.name = 'some other name'; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate2]) .expect(200); @@ -355,7 +379,7 @@ export default ({ getService }: FtrProviderContext) => { delete ruleUpdate.rule_id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate]) .expect(200); @@ -377,7 +401,7 @@ export default ({ getService }: FtrProviderContext) => { delete ruleUpdate.id; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate]) .expect(200); @@ -403,7 +427,7 @@ export default ({ getService }: FtrProviderContext) => { // update one rule name and give a fake id for the second const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([ruleUpdate, ruleUpdate2]) .expect(200); @@ -440,7 +464,7 @@ export default ({ getService }: FtrProviderContext) => { rule2.name = 'some other name'; const { body } = await supertest - .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([rule1, rule2]) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index de66002343212..2087e0d6ab523 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -52,6 +52,7 @@ import { DETECTION_ENGINE_INDEX_URL, DETECTION_ENGINE_PREPACKAGED_URL, DETECTION_ENGINE_QUERY_SIGNALS_URL, + DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL, DETECTION_ENGINE_SIGNALS_MIGRATION_URL, @@ -513,18 +514,9 @@ export const deleteAllAlerts = async ( ): Promise => { await countDownTest( async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find?per_page=9999`) - .set('kbn-xsrf', 'true') - .send(); - - const ids = body.data.map((rule: FullResponseSchema) => ({ - id: rule.id, - })); - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) - .send(ids) + .post(DETECTION_ENGINE_RULES_BULK_ACTION) + .send({ action: 'delete', query: '' }) .set('kbn-xsrf', 'true'); const { body: finalCheck } = await supertest From eaf6c84c20144383dea5e3a38bdf8689360b4a64 Mon Sep 17 00:00:00 2001 From: Baturalp Gurdin <9674241+suchcodemuchwow@users.noreply.github.com> Date: Tue, 12 Apr 2022 15:11:14 +0300 Subject: [PATCH 13/32] add login journey (#129976) --- .buildkite/scripts/steps/functional/performance_playwright.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index 9a4301e94f7fe..d739f136992e7 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -20,7 +20,7 @@ sleep 120 cd "$XPACK_DIR" -journeys=("ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard") +journeys=("login" "ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard") for i in "${journeys[@]}"; do echo "JOURNEY[${i}] is running" From b0591a48db6379e76d7074d44337117907ca0b40 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 12 Apr 2022 15:16:32 +0300 Subject: [PATCH 14/32] [Maps][Visualize][Graph][Dashboard] Fixes the listingLimit settings url (#129701) * [Maps][Visualize][Graph][Dashboard] Fixes the listingLimit settings url * Check if user has permissions to alter the advanced setting * Use the uiSettings service instead of the deprecated savedObjects one * Address review comments - maps Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../dashboard_listing.test.tsx.snap | 238 ++++++++++++++++-- .../application/listing/dashboard_listing.tsx | 10 +- .../table_list_view/table_list_view.test.tsx | 3 +- .../table_list_view/table_list_view.tsx | 58 +++-- .../visualizations/common/constants.ts | 2 + src/plugins/visualizations/public/index.ts | 6 +- src/plugins/visualizations/public/plugin.ts | 1 - .../components/visualize_listing.tsx | 13 +- .../public/visualize_app/types.ts | 2 - .../graph/public/apps/listing_route.tsx | 12 +- x-pack/plugins/maps/public/kibana_services.ts | 2 +- .../routes/list_page/maps_list_view.tsx | 15 +- 12 files changed, 308 insertions(+), 54 deletions(-) diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap index 598254ad2173f..6706deeaa1de4 100644 --- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -22,6 +22,38 @@ exports[`after fetch When given a title that matches multiple dashboards, filter title="search by title" > Promise.resolve({ total: 0, hits: [] })), theme: themeServiceMock.createStartContract(), + application: applicationServiceMock.createStartContract(), }; describe('TableListView', () => { diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index dd023d522dbb6..55e822e68f489 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { ThemeServiceStart, HttpFetchError, ToastsStart } from 'kibana/public'; +import { ThemeServiceStart, HttpFetchError, ToastsStart, ApplicationStart } from 'kibana/public'; import { debounce, keyBy, sortBy, uniq } from 'lodash'; import React from 'react'; import { KibanaPageTemplate } from '../page_template'; @@ -58,6 +58,7 @@ export interface TableListViewProps { tableCaption: string; searchFilters?: SearchFilterConfig[]; theme: ThemeServiceStart; + application: ApplicationStart; } export interface TableListViewState { @@ -275,6 +276,11 @@ class TableListView extends React.Component< renderListingLimitWarning() { if (this.state.showLimitError) { + const canEditAdvancedSettings = this.props.application.capabilities.advancedSettings.save; + const setting = 'savedObjects:listingLimit'; + const advancedSettingsLink = this.props.application.getUrlForApp('management', { + path: `/kibana/settings?query=${setting}`, + }); return ( extends React.Component< iconType="help" >

- listingLimit, - advancedSettingsLink: ( - - - - ), - }} - /> + values={{ + entityNamePlural: this.props.entityNamePlural, + totalItems: this.state.totalItems, + listingLimitValue: this.props.listingLimit, + listingLimitText: listingLimit, + advancedSettingsLink: ( + + + + ), + }} + /> + ) : ( + listingLimit, + }} + /> + )}

diff --git a/src/plugins/visualizations/common/constants.ts b/src/plugins/visualizations/common/constants.ts index b901dacc62971..0b840c8ff13fc 100644 --- a/src/plugins/visualizations/common/constants.ts +++ b/src/plugins/visualizations/common/constants.ts @@ -7,6 +7,8 @@ */ export const VISUALIZE_ENABLE_LABS_SETTING = 'visualize:enableLabs'; +export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; +export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; export const VISUALIZE_EMBEDDABLE_TYPE = 'visualization'; export const STATE_STORAGE_KEY = '_a'; diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index de2af1d5cdcfb..73f02a44dac13 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -52,7 +52,11 @@ export type { VisualizationListItem, VisualizationStage, } from './vis_types/vis_type_alias_registry'; -export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; +export { + VISUALIZE_ENABLE_LABS_SETTING, + SAVED_OBJECTS_LIMIT_SETTING, + SAVED_OBJECTS_PER_PAGE_SETTING, +} from '../common/constants'; export type { SavedVisState, VisParams, Dimension } from '../common'; export { prepareLogTable } from '../common'; export type { ExpressionValueVisDimension } from '../common/expression_functions/vis_dimension'; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 997d78b31163d..a4934e0b8edff 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -281,7 +281,6 @@ export class VisualizationsPlugin stateTransferService: pluginsStart.embeddable.getStateTransfer(), setActiveUrl, createVisEmbeddableFromObject: createVisEmbeddableFromObject({ start }), - savedObjectsPublic: pluginsStart.savedObjects, scopedHistory: params.history, restorePreviousUrl, setHeaderActionMenu: params.setHeaderActionMenu, diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx index a180cf78feeb2..e38d854247786 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx @@ -22,7 +22,11 @@ import { showNewVisModal } from '../../wizard'; import { getTypes } from '../../services'; import { SavedObjectsFindOptionsReference } from '../../../../../core/public'; import { useKibana, TableListView, useExecutionContext } from '../../../../kibana_react/public'; -import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../visualizations/public'; +import { + VISUALIZE_ENABLE_LABS_SETTING, + SAVED_OBJECTS_LIMIT_SETTING, + SAVED_OBJECTS_PER_PAGE_SETTING, +} from '../../../../visualizations/public'; import { VisualizeServices } from '../types'; import { VisualizeConstants } from '../../../common/constants'; import { getTableColumns, getNoItemsMessage } from '../utils'; @@ -37,7 +41,6 @@ export const VisualizeListing = () => { toastNotifications, stateTransferService, savedObjects, - savedObjectsPublic, savedObjectsTagging, uiSettings, visualizeCapabilities, @@ -48,7 +51,8 @@ export const VisualizeListing = () => { } = useKibana(); const { pathname } = useLocation(); const closeNewVisModal = useRef(() => {}); - const listingLimit = savedObjectsPublic.settings.getListingLimit(); + const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); + const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); useExecutionContext(executionContext, { type: 'application', @@ -193,7 +197,7 @@ export const VisualizeListing = () => { editItem={visualizeCapabilities.save ? editItem : undefined} tableColumns={tableColumns} listingLimit={listingLimit} - initialPageSize={savedObjectsPublic.settings.getPerPage()} + initialPageSize={initialPageSize} initialFilter={''} rowHeader="title" emptyPrompt={noItemsFragment} @@ -209,6 +213,7 @@ export const VisualizeListing = () => { toastNotifications={toastNotifications} searchFilters={searchFilters} theme={theme} + application={application} > {dashboardCapabilities.createNew && ( <> diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts index 7c4c8155a9405..59cd94d210269 100644 --- a/src/plugins/visualizations/public/visualize_app/types.ts +++ b/src/plugins/visualizations/public/visualize_app/types.ts @@ -39,7 +39,6 @@ import type { Filter } from '@kbn/es-query'; import type { Query, DataPublicPluginStart, TimeRange } from 'src/plugins/data/public'; import type { DataViewsPublicPluginStart } from 'src/plugins/data_views/public'; import type { SharePluginStart } from 'src/plugins/share/public'; -import type { SavedObjectsStart } from 'src/plugins/saved_objects/public'; import type { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; import type { UrlForwardingStart } from 'src/plugins/url_forwarding/public'; import type { PresentationUtilPluginStart } from 'src/plugins/presentation_util/public'; @@ -97,7 +96,6 @@ export interface VisualizeServices extends CoreStart { share?: SharePluginStart; visualizeCapabilities: Record>; dashboardCapabilities: Record>; - savedObjectsPublic: SavedObjectsStart; setActiveUrl: (newUrl: string) => void; createVisEmbeddableFromObject: ReturnType; restorePreviousUrl: () => void; diff --git a/x-pack/plugins/graph/public/apps/listing_route.tsx b/x-pack/plugins/graph/public/apps/listing_route.tsx index dc70d84155bf9..1683388841729 100644 --- a/x-pack/plugins/graph/public/apps/listing_route.tsx +++ b/x-pack/plugins/graph/public/apps/listing_route.tsx @@ -17,15 +17,18 @@ import { getEditPath, getEditUrl, getNewPath, setBreadcrumbs } from '../services import { GraphWorkspaceSavedObject } from '../types'; import { GraphServices } from '../application'; +const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; +const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; + export interface ListingRouteProps { - deps: GraphServices; + deps: Omit; } export function ListingRoute({ - deps: { chrome, savedObjects, savedObjectsClient, coreStart, capabilities, addBasePath }, + deps: { chrome, savedObjectsClient, coreStart, capabilities, addBasePath, uiSettings }, }: ListingRouteProps) { - const listingLimit = savedObjects.settings.getListingLimit(); - const initialPageSize = savedObjects.settings.getPerPage(); + const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); + const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); const history = useHistory(); const query = new URLSearchParams(useLocation().search); const initialFilter = query.get('filter') || ''; @@ -103,6 +106,7 @@ export function ListingRoute({ defaultMessage: 'Graphs', })} theme={coreStart.theme} + application={coreStart.application} /> ); diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index f2345d2102a12..8b51fb5fbebc6 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -51,7 +51,6 @@ export const getVisualizeCapabilities = () => coreStart.application.capabilities export const getDocLinks = () => coreStart.docLinks; export const getCoreOverlays = () => coreStart.overlays; export const getData = () => pluginsStart.data; -export const getSavedObjects = () => pluginsStart.savedObjects; export const getUiActions = () => pluginsStart.uiActions; export const getCore = () => coreStart; export const getNavigation = () => pluginsStart.navigation; @@ -66,6 +65,7 @@ export const getSpacesApi = () => pluginsStart.spaces; export const getTheme = () => coreStart.theme; export const getUsageCollection = () => pluginsStart.usageCollection; export const getSharedUXPluginContext = () => pluginsStart.sharedUX; +export const getApplication = () => coreStart.application; // xpack.maps.* kibana.yml settings from this plugin let mapAppConfig: MapsConfigType; diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index dab284b0b71e4..f81d88da80b09 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -21,12 +21,16 @@ import { getNavigateToApp, getSavedObjectsClient, getSavedObjectsTagging, - getSavedObjects, + getUiSettings, getTheme, + getApplication, } from '../../kibana_services'; import { getAppTitle } from '../../../common/i18n_getters'; import { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; +const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; +const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; + interface MapItem { id: string; title: string; @@ -93,7 +97,7 @@ async function findMaps(searchQuery: string) { const resp = await getSavedObjectsClient().find({ type: MAP_SAVED_OBJECT_TYPE, search: searchTerm ? `${searchTerm}*` : undefined, - perPage: getSavedObjects().settings.getListingLimit(), + perPage: getUiSettings().get(SAVED_OBJECTS_LIMIT_SETTING), page: 1, searchFields: ['title^3', 'description'], defaultSearchOperator: 'AND', @@ -129,6 +133,8 @@ export function MapsListView() { }); const isReadOnly = !getMapsCapabilities().save; + const listingLimit = getUiSettings().get(SAVED_OBJECTS_LIMIT_SETTING); + const initialPageSize = getUiSettings().get(SAVED_OBJECTS_PER_PAGE_SETTING); getCoreChrome().docTitle.change(getAppTitle()); getCoreChrome().setBreadcrumbs([{ text: getAppTitle() }]); @@ -141,9 +147,9 @@ export function MapsListView() { findItems={findMaps} deleteItems={isReadOnly ? undefined : deleteMaps} tableColumns={tableColumns} - listingLimit={getSavedObjects().settings.getListingLimit()} + listingLimit={listingLimit} initialFilter={''} - initialPageSize={getSavedObjects().settings.getPerPage()} + initialPageSize={initialPageSize} entityName={i18n.translate('xpack.maps.mapListing.entityName', { defaultMessage: 'map', })} @@ -157,6 +163,7 @@ export function MapsListView() { toastNotifications={getToasts()} searchFilters={searchFilters} theme={getTheme()} + application={getApplication()} /> ); } From 092c71a21b771bb6ad4468f85cf1d442fd2deea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Tue, 12 Apr 2022 13:24:33 +0100 Subject: [PATCH 15/32] [Form lib] Fixes (#128489) --- .../components/use_array.test.tsx | 112 ++++++++++ .../hook_form_lib/components/use_array.ts | 8 +- .../components/use_field.test.tsx | 198 ++++++++++++++---- .../hook_form_lib/components/use_field.tsx | 9 + .../forms/hook_form_lib/hooks/use_field.ts | 62 +++--- .../forms/hook_form_lib/hooks/use_form.ts | 15 +- .../hooks/use_form_is_modified.test.tsx | 8 +- .../hooks/use_form_is_modified.ts | 42 ++-- .../static/forms/hook_form_lib/lib/index.ts | 4 +- .../forms/hook_form_lib/lib/utils.test.ts | 43 ++++ .../static/forms/hook_form_lib/lib/utils.ts | 43 +++- 11 files changed, 445 insertions(+), 99 deletions(-) create mode 100644 src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx create mode 100644 src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.test.ts diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx new file mode 100644 index 0000000000000..dc8695190bdaf --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect } from 'react'; +import { act } from 'react-dom/test-utils'; + +import { registerTestBed } from '../shared_imports'; +import { useForm } from '../hooks/use_form'; +import { useFormData } from '../hooks/use_form_data'; +import { Form } from './form'; +import { UseField } from './use_field'; +import { UseArray } from './use_array'; + +describe('', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + test('it should render by default 1 array item', () => { + const TestComp = () => { + const { form } = useForm(); + return ( + + + {({ items }) => { + return ( + <> + {items.map(({ id }) => { + return ( +

+ Array item +

+ ); + })} + + ); + }} +
+ + ); + }; + + const setup = registerTestBed(TestComp, { + memoryRouter: { wrapComponent: false }, + }); + + const { find } = setup(); + + expect(find('arrayItem').length).toBe(1); + }); + + test('it should allow to listen to array item field value change', async () => { + const onFormData = jest.fn(); + + const TestComp = ({ onData }: { onData: (data: any) => void }) => { + const { form } = useForm(); + const [formData] = useFormData({ form, watch: 'users[0].name' }); + + useEffect(() => { + onData(formData); + }, [onData, formData]); + + return ( +
+ + {({ items }) => { + return ( + <> + {items.map(({ id, path }) => { + return ( + + ); + })} + + ); + }} + +
+ ); + }; + + const setup = registerTestBed(TestComp, { + defaultProps: { onData: onFormData }, + memoryRouter: { wrapComponent: false }, + }); + + const { + form: { setInputValue }, + } = setup(); + + await act(async () => { + setInputValue('nameField__0', 'John'); + }); + + const formData = onFormData.mock.calls[onFormData.mock.calls.length - 1][0]; + + expect(formData.users[0].name).toEqual('John'); + }); +}); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts index 3e7b061603458..78379aa9fffbf 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import uuid from 'uuid'; import { useEffect, useRef, useCallback, useMemo } from 'react'; import { FormHook, FieldConfig } from '../types'; @@ -53,7 +54,7 @@ export interface FormArrayField { */ export const UseArray = ({ path, - initialNumberOfItems, + initialNumberOfItems = 1, validations, readDefaultValueOnForm = true, children, @@ -92,6 +93,9 @@ export const UseArray = ({ // Create an internal hook field which behaves like any other form field except that it is not // outputed in the form data (when calling form.submit() or form.getFormData()) // This allow us to run custom validations (passed to the props) on the Array items + + const internalFieldPath = useMemo(() => `${path}__${uuid.v4()}`, [path]); + const fieldConfigBase: FieldConfig & InternalFieldConfig = { defaultValue: fieldDefaultValue, initialValue: fieldDefaultValue, @@ -103,7 +107,7 @@ export const UseArray = ({ ? { validations, ...fieldConfigBase } : fieldConfigBase; - const field = useField(form, path, fieldConfig); + const field = useField(form, internalFieldPath, fieldConfig); const { setValue, value, isChangingValue, errors } = field; // Derived state from the field diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index cbf0d9d619636..36fd16209f5d4 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -26,41 +26,91 @@ describe('', () => { jest.useRealTimers(); }); - test('should read the default value from the prop and fallback to the config object', () => { - const onFormData = jest.fn(); + describe('defaultValue', () => { + test('should read the default value from the prop and fallback to the config object', () => { + const onFormData = jest.fn(); - const TestComp = ({ onData }: { onData: OnUpdateHandler }) => { - const { form } = useForm(); - const { subscribe } = form; + const TestComp = ({ onData }: { onData: OnUpdateHandler }) => { + const { form } = useForm(); + const { subscribe } = form; - useEffect(() => subscribe(onData).unsubscribe, [subscribe, onData]); + useEffect(() => subscribe(onData).unsubscribe, [subscribe, onData]); - return ( -
- - - - ); - }; + return ( +
+ + + + ); + }; + + const setup = registerTestBed(TestComp, { + defaultProps: { onData: onFormData }, + memoryRouter: { wrapComponent: false }, + }); - const setup = registerTestBed(TestComp, { - defaultProps: { onData: onFormData }, - memoryRouter: { wrapComponent: false }, + setup(); + + const [{ data }] = onFormData.mock.calls[ + onFormData.mock.calls.length - 1 + ] as Parameters; + + expect(data.internal).toEqual({ + name: 'John', + lastName: 'Snow', + }); }); - setup(); + test('should update the form.defaultValue when a field defaultValue is provided through prop', () => { + let formHook: FormHook | null = null; - const [{ data }] = onFormData.mock.calls[ - onFormData.mock.calls.length - 1 - ] as Parameters; + const TestComp = () => { + const [isFieldVisible, setIsFieldVisible] = useState(true); + const { form } = useForm(); + formHook = form; + + return ( +
+ {isFieldVisible && ( + <> + + + + + + + )} + + + ); + }; + + const setup = registerTestBed(TestComp, { + memoryRouter: { wrapComponent: false }, + }); + + const { find } = setup(); + + expect(formHook!.__getFormDefaultValue()).toEqual({ + name: 'John', + myArray: [ + { name: 'John', lastName: 'Snow' }, + { name: 'Foo', lastName: 'Bar' }, + ], + }); + + // Unmounts the field and make sure the form.defaultValue has been updated + act(() => { + find('unmountField').simulate('click'); + }); - expect(data.internal).toEqual({ - name: 'John', - lastName: 'Snow', + expect(formHook!.__getFormDefaultValue()).toEqual({}); }); }); @@ -205,7 +255,7 @@ describe('', () => { describe('validation', () => { let formHook: FormHook | null = null; - let fieldHook: FieldHook | null = null; + let fieldHook: FieldHook | null = null; beforeEach(() => { formHook = null; @@ -216,19 +266,24 @@ describe('', () => { formHook = form; }; - const onFieldHook = (field: FieldHook) => { + const onFieldHook = (field: FieldHook) => { fieldHook = field; }; - const getTestComp = (fieldConfig: FieldConfig) => { + const getTestComp = (fieldConfig?: FieldConfig) => { const TestComp = () => { - const { form } = useForm(); + const { form } = useForm(); const [isFieldActive, setIsFieldActive] = useState(true); + const [fieldPath, setFieldPath] = useState('name'); const unmountField = () => { setIsFieldActive(false); }; + const changeFieldPath = () => { + setFieldPath('newPath'); + }; + useEffect(() => { onFormHook(form); }, [form]); @@ -236,16 +291,12 @@ describe('', () => { return (
{isFieldActive && ( - + path={fieldPath} config={fieldConfig}> {(field) => { onFieldHook(field); return ( - + ); }} @@ -253,20 +304,23 @@ describe('', () => { + ); }; return TestComp; }; - const setup = (fieldConfig: FieldConfig) => { + const setup = (fieldConfig?: FieldConfig) => { return registerTestBed(getTestComp(fieldConfig), { memoryRouter: { wrapComponent: false }, })() as TestBed; }; test('should update the form validity whenever the field value changes', async () => { - const fieldConfig: FieldConfig = { + const fieldConfig: FieldConfig = { defaultValue: '', // empty string, which is not valid validations: [ { @@ -317,7 +371,7 @@ describe('', () => { }); test('should not update the state if the field has unmounted while validating', async () => { - const fieldConfig: FieldConfig = { + const fieldConfig: FieldConfig = { validations: [ { validator: () => { @@ -369,6 +423,40 @@ describe('', () => { console.error = originalConsoleError; // eslint-disable-line no-console }); + test('should not validate the field if the "path" changes but the value has not changed', async () => { + // This happens with the UseArray. When we delete an item from the array the path for + // the remaining items are recalculated and thus changed for every inside + // the array. We should not re-run the validation when adding/removing array items. + + const validator = jest.fn(); + const fieldConfig: FieldConfig = { + validations: [ + { + validator, + }, + ], + }; + + const { + find, + form: { setInputValue }, + } = setup(fieldConfig); + + await act(async () => { + setInputValue('myField', 'changedValue'); + }); + + expect(validator).toHaveBeenCalledTimes(1); + validator.mockReset(); + + await act(async () => { + // Change the field path + find('changeFieldPathBtn').simulate('click'); + }); + + expect(validator).not.toHaveBeenCalled(); + }); + describe('dynamic data', () => { let nameFieldHook: FieldHook | null = null; let lastNameFieldHook: FieldHook | null = null; @@ -708,32 +796,54 @@ describe('', () => { }); describe('change handlers', () => { + const onChange = jest.fn(); const onError = jest.fn(); beforeEach(() => { jest.resetAllMocks(); }); - const getTestComp = (fieldConfig: FieldConfig) => { + const getTestComp = (fieldConfig?: FieldConfig) => { const TestComp = () => { const { form } = useForm(); return (
- + ); }; return TestComp; }; - const setup = (fieldConfig: FieldConfig) => { + const setup = (fieldConfig?: FieldConfig) => { return registerTestBed(getTestComp(fieldConfig), { memoryRouter: { wrapComponent: false }, })() as TestBed; }; - it('calls onError when validation state changes', async () => { + test('calls onChange() prop when value state changes', async () => { + const { + form: { setInputValue }, + } = setup(); + + expect(onChange).toBeCalledTimes(0); + + await act(async () => { + setInputValue('myField', 'foo'); + }); + + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith('foo'); + }); + + test('calls onError() prop when validation state changes', async () => { const { form: { setInputValue }, } = setup({ diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index bc4e2ccf58294..7e216e3126ed8 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -128,11 +128,20 @@ function UseFieldComp(props: Props { + let needsCleanUp = false; + if (defaultValue !== undefined) { + needsCleanUp = true; // Update the form "defaultValue" ref object. // This allows us to reset the form and put back the defaultValue of each field __updateDefaultValueAt(path, defaultValue); } + + return () => { + if (needsCleanUp) { + __updateDefaultValueAt(path, undefined); + } + }; }, [path, defaultValue, __updateDefaultValueAt]); // Children prevails over anything else provided. diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index f0c9c50c1033e..7ba06304b971b 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -26,6 +26,10 @@ export interface InternalFieldConfig { isIncludedInOutput?: boolean; } +const errorsToString = (errors: ValidationError[]): string[] | null => { + return errors.length ? errors.map((error) => error.message) : null; +}; + export const useField = ( form: FormHook, path: string, @@ -81,14 +85,13 @@ export const useField = ( const isMounted = useRef(false); const validateCounter = useRef(0); const changeCounter = useRef(0); - const hasBeenReset = useRef(false); const inflightValidation = useRef(null); const debounceTimeout = useRef(null); // Keep a ref of the last state (value and errors) notified to the consumer so they don't get // loads of updates whenever they don't wrap the "onChange()" and "onError()" handlers with a useCallback // e.g. { // inline code }} const lastNotifiedState = useRef<{ value?: I; errors: string[] | null }>({ - value: undefined, + value: initialValueDeserialized, errors: null, }); @@ -100,6 +103,9 @@ export const useField = ( [validations] ); + const valueHasChanged = value !== lastNotifiedState.current.value; + const errorsHaveChanged = lastNotifiedState.current.errors !== errorsToString(errors); + // ---------------------------------- // -- HELPERS // ---------------------------------- @@ -519,8 +525,8 @@ export const useField = ( setStateErrors([]); if (resetValue) { - hasBeenReset.current = true; const newValue = deserializeValue(updatedDefaultValue ?? defaultValue); + lastNotifiedState.current.value = newValue; setValue(newValue); return newValue; } @@ -604,36 +610,29 @@ export const useField = ( // might not be wrapped inside a "useCallback" and that would trigger a possible infinite // amount of effect executions. useEffect(() => { - if (!isMounted.current) { + if (!isMounted.current || value === undefined) { return; } - if (valueChangeListener && value !== lastNotifiedState.current.value) { + if (valueChangeListener && valueHasChanged) { valueChangeListener(value); - lastNotifiedState.current.value = value; } - }, [value, valueChangeListener]); + }, [value, valueHasChanged, valueChangeListener]); // Value change: update state and run validations useEffect(() => { - if (!isMounted.current) { + if (!isMounted.current || !valueHasChanged) { return; } - if (hasBeenReset.current) { - // If the field value has just been reset (triggering this useEffect) - // we don't want to set the "isPristine" state to true and validate the field - hasBeenReset.current = false; - } else { - setPristine(false); - setIsChangingValue(true); - - runValidationsOnValueChange(() => { - if (isMounted.current) { - setIsChangingValue(false); - } - }); - } + setPristine(false); + setIsChangingValue(true); + + runValidationsOnValueChange(() => { + if (isMounted.current) { + setIsChangingValue(false); + } + }); return () => { if (debounceTimeout.current) { @@ -641,7 +640,7 @@ export const useField = ( debounceTimeout.current = null; } }; - }, [value, runValidationsOnValueChange]); + }, [valueHasChanged, runValidationsOnValueChange]); // Value change: set "isModified" state useEffect(() => { @@ -659,13 +658,18 @@ export const useField = ( return; } - const errorMessages = errors.length ? errors.map((error) => error.message) : null; - - if (errorChangeListener && lastNotifiedState.current.errors !== errorMessages) { - errorChangeListener(errorMessages); - lastNotifiedState.current.errors = errorMessages; + if (errorChangeListener && errorsHaveChanged) { + errorChangeListener(errorsToString(errors)); } - }, [errors, errorChangeListener]); + }, [errors, errorsHaveChanged, errorChangeListener]); + + useEffect(() => { + lastNotifiedState.current.value = value; + }, [value]); + + useEffect(() => { + lastNotifiedState.current.errors = errorsToString(errors); + }, [errors]); useEffect(() => { isMounted.current = true; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index 2160c09ef720e..3966f9cc61a70 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -11,7 +11,7 @@ import { get } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; import { FormHook, FieldHook, FormData, FieldsMap, FormConfig } from '../types'; -import { mapFormFields, unflattenObject, Subject, Subscription } from '../lib'; +import { mapFormFields, unflattenObject, flattenObject, Subject, Subscription } from '../lib'; const DEFAULT_OPTIONS = { valueChangeDebounceTime: 500, @@ -205,7 +205,18 @@ export function useForm( if (defaultValueDeserialized.current === undefined) { defaultValueDeserialized.current = {} as I; } - set(defaultValueDeserialized.current!, path, value); + + // We allow "undefined" to be passed to be able to remove a value from the form `defaultValue` object. + // When mounts it calls `updateDefaultValueAt("foo", "bar")` to + // update the form "defaultValue" object. When that component unmounts we want to be able to clean up and + // remove its defaultValue on the form. + if (value === undefined) { + const updated = flattenObject(defaultValueDeserialized.current!); + delete updated[path]; + defaultValueDeserialized.current = unflattenObject(updated); + } else { + set(defaultValueDeserialized.current!, path, value); + } }, [] ); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx index dc89cfe4f1fb6..7c0cd960999e8 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { act } from 'react-dom/test-utils'; import { registerTestBed } from '../shared_imports'; @@ -36,8 +36,10 @@ describe('useFormIsModified()', () => { const [isNameVisible, setIsNameVisible] = useState(true); const [isLastNameVisible, setIsLastNameVisible] = useState(true); - // Call our jest.spy() with the latest hook value - onIsModifiedChange(isModified); + useEffect(() => { + // Call our jest.spy() with the latest hook value + onIsModifiedChange(isModified); + }, [onIsModifiedChange, isModified]); return (
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.ts index 08f5eaf76a083..e5e0fd6d61472 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { get } from 'lodash'; import { FieldHook, FormHook } from '../types'; @@ -36,6 +36,8 @@ export const useFormIsModified = ({ form: formFromOptions, discard: fieldPathsToDiscard = [], }: Options = {}): boolean => { + const [isFormModified, setIsFormModified] = useState(false); + // Hook calls can not be conditional we first try to access the form through context let form = useFormContext({ throwIfNotFound: false }); @@ -76,28 +78,34 @@ export const useFormIsModified = ({ ? ([path]: [string, FieldHook]) => fieldsToDiscard[path] !== true : () => true; + // Calculate next state value // 1. Check if any field has been modified - let isModified = Object.entries(getFields()) + let nextIsModified = Object.entries(getFields()) .filter(isFieldIncluded) .some(([_, field]) => field.isModified); - if (isModified) { - return isModified; - } + if (!nextIsModified) { + // 2. Check if any field has been removed. + // If somme field has been removed **and** they were originaly present on the + // form "defaultValue" then the form has been modified. + const formDefaultValue = __getFormDefaultValue(); + const fieldOnFormDefaultValue = (path: string) => Boolean(get(formDefaultValue, path)); - // 2. Check if any field has been removed. - // If somme field has been removed **and** they were originaly present on the - // form "defaultValue" then the form has been modified. - const formDefaultValue = __getFormDefaultValue(); - const fieldOnFormDefaultValue = (path: string) => Boolean(get(formDefaultValue, path)); + const fieldsRemovedFromDOM: string[] = fieldsToDiscard + ? Object.keys(__getFieldsRemoved()) + .filter((path) => fieldsToDiscard[path] !== true) + .filter(fieldOnFormDefaultValue) + : Object.keys(__getFieldsRemoved()).filter(fieldOnFormDefaultValue); - const fieldsRemovedFromDOM: string[] = fieldsToDiscard - ? Object.keys(__getFieldsRemoved()) - .filter((path) => fieldsToDiscard[path] !== true) - .filter(fieldOnFormDefaultValue) - : Object.keys(__getFieldsRemoved()).filter(fieldOnFormDefaultValue); + nextIsModified = fieldsRemovedFromDOM.length > 0; + } - isModified = fieldsRemovedFromDOM.length > 0; + // Update the state **only** if it has changed to avoid creating an infinite re-render + if (nextIsModified && !isFormModified) { + setIsFormModified(true); + } else if (!nextIsModified && isFormModified) { + setIsFormModified(false); + } - return isModified; + return isFormModified; }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts index 0bbaedcf2e90e..b65dc0570acba 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/index.ts @@ -7,5 +7,7 @@ */ export type { Subscription } from './subject'; + export { Subject } from './subject'; -export * from './utils'; + +export { flattenObject, unflattenObject, mapFormFields } from './utils'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.test.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.test.ts new file mode 100644 index 0000000000000..f7d7429889eb2 --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { flattenObject } from './utils'; + +describe('Form lib utils', () => { + describe('flattenObject', () => { + test('should flatten an object', () => { + const obj = { + a: true, + b: { + foo: 'bar', + baz: [ + { + a: false, + b: 'foo', + }, + 'bar', + true, + [1, 2, { 3: false }], + ], + }, + }; + + expect(flattenObject(obj)).toEqual({ + a: true, + 'b.baz[0].a': false, + 'b.baz[0].b': 'foo', + 'b.baz[1]': 'bar', + 'b.baz[2]': true, + 'b.foo': 'bar', + 'b.baz[3][0]': 1, + 'b.baz[3][1]': 2, + 'b.baz[3][2].3': false, + }); + }); + }); +}); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts index 9d8801b1448c0..8df6506ec2e7b 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts @@ -9,12 +9,53 @@ import { set } from '@elastic/safer-lodash-set'; import { FieldHook } from '../types'; -export const unflattenObject = (object: object): T => +interface GenericObject { + [key: string]: any; +} + +export const unflattenObject = (object: object): T => Object.entries(object).reduce((acc, [key, value]) => { set(acc, key, value); return acc; }, {} as T); +/** + * Wrap the key with [] if it is a key from an Array + * @param key The object key + * @param isArrayItem Flag to indicate if it is the key of an Array + */ +const renderKey = (key: string, isArrayItem: boolean): string => (isArrayItem ? `[${key}]` : key); + +export const flattenObject = ( + obj: GenericObject, + prefix: string[] = [], + isArrayItem = false +): GenericObject => + Object.keys(obj).reduce((acc, k) => { + const nextValue = obj[k]; + + if (typeof nextValue === 'object' && nextValue !== null) { + const isNextValueArray = Array.isArray(nextValue); + const dotSuffix = isNextValueArray ? '' : '.'; + + if (Object.keys(nextValue).length > 0) { + return { + ...acc, + ...flattenObject( + nextValue, + [...prefix, `${renderKey(k, isArrayItem)}${dotSuffix}`], + isNextValueArray + ), + }; + } + } + + const fullPath = `${prefix.join('')}${renderKey(k, isArrayItem)}`; + acc[fullPath] = nextValue; + + return acc; + }, {}); + /** * Helper to map the object of fields to any of its value * From c2003d0663341579f533a0be3d7be48e834567c2 Mon Sep 17 00:00:00 2001 From: Or Ouziel Date: Tue, 12 Apr 2022 15:44:57 +0300 Subject: [PATCH 16/32] [Cloud Posture] add findings framework sources icons (#129883) --- .../public/assets/icons/cis_logo.svg | 25 +++++++++++++++++++ .../public/assets/icons/k8s_logo.svg | 7 ++++++ .../public/pages/findings/findings_flyout.tsx | 15 ++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/assets/icons/cis_logo.svg create mode 100644 x-pack/plugins/cloud_security_posture/public/assets/icons/k8s_logo.svg diff --git a/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_logo.svg b/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_logo.svg new file mode 100644 index 0000000000000..c5e4849425d8a --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_logo.svg @@ -0,0 +1,25 @@ + + Logos/CIS/CIS_logo_no-tag_stacked_large + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/x-pack/plugins/cloud_security_posture/public/assets/icons/k8s_logo.svg b/x-pack/plugins/cloud_security_posture/public/assets/icons/k8s_logo.svg new file mode 100644 index 0000000000000..1949c06d51520 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/assets/icons/k8s_logo.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx index f53d76b82c177..65493bd493342 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx @@ -21,12 +21,15 @@ import { EuiFlexGrid, EuiCard, EuiFlexGroup, + EuiIcon, type PropsOf, } from '@elastic/eui'; import { assertNever } from '@kbn/std'; import type { CspFinding } from './types'; import { CspEvaluationBadge } from '../../components/csp_evaluation_badge'; import * as TEXT from './translations'; +import cisLogoIcon from '../../assets/icons/cis_logo.svg'; +import k8sLogoIcon from '../../assets/icons/k8s_logo.svg'; const tabs = ['remediation', 'resource', 'general'] as const; @@ -163,7 +166,17 @@ const getGeneralCards = ({ rule }: CspFinding): Card[] => [ [TEXT.SEVERITY, ''], [TEXT.INDEX, ''], [TEXT.RULE_EVALUATED_AT, ''], - [TEXT.FRAMEWORK_SOURCES, ''], + [ + TEXT.FRAMEWORK_SOURCES, + + + + + + + + , + ], [TEXT.SECTION, ''], [TEXT.PROFILE_APPLICABILITY, ''], [TEXT.AUDIT, ''], From bced9d2bd47cb74e047cec93463e313880309332 Mon Sep 17 00:00:00 2001 From: Karl Godard Date: Tue, 12 Apr 2022 07:11:07 -0700 Subject: [PATCH 17/32] [Session View] [8.2] beta label added to sessions tabs in timeline (#129965) * beta label added to sessions tabs in timeline * added test for isBeta * added test for isBeta * test revised Co-authored-by: mitodrummer --- .../navigation/tab_navigation/index.test.tsx | 18 ++++++++++++++++++ .../navigation/tab_navigation/index.tsx | 6 +++++- .../navigation/tab_navigation/types.ts | 1 + .../common/components/navigation/types.ts | 1 + .../public/common/translations.ts | 4 ++++ .../public/hosts/pages/nav_tabs.tsx | 1 + .../components/timeline/tabs_content/index.tsx | 4 +++- 7 files changed, 33 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index d90709f69ee03..45eebab8c093f 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -126,4 +126,22 @@ describe('Table Navigation', () => { `/app/securitySolutionUI/hosts/siem-window/authentications${SEARCH_QUERY}` ); }); + + test('it renders a EuiBetaBadge only on the sessions tab', () => { + Object.keys(HostsTableType).forEach((tableType) => { + if (tableType !== HostsTableType.sessions) { + const wrapper = mount(); + + const betaBadge = wrapper.find( + `EuiTab[data-test-subj="navigation-${tableType}"] EuiBetaBadge` + ); + + if (tableType === HostsTableType.sessions) { + expect(betaBadge).toBeTruthy(); + } else { + expect(betaBadge).toEqual({}); + } + } + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx index 4d9a8a704dde5..03437bab93f38 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiTab, EuiTabs } from '@elastic/eui'; +import { EuiTab, EuiTabs, EuiBetaBadge } from '@elastic/eui'; import { getOr } from 'lodash/fp'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; @@ -14,6 +14,7 @@ import deepEqual from 'fast-deep-equal'; import { useNavigation } from '../../../lib/kibana'; import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry'; import { TabNavigationProps, TabNavigationItemProps } from './types'; +import { BETA } from '../../../translations'; const TabNavigationItemComponent = ({ disabled, @@ -21,6 +22,7 @@ const TabNavigationItemComponent = ({ id, name, isSelected, + isBeta, }: TabNavigationItemProps) => { const { getAppUrl, navigateTo } = useNavigation(); @@ -45,6 +47,7 @@ const TabNavigationItemComponent = ({ isSelected={isSelected} href={appHref} onClick={handleClick} + append={isBeta && } > {name} @@ -92,6 +95,7 @@ export const TabNavigationComponent: React.FC = ({ name={tab.name} disabled={tab.disabled} isSelected={isSelected} + isBeta={tab.isBeta} /> ); }), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts index 75f18abf75559..5630978bae87a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts @@ -32,4 +32,5 @@ export interface TabNavigationItemProps { disabled: boolean; name: string; isSelected: boolean; + isBeta?: boolean; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index b1903ef869d3d..1cb8a918ea481 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -38,6 +38,7 @@ export interface NavTab { disabled: boolean; urlKey?: UrlStateType; pageId?: SecurityPageName; + isBeta?: boolean; } export type SecurityNavKey = diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 2058eaf03b5e1..64d31e7f6530d 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -63,6 +63,10 @@ export const EMPTY_ACTION_ENDPOINT_DESCRIPTION = i18n.translate( } ); +export const BETA = i18n.translate('xpack.securitySolution.pages.common.beta', { + defaultMessage: 'Beta', +}); + export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) => i18n.translate('xpack.securitySolution.pages.common.updateAlertStatusFailed', { values: { conflicts }, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx index 59fb0d08f7dc7..ea46180f8df80 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx @@ -71,6 +71,7 @@ export const navTabsHosts = ({ name: i18n.NAVIGATION_SESSIONS_TITLE, href: getTabsOnHostsUrl(HostsTableType.sessions), disabled: false, + isBeta: true, }, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 2f5262c7c598f..b8b9d73932afd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBadge, EuiLoadingContent, EuiTabs, EuiTab } from '@elastic/eui'; +import { EuiBadge, EuiBetaBadge, EuiLoadingContent, EuiTabs, EuiTab } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo } from 'react'; import { useDispatch } from 'react-redux'; @@ -37,6 +37,7 @@ import { getEventIdToNoteIdsSelector, } from './selectors'; import * as i18n from './translations'; +import { BETA } from '../../../../common/translations'; const HideShowContainer = styled.div.attrs<{ $isVisible: boolean; isOverflowYScroll: boolean }>( ({ $isVisible = false, isOverflowYScroll = false }) => ({ @@ -357,6 +358,7 @@ const TabsContentComponent: React.FC = ({ isSelected={activeTab === TimelineTabs.session} disabled={sessionViewConfig === null} key={TimelineTabs.session} + append={} > {i18n.SESSION_TAB} From e2ba9f19e35c43e0852f23a05e2ee4c9ba7561ba Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 12 Apr 2022 16:57:04 +0200 Subject: [PATCH 18/32] unskips and refactors alert details for unmapped fields tests (#130002) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../detection_alerts/alerts_details.spec.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 3e9adc9e66b44..e3612a9a125c9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -31,8 +31,6 @@ describe('Alert details with unmapped fields', () => { esArchiverLoad('unmapped_fields'); login(); createCustomRuleEnabled(getUnmappedRule()); - }); - beforeEach(() => { visitWithoutDateRange(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); @@ -52,10 +50,8 @@ describe('Alert details with unmapped fields', () => { }); }); - // This test needs to be updated to not look for the field in a specific row, as it prevents us from adding/removing fields - it.skip('Displays the unmapped field on the table', () => { + it('Displays the unmapped field on the table', () => { const expectedUnmmappedField = { - row: 83, field: 'unmapped', text: 'This is the unmapped field', }; @@ -63,10 +59,9 @@ describe('Alert details with unmapped fields', () => { openTable(); cy.get(ALERT_FLYOUT) .find(TABLE_ROWS) - .eq(expectedUnmmappedField.row) .within(() => { - cy.get(CELL_TEXT).eq(2).should('have.text', expectedUnmmappedField.field); - cy.get(CELL_TEXT).eq(4).should('have.text', expectedUnmmappedField.text); + cy.get(CELL_TEXT).should('contain', expectedUnmmappedField.field); + cy.get(CELL_TEXT).should('contain', expectedUnmmappedField.text); }); }); From 294fe2b93254d02ec21dd33cef91452ff007d9c3 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 12 Apr 2022 10:15:17 -0500 Subject: [PATCH 19/32] [DOCS] Adds docs for URL drilldown support (#129666) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/user/dashboard/make-dashboards-interactive.asciidoc | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/user/dashboard/make-dashboards-interactive.asciidoc b/docs/user/dashboard/make-dashboards-interactive.asciidoc index 2cb750afbe7d5..06c6675bedba3 100644 --- a/docs/user/dashboard/make-dashboards-interactive.asciidoc +++ b/docs/user/dashboard/make-dashboards-interactive.asciidoc @@ -196,7 +196,7 @@ image::images/drilldown_on_panel.png[Drilldown on data table that navigates to a [[url-drilldowns]] ==== Create URL drilldowns -URL drilldowns enable you to navigate from a dashboard to external websites. Destination URLs can be dynamic, depending on the dashboard context or user interaction with a panel. To create URL drilldowns, you add <> to a URL template, which configures the behavior of the drilldown. +URL drilldowns enable you to navigate from a dashboard to external websites. Destination URLs can be dynamic, depending on the dashboard context or user interaction with a panel. To create URL drilldowns, you add <> to a URL template, which configures the behavior of the drilldown. All panels that you create with the visualization editors support dashboard drilldowns. [role="screenshot"] image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] @@ -210,13 +210,6 @@ The <> you use to create a < Date: Tue, 12 Apr 2022 17:36:12 +0200 Subject: [PATCH 20/32] [ILM] Fixed snapshot repositories and policies routes (#129200) * [ILM] Fixed snapshot repositories and policies routes * [ILM] Updated console.log to error Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../snapshot_policies/register_fetch_route.ts | 12 +--- .../register_fetch_route.ts | 2 +- .../{constants.js => constants.ts} | 2 + .../index_lifecycle_management/index.js | 2 + .../snapshot_policies.helpers.ts | 50 +++++++++++++++ .../snapshot_policies.ts | 45 +++++++++++++ .../snapshot_repositories.helpers.ts | 55 ++++++++++++++++ .../snapshot_repositories.ts | 63 +++++++++++++++++++ 8 files changed, 220 insertions(+), 11 deletions(-) rename x-pack/test/api_integration/apis/management/index_lifecycle_management/{constants.js => constants.ts} (84%) create mode 100644 x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.helpers.ts create mode 100644 x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.ts create mode 100644 x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.helpers.ts create mode 100644 x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.ts diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_policies/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_policies/register_fetch_route.ts index 6003ddf712a70..ebe8a2388a025 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_policies/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_policies/register_fetch_route.ts @@ -5,24 +5,16 @@ * 2.0. */ -import { ElasticsearchClient } from 'kibana/server'; - import { RouteDependencies } from '../../../types'; import { addBasePath } from '../../../services'; -async function fetchSnapshotPolicies(client: ElasticsearchClient): Promise { - const response = await client.slm.getLifecycle(); - return response.body; -} - export function registerFetchRoute({ router, license, lib: { handleEsError } }: RouteDependencies) { router.get( { path: addBasePath('/snapshot_policies'), validate: false }, license.guardApiRoute(async (context, request, response) => { try { - const policiesByName = await fetchSnapshotPolicies( - context.core.elasticsearch.client.asCurrentUser - ); + const policiesByName = + await context.core.elasticsearch.client.asCurrentUser.slm.getLifecycle(); return response.ok({ body: Object.keys(policiesByName) }); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_repositories/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_repositories/register_fetch_route.ts index 8787be8e936ba..a4bdbd5fc7afc 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_repositories/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_repositories/register_fetch_route.ts @@ -32,7 +32,7 @@ export const registerFetchRoute = ({ router, license }: RouteDependencies) => { name: '*', }); const repos: ListSnapshotReposResponse = { - repositories: Object.keys(esResult.body), + repositories: Object.keys(esResult), }; return response.ok({ body: repos }); } catch (e) { diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/constants.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/constants.ts similarity index 84% rename from x-pack/test/api_integration/apis/management/index_lifecycle_management/constants.js rename to x-pack/test/api_integration/apis/management/index_lifecycle_management/constants.ts index a907737fca125..b2ae24d03b990 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/constants.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/constants.ts @@ -10,3 +10,5 @@ export const DEFAULT_POLICY_NAME = 'watch-history-ilm-policy'; export const INDEX_TEMPLATE_NAME = 'api-integration-tests-template'; export const INDEX_TEMPLATE_PATTERN_PREFIX = 'api_integration_tests_'; export const NODE_CUSTOM_ATTRIBUTE = 'name:apiIntegrationTestNode'; +export const SNAPSHOT_REPOSITORY_NAME = 'test_repo'; +export const CLOUD_REPOSITORY_NAME = 'found-snapshots'; diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/index.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/index.js index 164ca6634a28a..1e9cb006f1663 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/index.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/index.js @@ -11,5 +11,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./templates')); loadTestFile(require.resolve('./indices')); loadTestFile(require.resolve('./nodes')); + loadTestFile(require.resolve('./snapshot_policies')); + loadTestFile(require.resolve('./snapshot_repositories')); }); } diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.helpers.ts b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.helpers.ts new file mode 100644 index 0000000000000..80c1794d675a6 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.helpers.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_BASE_PATH, SNAPSHOT_REPOSITORY_NAME } from './constants'; + +export const registerSnapshotPoliciesHelpers = (getService: FtrProviderContext['getService']) => { + const supertest = getService('supertest'); + const es = getService('es'); + + let policiesCreated: string[] = []; + + const loadSnapshotPolicies = () => supertest.get(`${API_BASE_PATH}/snapshot_policies`); + + const createSnapshotPolicy = (policyName: string, repositoryName?: string) => { + return es.slm + .putLifecycle({ + policy_id: policyName, + config: { + indices: 'test_index', + }, + name: policyName, + repository: repositoryName ?? SNAPSHOT_REPOSITORY_NAME, + schedule: '0 30 1 * * ?', + }) + .then(() => policiesCreated.push(policyName)); + }; + + const deletePolicy = (policyName: string) => es.slm.deleteLifecycle({ policy_id: policyName }); + + const cleanupPolicies = () => + Promise.all(policiesCreated.map(deletePolicy)) + .then(() => { + policiesCreated = []; + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error(`[Cleanup error] Error deleting ES resources: ${err.message}`); + }); + + return { + loadSnapshotPolicies, + createSnapshotPolicy, + cleanupPolicies, + }; +}; diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.ts b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.ts new file mode 100644 index 0000000000000..2bfd0f0a3f56f --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_policies.ts @@ -0,0 +1,45 @@ +/* + * 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 { registerSnapshotPoliciesHelpers } from './snapshot_policies.helpers'; +import { registerSnapshotRepositoriesHelpers } from './snapshot_repositories.helpers'; +import { SNAPSHOT_REPOSITORY_NAME } from './constants'; + +const snapshotPolicyName = 'test_snapshot_policy'; +export default function ({ getService }: FtrProviderContext) { + const deployment = getService('deployment'); + + const { loadSnapshotPolicies, createSnapshotPolicy, cleanupPolicies } = + registerSnapshotPoliciesHelpers(getService); + + const { createSnapshotRepository, cleanupRepositories } = + registerSnapshotRepositoriesHelpers(getService); + + describe('snapshot policies', () => { + before(async () => Promise.all([cleanupPolicies(), cleanupRepositories()])); + after(async () => Promise.all([cleanupPolicies(), cleanupRepositories()])); + + it('returns empty array if no policies', async () => { + const { body } = await loadSnapshotPolicies().expect(200); + expect(body).to.eql([]); + }); + + it('returns policies', async () => { + const isCloud = await deployment.isCloud(); + if (!isCloud) { + await createSnapshotRepository(SNAPSHOT_REPOSITORY_NAME); + } + await createSnapshotPolicy(snapshotPolicyName); + const { body } = await loadSnapshotPolicies().expect(200); + + expect(body).to.have.length(1); + expect(body[0]).to.eql(snapshotPolicyName); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.helpers.ts b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.helpers.ts new file mode 100644 index 0000000000000..9d30d36b24917 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.helpers.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_BASE_PATH } from './constants'; + +export const registerSnapshotRepositoriesHelpers = ( + getService: FtrProviderContext['getService'] +) => { + const supertest = getService('supertest'); + const es = getService('es'); + + let repositoriesCreated: string[] = []; + + const loadSnapshotRepositories = () => supertest.get(`${API_BASE_PATH}/snapshot_repositories`); + + const createSnapshotRepository = (repositoryName: string) => { + return es.snapshot + .createRepository({ + name: repositoryName, + body: { + type: 'fs', + settings: { + location: '/tmp/repo', + }, + }, + verify: false, + }) + .then(() => repositoriesCreated.push(repositoryName)); + }; + + const deleteRepository = (repositoryName: string) => { + return es.snapshot.deleteRepository({ name: repositoryName }); + }; + + const cleanupRepositories = () => + Promise.all(repositoriesCreated.map(deleteRepository)) + .then(() => { + repositoriesCreated = []; + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error(`[Cleanup error] Error deleting ES resources: ${err.message}`); + }); + + return { + loadSnapshotRepositories, + createSnapshotRepository, + cleanupRepositories, + }; +}; diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.ts b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.ts new file mode 100644 index 0000000000000..bc1acfd9a5a07 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/snapshot_repositories.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { registerSnapshotRepositoriesHelpers } from './snapshot_repositories.helpers'; +import { CLOUD_REPOSITORY_NAME } from './constants'; + +const repositoryName = 'test_repository'; + +export default function ({ getService }: FtrProviderContext) { + const deployment = getService('deployment'); + let isCloud: boolean; + + const { loadSnapshotRepositories, createSnapshotRepository, cleanupRepositories } = + registerSnapshotRepositoriesHelpers(getService); + + describe('snapshot repositories', () => { + before(async () => { + isCloud = await deployment.isCloud(); + await Promise.all([cleanupRepositories()]); + }); + after(async () => Promise.all([cleanupRepositories()])); + + it('returns empty array if no repositories ', async () => { + const { + body: { repositories }, + } = await loadSnapshotRepositories().expect(200); + if (!isCloud) { + expect(repositories).to.eql([]); + } + }); + + it('returns cloud default repository if on Cloud', async () => { + const { + body: { repositories }, + } = await loadSnapshotRepositories().expect(200); + if (isCloud) { + expect(repositories).to.have.length(1); + expect(repositories).to.eql([CLOUD_REPOSITORY_NAME]); + } + }); + + it('returns repositories', async () => { + await createSnapshotRepository(repositoryName); + const { + body: { repositories }, + } = await loadSnapshotRepositories().expect(200); + + if (isCloud) { + expect(repositories).to.have.length(2); + expect(repositories[0]).to.contain(repositoryName); + } else { + expect(repositories).to.have.length(1); + expect(repositories[0]).to.eql(repositoryName); + } + }); + }); +} From a395f411b7e717c612d714f0fcee874eaba9cfa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 12 Apr 2022 11:40:51 -0400 Subject: [PATCH 21/32] [APM] Service environment should be selected when you edit the agent configuration (#129929) * fixing env selected * lets see if it works now * fixing test --- .../settings/agent_configurations.spec.ts | 106 ++++++++++++++++++ .../service_page/form_row_select.tsx | 24 ++-- .../form_row_suggestions_select.tsx | 3 + .../service_page/service_page.tsx | 2 + 4 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts new file mode 100644 index 0000000000000..61749461bdbda --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/agent_configurations.spec.ts @@ -0,0 +1,106 @@ +/* + * 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 { apm, timerange } from '@elastic/apm-synthtrace'; +import url from 'url'; +import { synthtrace } from '../../../../synthtrace'; + +const timeRange = { + rangeFrom: '2021-10-10T00:00:00.000Z', + rangeTo: '2021-10-10T00:15:00.000Z', +}; + +const agentConfigHref = url.format({ + pathname: '/app/apm/settings/agent-configuration', +}); + +function generateData({ + from, + to, + serviceName, +}: { + from: number; + to: number; + serviceName: string; +}) { + const range = timerange(from, to); + + const service1 = apm + .service(serviceName, 'production', 'java') + .instance('service-1-prod-1') + .podId('service-1-prod-1-pod'); + + const service2 = apm + .service(serviceName, 'development', 'nodejs') + .instance('opbeans-node-prod-1'); + + return range + .interval('1m') + .rate(1) + .spans((timestamp, index) => [ + ...service1 + .transaction('GET /apple 🍎 ') + .timestamp(timestamp) + .duration(1000) + .success() + .serialize(), + ...service2 + .transaction('GET /banana 🍌') + .timestamp(timestamp) + .duration(500) + .success() + .serialize(), + ]); +} + +describe('Agent configuration', () => { + before(async () => { + const { rangeFrom, rangeTo } = timeRange; + + await synthtrace.index( + generateData({ + from: new Date(rangeFrom).getTime(), + to: new Date(rangeTo).getTime(), + serviceName: 'opbeans-node', + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + + beforeEach(() => { + cy.loginAsPowerUser(); + cy.visit(agentConfigHref); + }); + + it('persists service enviroment when clicking on edit button', () => { + cy.intercept( + 'GET', + '/api/apm/settings/agent-configuration/environments?*' + ).as('serviceEnvironmentApi'); + cy.contains('Create configuration').click(); + cy.get('[data-test-subj="serviceNameComboBox"]') + .click() + .type('opbeans-node') + .type('{enter}'); + + cy.contains('opbeans-node').realClick(); + cy.wait('@serviceEnvironmentApi'); + + cy.get('[data-test-subj="serviceEnviromentComboBox"]') + .click({ force: true }) + .type('prod') + .type('{enter}'); + cy.contains('production').realClick(); + cy.contains('Next step').click(); + cy.contains('Create configuration'); + cy.contains('Edit').click(); + cy.wait('@serviceEnvironmentApi'); + cy.contains('production'); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_select.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_select.tsx index be716042a63ce..bfde04af12b94 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_select.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_select.tsx @@ -5,14 +5,14 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; import { - EuiDescribedFormGroup, + EuiComboBox, EuiComboBoxOptionOption, + EuiDescribedFormGroup, EuiFormRow, - EuiComboBox, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; interface Props { title: string; description: string; @@ -22,6 +22,7 @@ interface Props { isDisabled: boolean; value?: string; onChange: (value?: string) => void; + dataTestSubj?: string; } export function FormRowSelect({ @@ -32,23 +33,21 @@ export function FormRowSelect({ options, isDisabled, onChange, + value, + dataTestSubj, }: Props) { - const [selectedOptions, setSelected] = useState< - Array> | undefined - >([]); + const selectedOptions = useMemo(() => { + const optionFound = options?.find((option) => option.value === value); + return optionFound ? [optionFound] : undefined; + }, [options, value]); const handleOnChange = ( nextSelectedOptions: Array> ) => { const [selectedOption] = nextSelectedOptions; - setSelected(nextSelectedOptions); onChange(selectedOption.value); }; - useEffect(() => { - setSelected(undefined); - }, [isLoading]); - return ( diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_suggestions_select.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_suggestions_select.tsx index f3f680ff4a9ff..5fa3a46b00901 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_suggestions_select.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/form_row_suggestions_select.tsx @@ -19,6 +19,7 @@ interface Props { value?: string; allowAll?: boolean; onChange: (value?: string) => void; + dataTestSubj?: string; } export function FormRowSuggestionsSelect({ @@ -29,6 +30,7 @@ export function FormRowSuggestionsSelect({ value, allowAll = true, onChange, + dataTestSubj, }: Props) { return ( diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx index 9f8d3ca1318b5..1ede5cd5405c7 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/service_page/service_page.tsx @@ -105,6 +105,7 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) { service: { name, environment: '' }, })); }} + dataTestSubj="serviceNameComboBox" /> {/* Environment options */} From 33b0578a1748e0b6520c95a05387e0183baff3b1 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Tue, 12 Apr 2022 11:57:44 -0400 Subject: [PATCH 22/32] [Security Solution] Add fixtures for 7.17, 8.0, and 8.1 (#129768) * Add fixtures for 7.17, 8.0, and 8.1 * Compress fixtures --- .../security_solution/alerts/7.16.0/data.json | 3590 ---------- .../alerts/7.16.0/data.json.gz | Bin 0 -> 4755 bytes .../alerts/7.16.0/mappings.json | 5819 ----------------- .../alerts/7.16.0/mappings.json.gz | Bin 0 -> 9745 bytes .../alerts/7.17.0/data.json.gz | Bin 0 -> 4744 bytes .../alerts/7.17.0/mappings.json.gz | Bin 0 -> 9846 bytes .../alerts/8.0.0/data.json.gz | Bin 0 -> 9231 bytes .../alerts/8.0.0/mappings.json.gz | Bin 0 -> 9711 bytes .../alerts/8.1.0/data.json.gz | Bin 0 -> 9500 bytes .../alerts/8.1.0/mappings.json.gz | Bin 0 -> 9739 bytes 10 files changed, 9409 deletions(-) delete mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json.gz delete mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/mappings.json create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/mappings.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/7.17.0/data.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/7.17.0/mappings.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz create mode 100644 x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json b/x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json deleted file mode 100644 index 9f15ea353570e..0000000000000 --- a/x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json +++ /dev/null @@ -1,3590 +0,0 @@ -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "0bb0c0d5488d757907f6be6e4c27ff698666948e2cf01d53e8fa43958b36c6a8", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:32.045Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.493Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-1", - "field": "host.name", - "id": "M2yvt38BIyEvspK01XQt", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "P2yvt38BIyEvspK05HSe", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "P2yvt38BIyEvspK05HSe", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-1 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "P2yvt38BIyEvspK05HSe", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:32.045Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:32.045Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "0dd11069ba6c63ec60ac902d6fb0a8a52c4f5ab20f03babe7b861c6d34431bad", - "source": { - "agent": { - "name": "security-linux-3.example.dev", - "id": "06851da1-73e7-41e3-97d6-ff1d62c98dc5", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-3", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.196", - "name": "security-linux-3", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.654Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.495Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-3", - "field": "host.name", - "id": "NWyvt38BIyEvspK013R3", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "Pmyvt38BIyEvspK043Ri", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "Pmyvt38BIyEvspK043Ri", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-3 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "Pmyvt38BIyEvspK043Ri", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.654Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.654Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "999fef09ceb58f30dcbbe2a5fd410f8a22dda6179fa5f1041c7a759a31932ef9", - "source": { - "agent": { - "name": "security-linux-2.example.dev", - "id": "87c417dd-08d6-4e24-ad69-285cb8de84e9", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-2", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.195", - "name": "security-linux-2", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.330Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.496Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-2", - "field": "host.name", - "id": "NGyvt38BIyEvspK01nQn", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "PWyvt38BIyEvspK04XTd", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "PWyvt38BIyEvspK04XTd", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-2 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "PWyvt38BIyEvspK04XTd", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.330Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.330Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "f75bc411e6b0c30c26aa310c1e65ff8430cc0a98ddf74c335941dd7456858e85", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.001Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.497Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-1", - "field": "host.name", - "id": "M2yvt38BIyEvspK01XQt", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "PGyvt38BIyEvspK04HSX", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "PGyvt38BIyEvspK04HSX", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-1 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "PGyvt38BIyEvspK04HSX", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.001Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.001Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "b46b35ce011486304a3a1e1b1dc2b772e2b80684a3a8663e9cd101691cff7429", - "source": { - "agent": { - "name": "security-linux-3.example.dev", - "id": "06851da1-73e7-41e3-97d6-ff1d62c98dc5", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-3", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.196", - "name": "security-linux-3", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.665Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.498Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-3", - "field": "host.name", - "id": "NWyvt38BIyEvspK013R3", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "O2yvt38BIyEvspK033RN", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "O2yvt38BIyEvspK033RN", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-3 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "O2yvt38BIyEvspK033RN", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:30.665Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.665Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "570caf7637457b9721fd46ec22166adb57916298bf68ef31df07bd0bbac95d7c", - "source": { - "agent": { - "name": "security-linux-2.example.dev", - "id": "87c417dd-08d6-4e24-ad69-285cb8de84e9", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-2", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.195", - "name": "security-linux-2", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.353Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.499Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-2", - "field": "host.name", - "id": "NGyvt38BIyEvspK01nQn", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "Omyvt38BIyEvspK03nQB", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "Omyvt38BIyEvspK03nQB", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-2 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "Omyvt38BIyEvspK03nQB", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:30.353Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.353Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "0c7bfb7198c9db281b639b1044c74db2b881e3152ee863e6c9304a6fb5d0e5bb", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.031Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.501Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-1", - "field": "host.name", - "id": "M2yvt38BIyEvspK01XQt", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "OWyvt38BIyEvspK03HTF", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "OWyvt38BIyEvspK03HTF", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-1 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "OWyvt38BIyEvspK03HTF", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:30.031Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.031Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "ae1c6e5c7680cdc986ff52b1913e93ba2a010ea207364d4782550adf180e49ee", - "source": { - "agent": { - "name": "security-linux-3.example.dev", - "id": "06851da1-73e7-41e3-97d6-ff1d62c98dc5", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-3", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.196", - "name": "security-linux-3", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.715Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.502Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-3", - "field": "host.name", - "id": "NWyvt38BIyEvspK013R3", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "OGyvt38BIyEvspK023SI", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "OGyvt38BIyEvspK023SI", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-3 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "OGyvt38BIyEvspK023SI", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:29.715Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.715Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "a73fda6bdb25425c8597f63e2b87b662798ad46f195c47ac4243d9d0b9705dd8", - "source": { - "agent": { - "name": "security-linux-2.example.dev", - "id": "87c417dd-08d6-4e24-ad69-285cb8de84e9", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-2", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.195", - "name": "security-linux-2", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.387Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.503Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-2", - "field": "host.name", - "id": "NGyvt38BIyEvspK01nQn", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "N2yvt38BIyEvspK02nRK", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "N2yvt38BIyEvspK02nRK", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-2 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "N2yvt38BIyEvspK02nRK", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:29.387Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.387Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "77038fe81327ce7b578e69896fdd1869fab16d13633b5fb0cb7743bae9120ca5", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:28.994Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:51.504Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "threat": { - "enrichments": [ - { - "indicator": {}, - "matched": { - "atomic": "security-linux-1", - "field": "host.name", - "id": "M2yvt38BIyEvspK01XQt", - "index": "threat-index-000001", - "type": "indicator_match_rule" - } - } - ] - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "Nmyvt38BIyEvspK02HTJ", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "Nmyvt38BIyEvspK02HTJ", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "62f9a8c0-aac9-11ec-aa31-c9ea2cb79db7", - "actions": [], - "interval": "1m", - "name": "threat-match-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:47.264Z", - "updated_at": "2022-03-23T16:50:48.396Z", - "description": "a simple threat match rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "bef9b0da-8c2f-4b82-930f-37ffa1b57fc1", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threat_match", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threat_query": "*", - "threat_mapping": [ - { - "entries": [ - { - "field": "host.name", - "type": "mapping", - "value": "host.name" - } - ] - } - ], - "threat_language": "kuery", - "threat_index": [ - "threat-index-*" - ], - "threat_indicator_path": "threat.indicator" - }, - "reason": "event on security-linux-1 created low alert threat-match-rule.", - "depth": 1, - "parent": { - "id": "Nmyvt38BIyEvspK02HTJ", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:28.994Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:28.994Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "21d26a1ad7b01b28667638d5f8db96f6e94957394efe7a16057948095a445ac4", - "source": { - "@timestamp": "2022-03-23T16:50:48.441Z", - "host.name": "security-linux-1", - "event": { - "kind": "signal" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "8e75aa13-6b35-5d96-b52b-1d62909a9d75", - "type": "event", - "index": "events-index-*", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "8e75aa13-6b35-5d96-b52b-1d62909a9d75", - "type": "event", - "index": "events-index-*", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "60b8b970-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "threshold-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:44.260Z", - "updated_at": "2022-03-23T16:50:45.341Z", - "description": "a simple threshold rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "b97ae2a4-f188-43b2-b082-69667b563152", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threshold", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threshold": { - "field": [ - "host.name" - ], - "value": 1 - } - }, - "reason": "event created low alert threshold-rule.", - "depth": 1, - "parent": { - "id": "8e75aa13-6b35-5d96-b52b-1d62909a9d75", - "type": "event", - "index": "events-index-*", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:32.045Z", - "threshold_result": { - "terms": [ - { - "field": "host.name", - "value": "security-linux-1" - } - ], - "count": 4, - "from": "2022-03-23T06:50:48.395Z" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "4c2a3865ca7df72e4cc17b5114feb2535b2459fd52f6fbd0669d4884f5956dc2", - "source": { - "@timestamp": "2022-03-23T16:50:48.442Z", - "host.name": "security-linux-2", - "event": { - "kind": "signal" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "9c957c24-8ce5-516b-ba8e-44b582da6579", - "type": "event", - "index": "events-index-*", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "9c957c24-8ce5-516b-ba8e-44b582da6579", - "type": "event", - "index": "events-index-*", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "60b8b970-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "threshold-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:44.260Z", - "updated_at": "2022-03-23T16:50:45.341Z", - "description": "a simple threshold rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "b97ae2a4-f188-43b2-b082-69667b563152", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threshold", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threshold": { - "field": [ - "host.name" - ], - "value": 1 - } - }, - "reason": "event created low alert threshold-rule.", - "depth": 1, - "parent": { - "id": "9c957c24-8ce5-516b-ba8e-44b582da6579", - "type": "event", - "index": "events-index-*", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.330Z", - "threshold_result": { - "terms": [ - { - "field": "host.name", - "value": "security-linux-2" - } - ], - "count": 3, - "from": "2022-03-23T06:50:48.395Z" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "3754896311b1d9f9dee45ecf06aa5160f8cd3d4504ef5c856ba285edd61d059d", - "source": { - "@timestamp": "2022-03-23T16:50:48.442Z", - "host.name": "security-linux-3", - "event": { - "kind": "signal" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "326cc81c-b55f-5b69-8222-e930bcb24692", - "type": "event", - "index": "events-index-*", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "326cc81c-b55f-5b69-8222-e930bcb24692", - "type": "event", - "index": "events-index-*", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "60b8b970-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "threshold-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:44.260Z", - "updated_at": "2022-03-23T16:50:45.341Z", - "description": "a simple threshold rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "b97ae2a4-f188-43b2-b082-69667b563152", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "threshold", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [], - "threshold": { - "field": [ - "host.name" - ], - "value": 1 - } - }, - "reason": "event created low alert threshold-rule.", - "depth": 1, - "parent": { - "id": "326cc81c-b55f-5b69-8222-e930bcb24692", - "type": "event", - "index": "events-index-*", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.654Z", - "threshold_result": { - "terms": [ - { - "field": "host.name", - "value": "security-linux-3" - } - ], - "count": 3, - "from": "2022-03-23T06:50:48.395Z" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "5cddda6852c5f8b6c32d4bfa5e876aa51884e0c7a2d4faaababf91ec9cb68de7", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:28.994Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.440Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "Nmyvt38BIyEvspK02HTJ", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "Nmyvt38BIyEvspK02HTJ", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-1 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "Nmyvt38BIyEvspK02HTJ", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:28.994Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:28.994Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "5050902fa762858249c32b1d228dd71ca9217ace612b65f9669fb3a5f371ab63", - "source": { - "agent": { - "name": "security-linux-2.example.dev", - "id": "87c417dd-08d6-4e24-ad69-285cb8de84e9", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-2", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.195", - "name": "security-linux-2", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.387Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.477Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "N2yvt38BIyEvspK02nRK", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "N2yvt38BIyEvspK02nRK", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-2 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "N2yvt38BIyEvspK02nRK", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:29.387Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.387Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "525833fe5aa3cabce849adf9291b4d4009c25edbe528d5d2add1dc749c00513b", - "source": { - "agent": { - "name": "security-linux-3.example.dev", - "id": "06851da1-73e7-41e3-97d6-ff1d62c98dc5", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-3", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.196", - "name": "security-linux-3", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.715Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.499Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "OGyvt38BIyEvspK023SI", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "OGyvt38BIyEvspK023SI", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-3 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "OGyvt38BIyEvspK023SI", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:29.715Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:29.715Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "4f9c5a7581544f9dc1fa4c9f541c7e7573d7460ddeeda1875bee081e6615035b", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.031Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.510Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "OWyvt38BIyEvspK03HTF", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "OWyvt38BIyEvspK03HTF", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-1 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "OWyvt38BIyEvspK03HTF", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:30.031Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.031Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "d791d45b87a37e3b8a8388d7d6237728aa14ab6ec81bfa84f96457bd42b39e4a", - "source": { - "agent": { - "name": "security-linux-2.example.dev", - "id": "87c417dd-08d6-4e24-ad69-285cb8de84e9", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-2", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.195", - "name": "security-linux-2", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.353Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.533Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "Omyvt38BIyEvspK03nQB", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "Omyvt38BIyEvspK03nQB", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-2 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "Omyvt38BIyEvspK03nQB", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:30.353Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.353Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "747a4cfd4dbc1dd3924b341b0d3d94098252579354bf140e1621cb4b8681e911", - "source": { - "agent": { - "name": "security-linux-3.example.dev", - "id": "06851da1-73e7-41e3-97d6-ff1d62c98dc5", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-3", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.196", - "name": "security-linux-3", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.665Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.547Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "O2yvt38BIyEvspK033RN", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "O2yvt38BIyEvspK033RN", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-3 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "O2yvt38BIyEvspK033RN", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:30.665Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:30.665Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "5a217bc36610a820dbbb20f7b189065d631038a9dbb33bde1511f0f6a63183d2", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.001Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.561Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "PGyvt38BIyEvspK04HSX", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "PGyvt38BIyEvspK04HSX", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-1 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "PGyvt38BIyEvspK04HSX", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.001Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.001Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "fde1f09c4420ce5747f04ca051bcdc90762394ea019a7cc2cfee8de3bd575a59", - "source": { - "agent": { - "name": "security-linux-2.example.dev", - "id": "87c417dd-08d6-4e24-ad69-285cb8de84e9", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-2", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.195", - "name": "security-linux-2", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.330Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.593Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "PWyvt38BIyEvspK04XTd", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "PWyvt38BIyEvspK04XTd", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-2 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "PWyvt38BIyEvspK04XTd", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.330Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.330Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "337f39b1fb862a4c6910605b16e6b5b59623219e99dcb7d442cd334229ad3a7e", - "source": { - "agent": { - "name": "security-linux-3.example.dev", - "id": "06851da1-73e7-41e3-97d6-ff1d62c98dc5", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-3", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.196", - "name": "security-linux-3", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.654Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.606Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "Pmyvt38BIyEvspK043Ri", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "Pmyvt38BIyEvspK043Ri", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-3 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "Pmyvt38BIyEvspK043Ri", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:31.654Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:31.654Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - -{ - "type": "doc", - "value": { - "index": ".siem-signals-default-000001-7.16.0", - "id": "44f8d6e34631ced611f6588e7f0cdf52ac5647eff09cfbd36a38ad2a7d4bf32f", - "source": { - "agent": { - "name": "security-linux-1.example.dev", - "id": "d8f66724-3cf2-437c-b124-6ac9fb0e2311", - "type": "filebeat", - "version": "7.16.0" - }, - "log": { - "file": { - "path": "/opt/Elastic/Agent/data/elastic-agent-a13c93/logs/default/filebeat-20220301-3.ndjson" - }, - "offset": 148938 - }, - "cloud": { - "availability_zone": "us-central1-c", - "instance": { - "name": "security-linux-1", - "id": "8995531128842994872" - }, - "provider": "gcp", - "service": { - "name": "GCE" - }, - "machine": { - "type": "g1-small" - }, - "project": { - "id": "elastic-siem" - }, - "account": { - "id": "elastic-siem" - } - }, - "ecs": { - "version": "7.16.0" - }, - "host": { - "hostname": "security-linux-1", - "os": { - "kernel": "4.19.0-18-cloud-amd64", - "codename": "buster", - "name": "Debian GNU/Linux", - "type": "linux", - "family": "debian", - "version": "10 (buster)", - "platform": "debian" - }, - "containerized": false, - "ip": "11.200.0.194", - "name": "security-linux-1", - "architecture": "x86_64" - }, - "event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:32.045Z", - "dataset": "elastic_agent.filebeat", - "kind": "signal" - }, - "service.name": "filebeat", - "message": "Status message.", - "@timestamp": "2022-03-23T16:50:40.624Z", - "data_stream": { - "namespace": "default", - "type": "logs", - "dataset": "elastic_agent.filebeat" - }, - "signal": { - "_meta": { - "version": 57 - }, - "parents": [ - { - "id": "P2yvt38BIyEvspK05HSe", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "ancestors": [ - { - "id": "P2yvt38BIyEvspK05HSe", - "type": "event", - "index": "events-index-000001", - "depth": 0 - } - ], - "status": "open", - "rule": { - "id": "5b7cd9a0-aac9-11ec-bb53-fd375b7a173a", - "actions": [], - "interval": "1m", - "name": "query-rule", - "tags": [], - "enabled": true, - "created_by": "elastic", - "updated_by": "elastic", - "throttle": null, - "created_at": "2022-03-23T16:50:34.234Z", - "updated_at": "2022-03-23T16:50:36.214Z", - "description": "a simple query rule", - "risk_score": 21, - "severity": "low", - "license": "", - "output_index": ".siem-signals-default-000001", - "author": [], - "false_positives": [], - "from": "now-36000s", - "rule_id": "1fcc46ae-7e1e-4002-a4e1-e456029cb7ec", - "max_signals": 100, - "risk_score_mapping": [], - "severity_mapping": [], - "threat": [], - "to": "now", - "references": [], - "version": 1, - "exceptions_list": [], - "immutable": false, - "type": "query", - "language": "kuery", - "index": [ - "events-index-*" - ], - "query": "*", - "filters": [] - }, - "reason": "event on security-linux-1 created low alert query-rule.", - "depth": 1, - "parent": { - "id": "P2yvt38BIyEvspK05HSe", - "type": "event", - "index": "events-index-000001", - "depth": 0 - }, - "original_time": "2022-03-23T16:50:32.045Z", - "original_event": { - "agent_id_status": "verified", - "ingested": "2022-03-23T16:50:32.045Z", - "dataset": "elastic_agent.filebeat" - } - } - } - } -} - diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/7.16.0/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..11557b383024833f7989c1b59a378409110131de GIT binary patch literal 4755 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/7.17.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/7.17.0/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e63a4ccc0ab4f8efa6fc6c61d8980086287bdf50 GIT binary patch literal 4744 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/7.17.0/mappings.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/7.17.0/mappings.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..fb737e83c010dacdb1347235cd72f8acf1459236 GIT binary patch literal 9846 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..26952621f10e4bce828d8cb5e86504a8715be539 GIT binary patch literal 9231 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 DK?YAz literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3a26e140e7eaa87becd588b84b6cfae9955a2905 GIT binary patch literal 9711 zcmVhJd10o>_Q{cmVsH;5T|GoeysYDTv1dyOqZFYpiDv8WYyb{RQhkrf&@WZBm zYv|@5KWtc4(AUk6@~b1^gZ%Da;t%mB;f!>&yL=(OdM2EdYY(lo5vPa0jkRitFOq^cNiF0yAO?a5SD@hI&YlB>Qwx8}$9-(T34 zRMU54&(pSJbtT{V_4(8Dr_JTZKW81*v~?p|z$POjzW=wL3D!L+n_KGVp)N>IuP9$h zMfSAnZ(lPoQon5n#*5k4_LtP}b<6fl42$;HD_LLY+qb$cHnV5`e6sz0)hI=dHV0bL zmhg;e#kI1#C5AvkPLF6Jr-$Q+yiYk#I#0UK*OMvd%yTx8qG)NSn=yWEB3crzDOnBW zmbR`5$?IybnT<`lGyB8wUD3^Y&%p@}|6(ykN+xd5Xi)#DUYvs1|)h&sl zE(r^md^cqFvH(_U$j%YdvZAG-#%*2Mn_a z>j7guV5|p>^?>o+959}ApC`_ny6TBob+^M}b>LuRHiPWS19gFD$=| z@O|B~{!rqomsg?Hot?R@MVFHb=;jfPWz}9^sSz%zIP3L8#IWl~4$3Q#;50@_iQRrv zSa4vK_>h?iAB&^pAwQ8*ZlPbu=<)KIe)mi!ZqI=s-SW6a4whS8S4BKL3nM zvMVTcPDdn*1I0PlkX}1s{D=f;anj}o)(dT;-v!)aXhcyj<#pMR>J61tZm6w9=nrs@s`rcOzHbH!*NQ9zjmD{%2r2 zb@P+-x3#x%1o2M*z0+ueJG~c7uRKsv&^Fs~vX2iLAVjl-WpD7o`O*1(kkJ!;63g=Q z7vCfTcxS??+dMVlQ~_R_2;$2vnU;1nKZ>4@bBYQwOqXTBTS9~*Np<>kM=xQD zp*6(}J%S9hifoR_Be3);XO;BX#SYF3v-bx~;RrK>$Imtk;EN}rp%*4Af-R4i-Ap)gOwT_BIw4;6QCEkh6K;kz= zQt633py^&*mWcThN3Gk4V;iutoEz_s%(Au|ha_G&yH@MGooVt{nhz+OcrO&s-oDJM zQP5j&l0}GQN^vHhMy?Ran<0za z>U2cq43!y@Y7(I>T>2_A;GD{E$HhiQD+&~Lbe5ZlLXw`ymO0{K6ZwMDlWgwmcV%`mCf3I5aOE)#1L9=W`fX1!3w0K`%)~H1RhLj_UEb02&900(d%9^&irmg-((pGQdeEMUfN2G9sbIMSb5Kg2IVV8gX^69sr z)y&mpI=ysusvB-_XLHvLaqcjo6iRz_G)y{!geM-VY(AYsj7gY|{lL0|I4mb^iY(OU z`dB&+0|43vE)$`)hKi-L*=0;j`oc4nJz@q7^WYS0PE~cEk)1x^FyRk(b4!w2y3=Ql z1k!AVEoXTqIw4U+^@>40v`6Ia>YccZ0aWuu#2I~fUj7wvi+>}4a7SAvmI4es$IrAl z2i6k}<|#tFZUXBjux4Ata!UyIsZS?2$v=`K7Ge z4Q0<9sAdQ`-VcV#f{6f~P!~ccjR~JxU$=Zh<|FNhOs){`eE#(5Q=}m$6yi%rlUhoQ z$uW@4!~~~yc_3xrLEU!xF{Jk!%oSiRjPwdHr(RkC=2n0?R~@qGdK3>lMlsI=fZZYV zL`8e?P|z++_m&KS#db1N9)=Mh&xd&MCNEA(Yv<^`aopn+1SPQ}CF8h3BMFCxO7MlY z6~!fqzq4`D@AM^M;Y87sfuloiUNaIWy3@EK&zg|clkW4x80~v6>-<#M6tcp$bzxjz zch+U{9BuaBPKfKv-7gG+mA$QX0e)WOikFi1hZ>orC9fl#s;ZT3)A_r8yj>0>w8pv~ z9(+BdnQdbs9LUtd0BlYNdh)`yq`IxPuzMvr)fe-+@3K-=I962Opuv#0G=GCiYngL4e%RmrOk!KGNkWP#K{KFKO14 zppJ6mDB!$41EP&eQ_%}DzzA4jZkLjCb7-t|yip0wi+g$6(JL{?k`qUMbSb`l=?!weYKudN2r)XQ)~G@jQ2r{Ss_%7ok0=|r4quWFmI@YgeE z`rCwwyv~xccpkJOTMj!2hS7h`scJ!&ai)z5&G_i9%XP8Y5#G_PsXHd?i_%$%yS4`H z=V7PvvK)Fj90||;J#CK(Pq&=qR3GfXp4G^ z_xgJ>W-Q(jYxLygWZ75p2urrLs*CyCr{BK%S|Y`eC$&UQrb4>)E{_CeY(OOXa?WL< zliQXhbP*|5l0wS%^eK^h`h(naZR7!`H1*t0E)_#Un!mgjnMWehMXF203z1ztYaKy< z;T;W7#2kt-4O56SAds`=SMzVyQKtE!V!|e02il)$!hv>h?uIRNDEl%v+ zq2@&<{y*@z(tOeu(mCx2N`OrOM!9Qi#yS2U33nd~-5v|Y24&k(wRLISCs6-bKwO8m zrPZ|j-~;dGQH(w*_7k6=D&MZeGAovl?YWr}b z*tD#Moa=o+vh0?^U#Gk6^V_+Z=&z)`NNBu{_L_#ixZGZw(lneIfgt?UuBi^mGJv-v zBo{B0 zRt0WE1#$PB%z@eIuPlcUP|py~X1AM89(dHu|2v<=RwZl@VY48<(TaVBKzhYKyS%ny zpZ&06pUuzd>kj=9_n~!%Hi)p~4*i{={;fD`i;HrN$Lux zMt?~24O?38c<>`8&Ybno>ZMwIgyWQ@*KmZX5Wt!>j*WHAKJMKL(i^P_I4yR)f~w-h z(`N^ozjT9ckM|53S!+#wO+RVAb6KB_u&pf31 z@i!gfoM2KNJ;dtH=gtVnqSsck`L#$A$xP{%Uh(0AW|!-;LiVwC0`&wRHH-0;P-XE7ijRd-#&XghI;Irmr+NVt)CO zV#XXQ4_AX>9Gn(RS$x4Ys}JNeHo>32e8ndD<+Evr4=Hm099(PA#gKyfW#?!<}AQRo&f*CHM#hno?P z5y<5@VdsErvG4-BA1C~xn&)&y;<0ipd^|?z{b%^a4`Ijdwwy>5s5gB44Hn;qdJd{# z9PZ2s&+vuec5Nata=AGtY@#M90E7{T5#0TmfTzRFH{lpQYRhRBu#0rUGrVO$cc~9I z6lLacp-$w%QZ)eeHsSj~n-xxleeDET~uReHJP( zd)cGu4r9N*u-qT)M+mhpIX++Wp{j`S3V`J-(Z`ECH$(m15uVy5SsAcD%&0l|vYDDI*ziT)A=uK`~%HW5g#7@L>ZR*cObz8j3q zM?m)rO4!#5ZWUg`eB`7DV4u)3>}y_Kje|Y!K#O-%7>#c#L;6VjOE+whDD`!dscVd| zL^D}3PKXi{d$$N0G1N;`P{ajUlSq>I3TWTfgK=$vYD;W=2b0Snn|k^C1$9ODY(Lb+ z47xqE<}toGyY_w3eZIB8*vw#E!~~<&k+pSI8j98YXf+2~(pH?G5bWK%VddQtG1QKx zg-!I@&ckoGvuAMz?k?6D6+zcNyTS=Sx^6PeK=s?B`6cw}6K>ohKf@#{LZKnJEJm!U zLp%?tq}}U%lk1`f5MCGky6Bf&^ak-CCI%o&`EPAP`vJ7dY1h|nkPE+{jr%g<@vah` zfpm`7ZeG}yROFd(!R+}W(ep?iNe_gJ!JxXRD(GuMF%)^UMHViIq7b=`Of$zJo?bM~ zJefr-3_b{i^dH}jRHEE>Qk_IFOsD{(MQrT!7z&gEx`W!ciBq|rPfS2vZVB&WigaNE z6ABP+h!se(fC(eP3EZf-vGSa_fJBnnT;`%sERoJ!U?L4IC|DLN)0qwMgr9oYBnOZv z5btE9EQ-aQ4iGn@Ko$NAOFlx11xT2(F6Jp#*y5a+fCQ?CKCj(2TBJm0E+CP_{&r*l z2D(VbZkB+xAn zm|$&vV_5w1j+GE9VU#t*84c~y(7tTCJqwCT*eVE$(c6H zdL*wSxG+z%a<1uv1cHS3f+SIdVItJ-fLfRcH59qvQdyH`RIbS1*)sGt1%>#40CiZe zoM^)Zk#j1mh4s@ieAn;0Oj9;@HU0c-z1|FP7}qU|FiZqlRc<^|LgM?oR&>9tkTyO7 zYK^N!O3vqZW?B-aB-UOAPjqSdJ}p(xSu<@tnhI;~D7V(}dj4l@)db6aS}O{ugHVM0p{ce$#SMW7v#zIw=R2MdkIrect;vlJ6?awPGqgDrJnB& zH7_#p|A7Z3%}E?68OX9&;UM#qQ4Xqp3^6@#+xdJY^z+Es>*#L+W=b#Y$QgMr1YPCk zyF}*AP+UL)rHCoa-VL=wc}A zRd~>-w$)ahH8|puU$+hGEBh$FXE@QSk1h?d{E8MAGbmPJneABNh$qJB?j>&HvTfn& z0q|wsewU6q#}{XmdOzOP%>A5328uqHX0rHIj2roSY?$aY(c-J58_P(EyBXUcLG zzibec78~PViP;Y0&G2b-yn}g9NqLXHzJ%F*INQ|%h;2J`W|RgkQ_{_C`yPI*jbcdo4s&}0-uLR? z21h#-F&T)-1Qlo3;j_-0!Bz&Ekg;6Z(m!38;p?>A@j!M^JL2axA0D0@fuF~&(S(XH0Q)=HKdimaA(|d0#F$$8-rDzV%Y6`9~tWL z2=@pxqV6&=Iqxb0P{E}WAo(~rp0NM!(ps#%m%i+%!i4i;ZSW4xp7+(T;(YM){T8%T zDs|&J%Ow;*jHt1e&&VEi5drPdbOJPow~pn6XZ3Dp#<5#imX7`5-79#r;{KYIwf!Kv z0p_oq$%5AtXF(Et2n0s4re%Ci(TEmKpd+$-1OJNC*WgA$iU?Um{eYFpR)L}ynp>bKHr1s z&tJaYgYB2kkYh74+peJ0%awm8?ZYgWPx#Ad<5aSeLOgv)+!kxrGJgBTRCfKXTF8NZ zszq6V5o#Rzu8#V=E*nxotZ-buu+)(>yk*XXha(Y}IV+nq=STeLSl>YkV({HGnnD?};h(yFxD`a^ylXcyYCY&Y| zeQB?qnZ1%GoHiC--8Z1dx}H`0fprI3+`mg?$(W_4ei#qR5uR=3e* zb(^WX5Mb~;fzl*(p;dLCk)1o4MLmp{njqP3>COzanTT&QY&pv_(fN1G@VpWtAKK#` zj$H2o$Q+T%e8r|#2DcmELjYhL0g-t}TP9Qps5D{18pmaGPFlX>1UzNFt_O;=GOP!R zInnh%VIcaw94Jm6lw|5P%&Is1;#-x|c74`S3aV=|A688C_2?j%pP@xRF0_!Dq>+_4 z02Gl_EEjAg2z4Y4)5TipKq;9qv!Ing*e%vBH>wFPKN#9c5}u3osRJD)fEkL8HiD5% zgwX+#*r;-nRPmRjeGvym4CUW*!~_MD5h?3}?U)UpbRw01?^#e|CX~&Vqmi=xI?W07 zpfY9%xN|HAi7{g}gurtPP;ubpSU2Ia%^pOqHf_aP^WyuB@j_y7Mkb3TDL)E358G0Y znA0PLvxA72m)ZT7iln*FGd($>Xx}a;RYh9`25d$22-lJw+aG$zl8PYjST=F$acEIc zB%DA?a7iuFDk2N#akfOIOE1H0;I`42FmkJ8YxdD8?O_>i4e2`7E03n_BAaDDcTG5j zr$hWc(1l97A#i;7T)X^07c$vw=z#d#00@HkOeM-7t_h5?3p-L$;CMcssVF#|3Q9lm zI{EuWiLi4Up^+iv+{Tb-5UvOw5g_a|L$9b$I_B_554O?iAKkgPAqY(k!ALuYrh_2x z3P=G#I+^*UeV{W1rg|Wp+5FNw5E%NTbReCjywf<)&TxR#jde3#H)A8Qbu<2eo3U3~ zMKbe7^DPWeyCl7^bB!R_AVA{2F6~!8M|lFlH#7xNPY9LHs)D{IqmJW!h0U8M+ut_= zT5l`2OLa@hRMqXo*&Z*!GE8Y_$LN;H!^6Zo;*|iZUW05yGItM8)QxRUMH>;)0PetW zl=VA$yn?p`ClYzs6m^%7hS9ZN8UotCoX9<>%2d+?3#vG*1#UF)h%^omf_U6!21X5s z2mbuf!m&bt0EF-hD<`6{%ghC#f+JbC5F~I^Lwdo4IS}GEUUD7;8=Xm~IFY*}bfOEbJA$+mp*v31LQuIQIlz#(zx%tm(a0#UM&ox3n(Y+R;ZXxNC=tlkLN z$o^a?>NPIgG`X~}(zXxaEruakW9cj)jOm3P08Z5}fCWRWMqG4olxjq#P-JS{Nq-+q zC~pMnMEsXWC%BJh^G26OY_TVzv=3)=#R^1^PNwQYE(&!4Z&=YVP2u$4JE_ zAf#g)Uf$@&NIBOaMB}A51BZDPQ=tn&EPjMMG*cCD!c*p2`Zaxa%{E`N&F{i|%{B+i zy_SB>xvr&OzD(EBFJ!)J>DRg$ubc5-ZZrPirC(0eT*t<)qiIIU1tqdW5OQNP&4H-6 zSZorv28@1-!zgKM04TQ?1`dSVyEuShXtr@Y;0U%Te3JGT0I4>0<&6=Q_QF_)NUNjd zLZS^&rgrpMkV3Vi&Zh2NH^OW{uUOE{99D~#njJ~@Zq=Ats#e5U(0p)Y;DVi=5QJE) z$|DFW?1fhxavpA~yx%#_l&)Y$eT~z=FIJ@22M$=e5M5JiHVArafHra=wg!;2OzAK+ zWJYF%X&@KoU}RlT`rIlv@@galJK8Ex)fuxC+z6|Y8~~`Q%V?HB@spv}yAV_3PwY^X z)HqH~WYkFV05sG{!vl_h8X3QKqn@VTsVre`7`A{%css7MoFQ%aMN44sIaVGL#AA+> zhve%e$6*WfjANw-fxY4Q3}GH{EaS${>%Fw`0SPz9q+>!t%n|fG!^)AT5_l*XjS(Px z9C2QOhm0ebK*Pk5X9};-aP;}b5e{BAvUMX%&v4zy-r0dewW!DOC)?jQqHel+*MB40 zt#AcAoW0a=(u;#biQDQlj{7(loYy4kN#1L*Se;Yo(Fc5Kgv*}hhl>5K^)j!WsrOzF zTU)QGXCN+zgh~HNKYI>XxD^&}y*P8AqBb_S)iT;`VytSr&A?>rZCdQ3EF0)1@9OZQ#q7oBSSdCrW_R(?g?>qr>|a!)eGUlaP>mGvs>Ni zLCjWnI-JSsPG2{&bt79hG9y=%jqC%s(@mR(pR+=O^$V@t>95GT(9%NM-_ok+YcB`1 zuk%`$e&;7yOS_>&kZLsTR(A}b#p;>W9RvApId=@MdH+%TF$w%8t7l_95UvM84~FZ3 z@SWZ2*$85`dN$xpR?o(|k*yoqx{(>VqHJUzz_Vcnh4u3woSPc%l6ca6o@AJ`WH!Rq z920Ho4}ug`M{>>DT3(iU^{iC}V||*Z8_QA^>qp5_{pv|bU*&osqA%XOrDCaO^`vB} zTJ>v~p*q!99DNn4<@|I@!BlPP>t=LUmHJUMR*!m;u~dorQ36zh`Vq8LeMYN~Zni1e zbM_9T+G`JwPWP_0G&an|WWB08h@#XY_-RSD3rHRT`EV9TeXUj>Z(;%t@0ge71Q$bHwR2h&X1C5ncYJ*J=8q$8dbxR@f(T| z3;H%`0%J;;mV>9Apl(xnkeRh^dT_Z6vZR^O|ycs)EGH!%U4OW<_1Yc-dQCyPA>h4jAo?gNuXumfPdwm24 zWSmOnIPi+LWXm*xNW0&U^mJb%_A0us|DTu%CjBk=@rG3eeVuUKaJHlQTh3>J<v;1vy-5hyPDb-rjTJ-a1OsG#cspx>wh(QeJ4b4bDoH2eSG6 z_fMZTP96tV6zal*P2Udms*^Wk`b1XmvhF}y#cK2U`hgSf{w1Gh^UNxhKg=Ct@zW>y zZ$c|?sp{{n7)ud0pTGX??_YoU^7ZR)pMLpOFP6};Vq%cFV8eLl=;lBFPw;*D<3IlQ xm;YkLu+R3a`=9^*_v~3@BsS{WhdovC_V2dT4>%22F>V=AzsbML5fqemSJdI&Rhmj+ve{oiG2I;Z( zM+^&dePD}|wOY!p3Z*Hj7#(<`a3d;7Ga22ZveMH0N3`Wi^GK+jxVqB~r;X*KK_aUY z>-uN5E}S~X#22>M$vQmqeS0_W#6u*{Tv+vG`QyZmt(i6*CJI>g?9A*jbmK_QdBKu+ zyz8{n+(^CgR1VHu!{X;B;!~#vBa|ohdNe!E?G$KFzUuzE|Lt4(%bV6CoGW~67N{o` zRlJzaNM-h<1=;Hu!m#tS=kV*jjg5rH(vlVa=P-2(V%vdgpKrxPePOc2Os8t(ETu{`Z!3g_g@O+$HqLEkV6>lxlIa%;6R9SBtW zwGFN06EQWpZ<_AqGu2ZdGqbYX?8lh zBXSy~3Ivlg;0|F$Q(@x+b%#GerJ0x}Ua{j{IVb0IC7)F758r@?s}wih%ft<+`cA5F7Gn$C;~B>wn%z)@=K z9q3c(Hoa=${j{jNy0Kw0H9JuK+b6$G^8%}w38M{rYS_JL+38i-zS5OELEGB7;icun zDaEDBMW+>yql~B38q$ud!5@coUS%?9NpsSP>9}j^tV{6@I<33h9$qy+YRdu7PEJ`* zeNsIcnu$Ka5l;Pxm=%?(z4Yq-ugiI>B@ar|!;~BnY@Ce?8I>TXI3bGD>Sk;hA1vE> zqa ze2c~Y`<7zaj~e~CyyR08u6^&7w5%TIjHyX)1O}b}merhM&30M`wH>awnH_!*waW_a zkFhy;gN{4J7wvXtrb7&fe)t`=a(7KR-P7YWf~EI1{ch@!WrH1P@<7VhAsqgSrF1#) z)#);8?P;6;F(%w|Zsv3aYZ|z>XwmsV!X^wK770Ie$8JJkHAL)>!HmwGXzMvT&zOvR`nYBa@vUE5Ip8hChb#$ntq`AWkF_vGpCTLWWB)*gN$%ZO2=Mz%dq zY>#zpIugS7YI-YFJv-;1ukLhl3$l5bN41RmK1TEocI>eoHuZwx*mz+}1C;`Kigk@g zE^^ILjf3AS)-R}uNZAQX*?D&MKDsSiQ}0=V^_t1%s~yQuboZ)q>Y?7-$S~~t7&Zm= z>ghjS3!Cb;oIxbQ`(d^H`0qb<_ackj$g7=_R0B`9qTx-lnA8GpcEb3Ro*`T6oJfRT za<~UiEereD`tVdYu2C3*mP@=*Q8R+`+WhvoUcPZ69Ud^eOI)iv9-Fd-8dyG)oXI`- zyt@9$`v>T`|G`bo;mE|==P8MBY+ci*fI8TxUw^__(UtbQLa?~n2Hm#$3>fur{!A%# z!Bru{=C55vzC_8PPoGa~>dZk)KWjO26TZ(SC-T7xe@QmvEzFk4Ss^xjvYZ3*zRIlk zj=b6|UYF?aOQimgyPWarB;DAGtvpG?I$zEZbGrRdzh{PC4m&7O%i(J?m#L^(7lLwAbIaU$YE0Vgcjrz&VW@Py0*+SqC5m9i zdzpGL`C?h|(beiT$qJ{vek@$IWQP%(CZ_1sK%xVRAW|7>=9Zky9Pc^#l5Qs zOXKM>W8ksT?ZqEU(+&1s!|tWc8*4MT#$HXLv%SGoac}dc)8K9!4Nnrjs;| z;>f^hrJtc#^0v7Dm%b;|54Si82Q+(2QF5kDsO(m2yuV^NS0>c<4LwMMCggkHmD}>0 zX$KsNRuRMR-)RW>xr3`El9`|h);9sFS zlEWyyvOFUiV3I62uxQEM`=p^}Y6}0A3Ncq`?|ot;`UJal%hYpkW(I#s^qfk?5Z4Ja z>90=h5bAH%Tk(N*V+6hf^Ux*dZGS3k*isw*8}WQCRs=ORa1=4O?zXo~Y_!HzAp*-H z#0&NoiL-(O73B#`E6U^|)Q}Vva-QIRm3ABXdJ0s_D=M&ks~8qcObE2~?8ASbCvG0m z`hA+u#t|JFmu@qYeihPff1<;b9?_#<$!BG;$;qiT$pU;SY{{A6PmVPm^YZfxuwQpc zX$W14gFN2x_uA5d&B=@|1umnp`EzsViM>)B(GJbH(TbOL=PE4;*xWo)MPPvsodw{` z=-)nI$MKEE>nt*X+33ts{CW5Tz;f;z=6REp%%a~SRv0qr_{r&Pc6vCSjq*BeX>5}F zik|t8@N#2iaBhe@z*2#B*yrK3mw*K~+<+Z|ZFXQN@1mdV!rGAnn03o*chA3Mf+%yk zG%(#=BwiXAyu>~`;Ff>!+=NPV|*SsEY|H zapqb^sugMWr(NfhS%gd+$dL4F_p#|M*UdvxOzXBZd9rO2qy`d0fVI?Di3xXSsQ|k$ zz2Eo*!o8)HGssto;kEc%TmJ1+6La{8>oVQ}*6ZI557MCU~A00 zv;~HCONg2H*^63&|I(DWhMjxI20q>Z$~XrOfGdD;z#1a)Si%GJH(+qPNxj|>sjA0JlU4591Euo2<$Q&?;RO9Jpu zM-UHa{u_%ni1KMHU<@AAUh6EiA>V@kJvnTt*6V+OrH*ip-kOHS$5=e#d~8lB+=H%K zOxHr{y(idHNH%i(LHi`7=_ze0f9^IF0&`En`(#UI?!RYc%a=2o20qQsg5CRcye>BS z8P z?%&fDAEPaVZ_^~j_Hew^7VNZAeIuMq@k7;(pWG0%pb~w(WR)h%h=T$ssO(9+sZD#oMmC@w31ftG(%i&{?O$N)f|t^S@t(w`8$)H z)h;9C{ISlkdOyO^Or@IvR&KmKW8`qlU;X}YQ0^TzbFfSD8;wkcyqkl4T17_mChzW4 zr|?JdM<;)Qpsk-7OwO z&?P9FMr=0~a?mt^F{DKaT1JK$b;LwX_?+fCkGdg7qnToZBDkxx1++_}X zz{$EDeB&Opyj$yP+2PI@dqX5yJJU&GQkG`#PE<*~{foN|j&~cd#y2hvT1#C2YF_d+ zugEi?yGh`*xG}GI{Zai3BRQd8dpSNJLHAthhN?N=>P*#vC^775OxrZPk!T&QMa{B8 zkWf<%by>I+177h(JGvz$`1Mf2XPHb-tnWY9oOoBK5p8#){ssr-GAS+qai_C@A;0E9 zH!?=+SM1`5YHSPSmeXP#JG}OB5%Gn`c1x)1%1zbenWBoXe}YN4hgzN?f>u( zR!mLDynp{fSAoNxEAO0Z=46wJXSC~bt9i3*S3&Dv^Oi*M&3S#b_JRW$(zYt$H~N~8 z;phnNXlvoYyAdL?RyP>(lm8G_r#f48D&_lNXMukgu#m=fR#$;66sN^ZQ1RAM#6H2* zZ^H!>d{`W!XB4mu`7bjgU3t&U|K~plM_D+fOp7b8+^&Qt)94}h;>hDJUzu2~OXf*$ zQDp9t?gc#;-rkRorc`;OK|VX%n;IM~^@l;+aMDih0z+cFzUl;!WpP@jVp-pvoi^5c z*gt-Hs0KEHi`ubg6?=?)xY3i^!+F0DE%1TI(!6OddBH&x9?&Rt{H)slONseNW;N0h zY!C+>e%FPvGb&+uv;OB#syFN7xXrIm8RcR85AS(&xl_#qh0}yHKtq0_A5=%kAGhIj z_#ID2O=m()=LY-+vl89)0t_w_qH)^o&vZr`5`>Zmk}l^JHm2z0#p70UZ&VOwT1_i5 zlapsH^0HLJqJD9}S(Sf5PJcL^FdUzJN~(T@G1WaOi_m?({+TOL4T^Ysd+yt-heJGF z^4-JFmbNF#!(2YmIuE5e+3=R)v@&9nBJ^+BoDQF=u)^bJW&#*=+^u|$Kh{;p zl4>sJTA3gK;lg%treJ6*>ReX# zA#K2(WTowo{6=J?GtCkAh8XWD_?l@8@-omxUxtt&ou-+Azca;iV|+*|+|+dJlh-rf zu>+{MQmLJtVz(}yyTC*#DKcc?TJ+`lOx@ydZX$coCt&9F8Pvz6-AtnbYS;!`GsJGVwWOOB$JX26xuTOwi^@cf=L6RZKML_c9wbB#Y4uj(KO9|w%%rk0 z5GbP$e6~!9UJ!x%{4pywxjw*odk%$;E$qPs73h`WY6d@E6I`yk*n%<}@tnINN`eI0 zy>x~+^0a|GuR&zqLGxQr0c6>e(Fbd=Qn?bjpjH$vi2@l6ZJZzh##R&nd|B%-1e(3R zNBDv|w7|6oU5S1CoAfJkkc8$%V9v-O4+EUtrB$isV&;Vbnt3-UEv3<40)LR)dfnV& zV)np$bob`G{uy18TX&>gS_Mrr)zKgt`tv5G3ir%B+HYNiE1G;96do? z^tayia`~Ir!#)Mo7*Kx6HAT|-2!0$3$?Fo{|jp505#0NPzwR5jh#_j;tQl!J?TI%_=USFzlBELFJ!M(J8(0n ztUe~q7kl2V_y~-Xq%cP*^IrZ~%=^|o_)DJ%%G*8i{X$zostnB{J)hIXp*RA?I}_VS z=sM#`(K~gWec<(aF6B^GCB%W253*R@a>QkG>LO)yqtTgaaB{zFb>x5NxC4A_3XAc2vXCp7{^2Dgx{CERtn zIDTc7Y&Y22QT-x<3>$2H&!si~(ygmI)kI{mv;*CX)SxTA8RfI-#mIQvT?4a&;Yv!Q zGRw6M#uk{BGX>Y_OWm!d{Fh3_@70uEW+skyt&+xd_P)QlrE-7ej$$|8S{1i-Ebfu7 zLZU8?bWbhc$L;+~jjcaW`wa9eVhaQJvGmP)&`QSwu4ayQ5NP!V~yrCmcU2Rs7Z(7q5zZk5i$bb5^0z_9x{H zVCw`nF_IkC73#aqL&18kja9Z1E-`cf%&7CBAnb~Y3>aTP0TkX zti_)trf`cJ4LW&LD!#xxUg9=otMf`r z06Hh@5t|Cl^oYnXBScD*XlXv~IWD*#!O{6YXe6#7Zol+wXIt)H$!1P%s2!A z<2gD)!Eq}}eeclMa^&m;1d+LM(JN6-8oA3@H~)-`6tLi_j3Do8cLT8t0}rSqE~Z3% zyzBC)k%Q9c0?@7mHP7o$X}z{?t(@1VG;&#OA#3w@JUMSq7_8=lXRXk+Rx36ZlHv5J zCp%Y3ZaEkq`jKyDbxq2M5^q&T%E{4*WK?T(Tc@O{RAka{y0P+zeYWbh{fqZ{fenvl z0+g5S4?+pJyFRy%;@Usj5c=#rofl`thHxf%hNbF?oQk^m|5a+e7goaA?};cs+Zk27 z#W==mn^NqOp13NZIQ`|>lONHf1v)#^P{*7q9*f#zc0eER@)jowPxf$Jjp}iEq)my| z=0`>tzLXbkxBg)MFyR?=oeF(3Mk=3HcN)Oehgtw|Ia+HYL*7euMy8^8<5N*a+BGjB z!^I!$`g3GPZg^Y4?$c9qsXkS8zX&^}mw3mk(=rX-3$$;^^6B)_h zr9jHgY^=bcFl>)4BjHT)PgbKRt>=T;>8=B!%S>oN{Xa@$IUtSre@NrIpeqOeqckog zv73YR*GZy;DY66v)GabN_(I&5i|JIha=<$Gl=FD5wHE3rDLbn9#NR}>8z}5W%_*oLo{QyLWQFA<0LV+ z!>3n)4PZ}+i@6CYrT9&g`LiA^C@D_9kt1si{N;%)hg#@QiM?FCAg&uvexzg1%PS6x zO7N#gZm&ffDCsec4JzSt_#V;zR>t)YdiAO1HiP60o|6}VVp$&2A%_?P7mOly%8XuT zz=EO+yK=-H!9eF5+D;cBx9zAq0E0EFE5P_0nfe+v7KAOQ{pWLS9sF$IX>r#=3uA#9xYv*4v$ zI}gp9#SEXa_dcp>*~z!2sWa=HoD+Gy(Kkz_cGiIavKYGG&bB1B|K?Ad>o`e%W;zaE z+K~KU$zD(1eD5#d`?p11jRaex7bfqzwEnpHs3T0P+&4J#|J*lf()a!;TXArmb0vwp zyvJgRJ0=}vf%KfF&;^a(Oyvxvb*d!vTw%)IKNCe4U782-HzKq^S(<8w*>B?}^^_9+ zdgH#}Ky()5eX&UfpPWUO&z^j)MK?r1d4Mz8 zWIz4iN4j8W_9Sccfd{7aIb;U7-n1M!KZI6_*kuD=(9 zGa4Z!x#gh@^dM#r&X^&pWysDCac^75x~Pl<`^J96)vTB4ym>(m`svu|>sJ%2@cWR4 z1)@G$jj_Hkm)6Xjc@(AJk)0hW;yS=>tVUzi{2Z#-9UeQ1|LNGBRlvF>Oa2(1!f&W^ zdD)R8&CZcTZt3~kS9lGl;W6Z?)?TqXy1jmy0m)vVnHHt=q=e6K{XKjUqE#CFu6_Mu zd%wi|j@q<;-+<OA%ps>=ayY$l_N2XXq^H#@=D^4AHlwK!f zwWq6B?_zxLZK3Zi^;{YQTGu|xRoi+D?up7LwpoMY&Aiq0BaMUvZ11By;WTV;Yp}Xy zDqru1;{2l&aQr6fs8^sy6uxN>Tl|*zN1(J>?EEujb6X!1$7s5JhrAo(dSx+1GgKK$ zHrcd(DxBzgS2L*>y&j)U#CcV?u6jwm5ZUnbvy}nG&2aJM8?*S8jfoZPna2!O{sb?I zlG10EtBEKT=O$~=+Yl?E6K;i9*+a=4FS$OXEK0qh$YW&|Q`SjqfF$zUTTB%X{=AEd zEob8_RN~;|y#+5$jc?6Jjeh1uKGra(Q^k9%_s=3Of*cCWjj51O0HgFOG(DC>` zCAOaEJ*|J=Z83^&_Y}&xE}>|G?~8ah+1;LddDJJKQ906EH9Vb*0$7M;hN)wT>_*3) zP%G0?hFMHy+tw=;Wi^RA_U(H@SyN27)E-&%mev##=6IdZ1)OrwuE%F{&pzKp|5nUt zn)civS(yFm?vQU)=c@`g1AFlJO8UrQIb?%BzmuaaVd;&XGox0`(QTy21n#;DmAtFE z$7t)7Td0?@hV0?|xjXppXE9hbM1GbYDTC-O0x3aqZT5qS15=sU0`oH75UKIwXmlSx z#%%A8KX5vWMf>?o^GfQ!BNE^j9$on-=fk7bHHkXg(MQ(tq?(Bi$lf zCo-LHv5Ke7Kc#M$lhPikLbyEOX5At^vZ4hD52IGdZbhz89Pix?EfoY(z{Qkeg{kH zv7@*4N4dAoSFP*)R`h2G40S7m+yQX&;Am9uz*a`joXceK{GA9OVh8---xKk+pKY_j z;yUo~A;z-m2|Rr0A=SVl9kj~OCtXOP>A&U^Z$@%IZlZ}$EUHh0SQIwvf_}6&Sr6EL zWC zGC|gl4z2ibN&7VAnXSWhT%YAb4xY4kY2J|f?N|v*fkm+GncE(tDDM9n`4kf&m2xIT z%xgjH(?>ZgJ@((z&S{^yUss+oiWCEORoYT}@W^?>ccLzR(B*Wj+5>S77_m+3VYNSk zY*=!u)2@GGUXnjl5s5M&G6V~EertR&nn!65P7rfzdVCG=W!-I`_pf>8<&M0IhQN1Z zF=B4TzGah zC}O9JQJ;=_EN*xh{0fk(C{v?kYoITu2vuOmn@jrgeLIL!{92u#*$Y~4 zswryBB|R=o3`c!%`ob89HiEY*E8RIpJyb4)t901=Twd_E*7@@M+gBvOe)so6^`7;z z#p#edCxP+rUx_`Gr>A>T5kpze_zV#bdn0rdsy56dLs}t&g{{28b$`?E%6ei;#$UR<;Xd9*^EnS?uL&&b@w3 zFLjuCPz^jzRn8R_@MNNXWhrVettQ}s=l0^?{|>s$Hr`2X=`IfpT!B76$3AAL9xu@R zyk0|!U$)u`xi@{Wde2-w^_Y55=*79N)N1LU?{xCev$vf4xHo_Qv{i{EazJS-V>-ZJ zinX)wl1B;&*C$z!R6-p?_c3xUa4&EdF=DC6CO>eUY zhqAG@&ywqJmENJBTJxSMN|yYidfY=GqX zEIUn7CCh7yqvt?Z#)U{FyH;MUOkkfLR07_GyoO3V>lqi87v$+51na%kFc&8=M)u3? zp;9r$#@S$J?gI}qIm9Qc3UAjs))n8o6+}=!AAD*(C1@j00g6wu-Cb?d;1;x8gou5O)2FEFIc2j;VxP;e&eed2s;pQ6$c zzNyMEFoNNTk98LzY(Cd5mdme|AdzNEcaum1>!k2ImH!RHk+qWec3Ly0N#0K%rpb9s zojCT@lK7>})eP9*vCWYb;M~VdVizdD(N@< z=Lh<>9yj*R_AFO5@L@sIrn=2b0WJ^~c88&eWUnXjgqRdGhPGK++WHYsJM>JI(pFd< z<(3%Hxe{C7?q8tu@4*?YJ_SJ7iQ7$8%b(aEi73A~xQAGbf<~!dpC57cQw+qa-S{Yc zh-inpBcC>*k7Oui<5IarnhdQ8Kk4>yBml8*YKED})w?P-IBipUtD>P`ffY1IoE%IUai{G5aG&&hpNTeHjoDP3NOF{81j24C*E!QhA?!D}e zqK|Zf`BpB$p!%|C8~PNL6*+VqnUfZ>i;i2u&+VdxJ(fOhk+ssiQDhvivGmJBMbCz- zcS_lUsv%0a(MK}3*qN?v1F<+raj~$?b9ydlXMK6%MYv(*+!`eS25$TgTbm56iR`s- z2#UNe`Q&0j73wL%BIX`H_gV`2`BPRwRMSQ+{ZwzD9*!BOcwD~jwHm%R2>4&r2G^M_ z8P0eh)VcU(ND<%eySm>VwXN+!@Ft9-z2%a(eR|^Ht@_PPW&8o%rJ1Zh=W|>mN#tO3 zrBKGO(6xOtTfdE7nHtR^w$8yS9Ki8}jWu#3?#Iy-7vnNBgt8=Q?I`d=Su8E)Hw_>e zm<6V3|4#AnsYk^vl)Ls;B(fS7D6Xr~GuU5`(v^uozs4){i}N~UP5z?@)bKS`w(jA{ zq%!LdulC4wv4`}`c--Fs>a1i8%N|C!*|raDWm5;0zXrQT{}#W$y58y~@VKrg+;_jy zt=SfTk_`~w8~&W!P}umP8yO)*;HL&O?5()or!|1wlC}dOi7j%ObWl&Lbs=@v_T%q+ zsHg+73YUg-n+e#6T1qTra$Gil!@*D;|3GU`XOy6nkL9@Mbd8eTMbg<;tpw0(Rn;YH z#KAcnEo$k2pK3%3Fr%>RsunewHEl~`sXa+{OaoDK$8j|Z%n^;o)H};e4`bAI_60NW zAhpjh>ABy`v}7N6RJ^ji7beE_xJ?9I+PW7$ur>EaC$aoGOX)|?DDCc;mN}A%ncaJ; zFHAvWLHCn0syr1VL6k1)5YEzXiP)(yhcgHDW_$-aeOu=ex#Rx)F9Oz_f1Sv$XDuB> z;O=LTP2m8%9Aa6e#?Xrk0d(B*5A`=HbeM)-8czq0-el}U(;FRB^*+?%LZba~3d5twl_WG%tX{_;x@O5k#bhacOw$*GnI(ZZ179Xq}pC+qppj^8$;e$KhHfFp%Q$qx4D#as`R} zlgCxd#0;8oJ_>@Z6N3~q+Hg^!^Fi)T z)o9vRlpHH2c;FBI(XgZsAT?bYncui$LP}yP7y@eSiMgC9&fZl}M%H?&`YC^C2x&f* z_Aqdiu=?1=|kfO5JQ{GjR@!wqhO-pjGAYgTc)y7z;j-x4`1+}xlL zeI}&A6ZayT|M1L~Bch*1ih2M?<2y6MN`#rzGqNxKLqQQF8pWBq=#UMkJ>t=lBR8136qW&6`?v<*-bTAha!j^MGn55`$CKk%)>&7{{NA z62=k}ETpTs+gI9XAjp1XhF6gnB%q-w2-&KcJ^xaOFAwg}yDZn9gic{QIvAvhG_T5A zLP%cjJg!$%k&K5&Ugdlyz-%6GWgK1yqHv0A5kE8z>&j`K4^|(-&njwU?b0S|w*2*J zfR3nv1LFZXyAkcNHm+PQWV4(YJ_rOD39CME|J zCyPq(DGHypZciJzq?%=zc){BcQG571Pa1qub*faJO`~mY^W7SU)!SBhMDJ`yi#JId zg&qnA)bQEBiAF-v6z`H{gr&dx+HucD7Wo%5D)>v2uj72DoJ~Z#Qf$ilUCiIxiu-H0 zTkF+j;?z&|^WJ>}y|Alv*2YrCC`0DX@IXc7Ei>`U{MNjSR6gD(`3L?(R9mOCg8RG9 z-johVjrQhWoCV$zy5xS11F+_!hr~b`WhLf}04hcImiq5|b4Gm^OE8CYzV1Y3m(0=y@bYlpw>-|4puo4~4dr!e`|XDPdAD*pwP*`20{ ze!l14u`Ttvu-zixMLX?>LNn`F+B=5|%)!$cm~RK(puavv6r%UK=6Er!^w%-|Ckw5@ z7U&@SdA>gKrAz+QWLcU718%9s=&9^LP;}hzV-g@Rh*MogMWhQfgExf=hf^t<7r|oR6R@7rsbv`OS~Xf zz!VM{9fNq#Y-6f#IED?nUk_O{rm|kRH~A*|wjp2`G==f2o4E~>wh{5VSg($t-%gf! zX6R3?E?D~PsZ7Oxd0dO%t);y6u9i)J?bWhx;4`OQRM*WQ@lxRE)K{=OCQJ5Keg{bK z*)Y<}>!=C=#9vw>=djGO8TT3kXVn%P>N~t2nsnyT=y*_ky8c?7`5pZ>vg4o45oZGh zsd#~aGwbtzLwggefJ4SCgL-io0+`7Gyvbo@b+deO>Zi_@Q@nw9cI}wGg8nMW913R z`f$(t{%qNz+Qc-?3QHm&*<0K{d+DuL>m;`_A#Zm`cODi|VA&J+onMQiB?$-C6wL=_ zS?|D@*y_h-&0sSc8TGHG+e*-GC2I|v4y8}X2*e%%qS zOv}D=YE9F=ll0&3&hIanC%q@qRkjX;lP{RxgReP=1$1U1RtQLG5%C$|4LM7s>`i(a zK)58yLewKTcoF;Vswkhz{eB#oae9Qje#>~`U?XnU-4fakES-9t$8P|7RZ~5E+T2)t z!U@P3sIHtPaPGC0KcgUun1W8~qBL1n4xM0`tm{>b)%s5Y;V|MyyH;` zJ&V3dF3g}*Kq7SN1(m#pag+M_I7IsA|3nXB`N#| ztYgKQ1>eLbq8fH~apW~f33bU&KHCWd}dKhu~$fs4gH!^dVAgsyA=K}ro!vOuw)76;e-2?vo59ttI9_5+WqC=_i2s9zgLB6M0{ugs%=1)iLsU=-x zW1AL!dh&M19M? z>nSZkEgXZispd@SWU`szK)*P@%ZH}N6VR-Q5_pNGih+*Ms1-VF!Dk)tmt!+SPM4_~ zx+BRWQPA=K=~0XN|Jg$8c{nQN26meC_QgRvda`MDJqbDK{u9AWeA7g0yH3b%h)*TvMT^LBVITevCU7HQ^Z64|{0OmMP+Ag13Rl#qTv8qWdRypt31 zrD8N}J%wKMT$pc@*b&^jq9p6ye0c(ojYRMTKt+3;7$FZ1x6M1xWXKyQ5se%IQnbq% z^fL%^T_OteFas?-A&e`&sv!7KcMueVnO!>1q+cv0sFcVGAOWb1yMSiF*GvzfQACvf zNFmSR$BvjCumACmpFf?uiy;Li3fc1kX;qy5j1V$5BR>;q4D*7rF$)d|hlw{OO@AS{ zDXmK~se!=(PypV4IKY)jCi@=~6Mp%W#OP-7L5_2_ zO4hgygn18F+v#J$d%_Lvy%HzWBFB zJP^}HD$n2WZR}9f$nOvJ(WoUAe)Dp+miALp;$r?PJI}02urt2XwS)DHH?ln}wkDv> zkD@ky|HCyo(R(u3BdK_7Yj^}KlDVG=bSYwjNq#+}%ir6uck<)hS-9fC&3FfX!WF+z zjjh=UYv8gOLN@7Bz4-%AO{xY2KgAqEo|U>9K| zD^wd~elkc0A2MI0oUy3h`5rKgQbU|$n>{|X{~J-aFlLYF^PiyzFYW(}Dd*tpG;K(y zIcpk6#&ZJmhdWr(r@519!i%qq$a<9D0(Q(V2mp)=3I*|)B_kudOrg?9&s6lZhoN@I6KIcr}RZ2u*z`ldp(kDVVT$2%BoPl*)~ickEr!%khTS}rgJ%&XYgVr%_9 zAb61;NA^0==Z(^zR8Pm4j@uL>W0dp~#pGR#(sefCg2)<+H_y!z9!rpLjk$q4Px6kw zVSi9lA(QJrI)#gO!0KH$pFIe%KrGfr{qf$twUVb~`H$_nxE)m3uaaHHFXt2sl|aE1 z5;)v2F%Q-6^K0Rzv=7#~jQVI&{Hr!rf6`-BK?@vlHIo@{4vJb zD&R_SyzA#|ShUdYu&ZUQ=lWvC) z7;>6!*}3A)lKgHVF`R8h<~nr>R@|b9BF}cIM4~04C8@v`S{yHSGDpb}`VZWU4`ezV zGLy-ktW0f_e-laC#K`Xsmc%&S0)mrO0W5l&z~QMhtTOc<^Rfn^On2gX zIfi$J(HNEm0Lnux2+oCgIqm?SEL^1^LZo!(tQLcHum}uC#!GcQIb3emVlpAaW(+zK zRuLp^Uv#>Kh?$+dVZ9JE!4X6`E!fbcN?t14W2faJv3QRfHLXg5U!vJHIpB-BlsJq2 z{6Ls`29MvV_D|~%{6XLbMf(h?i4jS|DK-pYhn45Cy_vjv?$>ym5Vi30GzPza4ogl)@tGp?>{sZ2CkC1g&_zM>?FUJLZGj&vSsUDc?iC<#^(73t>^KNnJ3kihbqmnz=WCBj#`*y zUgy}wVp)uJnE8m_bt91AdMq=Eu#L%Ui*20!_qZ&<$#lu{S~(~XmIU} zN)a~}_bN>rQMbT5DdyQxB-;DfmT^)I$z5kIiDt%5WuL?WR;vOV+LsLq${E2ty zyPNp9ls;c5J!@Do0G~zI*95To)r#hxo_mXvjVj>+4xdI*$(jmVV031qM*u09)Y+BZ zl{8Q%!*+z*X0RdrPh2_amnHlPl{T>JtY4g<8UDp32JHpxbL_gae@m?HEDF0$R;V)+ zTPJXU_zXd_Efl+0O@AmhS+3DoO4D_ZP+L{PQLB383jzGW1);e5fHgJGUscP;B)w3R zX$rOC$6scBWUr5I)L%8mpl!}IlQ8DU7Q+bG8C$)8m5pEk^Q*^uQ-4d+gI@(^{o5Hk z+Ek^r4_lZicELs>cA~#&-i_fQH}e*H0|}VX$+g#c)#K-3FO`oD;^f(bKGLjqU!c#8 zpy1=rYaAOHrGUMbYvcYz)6prRbR5sc7@|uDV%)Xr$^cLQp%?LM%kbOFjAvMNEPi|0 zL=!sGr}T3cIo6h))x#(19L_XUJ-+Og{GW{kP!=PjA6ljz!`>YJ1jM(-yXtDYW(_Ph zo}{y$?O>)`DEav1VpCWa-$~(r=bGcKH9>Ay%+=B2aL^4uyE}IGqW~4fxXwzYg;ixj zE>8r_9hn3c$c%mtV>~9rzbP{Yep8ZSXCy$KS`xJSP+*+>5euf_d=4f1mxM@pUBt_V zNpn};$Wy}vfgN=nenaMQNKf99DIb3>*WD|qtub2NF+xo_~dT2 zIq$A+_j;Pw6LD&>d9Tk3+UfhYQEN*F=?LfGVLFG-o#i2;Ea*2-W5Ubb>09q~fnvR_ z(DGl&0#tL(vVcrjiZ~kHt&XyY8GcW!q+7=BL%7j1bm}+yE39CbNX+*2m1{)Grr2S{ zASQnZ;fzoueWhTjW9%2YxW=imHPl~^LRo>YZ;wLjfuBfhxxfui=yJp24*?re%Wi*= zku+gtN;($Cxx`_|dQ(Qs7E49TrPGQy_>(%zBt*Q*0%=XD^B0Nq}6^V~>()m^bY5##DttFw;*&?pG{^#tZ2|2^( z^W|e>RZiCLw)$wv5e)%zAD&c?on-2KYODa8+x+Z*CeQC+-`nFPp5P%=`vQ51($@8r zHlD#Iul&g_?!COiOcTx95E;9}!(8uJI(5OV?nOSh?*maTM<8B%Aq)`kVvF;|YvdGb z*;&lTx8^|K`@j9|;a2;=he}eoZA>6b{}y7L066LYvqBTb3vW*X%$Nim=p&;?tpQiX zWdb6gF?VCu?LUGL{NCRHBc+S48D^^F&P9tPIe;5>h$SXhVL06DJ51AWz~``LMO~?=KzSX49erLrrlgKIx# z|ECPI`on52lz^ky{)H$Z?Rw2dsS388O=2+Lj88TmzZL*xs27|F**Mq(pXu;I=Gtk) z@REbO_ph>JgmJWJGs?ZIW(nCd2${}CW6i=-^!HAm4`@)&L=>D)z)QV*@>SDs(z6;f zl*n{tT&@3|7wFJ{@)_%hQBRVH3#B-K(hZfj1!|(}bS1V@)#&@;TS9jBCBOqZzjbJm z=1-Dj9IPfcsJA{?Z!)`Td;YKk(e)A?c7`x7Zz6{r4WDiv!}cUkSxUf;oWh8rjabpE z!nhb1r>U>2ra+86-ZM%KWoxLs#$+DfW>SF~9KtOllkEs4TEx02yv>7~rjYttgwL~v zIfT}gDuX;vcN59lIH5|fh;&gx2N{Z>peEQK8Qw*$5E!3Dn>Ics1<>%`(%i{5)OIb1 zthtGb+|7jI*M`?+?StiFpGrK*m_^n+GsQm=m#g4cJZZr?B!2w{ z_PsIFUV`0gprDFyD=(Itq8wZ>D&rbV z?lQ*KT-k;zWW+kxR;xNajvkuk}*%F+k+tlO%kpNT6amfl#C#!@Dx zxakRIAxKD!9ze^8N?&w`m68CsQiGEdUA%ot2>~nmVQ2}N!bn6n-xZ>m;hO)TU(72Q z48qq>w?EsxUFkaXYv%4nyy`ljWcWFJ=C~Q?4ftm_)EkZaudOydvl^lviR_qs4CxKz zRdpwZ8OSWfry-2&L8=kj6@*YID zSN%ho&jP0Aa4|~=pudi2MgeNdOhiZ$7AahxwUR~H%H%5w{csH52-ayX%H`|DVd4@< zx!UPrYuBicZZ3M>uNs7g6(Wag*3Oqf(^yArz;lFsspEEEABhG{~Y^wg#)D z-GgSJZpRJhYZuJzO5OpJ+{Sp$%4IHX8DT?)dtz*Er1G{&&+}kQ2`R8iud;$)i?t0` z3nT)os+qFR5WP<=Uj2cle#GQS`^}b!hKAji(g%#dw(JO8Ml@mC=!dGZdZ9)JCl8X| zAfS(!M`t#T`=f-)`H83&1s;PY2>01=^EJ3E z*KCrFobCkft-$73wic@3o3t5}EUC;o!(EyS6sC0!?_$N>H)NSDmPGeE-{g8&9pA`H zv6oqtyc#L4u|oTSz386v3xlr&>m(5No%2h1WU3(^3e`>364cF+xU*qg6^&2hPg4)( zVws==d4T@^z&6b%Um@VhcYV`RQ3w*AkCO3CIcH9~mlNa2-{+qTQoUE4cCrk;_;T~v zp=`D;30G_-q`Ou8*3SDupGkT{dBb?q(^ryv4C^My8sAf1jnGDT6)*~Hi0$j-&FShZ z1R@3Kcg2a#yhi+%22EQ-W zLpJzBYCCf?E;y~Q`LF|9wEk8-0HN6Q>Nrf^1i^+jYRfa39x6LPD=q8_Qj&YRs3{OV z{843C0z$gfD)+{o^5pQuG`&?>%9{5aI zcI(=Qw!q`GtJdt^>Z{h`UjK$XJ)eKUkl2s7Vk)wu#_Ey#7Ky)OP#%btuv1!K&S+2) zm0e`o>V>B`%t4_-URy9o*YJhRV6MO;?OeE+Z z!Z>|#A3qzE8Gz(4lYj{Sql|beYyJ9{h1T2EX3h6_4XyeX+w@zN+N_Sk0RiJp*T6J| ztt*n6^WxQ*H9anS`~fZWL$N&^MPo+zCC|Gj%w-39)RvZ22~_4xG4AK%=lS=nN&9n& z+ka)H-l_ojDadJ=bh`jp8Xe5%w-{;#lY*6?@L&}eTZkv7!{-sNgkez72xV!jr)8cn z5BOarscZxSSP$@^`UOGJnCE>v=DmyUFrVU#k@ee7dU7`cj0CwU^*Gjzj>wX(JE=a! zOK3>YQNu>ygqGW0{(UK@Ed9a{RiMe*M2qk#`>raZVdr~Y#JraI!dt$bGF#2(*-M>c z2hpbK;bHDzFaec`PRqaPeFm$8jVCwnzsLupevBj5uEN$%4(sNoz^R&O2BwnvO%9)QxW2wqP=I?{nGNUS zvqxitL3!^y5n*0>4T|~WtJB;ZAx#n_1A@oFP=yn$FV{qFvp+?xmyQ=%y=nWa|K}jQ z*O%H{>+1;pcadr0+rvt$S0`}>r|O*BIy^me?!utImwSPK-=-c}f?kiEjGj#4i5YNU zvJtuCy%HV&-immBJ!XEr{r>tG3ksrZ5hM+4{QCWROuD9g;k8ejU^~73@$m8b`nqPs XQ{nMN`SpJE1#Y-!$WCVl0rr0YQDEFA literal 0 HcmV?d00001 From 062be578697509822923854b4a4accb466041cac Mon Sep 17 00:00:00 2001 From: Thomas Decaux Date: Tue, 12 Apr 2022 18:03:15 +0200 Subject: [PATCH 23/32] Add type murmur3 into the lens fields list (#129029) * Add murmur3 type to fields list To fix #129007 , naive approach. * Add murmur3 type to fields list * Add murmur3 type to fields list * removed translations * disable field preview * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * change icon * add specific murmur3 message * fix test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Reuter --- .../__snapshots__/field_icon.test.tsx.snap | 2 +- .../kbn-react-field/src/field_icon/field_icon.tsx | 2 +- .../public/indexpattern_datasource/datapanel.tsx | 2 ++ .../public/indexpattern_datasource/field_item.tsx | 12 ++++++++++++ .../operations/definitions/cardinality.tsx | 1 + x-pack/plugins/lens/public/types.ts | 8 +++++++- .../lens/public/xy_visualization/xy_suggestions.ts | 1 + 7 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap b/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap index cd81705dd3c19..7328e2c61b961 100644 --- a/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap +++ b/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap @@ -139,7 +139,7 @@ exports[`FieldIcon renders known field types murmur3 is rendered 1`] = ` diff --git a/packages/kbn-react-field/src/field_icon/field_icon.tsx b/packages/kbn-react-field/src/field_icon/field_icon.tsx index be62a8df60274..621b8d0199d04 100644 --- a/packages/kbn-react-field/src/field_icon/field_icon.tsx +++ b/packages/kbn-react-field/src/field_icon/field_icon.tsx @@ -46,7 +46,7 @@ export const typeToEuiIconMap: Partial> = { ip: { iconType: 'tokenIP' }, ip_range: { iconType: 'tokenIP' }, // is a plugin's data type https://www.elastic.co/guide/en/elasticsearch/plugins/current/mapper-murmur3-usage.html - murmur3: { iconType: 'tokenFile' }, + murmur3: { iconType: 'tokenSearchType' }, number: { iconType: 'tokenNumber' }, number_range: { iconType: 'tokenNumber' }, histogram: { iconType: 'tokenHistogram' }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 4f8c492de3c17..70aacb2978955 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -82,6 +82,7 @@ const supportedFieldTypes = new Set([ 'document', 'geo_point', 'geo_shape', + 'murmur3', ]); const fieldTypeNames: Record = { @@ -94,6 +95,7 @@ const fieldTypeNames: Record = { histogram: i18n.translate('xpack.lens.datatypes.histogram', { defaultMessage: 'histogram' }), geo_point: i18n.translate('xpack.lens.datatypes.geoPoint', { defaultMessage: 'geo_point' }), geo_shape: i18n.translate('xpack.lens.datatypes.geoShape', { defaultMessage: 'geo_shape' }), + murmur3: i18n.translate('xpack.lens.datatypes.murmur3', { defaultMessage: 'murmur3' }), }; // Wrapper around buildEsQuery, handling errors (e.g. because a query can't be parsed) by diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 82b4a0aa33409..d657a22ca8d26 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -472,6 +472,18 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { ); + } else if (field.type === 'murmur3') { + return ( + <> + {panelHeader} + + + {i18n.translate('xpack.lens.indexPattern.fieldStatsMurmur3Limited', { + defaultMessage: `Summary information is not available for murmur3 fields.`, + })} + + + ); } else if (field.type === 'geo_point' || field.type === 'geo_shape') { return ( <> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index e65d89547d567..eabff6f3e7f4b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -35,6 +35,7 @@ const supportedTypes = new Set([ 'ip_range', 'date', 'date_range', + 'murmur3', ]); const SCALE = 'ratio'; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 8d2a5b881392e..2143cb1704bf5 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -476,7 +476,13 @@ export type DatasourceDimensionDropHandlerProps = DatasourceDimensionDropProp dropType: DropType; }; -export type FieldOnlyDataType = 'document' | 'ip' | 'histogram' | 'geo_point' | 'geo_shape'; +export type FieldOnlyDataType = + | 'document' + | 'ip' + | 'histogram' + | 'geo_point' + | 'geo_shape' + | 'murmur3'; export type DataType = 'string' | 'number' | 'date' | 'boolean' | FieldOnlyDataType; // An operation represents a column in a table, not any information diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index 05bdb4fd65c88..671e225ef894b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -32,6 +32,7 @@ const columnSortOrder = { histogram: 6, geo_point: 7, geo_shape: 8, + murmur3: 9, }; /** From b7dc8f0e9c27dcebc2e1dc28770cf95c2c75e544 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Tue, 12 Apr 2022 12:21:23 -0400 Subject: [PATCH 24/32] Use a link to enable shift/right click of rule names in the popover (#128850) --- .../body/renderers/formatted_field.tsx | 2 + .../renderers/formatted_field_helpers.tsx | 77 ++++++++++++++----- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index f87c7ae9e58d6..ebd0fb974f3aa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -185,6 +185,8 @@ const FormattedFieldValueComponent: React.FC<{ eventId={eventId} fieldName={fieldName} isDraggable={isDraggable} + isButton={isButton} + onClick={onClick} linkValue={linkValue} title={title} truncate={truncate} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index 02088b2e7d0cc..04b69cd5fd3ff 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -32,17 +32,21 @@ import { useFormatUrl } from '../../../../../common/components/link_to'; import { useKibana } from '../../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../../common/constants'; import { LinkAnchor } from '../../../../../common/components/links'; +import { GenericLinkButton } from '../../../../../common/components/links/helpers'; const EventModuleFlexItem = styled(EuiFlexItem)` width: 100%; `; interface RenderRuleNameProps { + children?: React.ReactNode; Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; contextId: string; eventId: string; fieldName: string; isDraggable: boolean; + isButton?: boolean; + onClick?: () => void; linkValue: string | null | undefined; truncate?: boolean; title?: string; @@ -50,11 +54,14 @@ interface RenderRuleNameProps { } export const RenderRuleName: React.FC = ({ + children, Component, contextId, eventId, fieldName, isDraggable, + isButton, + onClick, linkValue, truncate, title, @@ -64,11 +71,6 @@ export const RenderRuleName: React.FC = ({ const ruleId = linkValue; const { search } = useFormatUrl(SecurityPageName.rules); const { navigateToApp, getUrlForApp } = useKibana().services.application; - const content = truncate ? ( - {value} - ) : ( - value - ); const goToRuleDetails = useCallback( (ev) => { @@ -90,24 +92,59 @@ export const RenderRuleName: React.FC = ({ [getUrlForApp, ruleId, search] ); const id = `event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`; - - if (isString(value) && ruleName.length > 0 && ruleId != null) { - const link = Component ? ( - - {title ?? value} - + const link = useMemo(() => { + const content = truncate ? ( + {value} ) : ( - - {content} - + value ); + if (isButton) { + return ( + + {children} + + ); + } else if (Component) { + return ( + + {title ?? value} + + ); + } else { + return ( + + {content} + + ); + } + }, [ + Component, + children, + fieldName, + goToRuleDetails, + href, + isButton, + onClick, + ruleName, + title, + truncate, + value, + ]); + if (isString(value) && ruleName.length > 0 && ruleId != null) { return isDraggable ? ( Date: Tue, 12 Apr 2022 17:32:36 +0100 Subject: [PATCH 25/32] [ML] Fix Single Metric Viewer chart failing to load if no points during calendar event (#130000) * [ML] Fix Single Metric Viewer chart load if no points during calendar event * [ML] Type fix --- .../_timeseriesexplorer.scss | 1 - .../timeseries_chart/timeseries_chart.js | 18 ++++-- .../get_focus_data.ts | 6 +- .../timeseriesexplorer_utils.d.ts | 6 +- .../timeseriesexplorer_utils.js | 60 ++++++++++++++++--- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss b/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss index 63cb8a57adba6..dc9178f7062f2 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss @@ -131,7 +131,6 @@ stroke-width: 1px; stroke: $euiColorDarkShade; fill: $euiColorLightShade; - pointer-events: none; } .forecast { diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 9a95cf787c70d..8daf2e8f86891 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -667,7 +667,8 @@ class TimeseriesChartIntl extends Component { return d.lower; } } - return metricValue; + // metricValue is undefined for scheduled events when there is no source data. + return metricValue || 0; }); yMax = d3.max(combinedData, (d) => { let metricValue = d.value; @@ -675,7 +676,8 @@ class TimeseriesChartIntl extends Component { // If an anomaly coincides with a gap in the data, use the anomaly actual value. metricValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; } - return d.upper !== undefined ? Math.max(metricValue, d.upper) : metricValue; + // metricValue is undefined for scheduled events when there is no source data. + return d.upper !== undefined ? Math.max(metricValue, d.upper) : metricValue || 0; }); if (yMax === yMin) { @@ -701,6 +703,7 @@ class TimeseriesChartIntl extends Component { // TODO needs revisiting to be a more robust normalization yMax += Math.abs(yMax - yMin) * ((maxLevel + 1) / 5); } + this.focusYScale.domain([yMin, yMax]); } else { // Display 10 unlabelled ticks. @@ -835,6 +838,10 @@ class TimeseriesChartIntl extends Component { scheduledEventMarkers .enter() .append('rect') + .on('mouseover', function (d) { + showFocusChartTooltip(d, this); + }) + .on('mouseout', () => hideFocusChartTooltip()) .attr('width', LINE_CHART_ANOMALY_RADIUS * 2) .attr('height', SCHEDULED_EVENT_SYMBOL_HEIGHT) .attr('class', 'scheduled-event-marker') @@ -844,7 +851,10 @@ class TimeseriesChartIntl extends Component { // Update all markers to new positions. scheduledEventMarkers .attr('x', (d) => this.focusXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) - .attr('y', (d) => this.focusYScale(d.value) - 3); + .attr('y', (d) => { + const focusYValue = this.focusYScale(d.value); + return isNaN(focusYValue) ? -focusHeight - 3 : focusYValue - 3; + }); // Plot any forecast data in scope. if (focusForecastData !== undefined) { @@ -1652,7 +1662,7 @@ class TimeseriesChartIntl extends Component { valueAccessor: 'prediction', }); } else { - if (marker.value !== undefined) { + if (marker.value !== undefined && marker.value !== null) { tooltipData.push({ label: i18n.translate( 'xpack.ml.timeSeriesExplorer.timeSeriesChart.withoutAnomalyScore.valueLabel', diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts index d4548a43f3f2b..80169d8914682 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts @@ -148,7 +148,11 @@ export function getFocusData( modelPlotEnabled, functionDescription ); - focusChartData = processScheduledEventsForChart(focusChartData, scheduledEvents); + focusChartData = processScheduledEventsForChart( + focusChartData, + scheduledEvents, + focusAggregationInterval + ); const refreshFocusData: FocusData = { scheduledEvents, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts index 0ff84931a8ba3..11da0acb506f6 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts @@ -21,7 +21,11 @@ export function processDataForFocusAnomalies( functionDescription: any ): any; -export function processScheduledEventsForChart(chartData: any, scheduledEvents: any): any; +export function processScheduledEventsForChart( + chartData: any, + scheduledEvents: any, + aggregationInterval: any +): any; export function findNearestChartPointToTime(chartData: any, time: any): any; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js index 588d19cfe9a63..5a508cbf4bb41 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js @@ -205,16 +205,44 @@ export function processDataForFocusAnomalies( // Adds a scheduledEvents property to any points in the chart data set // which correspond to times of scheduled events for the job. -export function processScheduledEventsForChart(chartData, scheduledEvents) { +export function processScheduledEventsForChart(chartData, scheduledEvents, aggregationInterval) { if (scheduledEvents !== undefined) { + const timesToAddPointsFor = []; + + // Iterate through the scheduled events making sure we have a chart point for each event. + const intervalMs = aggregationInterval.asMilliseconds(); + let lastChartDataPointTime = undefined; + if (chartData !== undefined && chartData.length > 0) { + lastChartDataPointTime = chartData[chartData.length - 1].date.getTime(); + } + + // In case there's no chart data/sparse data during these scheduled events + // ensure we add chart points at every aggregation interval for these scheduled events. + let sortRequired = false; each(scheduledEvents, (events, time) => { - const chartPoint = findNearestChartPointToTime(chartData, time); - if (chartPoint !== undefined) { - // Note if the scheduled event coincides with an absence of the underlying metric data, - // we don't worry about plotting the event. - chartPoint.scheduledEvents = events; + const exactChartPoint = findChartPointForScheduledEvent(chartData, +time); + + if (exactChartPoint !== undefined) { + exactChartPoint.scheduledEvents = events; + } else { + const timeToAdd = Math.floor(time / intervalMs) * intervalMs; + if (timesToAddPointsFor.indexOf(timeToAdd) === -1 && timeToAdd !== lastChartDataPointTime) { + const pointToAdd = { + date: new Date(timeToAdd), + value: null, + scheduledEvents: events, + }; + + chartData.push(pointToAdd); + sortRequired = true; + } } }); + + // Sort chart data by time if extra points were added at the end of the array for scheduled events. + if (sortRequired === true) { + chartData.sort((a, b) => a.date.getTime() - b.date.getTime()); + } } return chartData; @@ -240,12 +268,12 @@ export function findNearestChartPointToTime(chartData, time) { // grab the current and previous items and compare the time differences let foundItem; for (let i = 0; i < chartData.length; i++) { - const itemTime = chartData[i].date.getTime(); + const itemTime = chartData[i]?.date?.getTime(); if (itemTime > time) { const item = chartData[i]; const previousItem = chartData[i - 1]; - const diff1 = Math.abs(time - previousItem.date.getTime()); + const diff1 = Math.abs(time - previousItem?.date?.getTime()); const diff2 = Math.abs(time - itemTime); // foundItem should be the item with a date closest to bucketTime @@ -300,6 +328,22 @@ export function findChartPointForAnomalyTime(chartData, anomalyTime, aggregation return chartPoint; } +export function findChartPointForScheduledEvent(chartData, eventTime) { + let chartPoint; + if (chartData === undefined) { + return chartPoint; + } + + for (let i = 0; i < chartData.length; i++) { + if (chartData[i].date.getTime() === eventTime) { + chartPoint = chartData[i]; + break; + } + } + + return chartPoint; +} + export function calculateAggregationInterval(bounds, bucketsTarget, jobs, selectedJob) { // Aggregation interval used in queries should be a function of the time span of the chart // and the bucket span of the selected job(s). From d41fb22d923244ffb39a4523970ca9f76748bd5d Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 12 Apr 2022 18:14:23 +0100 Subject: [PATCH 26/32] chore(NA): upgrades rules_node_js to v5.4.0 (#130021) --- WORKSPACE.bazel | 4 ++-- package.json | 2 +- src/dev/bazel/pkg_npm_types.bzl | 2 +- yarn.lock | 18 +++++++++--------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index ff9014214d4c0..8100e93bcd4df 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -10,8 +10,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Fetch Node.js rules http_archive( name = "build_bazel_rules_nodejs", - sha256 = "523da2d6b50bc00eaf14b00ed28b1a366b3ab456e14131e9812558b26599125c", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.3.1/rules_nodejs-5.3.1.tar.gz"], + sha256 = "2b2004784358655f334925e7eadc7ba80f701144363df949b3293e1ae7a2fb7b", + urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.4.0/rules_nodejs-5.4.0.tar.gz"], ) # Build Node.js rules dependencies diff --git a/package.json b/package.json index 76948decf33a7..ff298329977ce 100644 --- a/package.json +++ b/package.json @@ -456,7 +456,7 @@ "@babel/traverse": "^7.17.3", "@babel/types": "^7.17.0", "@bazel/ibazel": "^0.16.2", - "@bazel/typescript": "5.3.1", + "@bazel/typescript": "5.4.0", "@cypress/code-coverage": "^3.9.12", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.6.0", diff --git a/src/dev/bazel/pkg_npm_types.bzl b/src/dev/bazel/pkg_npm_types.bzl index f90d60252af2d..392a8185d9563 100644 --- a/src/dev/bazel/pkg_npm_types.bzl +++ b/src/dev/bazel/pkg_npm_types.bzl @@ -6,7 +6,7 @@ # Side Public License, v 1. # -load("@npm//@bazel/typescript/internal:ts_config.bzl", "TsConfigInfo") +load("@rules_nodejs//nodejs/private:ts_config.bzl", "TsConfigInfo") load("@build_bazel_rules_nodejs//:providers.bzl", "run_node", "LinkablePackageInfo", "DeclarationInfo", "declaration_info") load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "module_mappings_aspect") diff --git a/yarn.lock b/yarn.lock index bf1639a09b849..399a31111ada9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1228,21 +1228,21 @@ resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.16.2.tgz#05dd7f06659759fda30f87b15534f1e42f1201bb" integrity sha512-KgqAWMH0emL6f3xH6nqyTryoBMqlJ627LBIe9PT1PRRQPz2FtHib3FIHJPukp1slzF3hJYZvdiVwgPnHbaSOOA== -"@bazel/typescript@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-5.3.1.tgz#f996abebfa57a29a170a83d0c84780286f06cb72" - integrity sha512-fP0fGzLbsOVdbAn6mlrX1LBAT0TJ+S0VVOap9SSSXCb2PknnKEHZupUww/raVSczy+cqcd/Vfpllzm/AcdOmyw== +"@bazel/typescript@5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-5.4.0.tgz#0a24e8d47152728f71b26b73adc3913645e5309e" + integrity sha512-AWmS9HcnjqGsPQv5lnqecA8xof+d4ChpbI/Z6aiSM0qOemc8e1WT9wb1VzM6g6hRfV5foWyIHt0VhTonTsPhYw== dependencies: - "@bazel/worker" "5.3.1" + "@bazel/worker" "5.4.0" protobufjs "6.8.8" semver "5.6.0" source-map-support "0.5.9" tsutils "3.21.0" -"@bazel/worker@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@bazel/worker/-/worker-5.3.1.tgz#87d850fdfc22cde24f380961f66daa5cbfd4acf7" - integrity sha512-KB+Xs198NLhN01zhlOPr/VEmz/tzwGq/pI1gdnCTLDMTCspV6LnJaIeGgicXuKrzz0V1LhvlvsI5lqJt9yuGYw== +"@bazel/worker@5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@bazel/worker/-/worker-5.4.0.tgz#ce50e34951ccf7233b651f0be665bbd43d7ed7f1" + integrity sha512-Rtkol9WoYs0NCZl1wt4Q8T7Rs0wbQjIvFiJzVeeoC/00TG0e/qi4mYE5iQwUJmO8st3N8VnaWz2N31UHc6yhRA== dependencies: google-protobuf "^3.6.1" From 8022b0aba7149b8d79f30099f55baadcacae7c62 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 12 Apr 2022 19:44:37 +0200 Subject: [PATCH 27/32] Add e2e for the apm integration policy form (#129860) * Grant fleet privileges and capabilities to power user * Add e2e for integration policy * Fix selector for Tail-based * Remove e2e releated to Fleet code * Decouple tests * Clean up assertion * Fix flaky test --- .buildkite/pipelines/flaky_tests/groups.json | 6 +- .../integration_policy.spec.ts | 138 ++++++++++++++++++ .../apm_server_not_installed.ts | 44 ------ .../settings_definition/apm_settings.ts | 3 +- .../settings_form/form_row_setting.tsx | 5 + .../apm_policy_form/typings.ts | 1 + .../roles/power_user_role.ts | 1 + 7 files changed, 152 insertions(+), 46 deletions(-) create mode 100644 x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts delete mode 100644 x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.ts diff --git a/.buildkite/pipelines/flaky_tests/groups.json b/.buildkite/pipelines/flaky_tests/groups.json index aa061af00bd6c..0a5d0d488cd5d 100644 --- a/.buildkite/pipelines/flaky_tests/groups.json +++ b/.buildkite/pipelines/flaky_tests/groups.json @@ -25,6 +25,10 @@ "key": "xpack/cypress/fleet_cypress", "name": "Fleet - Cypress" }, + { + "key": "xpack/cypress/apm_cypress", + "name": "APM - Cypress" + }, { "key": "xpack/cigroup", "name": "Default CI Group", @@ -43,4 +47,4 @@ "name": "Default Accessibility" } ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts new file mode 100644 index 0000000000000..5cdf21d7ca9de --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts @@ -0,0 +1,138 @@ +/* + * 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. + */ + +const integrationsPoliciesPath = '/app/integrations/detail/apm/policies'; +const policyName = 'apm-integration'; +const description = 'integration description'; +const host = 'myhost:8200'; +const url = 'http://myhost:8200'; + +const policyFormFields = [ + { + selector: 'packagePolicyNameInput', + value: 'apm-integration', + required: true, + }, + { + selector: 'packagePolicyDescriptionInput', + value: 'apm policy descrtiption', + required: false, + }, + { + selector: 'packagePolicyHostInput', + value: 'myhost:8200', + required: true, + }, + { + selector: 'packagePolicyUrlInput', + value: 'http://myhost:8200', + required: true, + }, +]; + +const apisToIntercept = [ + { + endpoint: 'api/fleet/agent_policies*', + name: 'fleetAgentPolicies', + method: 'POST', + }, + { + endpoint: 'api/fleet/agent_status*', + name: 'fleetAgentStatus', + method: 'GET', + }, + { + endpoint: 'api/fleet/package_policies', + name: 'fleetPackagePolicies', + method: 'POST', + }, +]; + +describe('when navigating to integration page', () => { + beforeEach(() => { + const integrationsPath = '/app/integrations/browse'; + + cy.loginAsPowerUser(); + cy.visit(integrationsPath); + + // open integration policy form + cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); + cy.contains('Elastic APM in Fleet').click(); + cy.contains('a', 'APM integration').click(); + cy.get('[data-test-subj="addIntegrationPolicyButton"]').click(); + }); + + it('checks validators for required fields', () => { + const requiredFields = policyFormFields.filter((field) => field.required); + + requiredFields.map((field) => { + cy.get(`[data-test-subj="${field.selector}"`).clear(); + cy.get('[data-test-subj="createPackagePolicySaveButton"').should( + 'be.disabled' + ); + cy.get(`[data-test-subj="${field.selector}"`).type(field.value); + }); + }); + + it('adds a new policy without agent', () => { + apisToIntercept.map(({ endpoint, method, name }) => { + cy.intercept(method, endpoint).as(name); + }); + + cy.url().should('include', 'app/fleet/integrations/apm/add-integration'); + policyFormFields.map((field) => { + cy.get(`[data-test-subj="${field.selector}"`).clear().type(field.value); + }); + cy.contains('Save and continue').click(); + cy.wait('@fleetAgentPolicies'); + cy.wait('@fleetAgentStatus'); + cy.wait('@fleetPackagePolicies'); + + cy.get('[data-test-subj="confirmModalCancelButton').click(); + + cy.url().should('include', '/app/integrations/detail/apm/policies'); + cy.contains(policyName); + }); + + it('updates an existing policy', () => { + apisToIntercept.map(({ endpoint, method, name }) => { + cy.intercept(method, endpoint).as(name); + }); + + policyFormFields.map((field) => { + cy.get(`[data-test-subj="${field.selector}"`) + .clear() + .type(`${field.value}-new`); + }); + + cy.contains('Save and continue').click(); + cy.wait('@fleetAgentPolicies'); + cy.wait('@fleetAgentStatus'); + cy.wait('@fleetPackagePolicies'); + + cy.get('[data-test-subj="confirmModalCancelButton').click(); + cy.contains(`${policyName}-new`).click(); + + policyFormFields.map((field) => { + cy.get(`[data-test-subj="${field.selector}"`) + .clear() + .type(`${field.value}-updated`); + }); + cy.contains('Save integration').click(); + cy.contains(`${policyName}-updated`); + }); + + it('should display Tail-based section on latest version', () => { + cy.visit('/app/fleet/integrations/apm/add-integration'); + cy.contains('Tail-based sampling').should('exist'); + }); + + it('should hide Tail-based section for 8.0.0 apm package', () => { + cy.visit('/app/fleet/integrations/apm-8.0.0/add-integration'); + cy.contains('Tail-based sampling').should('not.exist'); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.ts deleted file mode 100644 index 354ffa5e42e18..0000000000000 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/integration_settings/apm_server_not_installed.ts +++ /dev/null @@ -1,44 +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. - */ - -const integrationsPath = '/app/integrations/browse'; - -describe('when navigating to the integrations browse page', () => { - beforeEach(() => { - cy.loginAsReadOnlyUser(); - cy.visit(integrationsPath); - }); - - it('should display Elastic APM integration option', () => { - cy.get('[data-test-subj="integration-card:epr:apm:featured').should( - 'exist' - ); - cy.contains('Elastic APM'); - }); - - describe('when clicking on the Elastic APM option but Fleet is not installed', () => { - it('should display Elastic APM in Fleet tab', () => { - cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); - cy.get('[aria-selected="true"]').contains('Elastic APM in Fleet'); - cy.contains('Elastic APM now available in Fleet!'); - cy.contains('APM integration'); - }); - - it('should display no APM server detected when checking the apm server status', () => { - cy.intercept('POST', '/api/home/hits_status', { - count: 0, - }).as('hitsStatus'); - - cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); - cy.contains('Check APM Server status').click(); - cy.wait('@hitsStatus'); - cy.contains( - 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.' - ); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts index 03032a6160502..09fb0d03528ae 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts @@ -31,7 +31,7 @@ export function getApmSettings(): SettingsRow[] { 'Host defines the host and port the server is listening on. URL is the unchangeable, publicly reachable server URL for deployments on Elastic Cloud or ECK.', } ), - + dataTestSubj: 'packagePolicyHostInput', required: true, }, { @@ -44,6 +44,7 @@ export function getApmSettings(): SettingsRow[] { defaultMessage: 'URL', } ), + dataTestSubj: 'packagePolicyUrlInput', required: true, }, { diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/form_row_setting.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/form_row_setting.tsx index 56ba3c6d47eb5..7a5e073fed337 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/form_row_setting.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_form/form_row_setting.tsx @@ -45,6 +45,7 @@ export function FormRowSetting({ row, value, onChange, isDisabled }: Props) { case 'boolean': { return ( : undefined} @@ -70,6 +72,7 @@ export function FormRowSetting({ row, value, onChange, isDisabled }: Props) { case 'area': { return ( { @@ -82,6 +85,7 @@ export function FormRowSetting({ row, value, onChange, isDisabled }: Props) { case 'integer': { return ( { @@ -96,6 +100,7 @@ export function FormRowSetting({ row, value, onChange, isDisabled }: Props) { : []; return ( Date: Tue, 12 Apr 2022 10:53:42 -0700 Subject: [PATCH 28/32] [Security Solution] More Ransomware exceptionable fields (#130039) --- .../exceptions/exceptionable_endpoint_fields.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json index 5c483e83a5ae0..a4babfaaa538f 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json @@ -117,7 +117,15 @@ "user.id", "Ransomware.feature", "Ransomware.files.data", + "Ransomware.files.entropy", + "Ransomware.files.extension", + "Ransomware.files.operation", + "Ransomware.files.path", "Ransomware.child_processes.files.data", + "Ransomware.child_processes.files.entropy", + "Ransomware.child_processes.files.extension", + "Ransomware.child_processes.files.operation", + "Ransomware.child_processes.files.path", "Memory_protection.feature", "Memory_protection.self_injection", "dll.path", From 095595379986c515c550ce8b8a0a88e4f3050db2 Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 12 Apr 2022 11:00:15 -0700 Subject: [PATCH 29/32] Upgrade EUI to v54.0.0 (#129653) * Upgrade EUI to v54.0.0 * [Discover] Remove deprecated closePopover call - for closeCellPopover ref API * [Lens] Remove deprecated closePopover call - for closeCellPopover ref API * [Security/Timelines] Remove deprecated closePopover call - for closeCellPopover ref API * [Security/Timeline] Update Timeline datagrid to accept/pass `visibleCellActions` prop + update Security to show 3 visible cell actions * [APM] Account for removed EUI theme avatar sizes * Update emotion dependencies to latest * Remove styles from being rendered in emotion serializer * Update snapshots affected by emotion serializer `includeStyles: false` change * Update snapshot changes caused by EuiFormControlLayout changes * Update snapshot changes caused by EuiAvatar CSS-in-JS conversion * consolidate yarn.lock * [Spaces] Fix failing test due to new EuiAvatar emotion wrapper - which, due to mount() causes .first() to no longer work as expected - targeting .last() instead gets the actual div element which works * [Security] Fix cell expansion popover actions - EUI added 2 `.euiPopoverFooter`s for overflowing cell actions, and Security's CSS to hide the first 2 cell actions (replaced by their own custom cell actions) was unintentionally affecting other actions * Clean up spaces test snapshots * [Security feedback] Revert 793d208 and hard-code visibleCellActions Co-authored-by: Greg Thompson Co-authored-by: Joe Portner --- package.json | 12 +- .../elastic_agent_card.test.tsx.snap | 67 +++- .../__snapshots__/no_data_card.test.tsx.snap | 35 +- .../icon_button_group.test.tsx.snap | 233 ++++++++++---- packages/kbn-test/jest-preset.js | 2 +- packages/kbn-test/src/jest/setup/emotion.js | 14 + src/dev/license_checker/config.ts | 2 +- .../url/__snapshots__/url.test.tsx.snap | 2 +- .../discover_grid_cell_actions.test.tsx | 2 - .../__snapshots__/cron_editor.test.tsx.snap | 304 ++++-------------- .../components/table_vis_basic.test.tsx | 3 +- .../public/components/table_vis_basic.tsx | 16 +- .../public/components/table_vis_columns.tsx | 11 +- .../app/service_map/cytoscape_options.ts | 4 +- .../dropdown_filter.stories.storyshot | 10 +- .../extended_template.stories.storyshot | 4 +- .../date_format.stories.storyshot | 6 +- .../number_format.stories.storyshot | 6 +- .../color_manager.stories.storyshot | 109 ++++++- .../color_picker.stories.storyshot | 4 + .../workpad_table.stories.storyshot | 1 + .../saved_elements_modal.stories.storyshot | 2 +- .../text_style_picker.stories.storyshot | 4 +- .../__snapshots__/edit_var.stories.storyshot | 4 + ...orkpad_filters.component.stories.storyshot | 10 +- .../extended_template.stories.storyshot | 12 +- .../extended_template.stories.storyshot | 8 +- .../__snapshots__/settings.test.tsx.snap | 1 + .../autoplay_settings.stories.storyshot | 3 + .../components/columns.tsx | 11 +- .../components/table_basic.tsx | 7 +- .../nav_control/nav_control_service.test.ts | 2 +- .../public/common/components/page/index.tsx | 16 +- .../lib/cell_actions/default_cell_actions.tsx | 5 +- .../__snapshots__/index.test.tsx.snap | 120 +++---- .../spaces_grid_page.test.tsx.snap | 242 -------------- .../spaces_grid/spaces_grid_page.test.tsx | 26 +- .../space_avatar_internal.test.tsx.snap | 68 ---- .../space_avatar_internal.test.tsx | 53 ++- .../space_list/space_list_internal.test.tsx | 2 +- .../common/types/timeline/columns/index.tsx | 2 + .../public/components/t_grid/body/index.tsx | 7 + yarn.lock | 146 ++++----- 43 files changed, 692 insertions(+), 906 deletions(-) create mode 100644 packages/kbn-test/src/jest/setup/emotion.js delete mode 100644 x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_page.test.tsx.snap delete mode 100644 x-pack/plugins/spaces/public/space_avatar/__snapshots__/space_avatar_internal.test.tsx.snap diff --git a/package.json b/package.json index ff298329977ce..803159853514b 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.2.0-canary.2", "@elastic/ems-client": "8.2.0", - "@elastic/eui": "53.0.1", + "@elastic/eui": "54.0.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -117,10 +117,10 @@ "@elastic/request-crypto": "2.0.1", "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.6.0", - "@emotion/cache": "^11.4.0", - "@emotion/css": "^11.4.0", - "@emotion/react": "^11.4.0", - "@emotion/serialize": "^1.0.2", + "@emotion/cache": "^11.7.1", + "@emotion/css": "^11.9.0", + "@emotion/react": "^11.9.0", + "@emotion/serialize": "^1.0.3", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.4", "@hapi/cookie": "^11.0.2", @@ -466,7 +466,7 @@ "@elastic/makelogs": "^6.0.0", "@elastic/synthetics": "^1.0.0-beta.22", "@emotion/babel-preset-css-prop": "^11.2.0", - "@emotion/jest": "^11.3.0", + "@emotion/jest": "^11.9.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", "@jest/console": "^26.6.2", diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap index 828955b8489dd..b639f7e63610d 100644 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap +++ b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap @@ -1,11 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ElasticAgentCard renders 1`] = ` -.emotion-0 { - max-width: 400px; - margin-inline: auto; -} - + + + , + ], + }, + } + } + isStringTag={false} + serialized={ + Object { + "map": undefined, + "name": "1hu4pg0-EuiCard", + "next": undefined, + "styles": "max-width:400px;margin-inline:auto;;label:EuiCard;", + "toString": [Function], + } + } + />
is rendered 1`] = ` -.emotion-0.euiButtonGroupButton { - background-color: #FFF; - border: 1px solid #D3DAE6!important; -} - -.emotion-0.euiButtonGroupButton:first-of-type { - border-top-left-radius: 6px!important; - border-bottom-left-radius: 6px!important; -} - -.emotion-0.euiButtonGroupButton:last-of-type { - border-top-right-radius: 6px!important; - border-bottom-right-radius: 6px!important; -} - is rendered 1`] = ` > is rendered 1`] = ` size="m" title="Text" > - + + + + + , + , + , + , + ], + }, + } + } + isStringTag={false} + serialized={ + Object { + "map": undefined, + "name": "iuv015-EuiButtonGroup", + "next": undefined, + "styles": "&.euiButtonGroupButton{background-color:#FFF;border:1px solid #D3DAE6 !important;&:first-of-type{border-top-left-radius:6px !important;border-bottom-left-radius:6px !important;}&:last-of-type{border-top-right-radius:6px !important;border-bottom-right-radius:6px !important;}};;label:EuiButtonGroup;", + "toString": [Function], + } + } + /> + - - + + + +
diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index fd14c973683f7..1021283936495 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -84,7 +84,7 @@ module.exports = { snapshotSerializers: [ '/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts', '/node_modules/enzyme-to-json/serializer', - '/node_modules/@emotion/jest/serializer', + '/node_modules/@kbn/test/target_node/jest/setup/emotion.js', ], // The test environment that will be used for testing diff --git a/packages/kbn-test/src/jest/setup/emotion.js b/packages/kbn-test/src/jest/setup/emotion.js new file mode 100644 index 0000000000000..dc135f4e56c5a --- /dev/null +++ b/packages/kbn-test/src/jest/setup/emotion.js @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { createSerializer } from '@emotion/jest'; + +module.exports = createSerializer({ + classNameReplacer: (className) => className, + includeStyles: false, +}); diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index cb1c61b0a47e0..2a067a54d472e 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -77,6 +77,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.2.0': ['Elastic License 2.0'], - '@elastic/eui@53.0.1': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@54.0.0': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap b/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap index c37e1d28dab4c..a90cf78b4f46f 100644 --- a/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap +++ b/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap @@ -29,7 +29,7 @@ exports[`UrlFormatEditor should render normally 1`] = ` class="euiFormControlLayout__childrenWrapper" >
@@ -769,7 +754,7 @@ exports[`CronEditor is rendered with a DAY frequency 1`] = `
@@ -1794,7 +1759,7 @@ exports[`CronEditor is rendered with a HOUR frequency 1`] = ` >
@@ -3011,7 +2956,7 @@ exports[`CronEditor is rendered with a MINUTE frequency 1`] = ` >
@@ -4017,7 +3942,7 @@ exports[`CronEditor is rendered with a MONTH frequency 1`] = ` >
@@ -4944,7 +4849,7 @@ exports[`CronEditor is rendered with a MONTH frequency 1`] = `
@@ -6084,7 +5969,7 @@ exports[`CronEditor is rendered with a WEEK frequency 1`] = ` >
@@ -6867,7 +6732,7 @@ exports[`CronEditor is rendered with a WEEK frequency 1`] = `
@@ -8176,7 +8021,7 @@ exports[`CronEditor is rendered with a YEAR frequency 1`] = ` >
@@ -8968,7 +8793,7 @@ exports[`CronEditor is rendered with a YEAR frequency 1`] = `
{ sortedRows, table.formattedColumns, uiStateProps.columnsWidth, - props.fireEvent + props.fireEvent, + undefined ); const { onSort } = comp.find('EuiDataGrid').prop('sorting'); diff --git a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx index adfeb9f5f614c..300435997129e 100644 --- a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx +++ b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx @@ -6,8 +6,14 @@ * Side Public License, v 1. */ -import React, { memo, useCallback, useMemo } from 'react'; -import { EuiDataGrid, EuiDataGridProps, EuiDataGridSorting, EuiTitle } from '@elastic/eui'; +import React, { memo, useCallback, useMemo, useRef } from 'react'; +import { + EuiDataGrid, + EuiDataGridProps, + EuiDataGridRefProps, + EuiDataGridSorting, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; @@ -34,6 +40,8 @@ export const TableVisBasic = memo( title, uiStateProps: { columnsWidth, sort, setColumnsWidth, setSort }, }: TableVisBasicProps) => { + const dataGridRef = useRef(null); + const { columns, rows, formattedColumns } = table; // custom sorting is in place until the EuiDataGrid sorting gets rid of flaws -> https://github.com/elastic/eui/issues/4108 @@ -65,7 +73,8 @@ export const TableVisBasic = memo( sortedRows, formattedColumns, columnsWidth, - fireEvent + fireEvent, + dataGridRef.current?.closeCellPopover ); // Pagination config @@ -158,6 +167,7 @@ export const TableVisBasic = memo( sorting={{ columns: sortingColumns, onSort }} onColumnResize={onColumnResize} minSizeForControls={1} + ref={dataGridRef} /> ); diff --git a/src/plugins/vis_types/table/public/components/table_vis_columns.tsx b/src/plugins/vis_types/table/public/components/table_vis_columns.tsx index 17d32264998ea..ce323c7c38611 100644 --- a/src/plugins/vis_types/table/public/components/table_vis_columns.tsx +++ b/src/plugins/vis_types/table/public/components/table_vis_columns.tsx @@ -30,7 +30,8 @@ export const createGridColumns = ( rows: DatatableRow[], formattedColumns: FormattedColumns, columnsWidth: TableVisUiState['colWidth'], - fireEvent: IInterpreterRenderHandlers['event'] + fireEvent: IInterpreterRenderHandlers['event'], + closeCellPopover?: Function ) => { const onFilterClick = (data: FilterCellData, negate: boolean) => { fireEvent({ @@ -55,7 +56,7 @@ export const createGridColumns = ( const formattedColumn = formattedColumns[col.id]; const cellActions = formattedColumn.filterable ? [ - ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { const rowValue = rows[rowIndex][columnId]; if (rowValue == null) return null; const cellContent = formattedColumn.formatter.convert(rowValue); @@ -82,7 +83,7 @@ export const createGridColumns = ( data-test-subj="tbvChartCell__filterForCellValue" onClick={() => { onFilterClick({ row: rowIndex, column: colIndex, value: rowValue }, false); - closePopover?.(); + closeCellPopover?.(); }} iconType="plusInCircle" > @@ -90,7 +91,7 @@ export const createGridColumns = ( ); }, - ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { const rowValue = rows[rowIndex][columnId]; if (rowValue == null) return null; const cellContent = formattedColumn.formatter.convert(rowValue); @@ -116,7 +117,7 @@ export const createGridColumns = ( aria-label={filterOutAriaLabel} onClick={() => { onFilterClick({ row: rowIndex, column: colIndex, value: rowValue }, true); - closePopover?.(); + closeCellPopover?.(); }} iconType="minusInCircle" > diff --git a/x-pack/plugins/apm/public/components/app/service_map/cytoscape_options.ts b/x-pack/plugins/apm/public/components/app/service_map/cytoscape_options.ts index e137f2dbb0f78..24d605a85b69e 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/cytoscape_options.ts +++ b/x-pack/plugins/apm/public/components/app/service_map/cytoscape_options.ts @@ -98,7 +98,7 @@ const zIndexEdgeHighlight = 110; const zIndexEdgeHover = 120; export const getNodeHeight = (theme: EuiTheme): number => - parseInt(theme.eui.avatarSizing.l.size, 10); + parseInt(theme.eui.euiSizeXXL, 10); function isService(el: cytoscape.NodeSingular) { return el.data(SERVICE_NAME) !== undefined; @@ -156,7 +156,7 @@ const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => { 'text-max-width': '200px', 'text-valign': 'bottom', 'text-wrap': 'ellipsis', - width: theme.eui.avatarSizing.l.size, + width: theme.eui.euiSizeXXL, 'z-index': zIndexNode, }, }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot index 0ded42439fb95..6ddc57397fd5d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot @@ -11,7 +11,7 @@ exports[`Storyshots renderers/DropdownFilter default 1`] = ` className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -518,12 +538,24 @@ Array [ className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -561,12 +593,24 @@ Array [ className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -604,12 +648,24 @@ Array [ className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -647,12 +703,24 @@ Array [ className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -690,12 +758,24 @@ Array [ className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -733,12 +813,24 @@ Array [ className="euiFormControlLayout__childrenWrapper" > +
+ + + +
@@ -785,6 +877,7 @@ Array [ className="euiFormControlLayout__childrenWrapper" > void) | undefined, alignments: Record, headerRowHeight: 'auto' | 'single' | 'custom', - headerRowLines: number + headerRowLines: number, + closeCellPopover?: Function ) => { const columnsReverseLookup = table.columns.reduce< Record @@ -73,7 +74,7 @@ export const createGridColumns = ( const cellActions = filterable && handleFilterClick ? [ - ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { const { rowValue, contentsIsDefined, cellContent } = getContentData({ rowIndex, columnId, @@ -102,7 +103,7 @@ export const createGridColumns = ( data-test-subj="lensDatatableFilterFor" onClick={() => { handleFilterClick(field, rowValue, colIndex, rowIndex); - closePopover?.(); + closeCellPopover?.(); }} iconType="plusInCircle" > @@ -111,7 +112,7 @@ export const createGridColumns = ( ) ); }, - ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { const { rowValue, contentsIsDefined, cellContent } = getContentData({ rowIndex, columnId, @@ -140,7 +141,7 @@ export const createGridColumns = ( aria-label={filterOutAriaLabel} onClick={() => { handleFilterClick(field, rowValue, colIndex, rowIndex, true); - closePopover?.(); + closeCellPopover?.(); }} iconType="minusInCircle" > diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index 7c90f19949332..ebf68d5c2b226 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -13,6 +13,7 @@ import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect'; import { EuiButtonIcon, EuiDataGrid, + EuiDataGridRefProps, EuiDataGridControlColumn, EuiDataGridColumn, EuiDataGridSorting, @@ -55,6 +56,8 @@ export const DEFAULT_PAGE_SIZE = 10; const PAGE_SIZE_OPTIONS = [DEFAULT_PAGE_SIZE, 20, 30, 50, 100]; export const DatatableComponent = (props: DatatableRenderProps) => { + const dataGridRef = useRef(null); + const [firstTable] = Object.values(props.data.tables); const isInteractive = props.interactive; @@ -272,7 +275,8 @@ export const DatatableComponent = (props: DatatableRenderProps) => { onColumnHide, alignments, headerRowHeight, - headerRowLines + headerRowLines, + dataGridRef.current?.closeCellPopover ), [ bucketColumns, @@ -454,6 +458,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { onColumnResize={onColumnResize} toolbarVisibility={false} renderFooterCellValue={renderSummaryRow} + ref={dataGridRef} /> diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 21352f16d2354..bbbee1d710c07 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -85,7 +85,7 @@ describe('SecurityNavControlService', () => { >