diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 1e58e8a9e6135..5da373c8d9a51 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <> * <> * <> * <> @@ -45,6 +46,53 @@ Review important information about the {kib} 8.x releases. * <> -- +[[release-notes-8.9.1]] +== {kib} 8.9.1 + +coming::[8.9.1] + +Review the following information about the {kib} 8.9.1 release. + +[float] +[[breaking-changes-8.9.1]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.9.0, review the breaking changes, then mitigate the impact to your application. + +There are no breaking changes in the {kib} 8.9.1 release. + +To review the breaking changes in the previous release, check {kibana-ref-all}/8.9/release-notes-8.9.0.html#breaking-changes-8.9.0[8.9.0]. + +[float] +[[fixes-v8.9.1]] +=== Bug Fixes +APM:: +* Fixes flame graph rendering on the transaction detail page ({kibana-pull}162968[#162968]). +* Check if documents are missing `span.name` ({kibana-pull}162899[#162899]). +* Fixes transaction action menu for Trace Explorer and dependency operations ({kibana-pull}162213[#162213]). +Canvas:: +* Fixes embeddables not rendering in Canvas ({kibana-pull}163013[#163013]). +Discover:: +* Fixes grid styles to enable better content wrapping ({kibana-pull}162325[#162325]). +* Fixes search sessions using temporary data views ({kibana-pull}161029[#161029]). +* Make share links and search session information shorter for temporary data views ({kibana-pull}161180[#161180]). +Elastic Security:: +For the Elastic Security 8.9.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Fleet:: +* Fixes for query error on Agents list in the UI ({kibana-pull}162816[#162816]). +* Remove duplicate path being pushed to package archive ({kibana-pull}162724[#162724]). +Management:: +* Resolves potential errors present in v8.9.0 with data views that contain field filters that have been edited ({kibana-pull}162860[#162860]). +Uptime:: +* Fixes Monitor not found 404 message display ({kibana-pull}163501[#163501]). + +[float] +[[enhancement-v8.9.1]] +=== Enhancements +Discover:: +* Set legend width to extra large and enable text wrapping in legend labels ({kibana-pull}163009[#163009]). + [[release-notes-8.9.0]] == {kib} 8.9.0 diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7cad590c7f52c..923496d123682 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -215,7 +215,7 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |{kib-repo}blob/{branch}/src/plugins/interactive_setup/README.md[interactiveSetup] -|The plugin provides UI and APIs for the interactive setup mode. +|This plugin provides UI and APIs for interactive setup mode a.k.a "enrollment flow". |{kib-repo}blob/{branch}/src/plugins/kibana_overview/README.md[kibanaOverview] diff --git a/src/plugins/interactive_setup/README.md b/src/plugins/interactive_setup/README.md index 9fd43eb0445b6..1c4d9d56771c7 100644 --- a/src/plugins/interactive_setup/README.md +++ b/src/plugins/interactive_setup/README.md @@ -1,3 +1,43 @@ -# `interactiveSetup` plugin +# Interactive Setup Plugin -The plugin provides UI and APIs for the interactive setup mode. +This plugin provides UI and APIs for interactive setup mode a.k.a "enrollment flow". + +## How to run interactive setup locally + +Kibana does not start interactive setup mode if it detects that an Elasticsearch connection has already been configured. This is always the case when running `yarn start` so in order to trigger interactive setup we need to run Elasticsearch manually and pass a special command line flag to the Kibana start command. + +1. Start a clean copy of Elasticsearch from inside your Kibana working directory: + + ``` + cd /.es/cache + tar -xzf elasticsearch-8.10.0-SNAPSHOT-darwin-aarch64.tar.gz + cd ./elasticsearch-8.10.0-SNAPSHOT + ./bin/elasticsearch + ``` + + You should see the enrollment token get logged: + + ``` + Elasticsearch security features have been automatically configured! + + • Copy the following enrollment token and paste it into Kibana in your browser: + eyJ2ZXIiOiI4LjEwLjAiLCJhZHIiOlsiMTkyLjE2OC4xLjIxMTo5MjAwIl0sImZnciI6ImZiYWZjOTgxODM0MjAwNzQ0M2ZhMzNmNTQ2N2QzMTM0YTk1NzU2NjEwOTcxNmJmMjdlYWViZWNlYTE3NmM3MTkiLCJrZXkiOiJxdVVQallrQkhOTkFxOVBqNEY0ejpZUkVMaFR5ZlNlZTZGZW9PQVZwaDRnIn0= + ``` + +2. Start Kibana without dev credentials and config: + + ``` + yarn start --no-dev-credentials --no-dev-config + ``` + + You should see the magic link get logged: + + ``` + i Kibana has not been configured. + + Go to http://localhost:5601/tcu/?code=651822 to get started. + ``` + +3. Open the link and copy the enrollment token from Elasticsearch when prompted to complete setup. + +Note: If you want to go through the enrollment flow again you will need to clear all Elasticsearch settings (`elasticsearch.*`) from your `kibana.yml` file and trash your Elasticsearch folder before starting with Step 1. diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts index 32ce4ed198038..6014c3b3acf4c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import moment, { type MomentInput } from 'moment'; import type { Logger, ISavedObjectsRepository, SavedObject } from '@kbn/core/server'; import { type TestElasticsearchUtils, @@ -13,7 +14,7 @@ import { createTestServers, createRootWithCorePlugins, } from '@kbn/core-test-helpers-kbn-server'; -import { rollDailyData } from '../daily'; + import { metricsServiceMock } from '@kbn/core/server/mocks'; import { @@ -21,10 +22,21 @@ import { serializeSavedObjectId, EventLoopDelaysDaily, } from '../../saved_objects'; -import moment from 'moment'; +import { rollDailyData } from '../daily'; const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); +/* + * Mocking the constructor of moment, so we can control the time of the day. + * This is to avoid flaky tests when starting to run before midnight and ending the test after midnight + * because the logic might remove one extra document since we moved to the next day. + */ +jest.doMock('moment', () => { + const mockedMoment = (date?: MomentInput) => moment(date ?? '2023-07-04T10:00:00.000Z'); + Object.setPrototypeOf(mockedMoment, moment); // inherit the prototype of `moment` so it has all the same methods. + return mockedMoment; +}); + function createRawObject(date: moment.MomentInput): SavedObject { const pid = Math.round(Math.random() * 10000); const instanceUuid = 'mock_instance'; @@ -45,8 +57,8 @@ function createRawObject(date: moment.MomentInput): SavedObject { +describe(`daily rollups integration test`, () => { let esServer: TestElasticsearchUtils; let root: TestKibanaUtils['root']; let internalRepository: ISavedObjectsRepository; @@ -82,15 +93,6 @@ describe.skip(`daily rollups integration test`, () => { logger = root.logger.get('test daily rollups'); internalRepository = start.savedObjects.createInternalRepository([SAVED_OBJECTS_DAILY_TYPE]); - // If we are less than 1 second away from midnight, let's wait 1 second before creating the docs. - // Otherwise, we may receive 1 document less than the expected ones. - if (moment().endOf('day').diff(moment(), 's', true) < 1) { - logger.info( - 'Delaying the creation of the docs 1s, just in case we create them before midnight and run the tests on the following day.' - ); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - // Create the docs now const rawDailyDocs = createRawEventLoopDelaysDailyDocs(); rawEventLoopDelaysDaily = rawDailyDocs.rawEventLoopDelaysDaily; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 84079703affc9..27fef2a017824 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -110,6 +110,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'securitySolution:enableExpandableFlyout': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'securitySolution:enableCcsWarning': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index a48a3905c8705..81f7b0eb957f1 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -63,6 +63,7 @@ export interface UsageStats { 'securitySolution:defaultAnomalyScore': number; 'securitySolution:refreshIntervalDefaults': string; 'securitySolution:enableNewsFeed': boolean; + 'securitySolution:enableExpandableFlyout': boolean; 'securitySolution:enableCcsWarning': boolean; 'search:includeFrozen': boolean; 'courier:maxConcurrentShardRequests': number; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 52a54eb0335b1..00d8a96a59e0e 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -9207,6 +9207,12 @@ "description": "Non-default value of setting." } }, + "securitySolution:enableExpandableFlyout": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "securitySolution:enableCcsWarning": { "type": "boolean", "_meta": { diff --git a/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts index e4ac2346b5893..02e1e7656bf78 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts @@ -62,7 +62,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(event.properties.value4).to.be.a('number'); expect(event.properties.value5).to.be.a('number'); - if (browser.isChromium) { + if (browser.isChromium()) { // Kibana Loaded memory expect(meta).to.have.property('jsHeapSizeLimit'); expect(meta.jsHeapSizeLimit).to.be.a('number'); diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 393f093d54189..fd46e5ac1448b 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -7,8 +7,9 @@ */ import { setTimeout as setTimeoutAsync } from 'timers/promises'; -import { cloneDeepWith } from 'lodash'; +import { cloneDeepWith, isString } from 'lodash'; import { Key, Origin, WebDriver } from 'selenium-webdriver'; +import { Driver as ChromiumWebDriver } from 'selenium-webdriver/chrome'; import { modifyUrl } from '@kbn/std'; import sharp from 'sharp'; @@ -16,6 +17,7 @@ import { NoSuchSessionError } from 'selenium-webdriver/lib/error'; import { WebElementWrapper } from '../lib/web_element_wrapper'; import { FtrProviderContext, FtrService } from '../../ftr_provider_context'; import { Browsers } from '../remote/browsers'; +import { NetworkOptions, NetworkProfile, NETWORK_PROFILES } from '../remote/network_profiles'; export type Browser = BrowserService; @@ -25,19 +27,20 @@ class BrowserService extends FtrService { */ public readonly keys = Key; public readonly isFirefox: boolean; - public readonly isChromium: boolean; private readonly log = this.ctx.getService('log'); constructor( ctx: FtrProviderContext, public readonly browserType: string, - private readonly driver: WebDriver + protected readonly driver: WebDriver | ChromiumWebDriver ) { super(ctx); this.isFirefox = this.browserType === Browsers.Firefox; - this.isChromium = - this.browserType === Browsers.Chrome || this.browserType === Browsers.ChromiumEdge; + } + + public isChromium(): this is { driver: ChromiumWebDriver } { + return this.driver instanceof ChromiumWebDriver; } /** @@ -661,6 +664,68 @@ class BrowserService extends FtrService { } } } + + /** + * Get the network simulation for chromium browsers if available. + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#getNetworkConditions + * + * @return {Promise} + */ + public async getNetworkConditions() { + if (this.isChromium()) { + return this.driver.getNetworkConditions().catch(() => undefined); // Return undefined instead of throwing if no conditions are set. + } else { + const message = + 'WebDriver does not support the .getNetworkConditions method.\nProbably the browser in used is not chromium based.'; + this.log.error(message); + throw new Error(message); + } + } + + /** + * Delete the network simulation for chromium browsers if available. + * + * @return {Promise} + */ + public async restoreNetworkConditions() { + this.log.debug('Restore network conditions simulation.'); + return this.setNetworkConditions('NO_THROTTLING'); + } + + /** + * Set the network conditions for chromium browsers if available. + * + * __Sample Usage:__ + * + * browser.setNetworkConditions('FAST_3G') + * browser.setNetworkConditions('SLOW_3G') + * browser.setNetworkConditions('OFFLINE') + * browser.setNetworkConditions({ + * offline: false, + * latency: 5, // Additional latency (ms). + * download_throughput: 500 * 1024, // Maximal aggregated download throughput. + * upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + * }); + * + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#setNetworkConditions + * + * @return {Promise} + */ + public async setNetworkConditions(profileOrOptions: NetworkProfile | NetworkOptions) { + const networkOptions = isString(profileOrOptions) + ? NETWORK_PROFILES[profileOrOptions] + : profileOrOptions; + + if (this.isChromium()) { + this.log.debug(`Set network conditions with profile "${profileOrOptions}".`); + return this.driver.setNetworkConditions(networkOptions); + } else { + const message = + 'WebDriver does not support the .setNetworkCondition method.\nProbably the browser in used is not chromium based.'; + this.log.error(message); + throw new Error(message); + } + } } export async function BrowserProvider(ctx: FtrProviderContext) { diff --git a/test/functional/services/remote/network_profiles.ts b/test/functional/services/remote/network_profiles.ts index cb4076686270c..29e4a0feeaace 100644 --- a/test/functional/services/remote/network_profiles.ts +++ b/test/functional/services/remote/network_profiles.ts @@ -6,10 +6,13 @@ * Side Public License, v 1. */ -interface NetworkOptions { - DOWNLOAD: number; - UPLOAD: number; - LATENCY: number; +export type NetworkProfile = 'NO_THROTTLING' | 'FAST_3G' | 'SLOW_3G' | 'OFFLINE' | 'CLOUD_USER'; + +export interface NetworkOptions { + offline: boolean; + latency: number; + download_throughput: number; + upload_throughput: number; } const sec = 10 ** 3; @@ -17,6 +20,36 @@ const MBps = 10 ** 6 / 8; // megabyte per second (MB/s) (can be abbreviated as M // Selenium uses B/s (bytes) for network throttling // Download (B/s) Upload (B/s) Latency (ms) -export const NETWORK_PROFILES: { [key: string]: NetworkOptions } = { - CLOUD_USER: { DOWNLOAD: 6 * MBps, UPLOAD: 6 * MBps, LATENCY: 0.1 * sec }, + +export const NETWORK_PROFILES: Record = { + NO_THROTTLING: { + offline: false, + latency: 0, + download_throughput: -1, + upload_throughput: -1, + }, + FAST_3G: { + offline: false, + latency: 0.56 * sec, + download_throughput: 1.44 * MBps, + upload_throughput: 0.7 * MBps, + }, + SLOW_3G: { + offline: false, + latency: 2 * sec, + download_throughput: 0.4 * MBps, + upload_throughput: 0.4 * MBps, + }, + OFFLINE: { + offline: true, + latency: 0, + download_throughput: 0, + upload_throughput: 0, + }, + CLOUD_USER: { + offline: false, + latency: 0.1 * sec, + download_throughput: 6 * MBps, + upload_throughput: 6 * MBps, + }, }; diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 1486d02656d12..702f674b3c10d 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -30,7 +30,7 @@ import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; import { Browsers } from './browsers'; -import { NETWORK_PROFILES } from './network_profiles'; +import { NetworkProfile, NETWORK_PROFILES } from './network_profiles'; const throttleOption: string = process.env.TEST_THROTTLE_NETWORK as string; const headlessBrowser: string = process.env.TEST_BROWSER_HEADLESS as string; @@ -300,22 +300,17 @@ async function attemptToCreateCommand( const { session, consoleLog$ } = await buildDriverInstance(); if (throttleOption === '1' && browserType === 'chrome') { - const { KBN_NETWORK_TEST_PROFILE = 'CLOUD_USER' } = process.env; + const KBN_NETWORK_TEST_PROFILE = (process.env.KBN_NETWORK_TEST_PROFILE ?? + 'CLOUD_USER') as NetworkProfile; const profile = - KBN_NETWORK_TEST_PROFILE in Object.keys(NETWORK_PROFILES) - ? KBN_NETWORK_TEST_PROFILE - : 'CLOUD_USER'; + KBN_NETWORK_TEST_PROFILE in NETWORK_PROFILES ? KBN_NETWORK_TEST_PROFILE : 'CLOUD_USER'; - const { - DOWNLOAD: downloadThroughput, - UPLOAD: uploadThroughput, - LATENCY: latency, - } = NETWORK_PROFILES[`${profile}`]; + const networkProfileOptions = NETWORK_PROFILES[profile]; // Only chrome supports this option. log.debug( - `NETWORK THROTTLED with profile ${profile}: ${downloadThroughput} B/s down, ${uploadThroughput} B/s up, ${latency} ms latency.` + `NETWORK THROTTLED with profile ${profile}: ${networkProfileOptions.download_throughput} B/s down, ${networkProfileOptions.upload_throughput} B/s up, ${networkProfileOptions.latency} ms latency.` ); if (noCache) { @@ -326,12 +321,7 @@ async function attemptToCreateCommand( } // @ts-expect-error - session.setNetworkConditions({ - offline: false, - latency, - download_throughput: downloadThroughput, - upload_throughput: uploadThroughput, - }); + session.setNetworkConditions(networkProfileOptions); } if (attemptId !== attemptCounter) { diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx index aa1fb057bbd32..444ec69c9d266 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx @@ -168,6 +168,7 @@ export function DatasetSelector({ onPanelChange={changePanel} className="eui-yScroll" css={contextMenuStyles} + data-test-subj="datasetSelectorContextMenu" size="s" /> diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx index 9decd3732fda4..134bd7616ac8a 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx @@ -44,6 +44,7 @@ export const DatasetsList = ({ if (hasError) { return ( {noDatasetsLabel}} diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx index d6c9771da019d..7428ffbd47612 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx @@ -45,7 +45,8 @@ export const DatasetsPopover = ({ return ( {iconType ? ( @@ -74,7 +75,12 @@ export const DatasetsPopover = ({ {...(isMobile && { display: 'block' })} {...props} > - + <span>{selectDatasetLabel}</span> diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx index be9464204497d..8a9ee61d8e434 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx @@ -10,7 +10,7 @@ import { EuiPanel, EuiSkeletonText } from '@elastic/eui'; import { uncategorizedLabel } from '../constants'; export const DatasetSkeleton = () => ( - + ); diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx index 418ccbefffd00..42ff78fdd3e83 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx @@ -34,6 +34,7 @@ export const IntegrationsListStatus = ({ if (hasError) { return ( + , + 'data-test-subj': integration.id, panel: integration.id, ...(isLastIntegration && { buttonRef: spyRef }), }); @@ -85,6 +86,7 @@ export const createAllLogDatasetsItem = ({ onClick }: { onClick(): void }) => { const allLogDataset = Dataset.createAllLogsDataset(); return { name: allLogDataset.title, + 'data-test-subj': 'allLogDatasets', icon: allLogDataset.iconType && , onClick, }; @@ -93,6 +95,7 @@ export const createAllLogDatasetsItem = ({ onClick }: { onClick(): void }) => { export const createUnmanagedDatasetsItem = ({ onClick }: { onClick: LoadDatasets }) => { return { name: uncategorizedLabel, + 'data-test-subj': 'unmanagedDatasets', icon: , onClick, panel: UNMANAGED_STREAMS_PANEL_ID, @@ -103,5 +106,6 @@ export const createIntegrationStatusItem = (props: IntegrationsListStatusProps) return { disabled: true, name: , + 'data-test-subj': 'integrationStatusItem', }; }; diff --git a/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx b/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx index f2e1114eeefc7..a669ebea33942 100644 --- a/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx +++ b/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx @@ -12,6 +12,8 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { useControlPanels } from '../hooks/use_control_panels'; import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile'; +const DATASET_FILTERS_CUSTOMIZATION_ID = 'datasetFiltersCustomization'; + interface CustomDatasetFiltersProps { logExplorerProfileStateService: LogExplorerProfileStateService; data: DataPublicPluginStart; @@ -27,7 +29,7 @@ const CustomDatasetFilters = ({ ); return ( - + { describe('disableProtections', () => { it('disables all the protections in the default policy', () => { - expect(disableProtections(policyFactory())).toEqual(eventsOnlyPolicy); + expect(disableProtections(policyFactory())).toEqual(eventsOnlyPolicy()); }); it('does not enable supported fields', () => { @@ -51,20 +56,20 @@ describe('Policy Config helpers', () => { }; const expectedPolicyWithoutSupportedProtections: PolicyConfig = { - ...eventsOnlyPolicy, + ...eventsOnlyPolicy(), windows: { - ...eventsOnlyPolicy.windows, + ...eventsOnlyPolicy().windows, memory_protection: notSupported, behavior_protection: notSupportedBehaviorProtection, ransomware: notSupported, }, mac: { - ...eventsOnlyPolicy.mac, + ...eventsOnlyPolicy().mac, memory_protection: notSupported, behavior_protection: notSupportedBehaviorProtection, }, linux: { - ...eventsOnlyPolicy.linux, + ...eventsOnlyPolicy().linux, memory_protection: notSupported, behavior_protection: notSupportedBehaviorProtection, }, @@ -104,10 +109,10 @@ describe('Policy Config helpers', () => { }; const expectedPolicy: PolicyConfig = { - ...eventsOnlyPolicy, - windows: { ...eventsOnlyPolicy.windows, events: { ...windowsEvents } }, - mac: { ...eventsOnlyPolicy.mac, events: { ...macEvents } }, - linux: { ...eventsOnlyPolicy.linux, events: { ...linuxEvents } }, + ...eventsOnlyPolicy(), + windows: { ...eventsOnlyPolicy().windows, events: { ...windowsEvents } }, + mac: { ...eventsOnlyPolicy().mac, events: { ...macEvents } }, + linux: { ...eventsOnlyPolicy().linux, events: { ...linuxEvents } }, }; const inputPolicy = { @@ -120,11 +125,73 @@ describe('Policy Config helpers', () => { expect(disableProtections(inputPolicy)).toEqual(expectedPolicy); }); }); + + describe('setPolicyToEventCollectionOnly()', () => { + it('should set the policy to event collection only', () => { + expect(ensureOnlyEventCollectionIsAllowed(policyFactory())).toEqual(eventsOnlyPolicy()); + }); + }); + + describe('isPolicySetToEventCollectionOnly', () => { + let policy: PolicyConfig; + + beforeEach(() => { + policy = ensureOnlyEventCollectionIsAllowed(policyFactory()); + }); + + it.each([ + { + keyPath: `${PolicyOperatingSystem.windows}.malware.mode`, + keyValue: ProtectionModes.prevent, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.mac}.malware.mode`, + keyValue: ProtectionModes.off, + expectedResult: true, + }, + { + keyPath: `${PolicyOperatingSystem.windows}.ransomware.mode`, + keyValue: ProtectionModes.prevent, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.linux}.memory_protection.mode`, + keyValue: ProtectionModes.off, + expectedResult: true, + }, + { + keyPath: `${PolicyOperatingSystem.mac}.behavior_protection.mode`, + keyValue: ProtectionModes.detect, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.windows}.attack_surface_reduction.credential_hardening.enabled`, + keyValue: true, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.windows}.antivirus_registration.enabled`, + keyValue: true, + expectedResult: false, + }, + ])( + 'should return `$expectedResult` if `$keyPath` is set to `$keyValue`', + ({ keyPath, keyValue, expectedResult }) => { + set(policy, keyPath, keyValue); + + expect(isPolicySetToEventCollectionOnly(policy)).toEqual({ + isOnlyCollectingEvents: expectedResult, + message: expectedResult ? undefined : `property [${keyPath}] is set to [${keyValue}]`, + }); + } + ); + }); }); // This constant makes sure that if the type `PolicyConfig` is ever modified, // the logic for disabling protections is also modified due to type check. -export const eventsOnlyPolicy: PolicyConfig = { +export const eventsOnlyPolicy = (): PolicyConfig => ({ meta: { license: '', cloud: false, license_uid: '', cluster_name: '', cluster_uuid: '' }, windows: { events: { @@ -187,4 +254,4 @@ export const eventsOnlyPolicy: PolicyConfig = { capture_env_vars: 'LD_PRELOAD,LD_LIBRARY_PATH', }, }, -}; +}); diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts index 4bdca10547bc2..cb460e2f75f49 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts @@ -5,8 +5,63 @@ * 2.0. */ +import { get, set } from 'lodash'; import type { PolicyConfig } from '../types'; -import { ProtectionModes } from '../types'; +import { PolicyOperatingSystem, ProtectionModes } from '../types'; + +interface PolicyProtectionReference { + keyPath: string; + osList: PolicyOperatingSystem[]; + enableValue: unknown; + disableValue: unknown; +} + +const getPolicyProtectionsReference = (): PolicyProtectionReference[] => { + const allOsValues = [ + PolicyOperatingSystem.mac, + PolicyOperatingSystem.linux, + PolicyOperatingSystem.windows, + ]; + + return [ + { + keyPath: 'malware.mode', + osList: [...allOsValues], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'ransomware.mode', + osList: [PolicyOperatingSystem.windows], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'memory_protection.mode', + osList: [...allOsValues], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'behavior_protection.mode', + osList: [...allOsValues], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'attack_surface_reduction.credential_hardening.enabled', + osList: [PolicyOperatingSystem.windows], + disableValue: false, + enableValue: true, + }, + { + keyPath: 'antivirus_registration.enabled', + osList: [PolicyOperatingSystem.windows], + disableValue: false, + enableValue: true, + }, + ]; +}; /** * Returns a copy of the passed `PolicyConfig` with all protections set to disabled. @@ -106,3 +161,46 @@ const getDisabledWindowsSpecificPopups = (policy: PolicyConfig) => ({ enabled: false, }, }); + +/** + * Returns the provided with only event collection turned enabled + * @param policy + */ +export const ensureOnlyEventCollectionIsAllowed = (policy: PolicyConfig): PolicyConfig => { + const updatedPolicy = disableProtections(policy); + + set(updatedPolicy, 'windows.antivirus_registration.enabled', false); + + return updatedPolicy; +}; + +/** + * Checks to see if the provided policy is set to Event Collection only + */ +export const isPolicySetToEventCollectionOnly = ( + policy: PolicyConfig +): { isOnlyCollectingEvents: boolean; message?: string } => { + const protectionsRef = getPolicyProtectionsReference(); + let message: string | undefined; + + const hasEnabledProtection = protectionsRef.some(({ keyPath, osList, disableValue }) => { + const hasOsPropertyEnabled = osList.some((osValue) => { + const fullKeyPathForOs = `${osValue}.${keyPath}`; + const currentValue = get(policy, fullKeyPathForOs); + const isEnabled = currentValue !== disableValue; + + if (isEnabled) { + message = `property [${fullKeyPathForOs}] is set to [${currentValue}]`; + } + + return isEnabled; + }); + + return hasOsPropertyEnabled; + }); + + return { + isOnlyCollectingEvents: !hasEnabledProtection, + message, + }; +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts b/x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts new file mode 100644 index 0000000000000..92880d9322191 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type PromiseResolvedValue> = T extends Promise + ? Value + : never; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index b2e1cae53d97e..449092e86130a 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -76,10 +76,6 @@ export const allowedExperimentalValues = Object.freeze({ */ alertsPageChartsEnabled: true, alertTypeEnabled: false, - /** - * Enables the new security flyout over the current alert details flyout - */ - securityFlyoutEnabled: false, /* * Enables new Set of filters on the Alerts page. diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts index fc424eb192e05..0a626f2fff8d4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { disableExpandableFlyout } from '../../tasks/api_calls/kibana_advanced_settings'; import { getNewThreatIndicatorRule, indicatorRuleMatchingDoc } from '../../objects/rule'; import { cleanKibana } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -34,6 +35,7 @@ describe('CTI Enrichment', () => { cy.task('esArchiverLoad', 'suspicious_source_event'); login(); createRule({ ...getNewThreatIndicatorRule(), rule_id: 'rule_testing', enabled: true }); + disableExpandableFlyout(); }); after(() => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts index 2eacd9ece4865..95481b23174f1 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts @@ -24,6 +24,7 @@ import { scrollAlertTableColumnIntoView, closeAlertFlyout, } from '../../tasks/alerts'; +import { disableExpandableFlyout } from '../../tasks/api_calls/kibana_advanced_settings'; import { login, visit } from '../../tasks/login'; @@ -41,6 +42,7 @@ describe('Enrichment', () => { describe('Custom query rule', () => { beforeEach(() => { + disableExpandableFlyout(); cy.task('esArchiverLoad', 'risk_hosts'); deleteAlertsAndRules(); createRule(getNewRule({ rule_id: 'rule1' })); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts index 08bddea26288a..b3939ff3c7b27 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts @@ -35,7 +35,6 @@ import { duplicateFirstRule, importRules } from '../../../../../tasks/alerts_det import { goToActionsStepTab } from '../../../../../tasks/create_new_rule'; import { goToRuleEditSettings } from '../../../../../tasks/rule_details'; import { actionFormSelector } from '../../../../../screens/common/rule_actions'; -import { RULE_INDICES } from '../../../../../screens/create_new_rule'; import { addEmailConnectorAndRuleAction } from '../../../../../tasks/common/rule_actions'; import { saveEditedRule } from '../../../../../tasks/edit_rule'; import { DISABLED_SNOOZE_BADGE } from '../../../../../screens/rule_snoozing'; @@ -169,8 +168,7 @@ describe('rule snoozing', () => { }); }); - // SKIPPED: https://github.com/elastic/kibana/issues/159349 - describe.skip('Rule editing page / actions tab', () => { + describe('Rule editing page / actions tab', () => { beforeEach(() => { deleteConnectors(); }); @@ -178,8 +176,6 @@ describe('rule snoozing', () => { it('adds an action to a snoozed rule', () => { createSnoozedRule(getNewRule({ name: 'Snoozed rule' })).then(({ body: rule }) => { visitWithoutDateRange(ruleEditUrl(rule.id)); - // Wait for rule data being loaded - cy.get(RULE_INDICES).should('be.visible'); goToActionsStepTab(); addEmailConnectorAndRuleAction('abc@example.com', 'Test action'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts index 7304972a65790..bd3363572915d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts @@ -8,20 +8,19 @@ import { RULE_CHECKBOX, REFRESH_RULES_STATUS, - REFRESH_SETTINGS_SWITCH, - REFRESH_SETTINGS_SELECTION_NOTE, + RULES_TABLE_AUTOREFRESH_INDICATOR, + RULES_MANAGEMENT_TABLE, } from '../../../../screens/alerts_detection_rules'; import { - checkAutoRefresh, - waitForRulesTableToBeLoaded, selectAllRules, - openRefreshSettingsPopover, clearAllRuleSelection, selectNumberOfRules, mockGlobalClock, disableAutoRefresh, - checkAutoRefreshIsDisabled, - checkAutoRefreshIsEnabled, + expectAutoRefreshIsDisabled, + expectAutoRefreshIsEnabled, + expectAutoRefreshIsDeactivated, + expectNumberOfRules, } from '../../../../tasks/alerts_detection_rules'; import { login, visit, visitWithoutDateRange } from '../../../../tasks/login'; @@ -29,16 +28,16 @@ import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; import { createRule } from '../../../../tasks/api_calls/rules'; import { cleanKibana } from '../../../../tasks/common'; import { getNewRule } from '../../../../objects/rule'; -import { setRowsPerPageTo } from '../../../../tasks/table_pagination'; const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; +const NUM_OF_TEST_RULES = 6; -// TODO: See https://github.com/elastic/kibana/issues/154694 -describe.skip('Rules table: auto-refresh', () => { +describe('Rules table: auto-refresh', () => { before(() => { cleanKibana(); login(); - for (let i = 1; i < 7; i += 1) { + + for (let i = 1; i <= NUM_OF_TEST_RULES; ++i) { createRule(getNewRule({ name: `Test rule ${i}`, rule_id: `${i}` })); } }); @@ -48,31 +47,31 @@ describe.skip('Rules table: auto-refresh', () => { }); it('Auto refreshes rules', () => { + mockGlobalClock(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); - - // ensure rules have rendered. As there is no user interaction in this test, - // rules were not rendered before test completes - cy.get(RULE_CHECKBOX).should('have.length', 6); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); // // mock 1 minute passing to make sure refresh is conducted - mockGlobalClock(); - checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible'); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('be.visible'); cy.contains(REFRESH_RULES_STATUS, 'Updated now'); }); it('should prevent table from rules refetch if any rule selected', () => { + mockGlobalClock(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); selectNumberOfRules(1); // mock 1 minute passing to make sure refresh is not conducted - mockGlobalClock(); - checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'not.exist'); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); // ensure rule is still selected cy.get(RULE_CHECKBOX).first().should('be.checked'); @@ -82,50 +81,37 @@ describe.skip('Rules table: auto-refresh', () => { it('should disable auto refresh when any rule selected and enable it after rules unselected', () => { visit(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); - setRowsPerPageTo(5); + + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); // check refresh settings if it's enabled before selecting - openRefreshSettingsPopover(); - checkAutoRefreshIsEnabled(); + expectAutoRefreshIsEnabled(); selectAllRules(); - // auto refresh should be disabled after rules selected - openRefreshSettingsPopover(); - checkAutoRefreshIsDisabled(); - - // if any rule selected, refresh switch should be disabled and help note to users should displayed - cy.get(REFRESH_SETTINGS_SWITCH).should('be.disabled'); - cy.contains( - REFRESH_SETTINGS_SELECTION_NOTE, - 'Note: Refresh is disabled while there is an active selection.' - ); + // auto refresh should be deactivated (which means disabled without an ability to enable it) after rules selected + expectAutoRefreshIsDeactivated(); clearAllRuleSelection(); - // after all rules unselected, auto refresh should renew - openRefreshSettingsPopover(); - checkAutoRefreshIsEnabled(); + // after all rules unselected, auto refresh should be reset to its previous state + expectAutoRefreshIsEnabled(); }); it('should not enable auto refresh after rules were unselected if auto refresh was disabled', () => { visit(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); - setRowsPerPageTo(5); - openRefreshSettingsPopover(); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); + disableAutoRefresh(); selectAllRules(); - openRefreshSettingsPopover(); - checkAutoRefreshIsDisabled(); + expectAutoRefreshIsDeactivated(); clearAllRuleSelection(); // after all rules unselected, auto refresh should still be disabled - openRefreshSettingsPopover(); - checkAutoRefreshIsDisabled(); + expectAutoRefreshIsDisabled(); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts index 33799309fd3a6..1c180857c00b9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { disableExpandableFlyout } from '../../../tasks/api_calls/kibana_advanced_settings'; import { navigateFromHeaderTo } from '../../../tasks/security_header'; import { ALERTS, TIMELINES } from '../../../screens/security_header'; import { closeAlertFlyout, expandFirstAlert } from '../../../tasks/alerts'; @@ -36,6 +37,7 @@ describe('Guided onboarding tour', () => { }); beforeEach(() => { login(); + disableExpandableFlyout(); startAlertsCasesTour(); visit(ALERTS_URL); waitForAlertsToPopulate(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts index 7c2ec7a31e12a..7f5e0cde93b10 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -6,6 +6,7 @@ */ import type { DataTableModel } from '@kbn/securitysolution-data-table'; +import { disableExpandableFlyout } from '../../../tasks/api_calls/kibana_advanced_settings'; import { ALERT_FLYOUT, CELL_TEXT, @@ -39,6 +40,7 @@ describe('Alert details flyout', () => { before(() => { cleanKibana(); login(); + disableExpandableFlyout(); createRule(getNewRule()); visitWithoutDateRange(ALERTS_URL); waitForAlertsToPopulate(); @@ -64,6 +66,7 @@ describe('Alert details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visitWithoutDateRange(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); @@ -128,6 +131,7 @@ describe('Alert details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); @@ -173,6 +177,7 @@ describe('Alert details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts index 43531feb67d73..573286b921d56 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts @@ -24,34 +24,30 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel analyzer graph', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openGraphAnalyzerTab(); - }); +describe('Alert details expandable flyout left panel analyzer graph', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openGraphAnalyzerTab(); + }); - it('should display analyzer graph and node list under visualize', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) - .should('be.visible') - .and('have.text', 'Visualize'); + it('should display analyzer graph and node list under visualize', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) + .should('be.visible') + .and('have.text', 'Visualize'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) - .should('be.visible') - .and('have.text', 'Analyzer Graph'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) + .should('be.visible') + .and('have.text', 'Analyzer Graph'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); - cy.get(ANALYZER_NODE).first().should('be.visible'); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); + cy.get(ANALYZER_NODE).first().should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts index da6dccc082c7f..0b02939f5ca4f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts @@ -34,60 +34,54 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { login, visit } from '../../../../tasks/login'; import { ALERTS_URL } from '../../../../urls/navigation'; -describe( - 'Expandable flyout left panel correlations', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - createNewCaseFromExpandableFlyout(); - openInsightsTab(); - openCorrelationsTab(); - }); +describe('Expandable flyout left panel correlations', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + createNewCaseFromExpandableFlyout(); + openInsightsTab(); + openCorrelationsTab(); + }); - it('should render correlations details correctly', () => { - cy.log('link the alert to a new case'); + it('should render correlations details correctly', () => { + cy.log('link the alert to a new case'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).scrollIntoView(); - cy.log('should render the Insights header'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + cy.log('should render the Insights header'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.log('should render the inner tab switch'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.log('should render the inner tab switch'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.log('should render correlations tab activator / button'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) - .should('be.visible') - .and('have.text', 'Correlations'); + cy.log('should render correlations tab activator / button'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) + .should('be.visible') + .and('have.text', 'Correlations'); - cy.log('should render all the correlations sections'); + cy.log('should render all the correlations sections'); - cy.get(CORRELATIONS_ANCESTRY_SECTION) - .should('be.visible') - .and('have.text', '1 alert related by ancestry'); + cy.get(CORRELATIONS_ANCESTRY_SECTION) + .should('be.visible') + .and('have.text', '1 alert related by ancestry'); - cy.get(CORRELATIONS_SOURCE_SECTION) - .should('be.visible') - .and('have.text', '0 alerts related by source event'); + cy.get(CORRELATIONS_SOURCE_SECTION) + .should('be.visible') + .and('have.text', '0 alerts related by source event'); - cy.get(CORRELATIONS_SESSION_SECTION) - .should('be.visible') - .and('have.text', '1 alert related by session'); + cy.get(CORRELATIONS_SESSION_SECTION) + .should('be.visible') + .and('have.text', '1 alert related by session'); - cy.get(CORRELATIONS_CASES_SECTION).should('be.visible').and('have.text', '1 related case'); + cy.get(CORRELATIONS_CASES_SECTION).should('be.visible').and('have.text', '1 related case'); - expandCorrelationsSection(CORRELATIONS_ANCESTRY_SECTION); + expandCorrelationsSection(CORRELATIONS_ANCESTRY_SECTION); - cy.get(CORRELATIONS_ANCESTRY_TABLE).should('be.visible'); - }); - } -); + cy.get(CORRELATIONS_ANCESTRY_TABLE).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts index f48611f12af09..a680f783af336 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts @@ -25,38 +25,32 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel entities', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openEntitiesTab(); - }); +describe('Alert details expandable flyout left panel entities', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openEntitiesTab(); + }); - it('should display analyzer graph and node list under Insights Entities', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + it('should display analyzer graph and node list under Insights Entities', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) - .should('be.visible') - .and('have.text', 'Entities'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) + .should('be.visible') + .and('have.text', 'Entities'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts index 62d4932a017b8..833d591344f57 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts @@ -19,27 +19,23 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel investigation', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInvestigationTab(); - }); +describe('Alert details expandable flyout left panel investigation', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInvestigationTab(); + }); - it('should display investigation guide', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) - .should('be.visible') - .and('have.text', 'Investigation'); + it('should display investigation guide', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) + .should('be.visible') + .and('have.text', 'Investigation'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts index 3cfe58f22893c..ab06e9fea187e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts @@ -30,56 +30,50 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel prevalence', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openPrevalenceTab(); - }); +describe('Alert details expandable flyout left panel prevalence', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openPrevalenceTab(); + }); - it('should display prevalence tab', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + it('should display prevalence tab', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) - .should('be.visible') - .and('have.text', 'Prevalence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Prevalence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_TYPE_CELL) - .should('contain.text', 'host.name') - .and('contain.text', 'user.name'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_NAME_CELL) - .should('contain.text', 'siem-kibana') - .and('contain.text', 'test'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_ALERT_COUNT_CELL).should( - 'contain.text', - 2 - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_DOC_COUNT_CELL).should( - 'contain.text', - 0 - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_HOST_PREVALENCE_CELL).should( - 'contain.text', - 100 - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_USER_PREVALENCE_CELL).should( - 'contain.text', - 100 - ); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_TYPE_CELL) + .should('contain.text', 'host.name') + .and('contain.text', 'user.name'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_NAME_CELL) + .should('contain.text', 'siem-kibana') + .and('contain.text', 'test'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_ALERT_COUNT_CELL).should( + 'contain.text', + 2 + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_DOC_COUNT_CELL).should( + 'contain.text', + 0 + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_HOST_PREVALENCE_CELL).should( + 'contain.text', + 100 + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_USER_PREVALENCE_CELL).should( + 'contain.text', + 100 + ); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts index 19b9eac037a4c..19ed92dbff60f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts @@ -16,23 +16,19 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel investigation', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openResponseTab(); - }); +describe('Alert details expandable flyout left panel investigation', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openResponseTab(); + }); - it('should display empty response message', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_RESPONSE_EMPTY).should('be.visible'); - }); - } -); + it('should display empty response message', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_RESPONSE_EMPTY).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts index 762b5cf307b4f..afc3ac1b0b918 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts @@ -22,36 +22,32 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel session view', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - }); +describe('Alert details expandable flyout left panel session view', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + }); - it('should display session view under visualize', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) - .should('be.visible') - .and('have.text', 'Visualize'); + it('should display session view under visualize', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) + .should('be.visible') + .and('have.text', 'Visualize'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) - .should('be.visible') - .and('have.text', 'Session View'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) + .should('be.visible') + .and('have.text', 'Session View'); - // TODO ideally we would have a test for the session view component instead - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR) - .should('be.visible') - .and('contain.text', 'Unable to display session view') - .and('contain.text', 'There was an error displaying session view'); - }); - } -); + // TODO ideally we would have a test for the session view component instead + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR) + .should('be.visible') + .and('contain.text', 'Unable to display session view') + .and('contain.text', 'There was an error displaying session view'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts index d79c59ed71440..c5cd168836179 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts @@ -22,34 +22,28 @@ import { } from '../../../../screens/expandable_flyout/alert_details_left_panel'; import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON } from '../../../../screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab'; -describe( - 'Expandable flyout left panel threat intelligence', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openThreatIntelligenceTab(); - }); +describe('Expandable flyout left panel threat intelligence', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openThreatIntelligenceTab(); + }); - it('should serialize its state to url', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + it('should serialize its state to url', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); - cy.get(INDICATOR_MATCH_ENRICHMENT_SECTION).should('be.visible'); - }); - } -); + cy.get(INDICATOR_MATCH_ENRICHMENT_SECTION).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts index 116162983f0b2..cc48e4568d908 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts @@ -34,68 +34,63 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout rule preview panel', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); +describe('Alert details expandable flyout rule preview panel', () => { + const rule = getNewRule(); - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - clickRuleSummaryButton(); - }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + clickRuleSummaryButton(); + }); + + describe('rule preview', () => { + it('should display rule preview and its sub sections', () => { + cy.log('rule preview panel'); - describe('rule preview', () => { - it('should display rule preview and its sub sections', () => { - cy.log('rule preview panel'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).should('be.visible'); + cy.log('title'); - cy.log('title'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_CREATED_BY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_UPDATED_BY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_CREATED_BY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_UPDATED_BY).should('be.visible'); - cy.log('about'); + cy.log('about'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER) - .should('be.visible') - .and('contain.text', 'About'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT).should('be.visible'); - toggleRulePreviewAboutSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER) + .should('be.visible') + .and('contain.text', 'About'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT).should('be.visible'); + toggleRulePreviewAboutSection(); - cy.log('definition'); + cy.log('definition'); - toggleRulePreviewDefinitionSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_HEADER) - .should('be.visible') - .and('contain.text', 'Definition'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_CONTENT).should( - 'be.visible' - ); - toggleRulePreviewDefinitionSection(); + toggleRulePreviewDefinitionSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_HEADER) + .should('be.visible') + .and('contain.text', 'Definition'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_CONTENT).should('be.visible'); + toggleRulePreviewDefinitionSection(); - cy.log('schedule'); + cy.log('schedule'); - toggleRulePreviewScheduleSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_HEADER) - .should('be.visible') - .and('contain.text', 'Schedule'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible'); - toggleRulePreviewScheduleSection(); + toggleRulePreviewScheduleSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_HEADER) + .should('be.visible') + .and('contain.text', 'Schedule'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible'); + toggleRulePreviewScheduleSection(); - cy.log('footer'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); - }); + cy.log('footer'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); }); - } -); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index 11fc62c7f68f7..a166a72148fd3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -65,173 +65,167 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout right panel', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); - - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - }); - - it('should display header and footer basics', () => { - expandFirstAlertExpandableFlyout(); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON).should('be.visible'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('be.visible'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) - .should('be.visible') - .and('have.text', rule.risk_score); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE) - .should('be.visible') - .and('have.text', upperFirst(rule.severity)); - - cy.log('Verify all 3 tabs are visible'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB) - .should('be.visible') - .and('have.text', 'Overview'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); - - cy.log('Verify the expand/collapse button is visible and functionality works'); - - expandDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Collapse details'); - - collapseDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Expand details'); - - cy.log('Verify the take action button is visible on all tabs'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - openTableTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - openJsonTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - }); - - // TODO this will change when add to existing case is improved - // https://github.com/elastic/security-team/issues/6298 - it('should add to existing case', () => { - navigateToCasesPage(); - createNewCaseFromCases(); - - cy.get(CASE_DETAILS_PAGE_TITLE).should('be.visible').and('have.text', 'case'); - navigateToAlertsPage(); - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); - - cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); - cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); - }); - - // TODO this will change when add to new case is improved - // https://github.com/elastic/security-team/issues/6298 - it('should add to new case', () => { - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); - - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( - 'case description' - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); - - cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); - }); - - it('should mark as acknowledged', () => { - cy.get(ALERT_CHECKBOX).should('have.length', 2); - - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); +describe('Alert details expandable flyout right panel', () => { + const rule = getNewRule(); - // TODO figure out how to verify the toasts pops up - // cy.get(KIBANA_TOAST) - // .should('be.visible') - // .and('have.text', 'Successfully marked 1 alert as acknowledged.'); - cy.get(ALERT_CHECKBOX).should('have.length', 1); - }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + }); - it('should mark as closed', () => { - cy.get(ALERT_CHECKBOX).should('have.length', 2); + it('should display header and footer basics', () => { + expandFirstAlertExpandableFlyout(); - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) + .should('be.visible') + .and('have.text', rule.risk_score); - // TODO figure out how to verify the toasts pops up - // cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); - cy.get(ALERT_CHECKBOX).should('have.length', 1); - }); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE) + .should('be.visible') + .and('have.text', upperFirst(rule.severity)); - // these actions are now grouped together as we're not really testing their functionality but just the existence of the option in the dropdown - it('should test other action within take action dropdown', () => { - expandFirstAlertExpandableFlyout(); + cy.log('Verify all 3 tabs are visible'); - cy.log('should add endpoint exception'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).should('be.visible').and('have.text', 'Overview'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); + + cy.log('Verify the expand/collapse button is visible and functionality works'); + + expandDocumentDetailsExpandableFlyoutLeftSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) + .should('be.visible') + .and('have.text', 'Collapse details'); - // TODO figure out why this option is disabled in Cypress but not running the app locally - // https://github.com/elastic/security-team/issues/6300 - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); + collapseDocumentDetailsExpandableFlyoutLeftSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) + .should('be.visible') + .and('have.text', 'Expand details'); + + cy.log('Verify the take action button is visible on all tabs'); - cy.log('should add rule exception'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - // TODO this isn't fully testing the add rule exception yet - // https://github.com/elastic/security-team/issues/6301 - selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) - .should('be.visible') - .click(); + openTableTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - // cy.log('should isolate host'); + openJsonTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); + }); + + // TODO this will change when add to existing case is improved + // https://github.com/elastic/security-team/issues/6298 + it('should add to existing case', () => { + navigateToCasesPage(); + createNewCaseFromCases(); + + cy.get(CASE_DETAILS_PAGE_TITLE).should('be.visible').and('have.text', 'case'); + navigateToAlertsPage(); + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); + + cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); + }); + + // TODO this will change when add to new case is improved + // https://github.com/elastic/security-team/issues/6298 + it('should add to new case', () => { + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); + + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( + 'case description' + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); + + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); + }); + + it('should mark as acknowledged', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 2); + + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); + + // TODO figure out how to verify the toasts pops up + // cy.get(KIBANA_TOAST) + // .should('be.visible') + // .and('have.text', 'Successfully marked 1 alert as acknowledged.'); + cy.get(ALERT_CHECKBOX).should('have.length', 1); + }); - // TODO figure out why isolate host isn't showing up in the dropdown - // https://github.com/elastic/security-team/issues/6302 - // openTakeActionButton(); - // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + it('should mark as closed', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 2); - cy.log('should respond'); + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); - // TODO this will change when respond is improved - // https://github.com/elastic/security-team/issues/6303 - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); - - cy.log('should investigate in timeline'); - - selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) - .first() - .within(() => - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') - ); - }); - } -); + // TODO figure out how to verify the toasts pops up + // cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); + cy.get(ALERT_CHECKBOX).should('have.length', 1); + }); + + // these actions are now grouped together as we're not really testing their functionality but just the existence of the option in the dropdown + it('should test other action within take action dropdown', () => { + expandFirstAlertExpandableFlyout(); + + cy.log('should add endpoint exception'); + + // TODO figure out why this option is disabled in Cypress but not running the app locally + // https://github.com/elastic/security-team/issues/6300 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); + + cy.log('should add rule exception'); + + // TODO this isn't fully testing the add rule exception yet + // https://github.com/elastic/security-team/issues/6301 + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) + .should('be.visible') + .click(); + + // cy.log('should isolate host'); + + // TODO figure out why isolate host isn't showing up in the dropdown + // https://github.com/elastic/security-team/issues/6302 + // openTakeActionButton(); + // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + + cy.log('should respond'); + + // TODO this will change when respond is improved + // https://github.com/elastic/security-team/issues/6303 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); + + cy.log('should investigate in timeline'); + + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) + .first() + .within(() => + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') + ); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts index 7bd86e509ac18..5a1c9703ae83d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts @@ -16,25 +16,21 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout right panel json tab', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - openJsonTab(); - }); +describe('Alert details expandable flyout right panel json tab', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + openJsonTab(); + }); - it('should display the json component', () => { - // the json component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that vertically scrolls down to ensure Cypress finds it - scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 7000); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); - }); - } -); + it('should display the json component', () => { + // the json component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that vertically scrolls down to ensure Cypress finds it + scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 7000); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 9dc5dccbddcc3..ec8328cbc961f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -69,292 +69,287 @@ import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS, } from '../../../../screens/expandable_flyout/alert_details_left_panel_entities_tab'; -describe( - 'Alert details expandable flyout right panel overview tab', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); - - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); +describe('Alert details expandable flyout right panel overview tab', () => { + const rule = getNewRule(); + + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + }); + + describe('about section', () => { + it('should display about section', () => { + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER) + .should('be.visible') + .and('have.text', 'About'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_CONTENT).should('be.visible'); + + cy.log('description'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) + .should('be.visible') + .and('contain.text', 'Rule description'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) + .should('be.visible') + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON) + .should('be.visible') + .and('have.text', 'Rule summary'); + }); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS) + .should('be.visible') + .and('have.text', rule.description); + + cy.log('reason'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) + .should('be.visible') + .and('have.text', 'Alert reason'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) + .should('be.visible') + .and('contain.text', rule.name); + + cy.log('mitre attack'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) + .should('be.visible') + // @ts-ignore + .and('contain.text', rule.threat[0].framework); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS) + .should('be.visible') + // @ts-ignore + .and('contain.text', rule.threat[0].technique[0].name) + // @ts-ignore + .and('contain.text', rule.threat[0].tactic.name); }); + }); - describe('about section', () => { - it('should display about section', () => { - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER) - .should('be.visible') - .and('have.text', 'About'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_CONTENT).should('be.visible'); - - cy.log('description'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) - .should('be.visible') - .and('contain.text', 'Rule description'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) - .should('be.visible') - .within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON) - .should('be.visible') - .and('have.text', 'Rule summary'); - }); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS) - .should('be.visible') - .and('have.text', rule.description); - - cy.log('reason'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) - .should('be.visible') - .and('have.text', 'Alert reason'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) - .should('be.visible') - .and('contain.text', rule.name); - - cy.log('mitre attack'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) - .should('be.visible') - // @ts-ignore - .and('contain.text', rule.threat[0].framework); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS) - .should('be.visible') - // @ts-ignore - .and('contain.text', rule.threat[0].technique[0].name) - // @ts-ignore - .and('contain.text', rule.threat[0].tactic.name); - }); + describe('visualizations section', () => { + it('should display analyzer and session previews', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabVisualizationsSection(); + + cy.log('analyzer graph preview'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).should('be.visible'); + + cy.log('session view preview'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).should('be.visible'); }); + }); + + describe('investigation section', () => { + it('should display investigation section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInvestigationSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) + .should('be.visible') + .and('have.text', 'Investigation'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT).should( + 'be.visible' + ); + + cy.log('investigation guide button'); - describe('visualizations section', () => { - it('should display analyzer and session previews', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabVisualizationsSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON) + .should('be.visible') + .and('have.text', 'Investigation guide'); - cy.log('analyzer graph preview'); + cy.log('should navigate to left Investigation tab'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).should('be.visible'); + clickInvestigationGuideButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - cy.log('session view preview'); + cy.log('highlighted fields'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) + .should('be.visible') + .and('have.text', 'Highlighted fields'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) + .should('be.visible') + .and('contain.text', 'host.name'); + const hostNameCell = + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('siem-kibana'); + cy.get(hostNameCell).should('be.visible').and('have.text', 'siem-kibana'); + + cy.get(hostNameCell).click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); + + collapseDocumentDetailsExpandableFlyoutLeftSection(); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) + .should('be.visible') + .and('contain.text', 'user.name'); + const userNameCell = + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('test'); + cy.get(userNameCell).should('be.visible').and('have.text', 'test'); + + cy.get(userNameCell).click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); + }); + }); + + describe('insights section', () => { + it('should display entities section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER) + .should('be.visible') + .and('have.text', 'Entities'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER).should( + 'be.visible' + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT).should( + 'be.visible' + ); + + cy.log('should navigate to left panel Entities tab'); + + clickEntitiesViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + }); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).should('be.visible'); - }); + // TODO: skipping this due to flakiness + it.skip('should display threat intelligence section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER + ).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT + ).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT) + .should('be.visible') + .within(() => { + // threat match detected + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) + .eq(0) + .should('be.visible') + .and('have.text', '0 threat match detected'); // TODO work on getting proper IoC data to get proper data here + + // field with threat enrichement + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) + .eq(1) + .should('be.visible') + .and('have.text', '0 field enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here + }); + + cy.log('should navigate to left panel Threat Intelligence tab'); + + clickThreatIntelligenceViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Threat Intelligence sub tab directly }); - describe('investigation section', () => { - it('should display investigation section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInvestigationSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) - .should('be.visible') - .and('have.text', 'Investigation'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT).should( - 'be.visible' - ); - - cy.log('investigation guide button'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON) - .should('be.visible') - .and('have.text', 'Investigation guide'); - - cy.log('should navigate to left Investigation tab'); - - clickInvestigationGuideButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - - cy.log('highlighted fields'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) - .should('be.visible') - .and('have.text', 'Highlighted fields'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should( - 'be.visible' - ); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) - .should('be.visible') - .and('contain.text', 'host.name'); - const hostNameCell = - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('siem-kibana'); - cy.get(hostNameCell).should('be.visible').and('have.text', 'siem-kibana'); - - cy.get(hostNameCell).click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); - - collapseDocumentDetailsExpandableFlyoutLeftSection(); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) - .should('be.visible') - .and('contain.text', 'user.name'); - const userNameCell = - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('test'); - cy.get(userNameCell).should('be.visible').and('have.text', 'test'); - - cy.get(userNameCell).click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); - }); + // TODO: skipping this due to flakiness + it.skip('should display correlations section', () => { + cy.log('link the alert to a new case'); + + createNewCaseFromExpandableFlyout(); + + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER) + .should('be.visible') + .and('have.text', 'Correlations'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT) + .should('be.visible') + .within(() => { + // TODO the order in which these appear is not deterministic currently, hence this can cause flakiness + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + .eq(0) + .should('be.visible') + .and('have.text', '1 alert related by ancestry'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + .eq(1) + .should('be.visible') + .and('have.text', '1 related case'); + // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + // .eq(2) + // .should('be.visible') + // .and('have.text', '1 alert related by the same source event'); // TODO work on getting proper data to display some same source data here + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + .eq(2) + .should('be.visible') + .and('have.text', '1 alert related by session'); + }); + + cy.log('should navigate to left panel Correlations tab'); + + clickCorrelationsViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Correlations sub tab directly }); - describe('insights section', () => { - it('should display entities section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER) - .should('be.visible') - .and('have.text', 'Entities'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER).should( - 'be.visible' - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT).should( - 'be.visible' - ); - - cy.log('should navigate to left panel Entities tab'); - - clickEntitiesViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); - }); - - it('should display threat intelligence section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get( - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER - ).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); - cy.get( - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT - ).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT) - .should('be.visible') - .within(() => { - // threat match detected - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) - .eq(0) - .should('be.visible') - .and('have.text', '0 threat match detected'); // TODO work on getting proper IoC data to get proper data here - - // field with threat enrichement - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) - .eq(1) - .should('be.visible') - .and('have.text', '0 field enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here - }); - - cy.log('should navigate to left panel Threat Intelligence tab'); - - clickThreatIntelligenceViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Threat Intelligence sub tab directly - }); - - // TODO: skipping this due to flakiness - it.skip('should display correlations section', () => { - cy.log('link the alert to a new case'); - - createNewCaseFromExpandableFlyout(); - - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER) - .should('be.visible') - .and('have.text', 'Correlations'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT) - .should('be.visible') - .within(() => { - // TODO the order in which these appear is not deterministic currently, hence this can cause flakiness - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(0) - .should('be.visible') - .and('have.text', '1 alert related by ancestry'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(1) - .should('be.visible') - .and('have.text', '1 related case'); - // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - // .eq(2) - // .should('be.visible') - // .and('have.text', '1 alert related by the same source event'); // TODO work on getting proper data to display some same source data here - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(2) - .should('be.visible') - .and('have.text', '1 alert related by session'); - }); - - cy.log('should navigate to left panel Correlations tab'); - - clickCorrelationsViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Correlations sub tab directly - }); - - // TODO work on getting proper data to make the prevalence section work here - // we need to generate enough data to have at least one field with prevalence - it.skip('should display prevalence section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER) - .should('be.visible') - .and('have.text', 'Prevalence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT) - .should('be.visible') - .within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES) - .should('be.visible') - .and('have.text', 'is uncommon'); - }); - - cy.log('should navigate to left panel Prevalence tab'); - - clickPrevalenceViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Prevalence sub tab directly - }); + // TODO work on getting proper data to make the prevalence section work here + // we need to generate enough data to have at least one field with prevalence + it.skip('should display prevalence section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER) + .should('be.visible') + .and('have.text', 'Prevalence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT) + .should('be.visible') + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES) + .should('be.visible') + .and('have.text', 'is uncommon'); + }); + + cy.log('should navigate to left panel Prevalence tab'); + + clickPrevalenceViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Prevalence sub tab directly }); + }); - describe('response section', () => { - it('should display empty message', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabResponseSection(); + describe('response section', () => { + it('should display empty message', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabResponseSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_EMPTY_RESPONSE).should( - 'be.visible' - ); - }); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_EMPTY_RESPONSE).should( + 'be.visible' + ); }); - } -); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts index 9e30ba52b3cdd..ff89e16a02b03 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts @@ -31,52 +31,48 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout right panel table tab', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - openTableTab(); - }); +describe('Alert details expandable flyout right panel table tab', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + openTableTab(); + }); - it('should display and filter the table', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW).should('be.visible'); - filterTableTabTable('timestamp'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); - clearFilterTableTabTable(); - }); + it('should display and filter the table', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW).should('be.visible'); + filterTableTabTable('timestamp'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); + clearFilterTableTabTable(); + }); - it('should test cell actions', () => { - cy.log('cell actions filter in'); + it('should test cell actions', () => { + cy.log('cell actions filter in'); - filterInTableTabTable(); - cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); - removeKqlFilter(); + filterInTableTabTable(); + cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); + removeKqlFilter(); - cy.log('cell actions filter out'); + cy.log('cell actions filter out'); - filterOutTableTabTable(); - cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); - removeKqlFilter(); + filterOutTableTabTable(); + cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); + removeKqlFilter(); - cy.log('cell actions add to timeline'); + cy.log('cell actions add to timeline'); - addToTimelineTableTabTable(); - openActiveTimeline(); - cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); - closeTimeline(); + addToTimelineTableTabTable(); + openActiveTimeline(); + cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); + closeTimeline(); - cy.log('cell actions copy to clipboard'); + cy.log('cell actions copy to clipboard'); - copyToClipboardTableTabTable(); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); - }); - } -); + copyToClipboardTableTabTable(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts index fa268bfdfa341..e926e93e63301 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts @@ -15,43 +15,39 @@ import { closeFlyout } from '../../../../tasks/expandable_flyout/alert_details_r import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; import { DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE } from '../../../../screens/expandable_flyout/alert_details_right_panel'; -describe( - 'Expandable flyout state sync', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); +describe('Expandable flyout state sync', () => { + const rule = getNewRule(); - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + }); - it('should test flyout url sync', () => { - cy.url().should('not.include', 'eventFlyout'); + it('should test flyout url sync', () => { + cy.url().should('not.include', 'eventFlyout'); - expandFirstAlertExpandableFlyout(); + expandFirstAlertExpandableFlyout(); - cy.log('should serialize its state to url'); + cy.log('should serialize its state to url'); - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - cy.log('should reopen the flyout after browser refresh'); + cy.log('should reopen the flyout after browser refresh'); - cy.reload(); - waitForAlertsToPopulate(); + cy.reload(); + waitForAlertsToPopulate(); - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - cy.log('should clear the url state when flyout is closed'); + cy.log('should clear the url state when flyout is closed'); - closeFlyout(); + closeFlyout(); - cy.url().should('not.include', 'eventFlyout'); - }); - } -); + cy.url().should('not.include', 'eventFlyout'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts index 5a8c382bdf4af..5ef959899178a 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { disableExpandableFlyout } from '../../../tasks/api_calls/kibana_advanced_settings'; import { getNewRule } from '../../../objects/rule'; import { PROVIDER_BADGE, QUERY_TAB_BUTTON, TIMELINE_TITLE } from '../../../screens/timeline'; import { FILTER_BADGE } from '../../../screens/alerts'; @@ -53,6 +54,7 @@ describe('Investigate in timeline', () => { describe('From alerts details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 40d16dd088af8..ab5a2697d4be4 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -158,7 +158,7 @@ export const RULES_SELECTED_TAG = '.euiSelectableListItem[aria-checked="true"]'; export const SELECTED_RULES_NUMBER_LABEL = '[data-test-subj="selectedRules"]'; -export const REFRESH_SETTINGS_POPOVER = '[data-test-subj="refreshSettings-popover"]'; +export const AUTO_REFRESH_POPOVER_TRIGGER_BUTTON = '[data-test-subj="autoRefreshButton"]'; export const REFRESH_RULES_TABLE_BUTTON = '[data-test-subj="refreshRulesAction-linkIcon"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index d73a67cd6c271..221c3d6a61501 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -12,7 +12,6 @@ import { DELETE_RULE_ACTION_BTN, RULES_SELECTED_TAG, RULES_TABLE_INITIAL_LOADING_INDICATOR, - RULES_TABLE_AUTOREFRESH_INDICATOR, RULE_CHECKBOX, RULE_NAME, RULE_SWITCH, @@ -37,7 +36,6 @@ import { RULES_TAGS_POPOVER_WRAPPER, INTEGRATIONS_POPOVER, SELECTED_RULES_NUMBER_LABEL, - REFRESH_SETTINGS_POPOVER, REFRESH_SETTINGS_SWITCH, ELASTIC_RULES_BTN, TOASTER_ERROR_BTN, @@ -57,6 +55,7 @@ import { TOASTER_CLOSE_ICON, ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN, CONFIRM_DELETE_RULE_BTN, + AUTO_REFRESH_POPOVER_TRIGGER_BUTTON, } from '../screens/alerts_detection_rules'; import type { RULES_MONITORING_TABLE } from '../screens/alerts_detection_rules'; import { EUI_CHECKBOX } from '../screens/common/controls'; @@ -305,12 +304,6 @@ export const waitForRuleToUpdate = () => { cy.get(RULE_SWITCH_LOADER, { timeout: 300000 }).should('not.exist'); }; -export const checkAutoRefresh = (ms: number, condition: string) => { - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); - cy.tick(ms); - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should(condition); -}; - export const importRules = (rulesFile: string) => { cy.get(RULE_IMPORT_MODAL).click(); cy.get(INPUT_FILE).click({ force: true }); @@ -459,22 +452,45 @@ export const testMultipleSelectedRulesLabel = (rulesCount: number) => { cy.get(SELECTED_RULES_NUMBER_LABEL).should('have.text', `Selected ${rulesCount} rules`); }; -export const openRefreshSettingsPopover = () => { - cy.get(REFRESH_SETTINGS_POPOVER).click(); +const openRefreshSettingsPopover = () => { + cy.get(REFRESH_SETTINGS_SWITCH).should('not.exist'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).click(); cy.get(REFRESH_SETTINGS_SWITCH).should('be.visible'); }; -export const checkAutoRefreshIsDisabled = () => { +const closeRefreshSettingsPopover = () => { + cy.get(REFRESH_SETTINGS_SWITCH).should('be.visible'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).click(); + cy.get(REFRESH_SETTINGS_SWITCH).should('not.exist'); +}; + +export const expectAutoRefreshIsDisabled = () => { + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('be.enabled'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('have.text', 'Off'); + openRefreshSettingsPopover(); cy.get(REFRESH_SETTINGS_SWITCH).should('have.attr', 'aria-checked', 'false'); + closeRefreshSettingsPopover(); }; -export const checkAutoRefreshIsEnabled = () => { +export const expectAutoRefreshIsEnabled = () => { + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('be.enabled'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('have.text', 'On'); + openRefreshSettingsPopover(); cy.get(REFRESH_SETTINGS_SWITCH).should('have.attr', 'aria-checked', 'true'); + closeRefreshSettingsPopover(); +}; + +// Expects the auto refresh to be deactivated which means it's disabled without an ability to enable it +// so it's even impossible to open the popover +export const expectAutoRefreshIsDeactivated = () => { + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('be.disabled'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('have.text', 'Off'); }; export const disableAutoRefresh = () => { + openRefreshSettingsPopover(); cy.get(REFRESH_SETTINGS_SWITCH).click(); - checkAutoRefreshIsDisabled(); + expectAutoRefreshIsDisabled(); }; export const mockGlobalClock = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts index 0cadb50d72fe1..6256539beca1d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts @@ -27,3 +27,8 @@ export const enableRelatedIntegrations = () => { export const disableRelatedIntegrations = () => { kibanaSettings(relatedIntegrationsBody(false)); }; + +export const disableExpandableFlyout = () => { + const body = { changes: { 'securitySolution:enableExpandableFlyout': false } }; + kibanaSettings(body); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index cd226d4347af6..2e9493dc6ff42 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -10,6 +10,8 @@ import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { dataTableActions } from '@kbn/securitysolution-data-table'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; +import { ENABLE_EXPANDABLE_FLYOUT_SETTING } from '../../../../../common/constants'; import { RightPanelKey } from '../../../../flyout/right'; import type { SetEventsDeleted, @@ -21,7 +23,6 @@ import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/ import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline'; -import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; type Props = EuiDataGridCellValueElementProps & { columnHeaders: ColumnHeaderOptions[]; @@ -70,7 +71,7 @@ const RowActionComponent = ({ const { openFlyout } = useExpandableFlyoutContext(); const dispatch = useDispatch(); - const isSecurityFlyoutEnabled = useIsExperimentalFeatureEnabled('securityFlyoutEnabled'); + const [isSecurityFlyoutEnabled] = useUiSetting$(ENABLE_EXPANDABLE_FLYOUT_SETTING); const columnValues = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index d62114881adad..27ce67f23a978 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -104,10 +104,15 @@ export const createRule = async ({ rule, signal }: CreateRulesProps): Promise An updated rule + * + * In fact this function should return Promise but it'd require massive refactoring. + * It should be addressed as a part of OpenAPI schema adoption. + * * @throws An error if response is not OK */ -export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'PUT', body: JSON.stringify(rule), signal, @@ -198,6 +203,11 @@ export const fetchRules = async ({ * @param id Rule ID's (not rule_id) * @param signal to cancel request * + * @returns Promise + * + * In fact this function should return Promise but it'd require massive refactoring. + * It should be addressed as a part of OpenAPI schema adoption. + * * @throws An error if response is not OK */ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts index 4c8ee37e3e211..197b97effa1f8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts @@ -59,3 +59,27 @@ export const useInvalidateFetchRuleByIdQuery = () => { }); }, [queryClient]); }; + +/** + * We should use this hook to update the rules cache when modifying a rule. + * Use it with the new rule data after operations like rule edit. + * + * @returns A rules cache update callback + */ +export const useUpdateRuleByIdCache = () => { + const queryClient = useQueryClient(); + /** + * Use this method to update rules data cached by react-query. + * It is useful when we receive new rules back from a mutation query (bulk edit, etc.); + * we can merge those rules with the existing cache to avoid an extra roundtrip to re-fetch updated rules. + */ + return useCallback( + (updatedRuleResponse: Rule) => { + queryClient.setQueryData['data']>( + [...FIND_ONE_RULE_QUERY_KEY, updatedRuleResponse.id], + transformInput(updatedRuleResponse) + ); + }, + [queryClient] + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts index 53103287046be..932c50ff2a46f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts @@ -6,42 +6,43 @@ */ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import type { - RuleResponse, - RuleUpdateProps, -} from '../../../../../common/api/detection_engine/model/rule_schema'; +import type { RuleUpdateProps } from '../../../../../common/api/detection_engine/model/rule_schema'; import { transformOutput } from '../../../../detections/containers/detection_engine/rules/transforms'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { updateRule } from '../api'; import { useInvalidateFindRulesQuery } from './use_find_rules_query'; -import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query'; +import { useUpdateRuleByIdCache } from './use_fetch_rule_by_id_query'; import { useInvalidateFetchRuleManagementFiltersQuery } from './use_fetch_rule_management_filters_query'; import { useInvalidateFetchCoverageOverviewQuery } from './use_fetch_coverage_overview'; +import type { Rule } from '../../logic/types'; export const UPDATE_RULE_MUTATION_KEY = ['PUT', DETECTION_ENGINE_RULES_URL]; export const useUpdateRuleMutation = ( - options?: UseMutationOptions + options?: UseMutationOptions ) => { const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery(); - const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery(); const invalidateFetchCoverageOverviewQuery = useInvalidateFetchCoverageOverviewQuery(); + const updateRuleCache = useUpdateRuleByIdCache(); - return useMutation( + return useMutation( (rule: RuleUpdateProps) => updateRule({ rule: transformOutput(rule) }), { ...options, mutationKey: UPDATE_RULE_MUTATION_KEY, onSettled: (...args) => { invalidateFindRulesQuery(); - invalidateFetchRuleByIdQuery(); invalidateFetchRuleManagementFilters(); invalidateFetchCoverageOverviewQuery(); - if (options?.onSettled) { - options.onSettled(...args); + const [response] = args; + + if (response) { + updateRuleCache(response); } + + options?.onSettled?.(...args); }, } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx index 83499efd323c8..560be75abee26 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx @@ -41,9 +41,14 @@ const AutoRefreshButtonComponent = ({ setIsRefreshOn, }: AutoRefreshButtonProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); + const togglePopover = useCallback( + () => setIsPopoverOpen((prevState) => !prevState), + [setIsPopoverOpen] + ); const handleAutoRefreshSwitch = useCallback( - (closePopover: () => void) => (e: EuiSwitchEvent) => { + (e: EuiSwitchEvent) => { const refreshOn = e.target.checked; if (refreshOn) { reFetchRules(); @@ -51,18 +56,35 @@ const AutoRefreshButtonComponent = ({ setIsRefreshOn(refreshOn); closePopover(); }, - [reFetchRules, setIsRefreshOn] + [reFetchRules, setIsRefreshOn, closePopover] ); - const handleGetRefreshSettingsPopoverContent = useCallback( - (closePopover: () => void) => ( + return ( + + {isRefreshOn ? 'On' : 'Off'} + + } + > - ), - [isRefreshOn, handleAutoRefreshSwitch, isDisabled] - ); - - return ( - setIsPopoverOpen(false)} - button={ - setIsPopoverOpen(!isPopoverOpen)} - disabled={isDisabled} - css={css` - margin-left: 10px; - `} - > - {isRefreshOn ? 'On' : 'Off'} - - } - > - {handleGetRefreshSettingsPopoverContent(() => setIsPopoverOpen(false))} ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx index 620f562a7bf5a..94a96a0958725 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx @@ -156,7 +156,7 @@ describe('AlertDetailsRedirect', () => { const [{ search, pathname }] = historyMock.replace.mock.lastCall; - expect(search as string).toMatch(/eventFlyout.*right/); + expect(search as string).toMatch(/eventFlyout.*/); expect(pathname).toEqual(ALERTS_PATH); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx index 0786b4cdf3824..41516d06942ff 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx @@ -12,12 +12,16 @@ import { Redirect, useLocation, useParams } from 'react-router-dom'; import moment from 'moment'; import { encode } from '@kbn/rison'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import type { FilterItemObj } from '../../../common/components/filter_group/types'; -import { ALERTS_PATH, DEFAULT_ALERTS_INDEX } from '../../../../common/constants'; +import { + ALERTS_PATH, + DEFAULT_ALERTS_INDEX, + ENABLE_EXPANDABLE_FLYOUT_SETTING, +} from '../../../../common/constants'; import { URL_PARAM_KEY } from '../../../common/hooks/use_url_state'; import { inputsSelectors } from '../../../common/store'; import { formatPageFilterSearchParam } from '../../../../common/utils/format_page_filter_search_param'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { resolveFlyoutParams } from './utils'; import { FLYOUT_URL_PARAM } from '../../../flyout/shared/hooks/url/use_sync_flyout_state_with_url'; @@ -70,7 +74,7 @@ export const AlertDetailsRedirect = () => { const currentFlyoutParams = searchParams.get(FLYOUT_URL_PARAM); - const isSecurityFlyoutEnabled = useIsExperimentalFeatureEnabled('securityFlyoutEnabled'); + const [isSecurityFlyoutEnabled] = useUiSetting$(ENABLE_EXPANDABLE_FLYOUT_SETTING); const urlParams = new URLSearchParams({ [URL_PARAM_KEY.appQuery]: kqlAppQuery, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts index a62e877f018e4..5bb5021a3229c 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts @@ -11,7 +11,7 @@ import { closeAllToasts } from '../../tasks/toasts'; import { toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate'; import { cleanupRule, loadRule } from '../../tasks/api_fixtures'; import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; import { changeAlertsFilter } from '../../tasks/alerts'; @@ -60,6 +60,7 @@ describe('Automated Response Actions', () => { beforeEach(() => { login(); + disableExpandableFlyoutAdvancedSettings(); }); describe('From alerts', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts index ff4adacc9b73f..5ec6ed11c80a4 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts @@ -23,7 +23,7 @@ import { } from '../../tasks/isolate'; import { cleanupCase, cleanupRule, loadCase, loadRule } from '../../tasks/api_fixtures'; import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; @@ -72,6 +72,7 @@ describe('Isolate command', () => { beforeEach(() => { login(); + disableExpandableFlyoutAdvancedSettings(); }); describe('From manage', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts index 4d830c959399a..3ef371b1c847b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts @@ -6,6 +6,7 @@ */ import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations'; +import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common'; import { APP_ALERTS_PATH } from '../../../../../../common/constants'; import { closeAllToasts } from '../../../tasks/toasts'; import { fillUpNewRule } from '../../../tasks/response_actions'; @@ -39,6 +40,7 @@ describe('No License', { env: { ftrConfig: { license: 'basic' } } }, () => { const [endpointAgentId, endpointHostname] = generateRandomStringName(2); before(() => { login(); + disableExpandableFlyoutAdvancedSettings(); indexEndpointRuleAlerts({ endpointAgentId, endpointHostname, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts index bb3be124418f8..f0cac7527c19e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts @@ -6,6 +6,7 @@ */ import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations'; +import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common'; import { APP_ALERTS_PATH } from '../../../../../../common/constants'; import { closeAllToasts } from '../../../tasks/toasts'; import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; @@ -52,6 +53,7 @@ describe('Results', () => { describe('see results when has RBAC', () => { before(() => { login(ROLE.endpoint_response_actions_access); + disableExpandableFlyoutAdvancedSettings(); }); it('see endpoint action', () => { @@ -67,6 +69,7 @@ describe('Results', () => { describe('do not see results results when does not have RBAC', () => { before(() => { login(ROLE.endpoint_response_actions_no_access); + disableExpandableFlyoutAdvancedSettings(); }); it('show the permission denied callout', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts index f633fd25abdcc..8e3811c93ee0e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts @@ -24,7 +24,7 @@ import type { ReturnTypeFromChainable } from '../../types'; import { addAlertsToCase } from '../../tasks/add_alerts_to_case'; import { APP_ALERTS_PATH, APP_CASES_PATH, APP_PATH } from '../../../../../common/constants'; import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; import { indexNewCase } from '../../tasks/index_new_case'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; @@ -95,6 +95,7 @@ describe('Isolate command', () => { let hostname: string; before(() => { + disableExpandableFlyoutAdvancedSettings(); indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts index fb879fa5244b0..38866ec5b5d29 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts @@ -34,3 +34,23 @@ export const request = ({ headers: { ...COMMON_API_HEADERS, ...headers }, ...options, }); + +const API_HEADERS = Object.freeze({ 'kbn-xsrf': 'cypress' }); +export const rootRequest = ( + options: Partial +): Cypress.Chainable> => + cy.request({ + auth: API_AUTH, + headers: API_HEADERS, + ...options, + }); + +export const disableExpandableFlyoutAdvancedSettings = () => { + const body = { changes: { 'securitySolution:enableExpandableFlyout': false } }; + rootRequest({ + method: 'POST', + url: 'internal/kibana/settings', + body, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 50d4ae02eeb9b..058f8892013a0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -17,6 +17,7 @@ import type { import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions/types'; +import type { AppFeatures } from '../lib/app_features'; import { getPackagePolicyCreateCallback, getPackagePolicyUpdateCallback, @@ -69,6 +70,7 @@ export interface EndpointAppContextServiceStartContract { actionCreateService: ActionCreateService | undefined; cloud: CloudSetup; esClient: ElasticsearchClient; + appFeatures: AppFeatures; } /** @@ -106,6 +108,7 @@ export class EndpointAppContextService { featureUsageService, endpointMetadataService, esClient, + appFeatures, } = dependencies; registerIngestCallback( @@ -117,7 +120,8 @@ export class EndpointAppContextService { alerting, licenseService, exceptionListsClient, - cloud + cloud, + appFeatures ) ); @@ -134,7 +138,8 @@ export class EndpointAppContextService { featureUsageService, endpointMetadataService, cloud, - esClient + esClient, + appFeatures ) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts new file mode 100644 index 0000000000000..1d39b72670b98 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts @@ -0,0 +1,145 @@ +/* + * 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 { createMockEndpointAppContextServiceStartContract } from '../mocks'; +import type { Logger } from '@kbn/logging'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; +import type { AppFeatures } from '../../lib/app_features'; +import { createAppFeaturesMock } from '../../lib/app_features/mocks'; +import { ALL_APP_FEATURE_KEYS } from '../../../common'; +import { turnOffPolicyProtectionsIfNotSupported } from './turn_off_policy_protections'; +import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator'; +import type { PolicyData } from '../../../common/endpoint/types'; +import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; +import type { PromiseResolvedValue } from '../../../common/endpoint/types/utility_types'; +import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers'; + +describe('Turn Off Policy Protections Migration', () => { + let esClient: ElasticsearchClient; + let fleetServices: EndpointInternalFleetServicesInterface; + let appFeatures: AppFeatures; + let logger: Logger; + + const callTurnOffPolicyProtections = () => + turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatures, logger); + + beforeEach(() => { + const endpointContextStartContract = createMockEndpointAppContextServiceStartContract(); + + ({ esClient, appFeatures, logger } = endpointContextStartContract); + fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); + }); + + describe('and `endpointPolicyProtections` is enabled', () => { + it('should do nothing', async () => { + await callTurnOffPolicyProtections(); + + expect(fleetServices.packagePolicy.list as jest.Mock).not.toHaveBeenCalled(); + expect(logger.info).toHaveBeenLastCalledWith( + 'App feature [endpoint_policy_protections] is enabled. Nothing to do!' + ); + }); + }); + + describe('and `endpointPolicyProtections` is disabled', () => { + let policyGenerator: FleetPackagePolicyGenerator; + let page1Items: PolicyData[] = []; + let page2Items: PolicyData[] = []; + let bulkUpdateResponse: PromiseResolvedValue>; + + const generatePolicyMock = (withDisabledProtections = false): PolicyData => { + const policy = policyGenerator.generateEndpointPackagePolicy(); + + if (!withDisabledProtections) { + return policy; + } + + policy.inputs[0].config.policy.value = ensureOnlyEventCollectionIsAllowed( + policy.inputs[0].config.policy.value + ); + + return policy; + }; + + beforeEach(() => { + policyGenerator = new FleetPackagePolicyGenerator('seed'); + const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock; + + appFeatures = createAppFeaturesMock( + ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') + ); + + page1Items = [generatePolicyMock(), generatePolicyMock(true)]; + page2Items = [generatePolicyMock(true), generatePolicyMock()]; + + packagePolicyListSrv + .mockImplementationOnce(async () => { + return { + total: 1500, + page: 1, + perPage: 1000, + items: page1Items, + }; + }) + .mockImplementationOnce(async () => { + return { + total: 1500, + page: 2, + perPage: 1000, + items: page2Items, + }; + }); + + bulkUpdateResponse = { + updatedPolicies: [page1Items[0], page2Items[1]], + failedPolicies: [], + }; + + (fleetServices.packagePolicy.bulkUpdate as jest.Mock).mockImplementation(async () => { + return bulkUpdateResponse; + }); + }); + + it('should update only policies that have protections turn on', async () => { + await callTurnOffPolicyProtections(); + + expect(fleetServices.packagePolicy.list as jest.Mock).toHaveBeenCalledTimes(2); + expect(fleetServices.packagePolicy.bulkUpdate as jest.Mock).toHaveBeenCalledWith( + fleetServices.internalSoClient, + esClient, + [ + expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![0].id }), + expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![1].id }), + ], + { user: { username: 'elastic' } } + ); + expect(logger.info).toHaveBeenCalledWith( + 'Found 2 policies that need updates:\n' + + `Policy [${bulkUpdateResponse.updatedPolicies![0].id}][${ + bulkUpdateResponse.updatedPolicies![0].name + }] updated to disable protections. Trigger: [property [mac.malware.mode] is set to [prevent]]\n` + + `Policy [${bulkUpdateResponse.updatedPolicies![1].id}][${ + bulkUpdateResponse.updatedPolicies![1].name + }] updated to disable protections. Trigger: [property [mac.malware.mode] is set to [prevent]]` + ); + expect(logger.info).toHaveBeenCalledWith('Done. All updates applied successfully'); + }); + + it('should log failures', async () => { + bulkUpdateResponse.failedPolicies.push({ + error: new Error('oh oh'), + packagePolicy: bulkUpdateResponse.updatedPolicies![0], + }); + await callTurnOffPolicyProtections(); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('Done. 1 out of 2 failed to update:') + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts new file mode 100644 index 0000000000000..c4a63b8ec841c --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger, ElasticsearchClient } from '@kbn/core/server'; +import type { UpdatePackagePolicy } from '@kbn/fleet-plugin/common'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common'; +import { + isPolicySetToEventCollectionOnly, + ensureOnlyEventCollectionIsAllowed, +} from '../../../common/endpoint/models/policy_config_helpers'; +import type { PolicyData } from '../../../common/endpoint/types'; +import { AppFeatureSecurityKey } from '../../../common/types/app_features'; +import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; +import type { AppFeatures } from '../../lib/app_features'; +import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy'; + +export const turnOffPolicyProtectionsIfNotSupported = async ( + esClient: ElasticsearchClient, + fleetServices: EndpointInternalFleetServicesInterface, + appFeaturesService: AppFeatures, + logger: Logger +): Promise => { + const log = logger.get('endpoint', 'policyProtections'); + + if (appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) { + log.info( + `App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!` + ); + + return; + } + + log.info( + `App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance` + ); + + const { packagePolicy, internalSoClient, endpointPolicyKuery } = fleetServices; + const updates: UpdatePackagePolicy[] = []; + const messages: string[] = []; + const perPage = 1000; + let hasMoreData = true; + let total = 0; + let page = 1; + + do { + const currentPage = page++; + const { items, total: totalPolicies } = await packagePolicy.list(internalSoClient, { + page: currentPage, + kuery: endpointPolicyKuery, + perPage, + }); + + total = totalPolicies; + hasMoreData = currentPage * perPage < total; + + for (const item of items) { + const integrationPolicy = item as PolicyData; + const policySettings = integrationPolicy.inputs[0].config.policy.value; + const { message, isOnlyCollectingEvents } = isPolicySetToEventCollectionOnly(policySettings); + + if (!isOnlyCollectingEvents) { + messages.push( + `Policy [${integrationPolicy.id}][${integrationPolicy.name}] updated to disable protections. Trigger: [${message}]` + ); + + integrationPolicy.inputs[0].config.policy.value = + ensureOnlyEventCollectionIsAllowed(policySettings); + + updates.push({ + ...getPolicyDataForUpdate(integrationPolicy), + id: integrationPolicy.id, + }); + } + } + } while (hasMoreData); + + if (updates.length > 0) { + log.info(`Found ${updates.length} policies that need updates:\n${messages.join('\n')}`); + + const bulkUpdateResponse = await fleetServices.packagePolicy.bulkUpdate( + internalSoClient, + esClient, + updates, + { + user: { username: 'elastic' } as AuthenticatedUser, + } + ); + + log.debug(`Bulk update response:\n${JSON.stringify(bulkUpdateResponse, null, 2)}`); + + if (bulkUpdateResponse.failedPolicies.length > 0) { + log.error( + `Done. ${bulkUpdateResponse.failedPolicies.length} out of ${ + updates.length + } failed to update:\n${JSON.stringify(bulkUpdateResponse.failedPolicies, null, 2)}` + ); + } else { + log.info(`Done. All updates applied successfully`); + } + } else { + log.info(`Done. Checked ${total} policies and no updates needed`); + } +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index f38d96cbf7063..5a3c9ee2297ac 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -71,6 +71,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz'; import { EndpointFleetServicesFactory } from './services/fleet'; import { createLicenseServiceMock } from '../../common/license/mocks'; import { createFeatureUsageServiceMock } from './services/feature_usage/mocks'; +import { createAppFeaturesMock } from '../lib/app_features/mocks'; /** * Creates a mocked EndpointAppContext. @@ -163,6 +164,8 @@ export const createMockEndpointAppContextServiceStartContract = }, savedObjectsStart ); + const experimentalFeatures = config.experimentalFeatures; + const appFeatures = createAppFeaturesMock(undefined, experimentalFeatures, undefined, logger); packagePolicyService.list.mockImplementation(async (_, options) => { return { @@ -207,11 +210,12 @@ export const createMockEndpointAppContextServiceStartContract = cases: casesMock, cloud: cloudMock.createSetup(), featureUsageService: createFeatureUsageServiceMock(), - experimentalFeatures: createMockConfig().experimentalFeatures, + experimentalFeatures, messageSigningService: createMessageSigningServiceMock(), actionCreateService: undefined, createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), esClient: elasticsearchClientMock.createElasticsearchClient(), + appFeatures, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts index 658ff9f2a327e..1f3df9d6a67d3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts @@ -13,6 +13,8 @@ import type { PackagePolicyClient, PackageClient, } from '@kbn/fleet-plugin/server'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { createInternalSoClient } from '../../utils/create_internal_so_client'; import { createInternalReadonlySoClient } from '../../utils/create_internal_readonly_so_client'; export interface EndpointFleetServicesFactoryInterface { @@ -42,7 +44,10 @@ export class EndpointFleetServicesFactory implements EndpointFleetServicesFactor packages: packageService.asInternalUser, packagePolicy, + endpointPolicyKuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "endpoint"`, + internalReadonlySoClient: createInternalReadonlySoClient(this.savedObjectsStart), + internalSoClient: createInternalSoClient(this.savedObjectsStart), }; } } @@ -55,6 +60,8 @@ export interface EndpointFleetServicesInterface { agentPolicy: AgentPolicyServiceInterface; packages: PackageClient; packagePolicy: PackagePolicyClient; + /** The `kuery` that can be used to filter for Endpoint integration policies */ + endpointPolicyKuery: string; } export interface EndpointInternalFleetServicesInterface extends EndpointFleetServicesInterface { @@ -62,4 +69,7 @@ export interface EndpointInternalFleetServicesInterface extends EndpointFleetSer * An internal SO client (readonly) that can be used with the Fleet services that require it */ internalReadonlySoClient: SavedObjectsClientContract; + + /** Internal SO client. USE ONLY WHEN ABSOLUTELY NEEDED. Else, use the `internalReadonlySoClient` */ + internalSoClient: SavedObjectsClientContract; } diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts index d8bf7badec846..b621222e79c0a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts @@ -5,12 +5,8 @@ * 2.0. */ -import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; -import type { - KibanaRequest, - SavedObjectsClientContract, - SavedObjectsServiceStart, -} from '@kbn/core/server'; +import type { SavedObjectsClientContract, SavedObjectsServiceStart } from '@kbn/core/server'; +import { createInternalSoClient } from './create_internal_so_client'; import { EndpointError } from '../../../common/endpoint/errors'; type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; @@ -37,18 +33,7 @@ export class InternalReadonlySoClientMethodNotAllowedError extends EndpointError export const createInternalReadonlySoClient = ( savedObjectsServiceStart: SavedObjectsServiceStart ): SavedObjectsClientContract => { - const fakeRequest = { - headers: {}, - getBasePath: () => '', - path: '/', - route: { settings: {} }, - url: { href: {} }, - raw: { req: { url: '/' } }, - } as unknown as KibanaRequest; - - const internalSoClient = savedObjectsServiceStart.getScopedClient(fakeRequest, { - excludedExtensions: [SECURITY_EXTENSION_ID], - }); + const internalSoClient = createInternalSoClient(savedObjectsServiceStart); return new Proxy(internalSoClient, { get( diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts new file mode 100644 index 0000000000000..88e0d7a70a4c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; + +export const createInternalSoClient = ( + savedObjectsServiceStart: SavedObjectsServiceStart +): SavedObjectsClientContract => { + const fakeRequest = { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { href: {} }, + raw: { req: { url: '/' } }, + } as unknown as KibanaRequest; + + return savedObjectsServiceStart.getScopedClient(fakeRequest, { + excludedExtensions: [SECURITY_EXTENSION_ID], + }); +}; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 54258638f1230..f26531296b6a2 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -54,6 +54,9 @@ import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { disableProtections } from '../../common/endpoint/models/policy_config_helpers'; +import type { AppFeatures } from '../lib/app_features'; +import { createAppFeaturesMock } from '../lib/app_features/mocks'; +import { ALL_APP_FEATURE_KEYS } from '../../common'; jest.mock('uuid', () => ({ v4: (): string => 'NEW_UUID', @@ -74,6 +77,7 @@ describe('ingest_integration tests ', () => { }); const generator = new EndpointDocGenerator(); const cloudService = cloudMock.createSetup(); + let appFeatures: AppFeatures; beforeEach(() => { endpointAppContextMock = createMockEndpointAppContextServiceStartContract(); @@ -82,6 +86,7 @@ describe('ingest_integration tests ', () => { licenseEmitter = new Subject(); licenseService = new LicenseService(); licenseService.start(licenseEmitter); + appFeatures = endpointAppContextMock.appFeatures; jest .spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy') @@ -129,7 +134,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.alerting, licenseService, exceptionListClient, - cloudService + cloudService, + appFeatures ); return callback( @@ -363,6 +369,7 @@ describe('ingest_integration tests ', () => { ); }); }); + describe('package policy update callback (when the license is below platinum)', () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; @@ -379,7 +386,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -397,7 +405,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -419,6 +428,7 @@ describe('ingest_integration tests ', () => { beforeEach(() => { licenseEmitter.next(Platinum); // set license level to platinum }); + it('updates successfully when paid features are turned on', async () => { const mockPolicy = policyFactory(); mockPolicy.windows.popup.malware.message = 'paid feature'; @@ -429,7 +439,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -442,6 +453,50 @@ describe('ingest_integration tests ', () => { ); expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy); }); + + it('should turn off protections if endpointPolicyProtections appFeature is disabled', async () => { + appFeatures = createAppFeaturesMock( + ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') + ); + const callback = getPackagePolicyUpdateCallback( + endpointAppContextMock.logger, + licenseService, + endpointAppContextMock.featureUsageService, + endpointAppContextMock.endpointMetadataService, + cloudService, + esClient, + appFeatures + ); + + const updatedPolicy = await callback( + generator.generatePolicyPackagePolicy(), + soClient, + esClient, + requestContextMock.convertContext(ctx), + req + ); + + expect(updatedPolicy.inputs?.[0]?.config?.policy.value).toMatchObject({ + linux: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + mac: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + windows: { + antivirus_registration: { enabled: false }, + attack_surface_reduction: { credential_hardening: { enabled: false } }, + behavior_protection: { mode: 'off' }, + malware: { blocklist: false }, + memory_protection: { mode: 'off' }, + ransomware: { mode: 'off' }, + }, + }); + }); }); describe('package policy update callback when meta fields should be updated', () => { @@ -486,7 +541,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); @@ -520,7 +576,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); // values should be updated diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index b897441fe1e04..04bc9afa6d3a1 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -22,6 +22,12 @@ import type { } from '@kbn/fleet-plugin/common'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; +import { AppFeatureSecurityKey } from '../../common/types/app_features'; +import { + isPolicySetToEventCollectionOnly, + ensureOnlyEventCollectionIsAllowed, +} from '../../common/endpoint/models/policy_config_helpers'; +import type { AppFeatures } from '../lib/app_features'; import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; import type { LicenseService } from '../../common/license'; import type { ManifestManager } from '../endpoint/services'; @@ -72,7 +78,8 @@ export const getPackagePolicyCreateCallback = ( alerts: AlertsStartContract, licenseService: LicenseService, exceptionsClient: ExceptionListClient | undefined, - cloud: CloudSetup + cloud: CloudSetup, + appFeatures: AppFeatures ): PostPackagePolicyCreateCallback => { return async ( newPackagePolicy, @@ -140,7 +147,8 @@ export const getPackagePolicyCreateCallback = ( licenseService, endpointIntegrationConfig, cloud, - esClientInfo + esClientInfo, + appFeatures ); return { @@ -175,31 +183,38 @@ export const getPackagePolicyUpdateCallback = ( featureUsageService: FeatureUsageService, endpointMetadataService: EndpointMetadataService, cloud: CloudSetup, - esClient: ElasticsearchClient + esClient: ElasticsearchClient, + appFeatures: AppFeatures ): PutPackagePolicyUpdateCallback => { return async (newPackagePolicy: NewPackagePolicy): Promise => { if (!isEndpointPackagePolicy(newPackagePolicy)) { return newPackagePolicy; } + const endpointIntegrationData = newPackagePolicy as NewPolicyData; + // Validate that Endpoint Security policy is valid against current license validatePolicyAgainstLicense( // The cast below is needed in order to ensure proper typing for // the policy configuration specific for endpoint - newPackagePolicy.inputs[0].config?.policy?.value as PolicyConfig, + endpointIntegrationData.inputs[0].config?.policy?.value as PolicyConfig, licenseService, logger ); - notifyProtectionFeatureUsage(newPackagePolicy, featureUsageService, endpointMetadataService); + notifyProtectionFeatureUsage( + endpointIntegrationData, + featureUsageService, + endpointMetadataService + ); - const newEndpointPackagePolicy = newPackagePolicy.inputs[0].config?.policy + const newEndpointPackagePolicy = endpointIntegrationData.inputs[0].config?.policy ?.value as PolicyConfig; const esClientInfo: InfoResponse = await esClient.info(); if ( - newPackagePolicy.inputs[0].config?.policy?.value && + endpointIntegrationData.inputs[0].config?.policy?.value && shouldUpdateMetaValues( newEndpointPackagePolicy, licenseService.getLicenseType(), @@ -214,10 +229,25 @@ export const getPackagePolicyUpdateCallback = ( newEndpointPackagePolicy.meta.cluster_name = esClientInfo.cluster_name; newEndpointPackagePolicy.meta.cluster_uuid = esClientInfo.cluster_uuid; newEndpointPackagePolicy.meta.license_uid = licenseService.getLicenseUID(); - newPackagePolicy.inputs[0].config.policy.value = newEndpointPackagePolicy; + + endpointIntegrationData.inputs[0].config.policy.value = newEndpointPackagePolicy; + } + + // If no Policy Protection allowed (ex. serverless) + const eventsOnlyPolicy = isPolicySetToEventCollectionOnly(newEndpointPackagePolicy); + if ( + !appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections) && + !eventsOnlyPolicy.isOnlyCollectingEvents + ) { + logger.warn( + `Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] appFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]` + ); + + endpointIntegrationData.inputs[0].config.policy.value = + ensureOnlyEventCollectionIsAllowed(newEndpointPackagePolicy); } - return newPackagePolicy; + return endpointIntegrationData; }; }; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts index b707199aa4738..9208f9f7f22cd 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts @@ -19,6 +19,9 @@ import type { PolicyCreateCloudConfig, PolicyCreateEndpointConfig, } from '../types'; +import type { AppFeatures } from '../../lib/app_features'; +import { createAppFeaturesMock } from '../../lib/app_features/mocks'; +import { ALL_APP_FEATURE_KEYS } from '../../../common'; describe('Create Default Policy tests ', () => { const cloud = cloudMock.createSetup(); @@ -28,6 +31,7 @@ describe('Create Default Policy tests ', () => { const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold', uid: '' } }); let licenseEmitter: Subject; let licenseService: LicenseService; + let appFeatures: AppFeatures; const createDefaultPolicyCallback = async ( config: AnyPolicyCreateConfig | undefined @@ -35,7 +39,7 @@ describe('Create Default Policy tests ', () => { const esClientInfo = await elasticsearchServiceMock.createClusterClient().asInternalUser.info(); esClientInfo.cluster_name = ''; esClientInfo.cluster_uuid = ''; - return createDefaultPolicy(licenseService, config, cloud, esClientInfo); + return createDefaultPolicy(licenseService, config, cloud, esClientInfo, appFeatures); }; beforeEach(() => { @@ -43,7 +47,9 @@ describe('Create Default Policy tests ', () => { licenseService = new LicenseService(); licenseService.start(licenseEmitter); licenseEmitter.next(Platinum); // set license level to platinum + appFeatures = createAppFeaturesMock(); }); + describe('When no config is set', () => { it('Should return PolicyConfig for events only when license is at least platinum', async () => { const defaultPolicy = policyFactory(); @@ -174,6 +180,7 @@ describe('Create Default Policy tests ', () => { }); }); }); + it('Should return process, file and network events enabled when preset is EDR Essential', async () => { const config = createEndpointConfig({ preset: 'EDREssential' }); const policy = await createDefaultPolicyCallback(config); @@ -190,6 +197,7 @@ describe('Create Default Policy tests ', () => { }); }); }); + it('Should return the default config when preset is EDR Complete', async () => { const config = createEndpointConfig({ preset: 'EDRComplete' }); const policy = await createDefaultPolicyCallback(config); @@ -199,7 +207,37 @@ describe('Create Default Policy tests ', () => { defaultPolicy.meta.cloud = true; expect(policy).toMatchObject(defaultPolicy); }); + + it('should set policy to event collection only if endpointPolicyProtections appFeature is disabled', async () => { + appFeatures = createAppFeaturesMock( + ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') + ); + + await expect( + createDefaultPolicyCallback(createEndpointConfig({ preset: 'EDRComplete' })) + ).resolves.toMatchObject({ + linux: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + mac: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + windows: { + antivirus_registration: { enabled: false }, + attack_surface_reduction: { credential_hardening: { enabled: false } }, + behavior_protection: { mode: 'off' }, + malware: { blocklist: false }, + memory_protection: { mode: 'off' }, + ransomware: { mode: 'off' }, + }, + }); + }); }); + describe('When cloud config is set', () => { const createCloudConfig = (): PolicyCreateCloudConfig => ({ type: 'cloud', diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index d7c3994c05dc9..db053fd5c3b0e 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -7,6 +7,8 @@ import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; +import { AppFeatureSecurityKey } from '../../../common/types/app_features'; +import type { AppFeatures } from '../../lib/app_features'; import { policyFactory as policyConfigFactory, policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, @@ -20,7 +22,10 @@ import { ENDPOINT_CONFIG_PRESET_NGAV, ENDPOINT_CONFIG_PRESET_DATA_COLLECTION, } from '../constants'; -import { disableProtections } from '../../../common/endpoint/models/policy_config_helpers'; +import { + disableProtections, + ensureOnlyEventCollectionIsAllowed, +} from '../../../common/endpoint/models/policy_config_helpers'; /** * Create the default endpoint policy based on the current license and configuration type @@ -29,7 +34,8 @@ export const createDefaultPolicy = ( licenseService: LicenseService, config: AnyPolicyCreateConfig | undefined, cloud: CloudSetup, - esClientInfo: InfoResponse + esClientInfo: InfoResponse, + appFeatures: AppFeatures ): PolicyConfig => { const factoryPolicy = policyConfigFactory(); @@ -44,15 +50,21 @@ export const createDefaultPolicy = ( : factoryPolicy.meta.cluster_uuid; factoryPolicy.meta.license_uid = licenseService.getLicenseUID(); - const defaultPolicyPerType = + let defaultPolicyPerType: PolicyConfig = config?.type === 'cloud' ? getCloudPolicyConfig(factoryPolicy) : getEndpointPolicyWithIntegrationConfig(factoryPolicy, config); - // Apply license limitations in the final step, so it's not overriden (see malware popup) - return licenseService.isPlatinumPlus() - ? defaultPolicyPerType - : policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType); + if (!licenseService.isPlatinumPlus()) { + defaultPolicyPerType = policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType); + } + + // If no Policy Protection allowed (ex. serverless) + if (!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) { + defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType); + } + + return defaultPolicyPerType; }; /** diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts index 69c6c33d335c3..edeec0d533a40 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts @@ -59,7 +59,7 @@ export class AppFeatures { return this.appFeatures.has(appFeatureKey); } - private registerEnabledKibanaFeatures() { + protected registerEnabledKibanaFeatures() { if (this.featuresSetup == null) { throw new Error( 'Cannot sync kibana features as featuresSetup is not present. Did you call init?' diff --git a/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts b/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts new file mode 100644 index 0000000000000..1a5efc9c64e37 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; +import { AppFeatures } from './app_features'; +import type { AppFeatureKeys, ExperimentalFeatures } from '../../../common'; +import { ALL_APP_FEATURE_KEYS, allowedExperimentalValues } from '../../../common'; + +class AppFeaturesMock extends AppFeatures { + protected registerEnabledKibanaFeatures() { + // NOOP + } +} + +export const createAppFeaturesMock = ( + /** What features keys should be enabled. Default is all */ + enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS], + experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues }, + featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(), + logger: Logger = loggingSystemMock.create().get('appFeatureMock') +) => { + const appFeatures = new AppFeaturesMock(logger, experimentalFeatures); + + appFeatures.init(featuresPluginSetupContract); + + if (enabledFeatureKeys) { + appFeatures.set(enabledFeatureKeys); + } + + return appFeatures; +}; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index e64a5808eb3dd..4e7beb558d4de 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -17,6 +17,7 @@ import { Dataset } from '@kbn/rule-registry-plugin/server'; import type { ListPluginSetup } from '@kbn/lists-plugin/server'; import type { ILicense } from '@kbn/licensing-plugin/server'; +import { turnOffPolicyProtectionsIfNotSupported } from './endpoint/migrations/turn_off_policy_protections'; import { endpointSearchStrategyProvider } from './search_strategy/endpoint'; import { getScheduleNotificationResponseActionsService } from './lib/detection_engine/rule_response_actions/schedule_notification_response_actions'; import { siemGuideId, siemGuideConfig } from '../common/guided_onboarding/siem_guide_config'; @@ -438,6 +439,15 @@ export class Plugin implements ISecuritySolutionPlugin { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion plugins.fleet!; let manifestManager: ManifestManager | undefined; + const endpointFleetServicesFactory = new EndpointFleetServicesFactory( + { + agentService, + packageService, + packagePolicyService, + agentPolicyService, + }, + core.savedObjects + ); this.licensing$ = plugins.licensing.license$; @@ -459,17 +469,23 @@ export class Plugin implements ISecuritySolutionPlugin { esClient: core.elasticsearch.client.asInternalUser, }); - // Migrate artifacts to fleet and then start the minifest task after that is done + // Migrate artifacts to fleet and then start the manifest task after that is done plugins.fleet.fleetSetupCompleted().then(() => { - logger.info('Dependent plugin setup complete - Starting ManifestTask'); - if (this.manifestTask) { + logger.info('Dependent plugin setup complete - Starting ManifestTask'); this.manifestTask.start({ taskManager, }); } else { logger.error(new Error('User artifacts task not available.')); } + + turnOffPolicyProtectionsIfNotSupported( + core.elasticsearch.client.asInternalUser, + endpointFleetServicesFactory.asInternalUser(), + this.appFeatures, + logger + ); }); // License related start @@ -493,15 +509,7 @@ export class Plugin implements ISecuritySolutionPlugin { packagePolicyService, logger ), - endpointFleetServicesFactory: new EndpointFleetServicesFactory( - { - agentService, - packageService, - packagePolicyService, - agentPolicyService, - }, - core.savedObjects - ), + endpointFleetServicesFactory, security: plugins.security, alerting: plugins.alerting, config: this.config, @@ -522,6 +530,7 @@ export class Plugin implements ISecuritySolutionPlugin { ), createFleetActionsClient, esClient: core.elasticsearch.client.asInternalUser, + appFeatures: this.appFeatures, }); this.telemetryReceiver.start( diff --git a/x-pack/plugins/security_solution/server/ui_settings.ts b/x-pack/plugins/security_solution/server/ui_settings.ts index f5ff542a7833f..6a41844e9e032 100644 --- a/x-pack/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/plugins/security_solution/server/ui_settings.ts @@ -36,6 +36,7 @@ import { EXTENDED_RULE_EXECUTION_LOGGING_MIN_LEVEL_SETTING, DEFAULT_ALERT_TAGS_KEY, DEFAULT_ALERT_TAGS_VALUE, + ENABLE_EXPANDABLE_FLYOUT_SETTING, } from '../common/constants'; import type { ExperimentalFeatures } from '../common/experimental_features'; import { LogLevelSetting } from '../common/api/detection_engine/rule_monitoring'; @@ -163,6 +164,22 @@ export const initUiSettings = ( requiresPageReload: true, schema: schema.boolean(), }, + [ENABLE_EXPANDABLE_FLYOUT_SETTING]: { + name: i18n.translate('xpack.securitySolution.uiSettings.enableExpandableFlyoutLabel', { + defaultMessage: 'Expandable flyout', + }), + value: true, + description: i18n.translate( + 'xpack.securitySolution.uiSettings.enableExpandableFlyoutDescription', + { + defaultMessage: '

Enables the expandable flyout

', + } + ), + type: 'boolean', + category: [APP_ID], + requiresPageReload: true, + schema: schema.boolean(), + }, [DEFAULT_RULES_TABLE_REFRESH_SETTING]: { name: i18n.translate('xpack.securitySolution.uiSettings.rulesTableRefresh', { defaultMessage: 'Rules auto refresh', diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts index 972f8e727f50d..9dee0c1a73cf1 100644 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts +++ b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts @@ -12,6 +12,11 @@ import { cloneDeep } from 'lodash'; import { APP_INDEX_PRIVILEGES } from '../constants'; import { Privileges } from '../types/privileges'; +export interface PrivilegesAndCapabilities { + privileges: Privileges; + capabilities: Capabilities; +} + export interface TransformCapabilities { canGetTransform: boolean; canDeleteTransform: boolean; @@ -89,7 +94,7 @@ export const getPrivilegesAndCapabilities = ( clusterPrivileges: Record, hasOneIndexWithAllPrivileges: boolean, hasAllPrivileges: boolean -) => { +): PrivilegesAndCapabilities => { const privilegesResult: Privileges = { hasAllPrivileges: true, missingPrivileges: { diff --git a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts index f10b48de27e38..ac4b7fe49a38c 100644 --- a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts @@ -8,11 +8,6 @@ // actual mocks export const expandLiteralStrings = jest.fn(); export const XJsonMode = jest.fn(); -export const useRequest = jest.fn(() => ({ - isLoading: false, - error: null, - data: undefined, -})); export const getSavedSearch = jest.fn(); // just passing through the reimports diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index af411d0aeda6f..ba4a43bfa0876 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -7,14 +7,13 @@ import React, { useContext, FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Router, Routes, Route } from '@kbn/shared-ux-router'; - -import { ScopedHistory } from '@kbn/core/public'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { EuiErrorBoundary } from '@elastic/eui'; +import { Router, Routes, Route } from '@kbn/shared-ux-router'; +import { ScopedHistory } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; - import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { addInternalBasePath } from '../../common/constants'; @@ -23,7 +22,6 @@ import { SectionError } from './components'; import { SECTION_SLUG } from './common/constants'; import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; - import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; @@ -63,20 +61,23 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => { export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => { const I18nContext = appDependencies.i18n.Context; + const queryClient = new QueryClient(); render( - - - - - - - - - + + + + + + + + + + + , element ); diff --git a/x-pack/plugins/transform/public/app/hooks/index.ts b/x-pack/plugins/transform/public/app/hooks/index.ts index eb2be5f4b9b23..f6a4c72b39a44 100644 --- a/x-pack/plugins/transform/public/app/hooks/index.ts +++ b/x-pack/plugins/transform/public/app/hooks/index.ts @@ -12,4 +12,3 @@ export { useResetTransforms } from './use_reset_transform'; export { useScheduleNowTransforms } from './use_schedule_now_transform'; export { useStartTransforms } from './use_start_transform'; export { useStopTransforms } from './use_stop_transform'; -export { useRequest } from './use_request'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_request.ts b/x-pack/plugins/transform/public/app/hooks/use_request.ts deleted file mode 100644 index de1df3e561612..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/use_request.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { UseRequestConfig, useRequest as _useRequest } from '../../shared_imports'; - -import { useAppDependencies } from '../app_dependencies'; - -export const useRequest = (config: UseRequestConfig) => { - const { http } = useAppDependencies(); - return _useRequest(http, config); -}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx index 4271e1447b1e8..02bbe4e40a969 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx @@ -6,16 +6,20 @@ */ import React, { createContext } from 'react'; +import { useQuery } from '@tanstack/react-query'; -import { Privileges } from '../../../../../common/types/privileges'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { useRequest } from '../../../hooks'; +import type { Privileges } from '../../../../../common/types/privileges'; import { - TransformCapabilities, + type PrivilegesAndCapabilities, + type TransformCapabilities, INITIAL_CAPABILITIES, } from '../../../../../common/privilege/has_privilege_factory'; +import { useAppDependencies } from '../../../app_dependencies'; + interface Authorization { isLoading: boolean; apiError: Error | null; @@ -41,22 +45,36 @@ interface Props { } export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => { + const { http } = useAppDependencies(); + const { path, version } = privilegesEndpoint; + const { isLoading, error, data: privilegesData, - } = useRequest({ - path, - version, - method: 'get', - }); + } = useQuery( + ['transform-privileges-and-capabilities'], + async ({ signal }) => { + return await http.fetch(path, { + version, + method: 'GET', + signal, + }); + } + ); const value = { isLoading, - privileges: isLoading ? { ...initialValue.privileges } : privilegesData.privileges, - capabilities: isLoading ? { ...INITIAL_CAPABILITIES } : privilegesData.capabilities, - apiError: error ? (error as Error) : null, + privileges: + isLoading || privilegesData === undefined + ? { ...initialValue.privileges } + : privilegesData.privileges, + capabilities: + isLoading || privilegesData === undefined + ? { ...INITIAL_CAPABILITIES } + : privilegesData.capabilities, + apiError: error ? error : null, }; return ( diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index c24f792eacab0..63276cecc7a86 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -6,8 +6,6 @@ */ export { XJsonMode } from '@kbn/ace'; -export type { UseRequestConfig } from '@kbn/es-ui-shared-plugin/public'; -export { useRequest } from '@kbn/es-ui-shared-plugin/public'; export type { GetMlSharedImportsReturnType } from '@kbn/ml-plugin/public'; export { getMlSharedImports } from '@kbn/ml-plugin/public'; diff --git a/x-pack/plugins/transform/server/routes/api/privileges.ts b/x-pack/plugins/transform/server/routes/api/privileges.ts index cd6817f8be63c..0b93c8e503fc6 100644 --- a/x-pack/plugins/transform/server/routes/api/privileges.ts +++ b/x-pack/plugins/transform/server/routes/api/privileges.ts @@ -5,15 +5,17 @@ * 2.0. */ -import type { IScopedClusterClient } from '@kbn/core/server'; +import type { IKibanaResponse, IScopedClusterClient } from '@kbn/core/server'; import type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types'; -import { getPrivilegesAndCapabilities } from '../../../common/privilege/has_privilege_factory'; +import { + getPrivilegesAndCapabilities, + type PrivilegesAndCapabilities, +} from '../../../common/privilege/has_privilege_factory'; import { addInternalBasePath, APP_CLUSTER_PRIVILEGES, APP_INDEX_PRIVILEGES, } from '../../../common/constants'; -import type { Privileges } from '../../../common/types/privileges'; import type { RouteDependencies } from '../../types'; @@ -23,64 +25,60 @@ export function registerPrivilegesRoute({ router, license }: RouteDependencies) path: addInternalBasePath('privileges'), access: 'internal', }) - .addVersion( + .addVersion( { version: '1', validate: false, }, - license.guardApiRoute(async (ctx, req, res) => { - const privilegesResult: Privileges = { - hasAllPrivileges: true, - missingPrivileges: { - cluster: [], - index: [], - }, - }; - - if (license.getStatus().isSecurityEnabled === false) { - // If security isn't enabled, let the user use app. - return res.ok({ body: privilegesResult }); - } + license.guardApiRoute( + async (ctx, req, res): Promise> => { + if (license.getStatus().isSecurityEnabled === false) { + // If security isn't enabled, let the user use app. + return res.ok({ + body: getPrivilegesAndCapabilities({}, true, true), + }); + } - const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client; + const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client; - const esClusterPrivilegesReq: Promise = - esClient.asCurrentUser.security.hasPrivileges({ - body: { - cluster: APP_CLUSTER_PRIVILEGES, - }, - }); - const [esClusterPrivileges, userPrivileges] = await Promise.all([ - // Get cluster privileges - esClusterPrivilegesReq, - // // Get all index privileges the user has - esClient.asCurrentUser.security.getUserPrivileges(), - ]); + const esClusterPrivilegesReq: Promise = + esClient.asCurrentUser.security.hasPrivileges({ + body: { + cluster: APP_CLUSTER_PRIVILEGES, + }, + }); + const [esClusterPrivileges, userPrivileges] = await Promise.all([ + // Get cluster privileges + esClusterPrivilegesReq, + // // Get all index privileges the user has + esClient.asCurrentUser.security.getUserPrivileges(), + ]); - const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges; - const { indices } = userPrivileges; + const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges; + const { indices } = userPrivileges; - // Check if they have all the required index privileges for at least one index - const hasOneIndexWithAllPrivileges = - indices.find(({ privileges }: { privileges: string[] }) => { - if (privileges.includes('all')) { - return true; - } + // Check if they have all the required index privileges for at least one index + const hasOneIndexWithAllPrivileges = + indices.find(({ privileges }: { privileges: string[] }) => { + if (privileges.includes('all')) { + return true; + } - const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) => - privileges.includes(privilege) - ); + const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) => + privileges.includes(privilege) + ); - return indexHasAllPrivileges; - }) !== undefined; + return indexHasAllPrivileges; + }) !== undefined; - return res.ok({ - body: getPrivilegesAndCapabilities( - cluster, - hasOneIndexWithAllPrivileges, - hasAllPrivileges - ), - }); - }) + return res.ok({ + body: getPrivilegesAndCapabilities( + cluster, + hasOneIndexWithAllPrivileges, + hasAllPrivileges + ), + }); + } + ) ); } diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts index d7427a24657fa..d5e827d545a68 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts @@ -351,7 +351,7 @@ export default ({ getService }: FtrProviderContext): void => { threat: generateThreatArray(1), }), ]); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const expectedRule = await createRule(supertest, log, { ...getSimpleRule('rule-1'), diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts index 97717f00773d9..d9f710ba6afcf 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts @@ -15,6 +15,7 @@ import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; +import { installPrebuiltRulesPackageByVersion } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -55,18 +56,17 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); - const EPM_URL = `/api/fleet/epm/packages/security_detection_engine/99.0.0`; - - const bundledInstallResponse = await supertest - .post(EPM_URL) - .set('kbn-xsrf', 'xxxx') - .type('application/json') - .send({ force: true }) - .expect(200); + const bundledInstallResponse = await installPrebuiltRulesPackageByVersion( + es, + supertest, + '99.0.0' + ); // As opposed to "registry" - expect(bundledInstallResponse.body._meta.install_source).toBe('bundled'); + expect(bundledInstallResponse._meta.install_source).toBe('bundled'); + // Refresh ES indices to avoid race conditions between write and reading of indeces + // See implementation utility function at x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); // Verify that status is updated after package installation diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts index b1c32bf0e245e..fd69e3128c3e7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts @@ -4,11 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import expect from 'expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; +import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; @@ -38,8 +37,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); - await installPrebuiltRules(supertest); - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + await installPrebuiltRules(es, supertest); // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); @@ -48,11 +46,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Assert that installed rules are from package 99.0.0 and not from prerelease (beta) package expect(rulesResponse.data.length).toBe(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts index 9dd5e695c3772..c047413bdb90a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts @@ -5,7 +5,6 @@ * 2.0. */ import expect from 'expect'; -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, getPrebuiltRulesAndTimelinesStatus } from '../../utils'; import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; @@ -36,8 +35,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); // Install the package with 15000 prebuilt historical version of rules rules and 750 unique rules - await installPrebuiltRulesAndTimelines(supertest); - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + await installPrebuiltRulesAndTimelines(es, supertest); // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts index e48530ad16513..1433cb7cac2ff 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts @@ -5,7 +5,6 @@ * 2.0. */ import expect from 'expect'; -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, @@ -43,24 +42,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); await installPrebuiltRulesFleetPackage({ + es, supertest, overrideExistingPackage: true, }); - // Before we proceed, we need to refresh saved object indices. This comment will explain why. - // At the previous step we installed the Fleet package with prebuilt detection rules. - // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. - // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. - // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be - // successfully indexed, it doesn't wait until they become "visible" for subsequent read - // operations. Which is what we do next: we read these SOs in getPrebuiltRulesAndTimelinesStatus(). - // Now, the time left until the next refresh can be anything from 0 to the default value, and - // it depends on the time when savedObjectsClient.import() call happens relative to the time of - // the next refresh. Also, probably the refresh time can be delayed when ES is under load? - // Anyway, here we have a race condition between a write and subsequent read operation, and to - // fix it deterministically we have to refresh saved object indices and wait until it's done. - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); - // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusAfterPackageInstallation.rules_installed).toBe(0); @@ -68,19 +54,10 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); // Verify that all previously not installed rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(statusAfterPackageInstallation.rules_not_installed); expect(response.rules_updated).toBe(0); - // Similar to the previous refresh, we need to do it again between the two operations: - // - previous write operation: install prebuilt rules and timelines - // - subsequent read operation: get prebuilt rules and timelines status - // You may ask why? I'm not sure, probably because the write operation can install the Fleet - // package under certain circumstances, and it all works with `refresh: false` again. - // Anyway, there were flaky runs failing specifically at one of the next assertions, - // which means some kind of the same race condition we have here too. - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); - // Verify that status is updated after rules installation const statusAfterRuleInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusAfterRuleInstallation.rules_installed).toBe(response.rules_installed); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts index ee5730cee39a8..ae43e3bdd5098 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -95,7 +95,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); await deleteRule(supertest, 'rule-1'); const { stats } = await getPrebuiltRulesStatus(supertest); @@ -110,7 +110,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -130,7 +130,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return any available update if rule has been successfully upgraded', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -138,7 +138,7 @@ export default ({ getService }: FtrProviderContext): void => { ruleAssetSavedObjects[0]['security-rule'].version += 1; await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); // Upgrade all rules - await upgradePrebuiltRules(supertest); + await upgradePrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -152,7 +152,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return any updates if none are available', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -193,7 +193,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -206,7 +206,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); await deleteRule(supertest, 'rule-1'); const { stats } = await getPrebuiltRulesStatus(supertest); @@ -220,7 +220,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions available', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -238,7 +238,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions unavailable', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete the previous versions of rule assets await deleteAllPrebuiltRuleAssets(es); @@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return available rule updates after rule has been upgraded', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete the previous versions of rule assets await deleteAllPrebuiltRuleAssets(es); @@ -272,7 +272,7 @@ export default ({ getService }: FtrProviderContext): void => { ]); // Upgrade the rule - await upgradePrebuiltRules(supertest); + await upgradePrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -339,7 +339,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ @@ -352,7 +352,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); await deleteRule(supertest, 'rule-1'); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); @@ -367,7 +367,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -387,7 +387,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return any updates if none are available', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -428,7 +428,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ @@ -441,7 +441,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); await deleteRule(supertest, 'rule-1'); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); @@ -455,7 +455,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions available', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -473,7 +473,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions unavailable', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Delete the previous versions of rule assets await deleteAllPrebuiltRuleAssets(es); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts index 04275afe20dc9..05b34ffa98ed7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts @@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return the number of installed timeline templates after installing them', async () => { - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts index c36b81f93cf7c..85af64415c95e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts @@ -6,7 +6,6 @@ */ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, @@ -24,6 +23,7 @@ import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/ins import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; import { upgradePrebuiltRules } from '../../utils/prebuilt_rules/upgrade_prebuilt_rules'; +import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('using legacy endpoint', () => { it('should install prebuilt rules', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRulesAndTimelines(supertest); + const body = await installPrebuiltRulesAndTimelines(es, supertest); expect(body.rules_installed).toBe(RULES_COUNT); expect(body.rules_updated).toBe(0); @@ -58,14 +58,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should install correct prebuilt rule versions', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(rulesResponse.total).toBe(RULES_COUNT); @@ -82,7 +78,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -92,7 +88,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_installed).toBe(1); // Call the install prebuilt rules again and check that the missing rule was installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(1); expect(response.rules_updated).toBe(0); }); @@ -101,7 +97,7 @@ export default ({ getService }: FtrProviderContext): void => { // Install all prebuilt detection rules const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -114,7 +110,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_updated).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(1); }); @@ -122,7 +118,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); @@ -130,7 +126,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_updated).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(0); }); @@ -139,7 +135,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('using current endpoint', () => { it('should install prebuilt rules', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRules(supertest); + const body = await installPrebuiltRules(es, supertest); expect(body.summary.succeeded).toBe(RULES_COUNT); expect(body.summary.failed).toBe(0); @@ -148,7 +144,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install correct prebuilt rule versions', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRules(supertest); + const body = await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(body.results.created).toEqual( @@ -164,7 +160,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -174,7 +170,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(1); // Call the install prebuilt rules again and check that the missing rule was installed - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); }); @@ -182,7 +178,7 @@ export default ({ getService }: FtrProviderContext): void => { // Install all prebuilt detection rules const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -196,7 +192,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await upgradePrebuiltRules(supertest); + const response = await upgradePrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.skipped).toBe(0); }); @@ -204,7 +200,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesStatus(supertest); @@ -212,12 +208,12 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const installResponse = await installPrebuiltRules(supertest); + const installResponse = await installPrebuiltRules(es, supertest); expect(installResponse.summary.succeeded).toBe(0); expect(installResponse.summary.skipped).toBe(0); // Call the upgrade prebuilt rules endpoint and check that no rules were updated - const upgradeResponse = await upgradePrebuiltRules(supertest); + const upgradeResponse = await upgradePrebuiltRules(es, supertest); expect(upgradeResponse.summary.succeeded).toBe(0); expect(upgradeResponse.summary.skipped).toBe(0); }); @@ -237,7 +233,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('using legacy endpoint', () => { it('should install prebuilt rules', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRulesAndTimelines(supertest); + const body = await installPrebuiltRulesAndTimelines(es, supertest); expect(body.rules_installed).toBe(RULES_COUNT); expect(body.rules_updated).toBe(0); @@ -245,14 +241,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should install correct prebuilt rule versions', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(rulesResponse.total).toBe(RULES_COUNT); @@ -267,14 +259,14 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusResponse.rules_not_installed).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(0); }); @@ -282,7 +274,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -292,7 +284,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_installed).toBe(1); // Call the install prebuilt rules endpoint again and check that the missing rule was installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(1); expect(response.rules_updated).toBe(0); }); @@ -300,7 +292,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions available', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -312,7 +304,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_updated).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(1); @@ -324,7 +316,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -340,7 +332,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_installed).toBe(0); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(1); @@ -353,14 +345,14 @@ export default ({ getService }: FtrProviderContext): void => { describe('using current endpoint', () => { it('should install prebuilt rules', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRules(supertest); + const body = await installPrebuiltRules(es, supertest); expect(body.summary.succeeded).toBe(RULES_COUNT); }); it('should install correct prebuilt rule versions', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(response.summary.succeeded).toBe(RULES_COUNT); @@ -375,14 +367,14 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesStatus(supertest); expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(0); expect(response.summary.total).toBe(0); }); @@ -390,7 +382,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -400,7 +392,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(1); // Call the install prebuilt rules endpoint again and check that the missing rule was installed - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); }); @@ -408,7 +400,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions available', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -420,7 +412,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated - const response = await upgradePrebuiltRules(supertest); + const response = await upgradePrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); @@ -432,7 +424,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -448,7 +440,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated - const response = await upgradePrebuiltRules(supertest); + const response = await upgradePrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts index 0e25999a37e9b..1d7939e83f9ab 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts @@ -13,7 +13,6 @@ import { REPO_ROOT } from '@kbn/repo-info'; import JSON5 from 'json5'; import expect from 'expect'; import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, @@ -24,6 +23,8 @@ import { } from '../../utils'; import { reviewPrebuiltRulesToInstall } from '../../utils/prebuilt_rules/review_install_prebuilt_rules'; import { reviewPrebuiltRulesToUpgrade } from '../../utils/prebuilt_rules/review_upgrade_prebuilt_rules'; +import { installPrebuiltRulesPackageByVersion } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; +import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -103,17 +104,14 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_total_in_package).toBe(0); - const EPM_URL_FOR_PREVIOUS_VERSION = `/api/fleet/epm/packages/security_detection_engine/${previousVersion}`; - - const installPreviousPackageResponse = await supertest - .post(EPM_URL_FOR_PREVIOUS_VERSION) - .set('kbn-xsrf', 'xxxx') - .type('application/json') - .send({ force: true }) - .expect(200); + const installPreviousPackageResponse = await installPrebuiltRulesPackageByVersion( + es, + supertest, + previousVersion + ); - expect(installPreviousPackageResponse.body._meta.install_source).toBe('registry'); - expect(installPreviousPackageResponse.body.items.length).toBeGreaterThan(0); + expect(installPreviousPackageResponse._meta.install_source).toBe('registry'); + expect(installPreviousPackageResponse.items.length).toBeGreaterThan(0); // Verify that status is updated after the installation of package "N-1" const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); @@ -132,7 +130,8 @@ export default ({ getService }: FtrProviderContext): void => { // Verify that the _perform endpoint returns the same number of installed rules as the status endpoint // and the _review endpoint - const installPrebuiltRulesResponse = await installPrebuiltRules(supertest); + const installPrebuiltRulesResponse = await installPrebuiltRules(es, supertest); + expect(installPrebuiltRulesResponse.summary.succeeded).toBe( statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install ); @@ -141,11 +140,7 @@ export default ({ getService }: FtrProviderContext): void => { ); // Get installed rules - const { body: rulesResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Check that all prebuilt rules were actually installed expect(rulesResponse.total).toBe(installPrebuiltRulesResponse.summary.succeeded); @@ -160,16 +155,13 @@ export default ({ getService }: FtrProviderContext): void => { ); // PART 2: Now install the lastest (current) package, defined in fleet_packages.json - const EPM_URL_FOR_CURRENT_VERSION = `/api/fleet/epm/packages/security_detection_engine/${currentVersion}`; - - const installLatestPackageResponse = await supertest - .post(EPM_URL_FOR_CURRENT_VERSION) - .set('kbn-xsrf', 'xxxx') - .type('application/json') - .send({ force: true }) - .expect(200); - expect(installLatestPackageResponse.body.items.length).toBeGreaterThanOrEqual(0); + const installLatestPackageResponse = await installPrebuiltRulesPackageByVersion( + es, + supertest, + currentVersion + ); + expect(installLatestPackageResponse.items.length).toBeGreaterThanOrEqual(0); // Verify status after intallation of the latest package const statusAfterLatestPackageInstallation = await getPrebuiltRulesStatus(supertest); @@ -196,8 +188,10 @@ export default ({ getService }: FtrProviderContext): void => { // Install available rules and verify that the _perform endpoint returns the same number of // installed rules as the status endpoint and the _review endpoint const installPrebuiltRulesResponseAfterLatestPackageInstallation = await installPrebuiltRules( + es, supertest ); + expect(installPrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toBe( statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_install ); @@ -208,11 +202,7 @@ export default ({ getService }: FtrProviderContext): void => { ); // Get installed rules - const { body: rulesResponseAfterPackageUpdate } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponseAfterPackageUpdate = await getInstalledRules(supertest); // Check that the expected new prebuilt rules from the latest package were actually installed expect( @@ -239,8 +229,10 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade _perform endpoint and verify that the number of upgraded rules is the same as the one // returned by the _review endpoint and the status endpoint const upgradePrebuiltRulesResponseAfterLatestPackageInstallation = await upgradePrebuiltRules( + es, supertest ); + expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade ); @@ -249,11 +241,8 @@ export default ({ getService }: FtrProviderContext): void => { ); // Get installed rules - const { body: rulesResponseAfterPackageUpdateAndRuleUpgrades } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + + const rulesResponseAfterPackageUpdateAndRuleUpgrades = await getInstalledRules(supertest); // Check that the expected new prebuilt rules from the latest package were actually installed expect( diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts new file mode 100644 index 0000000000000..85eaee80ed3e8 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type SuperTest from 'supertest'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; +import { FindRulesResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +/** + * Get all installed security rules (both prebuilt + custom) + * + * @param es Elasticsearch client + * @param supertest SuperTest instance + * @param version Semver version of the `security_detection_engine` package to install + * @returns Fleet install package response + */ + +export const getInstalledRules = async ( + supertest: SuperTest.SuperTest +): Promise => { + const { body: rulesResponse } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + return rulesResponse; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts new file mode 100644 index 0000000000000..802626881b8e6 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.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 type { Client } from '@elastic/elasticsearch'; +import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; +import { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; + +/** + * Installs prebuilt rules package `security_detection_engine` by version. + * + * @param es Elasticsearch client + * @param supertest SuperTest instance + * @param version Semver version of the `security_detection_engine` package to install + * @returns Fleet install package response + */ + +export const installPrebuiltRulesPackageByVersion = async ( + es: Client, + supertest: SuperTest.SuperTest, + version: string +): Promise => { + const fleetResponse = await supertest + .post(`/api/fleet/epm/packages/security_detection_engine/${version}`) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ force: true }) + .expect(200); + + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the Fleet package with prebuilt detection rules. + // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. + // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // Now, the time left until the next refresh can be anything from 0 to the default value, and + // it depends on the time when savedObjectsClient.import() call happens relative to the time of + // the next refresh. Also, probably the refresh time can be delayed when ES is under load? + // Anyway, this can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + + return fleetResponse.body as InstallPackageResponse; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts index 6f9726ae6a194..0e15f416e1238 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts @@ -24,5 +24,5 @@ export const installMockPrebuiltRules = async ( ): Promise => { // Ensure there are prebuilt rule saved objects before installing rules await createPrebuiltRuleAssetSavedObjects(es); - return installPrebuiltRulesAndTimelines(supertest); + return installPrebuiltRulesAndTimelines(es, supertest); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts index c11ccb7b37abd..f05ea093cfc5d 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts @@ -10,7 +10,9 @@ import { RuleVersionSpecifier, PerformRuleInstallationResponseBody, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; /** * Installs available prebuilt rules in Kibana. Rules are @@ -27,6 +29,7 @@ import type SuperTest from 'supertest'; * @returns Install prebuilt rules response */ export const installPrebuiltRules = async ( + es: Client, supertest: SuperTest.SuperTest, rules?: RuleVersionSpecifier[] ): Promise => { @@ -42,5 +45,17 @@ export const installPrebuiltRules = async ( .send(payload) .expect(200); + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race conditions between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + return response.body; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts index 7954e2b47bbac..fdf87a94391c9 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts @@ -9,7 +9,9 @@ import { InstallPrebuiltRulesAndTimelinesResponse, PREBUILT_RULES_URL, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; /** * (LEGACY) @@ -28,6 +30,7 @@ import type SuperTest from 'supertest'; * @returns Install prebuilt rules response */ export const installPrebuiltRulesAndTimelines = async ( + es: Client, supertest: SuperTest.SuperTest ): Promise => { const response = await supertest @@ -36,5 +39,17 @@ export const installPrebuiltRulesAndTimelines = async ( .send() .expect(200); + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + return response.body; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts index 30435caa5a7c3..cc899ecc1dccc 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts @@ -5,7 +5,9 @@ * 2.0. */ +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { epmRouteService } from '@kbn/fleet-plugin/common'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; /** @@ -17,10 +19,12 @@ import type SuperTest from 'supertest'; * @param overrideExistingPackage Whether or not to force the install */ export const installPrebuiltRulesFleetPackage = async ({ + es, supertest, version, overrideExistingPackage, }: { + es: Client; supertest: SuperTest.SuperTest; version?: string; overrideExistingPackage: boolean; @@ -46,6 +50,22 @@ export const installPrebuiltRulesFleetPackage = async ({ }) .expect(200); } + + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the Fleet package with prebuilt detection rules. + // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. + // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // Now, the time left until the next refresh can be anything from 0 to the default value, and + // it depends on the time when savedObjectsClient.import() call happens relative to the time of + // the next refresh. Also, probably the refresh time can be delayed when ES is under load? + // Anyway, this can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); }; /** diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts index bb3299cb5dd9e..d9ea277fb1421 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts @@ -10,7 +10,9 @@ import { RuleVersionSpecifier, PerformRuleUpgradeResponseBody, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; /** * Upgrades available prebuilt rules in Kibana. @@ -23,6 +25,7 @@ import type SuperTest from 'supertest'; * @returns Upgrade prebuilt rules response */ export const upgradePrebuiltRules = async ( + es: Client, supertest: SuperTest.SuperTest, rules?: RuleVersionSpecifier[] ): Promise => { @@ -38,5 +41,18 @@ export const upgradePrebuiltRules = async ( .send(payload) .expect(200); + // Before we proceed, we need to refresh saved object indices. + // At the previous step we upgraded the prebuilt rules, which, under the hoods, installs new versions + // of the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race conditions between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + return response.body; }; diff --git a/x-pack/test/functional/apps/discover_log_explorer/columns_selection.ts b/x-pack/test/functional/apps/discover_log_explorer/columns_selection.ts new file mode 100644 index 0000000000000..c1a9aee81758a --- /dev/null +++ b/x-pack/test/functional/apps/discover_log_explorer/columns_selection.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const defaultLogColumns = ['@timestamp', 'message']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discover']); + + describe('Columns selection initialization and update', () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + describe('when the log explorer profile loads', () => { + it("should initialize the table columns to logs' default selection", async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql(defaultLogColumns); + }); + }); + + it('should restore the table columns from the URL state if exists', async () => { + await PageObjects.common.navigateToApp('discover', { + hash: '/p/log-explorer?_a=(columns:!(message,data_stream.namespace))', + }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql([ + ...defaultLogColumns, + 'data_stream.namespace', + ]); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/discover_log_explorer/customization.ts b/x-pack/test/functional/apps/discover_log_explorer/customization.ts index 2dba2ed30b695..6cd713a40f63a 100644 --- a/x-pack/test/functional/apps/discover_log_explorer/customization.ts +++ b/x-pack/test/functional/apps/discover_log_explorer/customization.ts @@ -25,14 +25,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('DatasetSelector should replace the DataViewPicker', async () => { // Assert does not render on discover app await PageObjects.common.navigateToApp('discover'); - await testSubjects.missingOrFail('dataset-selector-popover'); + await testSubjects.missingOrFail('datasetSelectorPopover'); // Assert it renders on log-explorer profile await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); - await testSubjects.existOrFail('dataset-selector-popover'); + await testSubjects.existOrFail('datasetSelectorPopover'); }); - it('the TopNav bar should hide New, Open and Save options', async () => { + it('the TopNav bar should hide then New, Open and Save options', async () => { // Assert does not render on discover app await PageObjects.common.navigateToApp('discover'); await testSubjects.existOrFail('discoverNewButton'); @@ -59,6 +59,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const results = await PageObjects.navigationalSearch.getDisplayedResults(); expect(results[0].label).to.eql('Discover / Logs Explorer'); }); + + it('should render a filter controls section as part of the unified search bar', async () => { + // Assert does not render on discover app + await PageObjects.common.navigateToApp('discover'); + await testSubjects.missingOrFail('datasetFiltersCustomization'); + + // Assert it renders on log-explorer profile + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + await testSubjects.existOrFail('datasetFiltersCustomization', { allowHidden: true }); + }); }); }); } diff --git a/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts b/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts index f795afb966493..c1c2b335358bc 100644 --- a/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts +++ b/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('when the "index" query param exist', () => { + describe('when the "index" query param exists', () => { it('should decode and restore the selection from a valid encoded index', async () => { const azureActivitylogsIndex = 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtFgF4CuATmAHRZzwBu8sAJ5VadAFTkANAlhRU3BPyEiQASklFS8lu2kC55AII6wAAgAyNEFN5hWIJGnIBGDgFYOAJgDM5deCgeFAAVQQAHMgdkaihVIA==='; @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(datasetSelectionTitle).to.be('[Azure Logs] activitylogs'); }); - it('should fallback to "All log datasets" selection and notify the user for an invalid encoded index', async () => { + it('should fallback to the "All log datasets" selection and notify the user of an invalid encoded index', async () => { const invalidEncodedIndex = 'invalid-encoded-index'; await PageObjects.common.navigateToApp('discover', { hash: `/p/log-explorer?_a=(index:${encodeURIComponent(invalidEncodedIndex)})`, diff --git a/x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts b/x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts new file mode 100644 index 0000000000000..b456da8bddb2a --- /dev/null +++ b/x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts @@ -0,0 +1,664 @@ +/* + * 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'; + +const initialPackageMap = { + apache: 'Apache HTTP Server', + aws: 'AWS', + system: 'System', +}; +const initialPackagesTexts = Object.values(initialPackageMap); + +const expectedUncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discoverLogExplorer']); + + describe('Dataset Selector', () => { + before(async () => { + await PageObjects.discoverLogExplorer.removeInstalledPackages(); + }); + + describe('without installed integrations or uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + describe('when open on the first navigation level', () => { + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display an error prompt if could not retrieve the integrations', async function () { + // Skip the test in case network condition utils are not available + try { + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + await PageObjects.common.sleep(5000); + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + it('should display a loading skeleton while loading', async function () { + // Skip the test in case network condition utils are not available + try { + await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await PageObjects.discoverLogExplorer.assertLoadingSkeletonExists(); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an error prompt if could not retrieve the data streams', async function () { + // Skip the test in case network condition utils are not available + try { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no data streams', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + const unamanagedDatasetEntries = + await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(unamanagedDatasetEntries.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + }); + }); + + describe('with installed integrations and uncategorized data streams', () => { + let cleanupIntegrationsSetup: () => Promise; + + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + cleanupIntegrationsSetup = await PageObjects.discoverLogExplorer.setupInitialIntegrations(); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + await cleanupIntegrationsSetup(); + }); + + describe('when open on the first navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display a list of installed integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + + expect(integrations.length).to.be(3); + expect(integrations).to.eql(initialPackagesTexts); + }); + + it('should sort the integrations list by the clicked sorting option', async () => { + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts.slice().reverse()); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + }); + + it('should filter the integrations list by the typed integration name', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache, initialPackageMap.aws]); + }); + }); + + it('should display an empty prompt when the search does not match any result', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('no result search text'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + }); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + it('should load more integrations by scrolling to the end of the list', async () => { + // Install more integrations and reload the page + const cleanupAdditionalSetup = + await PageObjects.discoverLogExplorer.setupAdditionalIntegrations(); + await browser.refresh(); + + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + // Initially fetched integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(15); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // Load more integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // No other integrations to load after scrolling to last integration + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + }); + + cleanupAdditionalSetup(); + }); + }); + + describe('when clicking on integration and moving into the second navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(await menuEntries[1].getVisibleText()).to.be('access'); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + }); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[0]); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('retail'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('logs-retail-*'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('logs-gaming-*'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('logs-gaming-*'); + }); + }); + }); + + describe('when open/close the selector', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should restore the latest navigation panel', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should restore the latest search results', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + }); + }); + + describe('when switching between integration panels', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + it('should remember the latest search and restore its results for each integration', async () => { + await PageObjects.discoverLogExplorer.openDatasetSelector(); + await PageObjects.discoverLogExplorer.clearSearchField(); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('apache'); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + + // Navigate back to integrations + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + panelTitleNode.click(); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('apache'); + + nodes[0].click(); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('err'); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/discover_log_explorer/index.ts b/x-pack/test/functional/apps/discover_log_explorer/index.ts index 719bd8a7fcb28..dd8b99db79ad0 100644 --- a/x-pack/test/functional/apps/discover_log_explorer/index.ts +++ b/x-pack/test/functional/apps/discover_log_explorer/index.ts @@ -9,7 +9,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Discover Log-Explorer profile', function () { + loadTestFile(require.resolve('./columns_selection')); loadTestFile(require.resolve('./customization')); loadTestFile(require.resolve('./dataset_selection_state')); + loadTestFile(require.resolve('./dataset_selector')); }); } diff --git a/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/data.json.gz b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/data.json.gz new file mode 100644 index 0000000000000..4e72a78a4f8b9 Binary files /dev/null and b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json new file mode 100644 index 0000000000000..ed9e5982f576f --- /dev/null +++ b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json @@ -0,0 +1,419 @@ +{ + "type": "data_stream", + "value": { + "data_stream": "logs-gaming-activity", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-gaming-activity" + ], + "name": "logs-gaming-activity", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-gaming-events", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-gaming-events" + ], + "name": "logs-gaming-events", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-gaming-scores", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-gaming-scores" + ], + "name": "logs-gaming-scores", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-manufacturing-downtime", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-manufacturing-downtime" + ], + "name": "logs-manufacturing-downtime", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-manufacturing-output", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-manufacturing-output" + ], + "name": "logs-manufacturing-output", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-manufacturing-quality", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-manufacturing-quality" + ], + "name": "logs-manufacturing-quality", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-customers", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-customers" + ], + "name": "logs-retail-customers", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-inventory", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-inventory" + ], + "name": "logs-retail-inventory", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-promotions", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-promotions" + ], + "name": "logs-retail-promotions", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-sales", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-sales" + ], + "name": "logs-retail-sales", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/discover_log_explorer.ts b/x-pack/test/functional/page_objects/discover_log_explorer.ts index 15c2dc8fbcc4e..282a703863dc2 100644 --- a/x-pack/test/functional/page_objects/discover_log_explorer.ts +++ b/x-pack/test/functional/page_objects/discover_log_explorer.ts @@ -7,13 +7,186 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; +export interface IntegrationPackage { + name: string; + version: string; +} + +const packages: IntegrationPackage[] = [ + { + name: 'apache', + version: '1.14.0', + }, + { + name: 'aws', + version: '1.51.0', + }, + { + name: 'system', + version: '1.38.1', + }, + { + name: '1password', + version: '1.18.0', + }, + { + name: 'activemq', + version: '0.13.0', + }, + { + name: 'akamai', + version: '2.14.0', + }, + { + name: 'apache_tomcat', + version: '0.12.1', + }, + { + name: 'apm', + version: '8.4.2', + }, + { + name: 'atlassian_bitbucket', + version: '1.14.0', + }, + { + name: 'atlassian_confluence', + version: '1.15.0', + }, + { + name: 'atlassian_jira', + version: '1.15.0', + }, + { + name: 'auditd', + version: '3.12.0', + }, + { + name: 'auditd_manager', + version: '1.12.0', + }, + { + name: 'auth0', + version: '1.10.0', + }, + { + name: 'aws_logs', + version: '0.5.0', + }, + { + name: 'azure', + version: '1.5.28', + }, + { + name: 'azure_app_service', + version: '0.0.1', + }, + { + name: 'azure_blob_storage', + version: '0.5.0', + }, + { + name: 'azure_frontdoor', + version: '1.1.0', + }, + { + name: 'azure_functions', + version: '0.0.1', + }, +]; + +const initialPackages = packages.slice(0, 3); +const additionalPackages = packages.slice(3); + export function DiscoverLogExplorerPageObject({ getService }: FtrProviderContext) { + const log = getService('log'); + const supertest = getService('supertest'); const testSubjects = getService('testSubjects'); const toasts = getService('toasts'); return { - async getDatasetSelectorButton() { - return testSubjects.find('dataset-selector-popover-button'); + uninstallPackage: ({ name, version }: IntegrationPackage) => { + return supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx'); + }, + + installPackage: ({ name, version }: IntegrationPackage) => { + return supertest + .post(`/api/fleet/epm/packages/${name}/${version}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }, + + getInstalledPackages: () => { + return supertest + .get(`/api/fleet/epm/packages/installed?dataStreamType=logs&perPage=1000`) + .set('kbn-xsrf', 'xxxx'); + }, + + async removeInstalledPackages(): Promise { + const response = await this.getInstalledPackages(); + + // Uninstall installed integration + await Promise.all( + response.body.items.map((pkg: IntegrationPackage) => this.uninstallPackage(pkg)) + ); + + return response.body.items; + }, + + async setupInitialIntegrations() { + log.info(`===== Setup initial integration packages. =====`); + log.info(`===== Uninstall initial integration packages. =====`); + const uninstalled = await this.removeInstalledPackages(); + log.info(`===== Install ${initialPackages.length} mock integration packages. =====`); + await Promise.all(initialPackages.map((pkg: IntegrationPackage) => this.installPackage(pkg))); + + return async () => { + log.info(`===== Uninstall ${initialPackages.length} mock integration packages. =====`); + await Promise.all( + initialPackages.map((pkg: IntegrationPackage) => this.uninstallPackage(pkg)) + ); + log.info(`===== Restore pre-existing integration packages. =====`); + await Promise.all(uninstalled.map((pkg: IntegrationPackage) => this.installPackage(pkg))); + }; + }, + + async setupAdditionalIntegrations() { + log.info(`===== Setup additional integration packages. =====`); + log.info(`===== Install ${additionalPackages.length} mock integration packages. =====`); + await Promise.all( + additionalPackages.map((pkg: IntegrationPackage) => this.installPackage(pkg)) + ); + + return async () => { + log.info(`===== Uninstall ${additionalPackages.length} mock integration packages. =====`); + await Promise.all( + additionalPackages.map((pkg: IntegrationPackage) => this.uninstallPackage(pkg)) + ); + }; + }, + + getDatasetSelector() { + return testSubjects.find('datasetSelectorPopover'); + }, + + getDatasetSelectorButton() { + return testSubjects.find('datasetSelectorPopoverButton'); + }, + + getDatasetSelectorContent() { + return testSubjects.find('datasetSelectorContent'); + }, + + getDatasetSelectorSearchControls() { + return testSubjects.find('datasetSelectorSearchControls'); + }, + + getDatasetSelectorContextMenu() { + return testSubjects.find('datasetSelectorContextMenu'); + }, + + getDatasetSelectorContextMenuPanelTitle() { + return testSubjects.find('contextMenuPanelTitleButton'); }, async getDatasetSelectorButtonText() { @@ -21,11 +194,115 @@ export function DiscoverLogExplorerPageObject({ getService }: FtrProviderContext return button.getVisibleText(); }, + async getCurrentPanelEntries() { + const contextMenu = await this.getDatasetSelectorContextMenu(); + return contextMenu.findAllByClassName('euiContextMenuItem', 2000); + }, + + getAllLogDatasetsButton() { + return testSubjects.find('allLogDatasets'); + }, + + getUnmanagedDatasetsButton() { + return testSubjects.find('unmanagedDatasets'); + }, + + async getIntegrations() { + const content = await this.getDatasetSelectorContent(); + + const nodes = await content.findAllByCssSelector('[data-test-subj*="integration-"]', 2000); + const integrations = await Promise.all(nodes.map((node) => node.getVisibleText())); + + return { + nodes, + integrations, + }; + }, + + async openDatasetSelector() { + const button = await this.getDatasetSelectorButton(); + return button.click(); + }, + + async closeDatasetSelector() { + const button = await this.getDatasetSelectorButton(); + const isOpen = await testSubjects.exists('datasetSelectorContent'); + + if (isOpen) return button.click(); + }, + + async clickSortButtonBy(direction: 'asc' | 'desc') { + const titleMap = { + asc: 'Ascending', + desc: 'Descending', + }; + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const sortingButton = await searchControlsContainer.findByCssSelector( + `[title=${titleMap[direction]}]` + ); + + return sortingButton.click(); + }, + + async getSearchFieldValue() { + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const searchField = await searchControlsContainer.findByCssSelector('input[type=search]'); + + return searchField.getAttribute('value'); + }, + + async typeSearchFieldWith(name: string) { + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const searchField = await searchControlsContainer.findByCssSelector('input[type=search]'); + + await searchField.clearValueWithKeyboard(); + return searchField.type(name); + }, + + async clearSearchField() { + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const searchField = await searchControlsContainer.findByCssSelector('input[type=search]'); + + return searchField.clearValueWithKeyboard(); + }, + async assertRestoreFailureToastExist() { const successToast = await toasts.getToastElement(1); expect(await successToast.getVisibleText()).to.contain( "We couldn't restore your datasets selection" ); }, + + assertLoadingSkeletonExists() { + return testSubjects.existOrFail('datasetSelectorSkeleton'); + }, + + async assertNoIntegrationsPromptExists() { + const integrationStatus = await testSubjects.find('integrationStatusItem'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No integrations found'); + }, + + async assertNoIntegrationsErrorExists() { + const integrationStatus = await testSubjects.find('integrationsErrorPrompt'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No integrations found'); + }, + + async assertNoDataStreamsPromptExists() { + const integrationStatus = await testSubjects.find('emptyDatasetPrompt'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No data streams found'); + }, + + async assertNoDataStreamsErrorExists() { + const integrationStatus = await testSubjects.find('datasetErrorPrompt'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No data streams found'); + }, }; } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts index 586b07261e76c..dc2183d6f0fee 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts @@ -197,6 +197,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { let indexedAlerts: IndexedEndpointRuleAlerts; before(async () => { + await getService('kibanaServer').request({ + path: `internal/kibana/settings`, + method: 'POST', + body: { changes: { 'securitySolution:enableExpandableFlyout': false } }, + }); + indexedAlerts = await detectionsTestService.loadEndpointRuleAlerts(endpointAgentId); await detectionsTestService.waitForAlerts( diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts new file mode 100644 index 0000000000000..dfba8f72a699d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const defaultLogColumns = ['@timestamp', 'message']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discover']); + + describe('Columns selection initialization and update', () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + describe('when the log explorer profile loads', () => { + it("should initialize the table columns to logs' default selection", async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql(defaultLogColumns); + }); + }); + + it('should restore the table columns from the URL state if exists', async () => { + await PageObjects.common.navigateToApp('discover', { + hash: '/p/log-explorer?_a=(columns:!(message,data_stream.namespace))', + }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql([ + ...defaultLogColumns, + 'data_stream.namespace', + ]); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts index 579f4e4b8f5c5..a647293a73145 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts @@ -24,11 +24,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('DatasetSelector should replace the DataViewPicker', async () => { // Assert does not render on discover app await PageObjects.common.navigateToApp('discover'); - await testSubjects.missingOrFail('dataset-selector-popover'); + await testSubjects.missingOrFail('datasetSelectorPopover'); // Assert it renders on log-explorer profile await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); - await testSubjects.existOrFail('dataset-selector-popover'); + await testSubjects.existOrFail('datasetSelectorPopover'); }); it('the TopNav bar should hide New, Open and Save options', async () => { @@ -50,6 +50,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('openInspectorButton'); await testSubjects.missingOrFail('discoverSaveButton'); }); + + it('should render a filter controls section as part of the unified search bar', async () => { + // Assert does not render on discover app + await PageObjects.common.navigateToApp('discover'); + await testSubjects.missingOrFail('datasetFiltersCustomization'); + + // Assert it renders on log-explorer profile + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + await testSubjects.existOrFail('datasetFiltersCustomization', { allowHidden: true }); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts new file mode 100644 index 0000000000000..cbb3ea9d95de5 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts @@ -0,0 +1,664 @@ +/* + * 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'; + +const initialPackageMap = { + apache: 'Apache HTTP Server', + aws: 'AWS', + system: 'System', +}; +const initialPackagesTexts = Object.values(initialPackageMap); + +const expectedUncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discoverLogExplorer']); + + describe('Dataset Selector', () => { + before(async () => { + await PageObjects.discoverLogExplorer.removeInstalledPackages(); + }); + + describe('without installed integrations or uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + describe('when open on the first navigation level', () => { + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display an error prompt if could not retrieve the integrations', async function () { + // Skip the test in case network condition utils are not available + try { + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + await PageObjects.common.sleep(5000); + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + it('should display a loading skeleton while loading', async function () { + // Skip the test in case network condition utils are not available + try { + await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await PageObjects.discoverLogExplorer.assertLoadingSkeletonExists(); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an error prompt if could not retrieve the data streams', async function () { + // Skip the test in case network condition utils are not available + try { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no data streams', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + const unamanagedDatasetEntries = + await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(unamanagedDatasetEntries.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + }); + }); + + describe('with installed integrations and uncategorized data streams', () => { + let cleanupIntegrationsSetup: () => Promise; + + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + cleanupIntegrationsSetup = await PageObjects.discoverLogExplorer.setupInitialIntegrations(); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + await cleanupIntegrationsSetup(); + }); + + describe('when open on the first navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display a list of installed integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + + expect(integrations.length).to.be(3); + expect(integrations).to.eql(initialPackagesTexts); + }); + + it('should sort the integrations list by the clicked sorting option', async () => { + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts.slice().reverse()); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + }); + + it('should filter the integrations list by the typed integration name', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache, initialPackageMap.aws]); + }); + }); + + it('should display an empty prompt when the search does not match any result', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('no result search text'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + }); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + it('should load more integrations by scrolling to the end of the list', async () => { + // Install more integrations and reload the page + const cleanupAdditionalSetup = + await PageObjects.discoverLogExplorer.setupAdditionalIntegrations(); + await browser.refresh(); + + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + // Initially fetched integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(15); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // Load more integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // No other integrations to load after scrolling to last integration + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + }); + + cleanupAdditionalSetup(); + }); + }); + + describe('when clicking on integration and moving into the second navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(await menuEntries[1].getVisibleText()).to.be('access'); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + }); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[0]); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('retail'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('logs-retail-*'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('logs-gaming-*'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('logs-gaming-*'); + }); + }); + }); + + describe('when open/close the selector', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should restore the latest navigation panel', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should restore the latest search results', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + }); + }); + + describe('when switching between integration panels', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + it('should remember the latest search and restore its results for each integration', async () => { + await PageObjects.discoverLogExplorer.openDatasetSelector(); + await PageObjects.discoverLogExplorer.clearSearchField(); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('apache'); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + + // Navigate back to integrations + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + panelTitleNode.click(); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('apache'); + + nodes[0].click(); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('err'); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts index e334c028cbaca..8e9843fc02815 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts @@ -9,7 +9,9 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function (loadTestFile: FtrProviderContext['loadTestFile']) { describe('Discover Log-Explorer profile', function () { + loadTestFile(require.resolve('./columns_selection')); loadTestFile(require.resolve('./customization')); loadTestFile(require.resolve('./dataset_selection_state')); + loadTestFile(require.resolve('./dataset_selector')); }); }