diff --git a/data/.empty b/data/.empty deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/package.json b/package.json index 9bc363242a332..b4b3cbe22b715 100644 --- a/package.json +++ b/package.json @@ -609,7 +609,7 @@ "cpy": "^8.1.1", "cronstrue": "^1.51.0", "css-loader": "^3.4.2", - "cypress": "^6.0.1", + "cypress": "^6.1.0", "cypress-cucumber-preprocessor": "^2.5.2", "cypress-multi-reporters": "^1.4.0", "d3": "3.5.17", diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 8b25c8f6d3fc2..937172272f179 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -872,7 +872,7 @@ describe('migration actions', () => { afterAll(async () => { await client.indices.delete({ index: 'yellow_then_green_index' }); }); - it('resolves right after waiting for an index status to be green if the index already existed', async () => { + it.skip('resolves right after waiting for an index status to be green if the index already existed', async () => { // Create a yellow index await client.indices.create( { diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 68b4ca45ec911..bdc51d09a2aa5 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -77,7 +77,7 @@ case $1 in --gid "<%= group %>" \ --shell /sbin/nologin \ --comment "kibana service user" \ - "<%= user %>" \ + "<%= user %>" echo " OK" fi diff --git a/vars/tasks.groovy b/vars/tasks.groovy index a01e63e218147..221e93fd7b839 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -115,15 +115,14 @@ def functionalXpack(Map params = [:]) { task(kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh')) } - // FLAKY: https://github.com/elastic/kibana/issues/86080 - // whenChanged([ - // 'x-pack/plugins/security_solution/', - // 'x-pack/test/security_solution_cypress/', - // 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', - // 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx', - // ]) { - // task(kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')) - // } + whenChanged([ + 'x-pack/plugins/security_solution/', + 'x-pack/test/security_solution_cypress/', + 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', + 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx', + ]) { + task(kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')) + } } } diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index 0c9a09b11532b..6288d27c6ebe0 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -4,18 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - AlertingPlugin, - AlertingPluginsSetup, - AlertingPluginsStart, - PluginSetupContract, -} from './plugin'; +import { AlertingPlugin, AlertingPluginsSetup, PluginSetupContract } from './plugin'; import { coreMock, statusServiceMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; -import { KibanaRequest, CoreSetup } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { featuresPluginMock } from '../../features/server/mocks'; import { KibanaFeature } from '../../features/server'; import { AlertsConfig } from './config'; @@ -41,27 +36,20 @@ describe('Alerting Plugin', () => { }); plugin = new AlertingPlugin(context); - coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); - const statusMock = statusServiceMock.createSetupContract(); - await plugin.setup( - ({ - ...coreSetup, - http: { - ...coreSetup.http, - route: jest.fn(), - }, - status: statusMock, - } as unknown) as CoreSetup, - ({ - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsSetup, - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - } as unknown) as AlertingPluginsSetup - ); - expect(statusMock.set).toHaveBeenCalledTimes(1); + const setupMocks = coreMock.createSetup(); + // need await to test number of calls of setupMocks.status.set, becuase it is under async function which awaiting core.getStartServices() + await plugin.setup(setupMocks, { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); + + expect(setupMocks.status.set).toHaveBeenCalledTimes(1); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(context.logger.get().warn).toHaveBeenCalledWith( 'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.' @@ -90,7 +78,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createSetup(), statusService: statusServiceMock.createSetupContract(), }; - setup = await plugin.setup(coreSetup, pluginsSetup); + setup = plugin.setup(coreSetup, pluginsSetup); }); it('should throw error when license type is invalid', async () => { @@ -120,13 +108,6 @@ describe('Alerting Plugin', () => { }); describe('start()', () => { - /** - * HACK: This test has put together to ensuire the function "getAlertsClientWithRequest" - * throws when needed. There's a lot of blockers for writing a proper test like - * misisng plugin start/setup mocks for taskManager and actions plugin, core.http.route - * is actually not a function in Kibana Platform, etc. This test contains what is needed - * to get to the necessary function within start(). - */ describe('getAlertsClientWithRequest()', () => { it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => { const context = coreMock.createPluginInitializerContext({ @@ -140,37 +121,24 @@ describe('Alerting Plugin', () => { }); const plugin = new AlertingPlugin(context); - const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); - await plugin.setup( - ({ - ...coreSetup, - http: { - ...coreSetup.http, - route: jest.fn(), - }, - } as unknown) as CoreSetup, - ({ - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsSetup, - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - } as unknown) as AlertingPluginsSetup - ); + plugin.setup(coreMock.createSetup(), { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); - const startContract = plugin.start( - coreMock.createStart() as ReturnType, - ({ - actions: { - execute: jest.fn(), - getActionsClientWithRequest: jest.fn(), - getActionsAuthorizationWithRequest: jest.fn(), - }, - encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), - features: mockFeatures(), - licensing: licensingMock.createStart(), - } as unknown) as AlertingPluginsStart - ); + const startContract = plugin.start(coreMock.createStart(), { + actions: actionsMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + features: mockFeatures(), + licensing: licensingMock.createStart(), + eventLog: eventLogMock.createStart(), + taskManager: taskManagerMock.createStart(), + }); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(() => @@ -192,40 +160,27 @@ describe('Alerting Plugin', () => { }); const plugin = new AlertingPlugin(context); - const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = { ...encryptedSavedObjectsMock.createSetup(), usingEphemeralEncryptionKey: false, }; - await plugin.setup( - ({ - ...coreSetup, - http: { - ...coreSetup.http, - route: jest.fn(), - }, - } as unknown) as CoreSetup, - ({ - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsSetup, - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - } as unknown) as AlertingPluginsSetup - ); + plugin.setup(coreMock.createSetup(), { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); - const startContract = plugin.start( - coreMock.createStart() as ReturnType, - ({ - actions: { - execute: jest.fn(), - getActionsClientWithRequest: jest.fn(), - getActionsAuthorizationWithRequest: jest.fn(), - }, - encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), - features: mockFeatures(), - licensing: licensingMock.createStart(), - } as unknown) as AlertingPluginsStart - ); + const startContract = plugin.start(coreMock.createStart(), { + actions: actionsMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + features: mockFeatures(), + licensing: licensingMock.createStart(), + eventLog: eventLogMock.createStart(), + taskManager: taskManagerMock.createStart(), + }); const fakeRequest = ({ headers: {}, @@ -242,7 +197,7 @@ describe('Alerting Plugin', () => { }, getSavedObjectsClient: jest.fn(), } as unknown) as KibanaRequest; - await startContract.getAlertsClientWithRequest(fakeRequest); + startContract.getAlertsClientWithRequest(fakeRequest); }); }); }); diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md index 3fd6154b1d265..0caea251ec6fb 100644 --- a/x-pack/plugins/enterprise_search/README.md +++ b/x-pack/plugins/enterprise_search/README.md @@ -31,8 +31,21 @@ To debug Kea state in-browser, Kea recommends [Redux Devtools](https://kea.js.or Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing +Jest tests can be run directly from the `x-pack/plugins/enterprise_search` folder. This also works for any subfolders or subcomponents. + +```bash +yarn test:jest +yarn test:jest --watch ``` -yarn test:jest x-pack/plugins/enterprise_search --watch + +Unfortunately coverage collection does not work as automatically, and requires using our handy jest.sh script if you want to run tests on a specific folder and only get coverage numbers for that folder: + +```bash +# Running the jest.sh script from the `x-pack/plugins/enterprise_search` folder (vs. kibana root) +# will save you time and allow you to Tab to complete folder dir names +sh jest.sh {YOUR_COMPONENT_DIR} +sh jest.sh public/applications/shared/kibana +sh jest.sh server/routes/app_search ``` ### E2E tests diff --git a/x-pack/plugins/enterprise_search/jest.config.js b/x-pack/plugins/enterprise_search/jest.config.js index db6a25a1f7efd..395297f1723c0 100644 --- a/x-pack/plugins/enterprise_search/jest.config.js +++ b/x-pack/plugins/enterprise_search/jest.config.js @@ -8,4 +8,11 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/enterprise_search'], + collectCoverage: true, + coverageReporters: ['text'], + collectCoverageFrom: [ + '/x-pack/plugins/enterprise_search/**/*.{ts,tsx}', + '!/x-pack/plugins/enterprise_search/public/*.ts', + '!/x-pack/plugins/enterprise_search/server/*.ts', + ], }; diff --git a/x-pack/plugins/enterprise_search/jest.sh b/x-pack/plugins/enterprise_search/jest.sh new file mode 100644 index 0000000000000..d7aa0b07fb89c --- /dev/null +++ b/x-pack/plugins/enterprise_search/jest.sh @@ -0,0 +1,18 @@ +#! /bin/bash + +# Whether to run Jest on the entire enterprise_search plugin or a specific component/folder +FOLDER="${1:-all}" +if [[ $FOLDER && $FOLDER != "all" ]] +then + FOLDER=${FOLDER%/} # Strip any trailing slash + FOLDER="${FOLDER}/ --collectCoverageFrom='/x-pack/plugins/enterprise_search/${FOLDER}/**/*.{ts,tsx}'" +else + FOLDER='' +fi + +# Pass all remaining arguments (e.g., ...rest) from the 2nd arg onwards +# as an open-ended string. Appends onto to the end the Jest CLI command +# @see https://jestjs.io/docs/en/cli#options +ARGS="${*:2}" + +yarn test:jest $FOLDER $ARGS diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 0fa1ddeee820d..d7cf391376076 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -20,6 +20,7 @@ export interface DataStreamsTabTestBed extends TestBed { clickEmptyPromptIndexTemplateLink: () => void; clickIncludeStatsSwitch: () => void; toggleViewFilterAt: (index: number) => void; + sortTableOnStorageSize: () => void; clickReloadButton: () => void; clickNameAt: (index: number) => void; clickIndicesAt: (index: number) => void; @@ -94,6 +95,14 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find, component } = testBed; + act(() => { + find('tableHeaderCell_storageSizeBytes_3.tableHeaderSortButton').simulate('click'); + }); + component.update(); + }; + const clickReloadButton = () => { const { find } = testBed; find('reloadButton').simulate('click'); @@ -205,6 +214,7 @@ export const setup = async (overridingDependencies: any = {}): Promise): DataSt health: 'green', indexTemplateName: 'indexTemplate', storageSize: '1b', + storageSizeBytes: 1, maxTimeStamp: 420, privileges: { delete_index: true, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 93899dece3308..b71e058e711cd 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -120,11 +120,21 @@ describe('Data Streams tab', () => { createNonDataStreamIndex('non-data-stream-index'), ]); - const dataStreamForDetailPanel = createDataStreamPayload({ name: 'dataStream1' }); + const dataStreamForDetailPanel = createDataStreamPayload({ + name: 'dataStream1', + storageSize: '5b', + storageSizeBytes: 5, + }); + setLoadDataStreamsResponse([ dataStreamForDetailPanel, - createDataStreamPayload({ name: 'dataStream2' }), + createDataStreamPayload({ + name: 'dataStream2', + storageSize: '1kb', + storageSizeBytes: 1000, + }), ]); + setLoadDataStreamResponse(dataStreamForDetailPanel); const indexTemplate = fixtures.getTemplate({ name: 'indexTemplate' }); @@ -181,8 +191,28 @@ describe('Data Streams tab', () => { // The table renders with the stats columns though. const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '1b', '1', 'Delete'], - ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1b', '1', 'Delete'], + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], + ]); + }); + + test('sorting on stats sorts by bytes value instead of human readable value', async () => { + // Guards against regression of #86122. + const { actions, table, component } = testBed; + + await act(async () => { + actions.clickIncludeStatsSwitch(); + }); + component.update(); + + actions.sortTableOnStorageSize(); + + // The table sorts by the underlying byte values in ascending order, instead of sorting by + // the human-readable string values. + const { tableCellsValues } = table.getMetaData('dataStreamTable'); + expect(tableCellsValues).toEqual([ + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], ]); }); diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index 333cb4b97f2aa..c1a3a728d4238 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -16,6 +16,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS template, ilm_policy: ilmPolicyName, store_size: storageSize, + store_size_bytes: storageSizeBytes, maximum_timestamp: maxTimeStamp, _meta, privileges, @@ -37,6 +38,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS indexTemplateName: template, ilmPolicyName, storageSize, + storageSizeBytes, maxTimeStamp, _meta, privileges, diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index fca10f85ab63c..57abc0e188e16 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -36,6 +36,7 @@ export interface DataStreamFromEs { template: string; ilm_policy?: string; store_size?: string; + store_size_bytes?: number; maximum_timestamp?: number; privileges: PrivilegesFromEs; hidden: boolean; @@ -57,6 +58,7 @@ export interface DataStream { indexTemplateName: string; ilmPolicyName?: string; storageSize?: string; + storageSizeBytes?: number; maxTimeStamp?: number; _meta?: Meta; privileges: Privileges; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index f74147f996701..dfad10ab62449 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -90,12 +90,14 @@ export const DataStreamTable: React.FunctionComponent = ({ }); columns.push({ - field: 'storageSize', + field: 'storageSizeBytes', name: i18n.translate('xpack.idxMgmt.dataStreamList.table.storageSizeColumnTitle', { defaultMessage: 'Storage size', }), truncateText: true, sortable: true, + render: (storageSizeBytes: DataStream['storageSizeBytes'], dataStream: DataStream) => + dataStream.storageSize, }); } diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index 4124d8e897b5b..4256107fc1818 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -23,6 +23,7 @@ interface PrivilegesFromEs { interface StatsFromEs { data_stream: string; store_size: string; + store_size_bytes: number; maximum_timestamp: number; } @@ -40,7 +41,7 @@ const enhanceDataStreams = ({ if (dataStreamsStats) { // eslint-disable-next-line @typescript-eslint/naming-convention - const { store_size, maximum_timestamp } = + const { store_size, store_size_bytes, maximum_timestamp } = dataStreamsStats.find( ({ data_stream: statsName }: { data_stream: string }) => statsName === dataStream.name ) || {}; @@ -48,6 +49,7 @@ const enhanceDataStreams = ({ enhancedDataStream = { ...enhancedDataStream, store_size, + store_size_bytes, maximum_timestamp, }; } diff --git a/x-pack/plugins/ml/common/constants/messages.ts b/x-pack/plugins/ml/common/constants/messages.ts index 1027ee5bf9a89..0e7303c6d7317 100644 --- a/x-pack/plugins/ml/common/constants/messages.ts +++ b/x-pack/plugins/ml/common/constants/messages.ts @@ -90,6 +90,38 @@ export const getMessages = once(() => { url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', }, + cardinality_no_results: { + status: VALIDATION_STATUS.WARNING, + heading: i18n.translate( + 'xpack.ml.models.jobValidation.messages.cardinalityNoResultsHeading', + { + defaultMessage: 'Field cardinality', + } + ), + text: i18n.translate('xpack.ml.models.jobValidation.messages.cardinalityNoResultsMessage', { + defaultMessage: `Cardinality checks could not be run. The query to validate fields didn't return any documents.`, + }), + }, + cardinality_field_not_exists: { + status: VALIDATION_STATUS.WARNING, + heading: i18n.translate( + 'xpack.ml.models.jobValidation.messages.cardinalityFieldNotExistsHeading', + { + defaultMessage: 'Field cardinality', + } + ), + text: i18n.translate( + 'xpack.ml.models.jobValidation.messages.cardinalityFieldNotExistsMessage', + { + defaultMessage: `Cardinality checks could not be run for field {fieldName}. The query to validate the field didn't return any documents.`, + values: { + fieldName: '"{{fieldName}}"', + }, + } + ), + url: + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', + }, cardinality_by_field: { status: VALIDATION_STATUS.WARNING, text: i18n.translate('xpack.ml.models.jobValidation.messages.cardinalityByFieldMessage', { diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js index 0c079bc11cffc..1ce4188d8e063 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js @@ -208,60 +208,103 @@ export class ValidateJobUI extends Component { const duration = typeof getDuration === 'function' ? getDuration() : undefined; const fields = this.props.fields; + // Run job validation only if a job config has been passed on and the duration makes sense to run it. + // Otherwise we skip the call and display a generic warning, but let the user move on to the next wizard step. if (typeof job === 'object') { - let shouldShowLoadingIndicator = true; - - this.props.ml - .validateJob({ duration, fields, job }) - .then((messages) => { - shouldShowLoadingIndicator = false; - this.setState({ - ...this.state, - ui: { - ...this.state.ui, - iconType: statusToEuiIconType(getMostSevereMessageStatus(messages)), - isLoading: false, - isModalVisible: true, - }, - data: { - messages, - success: true, - }, - title: job.job_id, - }); - if (typeof this.props.setIsValid === 'function') { - this.props.setIsValid( - messages.some((m) => m.status === VALIDATION_STATUS.ERROR) === false + if (typeof duration === 'object' && duration.start !== null && duration.end !== null) { + let shouldShowLoadingIndicator = true; + + this.props.ml + .validateJob({ duration, fields, job }) + .then((messages) => { + shouldShowLoadingIndicator = false; + + const messagesContainError = messages.some((m) => m.status === VALIDATION_STATUS.ERROR); + + if (messagesContainError) { + messages.push({ + id: 'job_validation_includes_error', + text: i18n.translate('xpack.ml.validateJob.jobValidationIncludesErrorText', { + defaultMessage: + 'Job validation has failed, but you can still continue and create the job. Please be aware the job may encounter problems when running.', + }), + status: VALIDATION_STATUS.WARNING, + }); + } + + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + iconType: statusToEuiIconType(getMostSevereMessageStatus(messages)), + isLoading: false, + isModalVisible: true, + }, + data: { + messages, + success: true, + }, + title: job.job_id, + }); + if (typeof this.props.setIsValid === 'function') { + this.props.setIsValid(!messagesContainError); + } + }) + .catch((error) => { + const { toasts } = this.props.kibana.services.notifications; + const toastNotificationService = toastNotificationServiceProvider(toasts); + toastNotificationService.displayErrorToast( + error, + i18n.translate('xpack.ml.jobService.validateJobErrorTitle', { + defaultMessage: 'Job Validation Error', + }) ); + }); + + // wait for 250ms before triggering the loading indicator + // to avoid flickering when there's a loading time below + // 250ms for the job validation data + const delay = 250; + setTimeout(() => { + if (shouldShowLoadingIndicator) { + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + isLoading: true, + isModalVisible: false, + }, + }); } - }) - .catch((error) => { - const { toasts } = this.props.kibana.services.notifications; - const toastNotificationService = toastNotificationServiceProvider(toasts); - toastNotificationService.displayErrorToast( - error, - i18n.translate('xpack.ml.jobService.validateJobErrorTitle', { - defaultMessage: 'Job Validation Error', - }) - ); + }, delay); + } else { + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + iconType: statusToEuiIconType(VALIDATION_STATUS.WARNING), + isLoading: false, + isModalVisible: true, + }, + data: { + messages: [ + { + id: 'job_validation_skipped', + text: i18n.translate('xpack.ml.validateJob.jobValidationSkippedText', { + defaultMessage: + 'Job validation could not be run because of insufficient sample data. Please be aware the job may encounter problems when running.', + }), + status: VALIDATION_STATUS.WARNING, + }, + ], + success: true, + }, + title: job.job_id, }); - - // wait for 250ms before triggering the loading indicator - // to avoid flickering when there's a loading time below - // 250ms for the job validation data - const delay = 250; - setTimeout(() => { - if (shouldShowLoadingIndicator) { - this.setState({ - ...this.state, - ui: { - ...this.state.ui, - isLoading: true, - isModalVisible: false, - }, - }); + if (typeof this.props.setIsValid === 'function') { + this.props.setIsValid(true); } - }, delay); + } } }; diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js index f2f785d91dcac..7e473f12ee50d 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js @@ -27,6 +27,7 @@ const job = { }; const getJobConfig = () => job; +const getDuration = () => ({ start: 0, end: 1 }); function prepareTest(messages) { const p = Promise.resolve(messages); @@ -40,7 +41,9 @@ function prepareTest(messages) { }, }; - const component = ; + const component = ( + + ); const wrapper = shallowWithIntl(component); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx index e22e48733cc39..7ec967ebfb935 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx @@ -26,6 +26,7 @@ import { SavedSearchQuery } from '../../../../../contexts/ml'; import { defaultSearchQuery, DataFrameAnalyticsConfig, + INDEX_STATUS, SEARCH_SIZE, getAnalysisType, } from '../../../../common'; @@ -54,11 +55,12 @@ const showingFirstDocs = i18n.translate( const getResultsSectionHeaderItems = ( columnsWithCharts: EuiDataGridColumn[], + status: INDEX_STATUS, tableItems: Array>, rowCount: number, colorRange?: ReturnType ): ExpandableSectionProps['headerItems'] => { - return columnsWithCharts.length > 0 && tableItems.length > 0 + return columnsWithCharts.length > 0 && (tableItems.length > 0 || status === INDEX_STATUS.LOADED) ? [ { id: 'explorationTableTotalDocs', @@ -109,11 +111,12 @@ export const ExpandableSectionResults: FC = ({ needsDestIndexPattern, searchQuery, }) => { - const { columnsWithCharts, tableItems } = indexData; + const { columnsWithCharts, status, tableItems } = indexData; // Results section header items and content const resultsSectionHeaderItems = getResultsSectionHeaderItems( columnsWithCharts, + status, tableItems, indexData.rowCount, colorRange @@ -137,14 +140,15 @@ export const ExpandableSectionResults: FC = ({ {(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) && indexPattern !== undefined && ( <> - {columnsWithCharts.length > 0 && tableItems.length > 0 && ( - - )} + {columnsWithCharts.length > 0 && + (tableItems.length > 0 || status === INDEX_STATUS.LOADED) && ( + + )} )} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx index 3bde32f40eeb5..224e2eacb21e0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useContext, useState, useEffect } from 'react'; +import React, { Fragment, FC, useContext, useEffect } from 'react'; import { WizardNav } from '../wizard_nav'; import { WIZARD_STEPS, StepProps } from '../step_types'; import { JobCreatorContext } from '../job_creator_context'; @@ -21,7 +21,6 @@ const idFilterList = [ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) => { const { jobCreator, jobCreatorUpdate, jobValidator } = useContext(JobCreatorContext); - const [nextActive, setNextActive] = useState(false); if (jobCreator.type === JOB_TYPE.ADVANCED) { // for advanced jobs, ignore time range warning as the @@ -50,13 +49,8 @@ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) }, []); // keep a record of the advanced validation in the jobValidator - // and disable the next button if any advanced checks have failed. - // note, it is not currently possible to get to a state where any of the - // advanced validation checks return an error because they are all - // caught in previous basic checks function setIsValid(valid: boolean) { jobValidator.advancedValid = valid; - setNextActive(valid); } return ( @@ -74,7 +68,7 @@ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) setCurrentStep(WIZARD_STEPS.JOB_DETAILS)} next={() => setCurrentStep(WIZARD_STEPS.SUMMARY)} - nextActive={nextActive} + nextActive={true} /> )} diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index d397d39d32b6b..8e88d1c1d0537 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -12,7 +12,7 @@ import type { MlClient } from '../../lib/ml_client'; const callAs = { fieldCaps: () => Promise.resolve({ body: { fields: [] } }), - search: () => Promise.resolve({ body: { hits: { total: { value: 0, relation: 'eq' } } } }), + search: () => Promise.resolve({ body: { hits: { total: { value: 1, relation: 'eq' } } } }), }; const mlClusterClient = ({ diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts index 2e2a9e21aa959..3996f42c48926 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts @@ -189,6 +189,38 @@ describe('ML - validateCardinality', () => { ); }); + it('cardinality no results', () => { + const job = getJobConfig('partition_field_name'); + + const mockCardinality = cloneDeep(mockResponses); + mockCardinality.search.hits.total.value = 0; + mockCardinality.search.aggregations.airline_count.doc_count = 0; + + return validateCardinality( + mlClusterClientFactory(mockCardinality), + (job as unknown) as CombinedJob + ).then((messages) => { + const ids = messages.map((m) => m.id); + expect(ids).toStrictEqual(['cardinality_no_results']); + }); + }); + + it('cardinality field not exists', () => { + const job = getJobConfig('partition_field_name'); + + const mockCardinality = cloneDeep(mockResponses); + mockCardinality.search.hits.total.value = 1; + mockCardinality.search.aggregations.airline_count.doc_count = 0; + + return validateCardinality( + mlClusterClientFactory(mockCardinality), + (job as unknown) as CombinedJob + ).then((messages) => { + const ids = messages.map((m) => m.id); + expect(ids).toStrictEqual(['cardinality_field_not_exists']); + }); + }); + it('fields not aggregatable', () => { const job = getJobConfig('partition_field_name'); job.analysis_config.detectors.push({ diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts index 822d1a1081874..3afb403554666 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts @@ -132,35 +132,54 @@ const validateFactory = (client: IScopedClusterClient, job: CombinedJob): Valida datafeedConfig ); - uniqueFieldNames.forEach((uniqueFieldName) => { - const field = stats.aggregatableExistsFields.find( - (fieldData) => fieldData.fieldName === uniqueFieldName - ); - if (field !== undefined && typeof field === 'object' && field.stats) { - modelPlotCardinality += - modelPlotConfigFieldCount > 0 ? modelPlotConfigFieldCount : field.stats.cardinality!; - - if (isInvalid(field.stats.cardinality!)) { - messages.push({ - id: messageId || (`cardinality_${type}_field` as MessageId), - fieldName: uniqueFieldName, - }); - } - } else { - // only report uniqueFieldName as not aggregatable if it's not part - // of a valid categorization configuration and if it's not a scripted field or runtime mapping. - if ( - !isValidCategorizationConfig(job, uniqueFieldName) && - !isScriptField(job, uniqueFieldName) && - !isRuntimeMapping(job, uniqueFieldName) - ) { + if (stats.totalCount === 0) { + messages.push({ + id: 'cardinality_no_results', + }); + } else { + uniqueFieldNames.forEach((uniqueFieldName) => { + const aggregatableNotExistsField = stats.aggregatableNotExistsFields.find( + (fieldData) => fieldData.fieldName === uniqueFieldName + ); + + if (aggregatableNotExistsField !== undefined) { messages.push({ - id: 'field_not_aggregatable', + id: 'cardinality_field_not_exists', fieldName: uniqueFieldName, }); + } else { + const field = stats.aggregatableExistsFields.find( + (fieldData) => fieldData.fieldName === uniqueFieldName + ); + if (field !== undefined && typeof field === 'object' && field.stats) { + modelPlotCardinality += + modelPlotConfigFieldCount > 0 + ? modelPlotConfigFieldCount + : field.stats.cardinality!; + + if (isInvalid(field.stats.cardinality!)) { + messages.push({ + id: messageId || (`cardinality_${type}_field` as MessageId), + fieldName: uniqueFieldName, + }); + } + } else { + // only report uniqueFieldName as not aggregatable if it's not part + // of a valid categorization configuration and if it's not a scripted field or runtime mapping. + if ( + !isValidCategorizationConfig(job, uniqueFieldName) && + !isScriptField(job, uniqueFieldName) && + !isRuntimeMapping(job, uniqueFieldName) + ) { + messages.push({ + id: 'field_not_aggregatable', + fieldName: uniqueFieldName, + }); + } + } } - } - }); + }); + } } catch (e) { // checkAggregatableFieldsExist may return an error if 'fielddata' is // disabled for text fields (which is the default). If there was only diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index a40c79acd8fd8..a15aad1bd8cc3 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -26,6 +26,7 @@ import { goToInProgressAlerts, } from '../tasks/alerts'; import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -34,16 +35,17 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS_URL); }); afterEach(() => { esArchiverUnload('alerts'); - removeSignalsIndex(); }); - it.skip('Closes and opens alerts', () => { + it('Closes and opens alerts', () => { waitForAlertsPanelToBeLoaded(); waitForAlertsToBeLoaded(); @@ -117,13 +119,11 @@ describe('Alerts', () => { `Showing ${expectedNumberOfOpenedAlerts.toString()} alerts` ); - cy.get( - '[data-test-subj="events-viewer-panel"] [data-test-subj="server-side-event-count"]' - ).should('have.text', expectedNumberOfOpenedAlerts.toString()); + cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfOpenedAlerts.toString()); }); }); - it.skip('Closes one alert when more than one opened alerts are selected', () => { + it('Closes one alert when more than one opened alerts are selected', () => { waitForAlertsToBeLoaded(); cy.get(ALERTS_COUNT) @@ -163,16 +163,17 @@ describe('Alerts', () => { context('Opening alerts', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('closed_alerts'); loginAndWaitForPage(DETECTIONS_URL); }); afterEach(() => { esArchiverUnload('closed_alerts'); - removeSignalsIndex(); }); - it.skip('Open one alert when more than one closed alerts are selected', () => { + it('Open one alert when more than one closed alerts are selected', () => { waitForAlerts(); goToClosedAlerts(); waitForAlertsToBeLoaded(); @@ -215,6 +216,8 @@ describe('Alerts', () => { context('Marking alerts as in-progress', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS_URL); }); @@ -224,7 +227,7 @@ describe('Alerts', () => { removeSignalsIndex(); }); - it.skip('Mark one alert in progress when more than one open alerts are selected', () => { + it('Mark one alert in progress when more than one open alerts are selected', () => { waitForAlerts(); waitForAlertsToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts index 23798fb38a794..fa48c0bc1abc6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts @@ -13,12 +13,12 @@ import { login, loginAndWaitForPageWithoutDateRange, waitForPageWithoutDateRange, - deleteRoleAndUser, } from '../tasks/login'; import { waitForAlertsIndexToBeCreated } from '../tasks/alerts'; import { goToRuleDetails } from '../tasks/alerts_detection_rules'; import { createCustomRule, deleteCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; import { getCallOut, waitForCallOutToBeShown, dismissCallOut } from '../tasks/common/callouts'; +import { cleanKibana } from '../tasks/common'; const loadPageAsReadOnlyUser = (url: string) => { waitForPageWithoutDateRange(url, ROLES.reader); @@ -41,6 +41,8 @@ describe('Detections > Callouts indicating read-only access to resources', () => before(() => { // First, we have to open the app on behalf of a priviledged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. + cleanKibana(); + removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer); waitForAlertsIndexToBeCreated(); @@ -48,12 +50,6 @@ describe('Detections > Callouts indicating read-only access to resources', () => login(ROLES.reader); }); - after(() => { - deleteRoleAndUser(ROLES.reader); - deleteRoleAndUser(ROLES.platform_engineer); - removeSignalsIndex(); - }); - context('On Detections home page', () => { beforeEach(() => { loadPageAsReadOnlyUser(DETECTIONS_URL); @@ -95,7 +91,6 @@ describe('Detections > Callouts indicating read-only access to resources', () => context('On Rule Details page', () => { beforeEach(() => { createCustomRule(newRule); - loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL); waitForPageTitleToBeShown(); goToRuleDetails(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts index 9137d6383a15e..265f4d43c71c1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts @@ -16,7 +16,7 @@ import { goToOpenedAlerts, waitForAlertsIndexToBeCreated, } from '../tasks/alerts'; -import { createCustomRule, deleteCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; import { goToRuleDetails } from '../tasks/alerts_detection_rules'; import { waitForAlertsToPopulate } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -33,10 +33,13 @@ import { import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; +import { cleanKibana } from '../tasks/common'; describe.skip('Exceptions', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule); @@ -58,9 +61,8 @@ describe.skip('Exceptions', () => { afterEach(() => { esArchiverUnload('auditbeat_for_exceptions'); esArchiverUnload('auditbeat_for_exceptions2'); - deleteCustomRule(); - removeSignalsIndex(); }); + context('From rule', () => { it('Creates an exception and deletes it', () => { goToExceptionsTab(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts index 2d21e3d333c07..4284b05205c69 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts @@ -33,15 +33,18 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants'; import { DETECTIONS_URL } from '../urls/navigation'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; describe('Alerts detection rules', () => { before(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('prebuilt_rules_loaded'); }); after(() => { esArchiverUnload('prebuilt_rules_loaded'); - cy.clock().invoke('restore'); }); it('Sorts by activated rules', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 3ce507c791f0a..fb196fde3ae83 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -75,7 +75,6 @@ import { import { changeToThreeHundredRowsPerPage, deleteFirstRule, - deleteRule, deleteSelectedRules, editFirstRule, filterByCustomRules, @@ -86,7 +85,8 @@ import { waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; import { removeSignalsIndex } from '../tasks/api_calls/rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRule, @@ -115,17 +115,13 @@ describe('Custom detection rules creation', () => { const rule = { ...newRule }; before(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(newRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - after(() => { - deleteRule(); - deleteTimeline(rule.timeline.id!); - removeSignalsIndex(); - }); - it('Creates and activates a new rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -219,6 +215,8 @@ describe('Custom detection rules creation', () => { describe.skip('Custom detection rules deletion and edition', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -227,7 +225,6 @@ describe.skip('Custom detection rules deletion and edition', () => { }); afterEach(() => { - removeSignalsIndex(); esArchiverUnload('custom_rules'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index 4c991a15b20b0..ebbbc65b12aeb 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -56,14 +56,15 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -77,7 +78,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Detection rules, EQL', () => { +describe.skip('Detection rules, EQL', () => { const expectedUrls = eqlRule.referenceUrls.join(''); const expectedFalsePositives = eqlRule.falsePositivesExamples.join(''); const expectedTags = eqlRule.tags.join(''); @@ -88,16 +89,13 @@ describe('Detection rules, EQL', () => { const rule = { ...eqlRule }; before(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(eqlRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - after(() => { - deleteTimeline(rule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new EQL rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -183,16 +181,13 @@ describe('Detection rules, sequence EQL', () => { const rule = { ...eqlSequenceRule }; before(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(eqlSequenceRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - afterEach(() => { - deleteTimeline(eqlSequenceRule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new EQL rule with a sequence', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index 8f9a0b4fc78fd..197c544d395a6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -11,7 +11,8 @@ import { waitForAlertsPanelToBeLoaded, } from '../tasks/alerts'; import { exportFirstRule } from '../tasks/alerts_detection_rules'; -import { createCustomRule, deleteCustomRule } from '../tasks/api_calls/rules'; +import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -19,6 +20,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe('Export rules', () => { let ruleResponse: Cypress.Response; before(() => { + cleanKibana(); + removeSignalsIndex(); cy.intercept( 'POST', '/api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson' @@ -31,10 +34,6 @@ describe('Export rules', () => { }); }); - after(() => { - deleteCustomRule(); - }); - it('Exports a custom rule', () => { goToManageAlertsDetectionRules(); exportFirstRule(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts index cbc4f21577864..4e97b619fc274 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts @@ -59,7 +59,6 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, @@ -67,6 +66,7 @@ import { waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -90,6 +90,8 @@ describe('Detection rules, Indicator Match', () => { const expectedNumberOfAlerts = 1; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('threat_indicator'); esArchiverLoad('threat_data'); }); @@ -97,11 +99,9 @@ describe('Detection rules, Indicator Match', () => { afterEach(() => { esArchiverUnload('threat_indicator'); esArchiverUnload('threat_data'); - deleteRule(); - removeSignalsIndex(); }); - it.skip('Creates and activates a new Indicator Match rule', () => { + it('Creates and activates a new Indicator Match rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts index 14f0ef46e7ac8..ae4cb204d6e17 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts @@ -46,13 +46,14 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -71,8 +72,9 @@ describe('Detection rules, machine learning', () => { const expectedMitre = formatMitreAttackDescription(machineLearningRule.mitre); const expectedNumberOfRules = 1; - after(() => { - deleteRule(); + before(() => { + cleanKibana(); + removeSignalsIndex(); }); it('Creates and activates a new ml rule', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index 4d013ee7d7232..a543dca00b010 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -63,14 +63,15 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleWithOverrideAndContinue, @@ -94,16 +95,13 @@ describe.skip('Detection rules, override', () => { const rule = { ...newOverrideRule }; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(newOverrideRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - afterEach(() => { - deleteTimeline(rule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new custom rule with override option', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts index e1451d78192b5..71c5647895385 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts @@ -30,23 +30,23 @@ import { waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver'; +import { esArchiverLoadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; describe('Alerts rules, prebuilt rules', () => { before(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoadEmptyKibana(); }); - after(() => { - esArchiverUnloadEmptyKibana(); - }); - - it.skip('Loads prebuilt rules', () => { + it('Loads prebuilt rules', () => { const expectedNumberOfRules = totalNumberOfPrebuiltRules; const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; @@ -78,11 +78,12 @@ describe('Alerts rules, prebuilt rules', () => { }); }); -describe.skip('Deleting prebuilt rules', () => { +describe('Deleting prebuilt rules', () => { beforeEach(() => { const expectedNumberOfRules = totalNumberOfPrebuiltRules; const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; + cleanKibana(); esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -98,10 +99,6 @@ describe.skip('Deleting prebuilt rules', () => { waitForRulesToBeLoaded(); }); - afterEach(() => { - esArchiverUnloadEmptyKibana(); - }); - it('Does not allow to delete one rule when more than one is selected', () => { const numberOfRulesToBeSelected = 2; selectNumberOfRules(numberOfRulesToBeSelected); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index cc6f1b3a8a0e0..812d0fa29f9b7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -58,14 +58,15 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -79,7 +80,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Detection rules, threshold', () => { +describe.skip('Detection rules, threshold', () => { const expectedUrls = newThresholdRule.referenceUrls.join(''); const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join(''); const expectedTags = newThresholdRule.tags.join(''); @@ -88,16 +89,13 @@ describe('Detection rules, threshold', () => { const rule = { ...newThresholdRule }; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(newThresholdRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - afterEach(() => { - deleteTimeline(rule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new threshold rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index 2bfe72033135b..d5fba65a70149 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -7,6 +7,8 @@ import { PROVIDER_BADGE } from '../screens/timeline'; import { investigateFirstAlertInTimeline, waitForAlertsPanelToBeLoaded } from '../tasks/alerts'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -14,6 +16,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe('Alerts timeline', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index b755de8efe757..d53b98b6c103d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -38,8 +38,9 @@ import { import { TIMELINE_DESCRIPTION, TIMELINE_QUERY, TIMELINE_TITLE } from '../screens/timeline'; import { goToCaseDetails, goToCreateNewCase } from '../tasks/all_cases'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; -import { deleteCase, openCaseTimeline } from '../tasks/case_details'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { openCaseTimeline } from '../tasks/case_details'; +import { cleanKibana } from '../tasks/common'; import { attachTimeline, backToCases, @@ -47,7 +48,6 @@ import { fillCasesMandatoryfields, } from '../tasks/create_new_case'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { closeTimeline } from '../tasks/timeline'; import { CASES_URL } from '../urls/navigation'; @@ -55,17 +55,12 @@ describe.skip('Cases', () => { const mycase = { ...case1 }; before(() => { + cleanKibana(); createTimeline(case1.timeline).then((response) => { mycase.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - after(() => { - closeTimeline(); - deleteTimeline(mycase.timeline.id!); - deleteCase(); - }); - it('Creates a new case with timeline and opens the timeline', () => { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts index 1d580b93af235..c41b79ef33653 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts @@ -22,12 +22,13 @@ import { fillServiceNowConnectorOptions, } from '../tasks/create_new_case'; import { goToCreateNewCase } from '../tasks/all_cases'; -import { deleteCase } from '../tasks/case_details'; import { CASES_URL } from '../urls/navigation'; import { CONNECTOR_CARD_DETAILS, CONNECTOR_TITLE } from '../screens/case_details'; +import { cleanKibana } from '../tasks/common'; describe('Cases connector incident fields', () => { before(() => { + cleanKibana(); cy.intercept('GET', '/api/cases/configure/connectors/_find', mockConnectorsResponse); cy.intercept('POST', `/api/actions/action/${connectorIds.jira}/_execute`, (req) => { const response = @@ -45,10 +46,6 @@ describe('Cases connector incident fields', () => { }); }); - after(() => { - deleteCase(); - }); - it('Correct incident fields show when connector is changed', () => { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index acb56d1f24668..8bd9f5b09f2c8 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -8,6 +8,7 @@ import { serviceNowConnector } from '../objects/case'; import { SERVICE_NOW_MAPPING, TOASTER } from '../screens/configure_cases'; import { goToEditExternalConnection } from '../tasks/all_cases'; +import { cleanKibana } from '../tasks/common'; import { addServiceNowConnector, openAddNewConnectorOption, @@ -38,6 +39,7 @@ describe('Cases connectors', () => { version: 'WzEwNCwxXQ==', }; before(() => { + cleanKibana(); cy.intercept('POST', '/api/actions/action').as('createConnector'); cy.intercept('POST', '/api/cases/configure', (req) => { const connector = req.body.connector; diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index 664de967b9aff..f7a19fa281bee 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -33,6 +33,7 @@ import { clearSearchBar, kqlSearch } from '../tasks/security_header'; import { HOSTS_URL } from '../urls/navigation'; import { resetFields } from '../tasks/timeline'; +import { cleanKibana } from '../tasks/common'; const defaultHeadersInDefaultEcsCategory = [ { id: '@timestamp' }, @@ -44,9 +45,10 @@ const defaultHeadersInDefaultEcsCategory = [ { id: 'destination.ip' }, ]; -describe('Events Viewer', () => { +describe.skip('Events Viewer', () => { context('Fields rendering', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -73,6 +75,7 @@ describe('Events Viewer', () => { context('Events viewer query modal', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -91,6 +94,7 @@ describe('Events Viewer', () => { context('Events viewer fields behaviour', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -122,6 +126,7 @@ describe('Events Viewer', () => { context('Events behaviour', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); @@ -144,6 +149,7 @@ describe('Events Viewer', () => { context('Events columns', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); cy.scrollTo('bottom'); diff --git a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index cb8949402b986..d99981b42d049 100644 --- a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -16,6 +16,7 @@ import { FIELDS_BROWSER_SELECTED_CATEGORY_COUNT, FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT, } from '../screens/fields_browser'; +import { cleanKibana } from '../tasks/common'; import { addsHostGeoCityNameToTimeline, @@ -47,6 +48,7 @@ const defaultHeaders = [ describe('Fields Browser', () => { context.skip('Fields Browser rendering', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); @@ -110,6 +112,7 @@ describe('Fields Browser', () => { context.skip('Editing the timeline', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts index b84b668a28502..6321be1e26151 100644 --- a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts @@ -9,6 +9,7 @@ import { INSPECT_MODAL, INSPECT_NETWORK_BUTTONS_IN_SECURITY, } from '../screens/inspect'; +import { cleanKibana } from '../tasks/common'; import { closesModal, openStatsAndTables } from '../tasks/inspect'; import { loginAndWaitForPage } from '../tasks/login'; @@ -20,6 +21,7 @@ import { HOSTS_URL, NETWORK_URL } from '../urls/navigation'; describe('Inspect', () => { context('Hosts stats and tables', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); }); afterEach(() => { @@ -36,6 +38,7 @@ describe('Inspect', () => { context('Network stats and tables', () => { before(() => { + cleanKibana(); loginAndWaitForPage(NETWORK_URL); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts index 7bdc461a7c73d..0d3890a5292e4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts @@ -5,6 +5,7 @@ */ import { KQL_INPUT } from '../screens/security_header'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -25,6 +26,10 @@ import { } from '../urls/ml_conditional_links'; describe('ml conditional links', () => { + before(() => { + cleanKibana(); + }); + it('sets the KQL from a single IP with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); cy.get(KQL_INPUT) diff --git a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts index 792eee3660429..c8e9af98fe6fd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts @@ -36,9 +36,11 @@ import { OVERVIEW_PAGE, TIMELINES_PAGE, } from '../screens/kibana_navigation'; +import { cleanKibana } from '../tasks/common'; describe('top-level navigation common to all pages in the Security app', () => { before(() => { + cleanKibana(); loginAndWaitForPage(TIMELINES_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index 0d12019adbc99..f72559b9f21e6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -13,8 +13,13 @@ import { OVERVIEW_URL } from '../urls/navigation'; import overviewFixture from '../fixtures/overview_search_strategy.json'; import emptyInstance from '../fixtures/empty_instance.json'; +import { cleanKibana } from '../tasks/common'; describe('Overview Page', () => { + before(() => { + cleanKibana(); + }); + it('Host stats render with correct values', () => { cy.stubSearchStrategyApi(overviewFixture, 'overviewHost'); loginAndWaitForPage(OVERVIEW_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts index d755735982517..2896b2dbc36c6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts @@ -6,6 +6,7 @@ import { PROCESS_NAME_FIELD, UNCOMMON_PROCESSES_TABLE } from '../screens/hosts/uncommon_processes'; import { FIRST_PAGE_SELECTOR, THIRD_PAGE_SELECTOR } from '../screens/pagination'; +import { cleanKibana } from '../tasks/common'; import { waitForAuthenticationsToBeLoaded } from '../tasks/hosts/authentications'; import { openAuthentications, openUncommonProcesses } from '../tasks/hosts/main'; @@ -18,6 +19,7 @@ import { HOSTS_PAGE_TAB_URLS } from '../urls/navigation'; describe('Pagination', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); waitForUncommonProcessesToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts index 15ca92714d4a3..7fcbc10f88b44 100644 --- a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts @@ -11,9 +11,11 @@ import { hostIpFilter } from '../objects/filter'; import { HOSTS_URL } from '../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; +import { cleanKibana } from '../tasks/common'; describe('SearchBar', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts index ead3fa6175107..b441d33d34baf 100644 --- a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts @@ -26,8 +26,13 @@ import { import { openTimelineUsingToggle } from '../tasks/security_main'; import { populateTimeline } from '../tasks/timeline'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; +import { cleanKibana } from '../tasks/common'; describe('Sourcerer', () => { + before(() => { + cleanKibana(); + }); + beforeEach(() => { loginAndWaitForPage(HOSTS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts index 936151d13f920..74bf4f03b0b14 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts @@ -16,12 +16,14 @@ import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { TIMELINE_CASE_ID } from '../objects/case'; import { caseTimeline, timeline } from '../objects/timeline'; import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; describe('attach timeline to case', () => { const myTimeline = { ...timeline }; context('without cases created', () => { before(() => { + cleanKibana(); createTimeline(timeline).then((response) => { myTimeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); @@ -61,13 +63,10 @@ describe('attach timeline to case', () => { context('with cases created', () => { before(() => { + cleanKibana(); esArchiverLoad('case_and_timeline'); }); - after(() => { - esArchiverUnload('case_and_timeline'); - }); - it('attach timeline to an existing case', () => { loginAndWaitForTimeline(caseTimeline.id!); attachTimelineToExistingCase(); @@ -81,6 +80,7 @@ describe('attach timeline to case', () => { }](${origin}/app/security/timelines?timeline=(id:%27${caseTimeline.id!}%27,isOpen:!t))` ); }); + esArchiverUnload('case_and_timeline'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts index 1fb751f344fd3..5d44c057c7383 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts @@ -25,7 +25,8 @@ import { TIMELINES_NOTES_COUNT, TIMELINES_FAVORITE, } from '../screens/timelines'; -import { deleteTimeline, getTimelineById } from '../tasks/api_calls/timelines'; +import { getTimelineById } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -50,8 +51,8 @@ import { OVERVIEW_URL } from '../urls/navigation'; describe.skip('Timelines', () => { let timelineId: string; - after(() => { - if (timelineId) deleteTimeline(timelineId); + before(() => { + cleanKibana(); }); it('Creates a timeline', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts index abf6ca38844e2..a103586e007e4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts @@ -23,11 +23,13 @@ import { openTimelineUsingToggle } from '../tasks/security_main'; import { closeTimeline, createNewTimeline } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; +import { cleanKibana } from '../tasks/common'; // FLAKY: https://github.com/elastic/kibana/issues/85098 // FLAKY: https://github.com/elastic/kibana/issues/62060 describe.skip('timeline data providers', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts index 33e8cc40b1239..acf245251d7e1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts @@ -5,6 +5,7 @@ */ import { TIMELINE_FLYOUT_HEADER, TIMELINE_DATA_PROVIDERS } from '../screens/timeline'; +import { cleanKibana } from '../tasks/common'; import { dragFirstHostToTimeline, waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; import { loginAndWaitForPage } from '../tasks/login'; @@ -14,6 +15,7 @@ import { HOSTS_URL } from '../urls/navigation'; describe('timeline flyout button', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index d518f9e42f21f..8b84ae7815452 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -4,28 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { reload } from '../tasks/common'; +import { cleanKibana, reload } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { HOSTS_URL } from '../urls/navigation'; import { openEvents } from '../tasks/hosts/main'; import { DRAGGABLE_HEADER } from '../screens/timeline'; import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; -import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events'; -import { removeColumn, resetFields } from '../tasks/timeline'; +import { waitsForEventsToBeLoaded } from '../tasks/hosts/events'; +import { removeColumn } from '../tasks/timeline'; // Failing: See https://github.com/elastic/kibana/issues/75794 describe.skip('persistent timeline', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); }); - afterEach(() => { - openEventsViewerFieldsBrowser(); - resetFields(); - }); - it('persist the deletion of a column', () => { cy.get(DRAGGABLE_HEADER).then((header) => { const currentNumberOfTimelineColumns = header.length; diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts index 814fcee2b0c5f..52274329034b1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts @@ -5,6 +5,7 @@ */ import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -12,8 +13,9 @@ import { executeTimelineKQL } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; -describe('timeline search or filter KQL bar', () => { +describe.skip('timeline search or filter KQL bar', () => { beforeEach(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts index fc7125288b743..f1aaa4ab8b980 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts @@ -23,6 +23,7 @@ import { TIMELINES_NOTES_COUNT, TIMELINES_FAVORITE, } from '../screens/timelines'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -44,6 +45,7 @@ import { OVERVIEW_URL } from '../urls/navigation'; describe('Timeline Templates', () => { before(() => { + cleanKibana(); cy.intercept('PATCH', '/api/timeline').as('timeline'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts index 8e1dc0f8aa1ee..015c0fc80e292 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts @@ -12,16 +12,15 @@ import { } from '../objects/timeline'; import { TIMELINE_TEMPLATES_URL } from '../urls/navigation'; -import { - createTimelineTemplate, - deleteTimeline as deleteTimelineTemplate, -} from '../tasks/api_calls/timelines'; +import { createTimelineTemplate } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { let templateResponse: Cypress.Response; let templateId: string; before(() => { + cleanKibana(); cy.intercept('POST', 'api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimelineTemplate(timelineTemplate).then((response) => { templateResponse = response; @@ -29,10 +28,6 @@ describe('Export timelines', () => { }); }); - after(() => { - deleteTimelineTemplate(templateId); - }); - it('Exports a custom timeline template', () => { loginAndWaitForPageWithoutDateRange(TIMELINE_TEMPLATES_URL); exportTimeline(templateId!); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts index 80693f3dd8074..9a03936c3683f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts @@ -11,7 +11,8 @@ import { TIMESTAMP_HEADER_FIELD, TIMESTAMP_TOGGLE_FIELD, } from '../screens/timeline'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -28,19 +29,14 @@ import { import { HOSTS_URL } from '../urls/navigation'; describe('toggle column in timeline', () => { - let timelineId: string; before(() => { + cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimeline(timeline).then((response) => { - timelineId = response.body.data.persistTimeline.timeline.savedObjectId; loginAndWaitForPage(HOSTS_URL); }); }); - after(() => { - deleteTimeline(timelineId); - }); - beforeEach(() => { openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts index 8e3cd3dfbde7a..064d98bf01b24 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -8,13 +8,15 @@ import { exportTimeline, waitForTimelinesPanelToBeLoaded } from '../tasks/timeli import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { TIMELINES_URL } from '../urls/navigation'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { createTimeline } from '../tasks/api_calls/timelines'; import { expectedExportedTimeline, timeline } from '../objects/timeline'; +import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { let timelineResponse: Cypress.Response; let timelineId: string; before(() => { + cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimeline(timeline).then((response) => { timelineResponse = response; @@ -22,10 +24,6 @@ describe('Export timelines', () => { }); }); - after(() => { - deleteTimeline(timelineId); - }); - it('Exports a custom timeline', () => { loginAndWaitForPageWithoutDateRange(TIMELINES_URL); waitForTimelinesPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts index 5b42897b065e3..58ef4cd2d96ba 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts @@ -12,6 +12,7 @@ import { DATE_PICKER_START_DATE_POPOVER_BUTTON, DATE_PICKER_END_DATE_POPOVER_BUTTON, } from '../screens/date_picker'; +import { cleanKibana } from '../tasks/common'; const ABSOLUTE_DATE = { endTime: '2019-08-01T20:33:29.186Z', @@ -19,6 +20,10 @@ const ABSOLUTE_DATE = { }; describe('URL compatibility', () => { + before(() => { + cleanKibana(); + }); + it('Redirects to Detection alerts from old Detections URL', () => { loginAndWaitForPage(DETECTIONS); diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index b911fc5b81f98..a13ae62eb7f84 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -38,6 +38,7 @@ import { ABSOLUTE_DATE_RANGE } from '../urls/state'; import { timeline } from '../objects/timeline'; import { TIMELINE } from '../screens/create_new_case'; +import { cleanKibana } from '../tasks/common'; const ABSOLUTE_DATE = { endTime: '2019-08-01T20:33:29.186Z', @@ -50,6 +51,10 @@ const ABSOLUTE_DATE = { // FLAKY: https://github.com/elastic/kibana/issues/61612 describe.skip('url state', () => { + before(() => { + cleanKibana(); + }); + it('sets the global start and end dates from the url', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url); cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).should( diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts index 341ca31715356..0b1ab12f37c91 100644 --- a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts @@ -26,10 +26,17 @@ import { exportValueList, } from '../tasks/lists'; import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW, VALUE_LISTS_MODAL_ACTIVATOR } from '../screens/lists'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; describe('value lists', () => { describe('management modal', () => { + before(() => { + cleanKibana(); + }); + beforeEach(() => { + removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); @@ -39,6 +46,7 @@ describe('value lists', () => { }); afterEach(() => { + removeSignalsIndex(); deleteAllValueListsFromUI(); }); diff --git a/x-pack/plugins/security_solution/cypress/support/index.js b/x-pack/plugins/security_solution/cypress/support/index.js index d6abecad74b91..0d5538758413b 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.js +++ b/x-pack/plugins/security_solution/cypress/support/index.js @@ -21,16 +21,13 @@ // Import commands.js using ES2015 syntax: import './commands'; -import 'cypress-promise/register'; Cypress.Cookies.defaults({ preserve: 'sid', }); -Cypress.on('uncaught:exception', (err) => { - if (err.message.includes('ResizeObserver loop limit exceeded')) { - return false; - } +Cypress.on('uncaught:exception', () => { + return false; }); // Alternatively you can use CommonJS syntax: diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index edf493d82c843..34fc00428d2cd 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -36,9 +36,13 @@ export const deleteCustomRule = () => { }; export const removeSignalsIndex = () => { - cy.request({ - method: 'DELETE', - url: `api/detection_engine/index`, - headers: { 'kbn-xsrf': 'delete-signals' }, + cy.request({ url: '/api/detection_engine/index', failOnStatusCode: false }).then((response) => { + if (response.status === 200) { + cy.request({ + method: 'DELETE', + url: `api/detection_engine/index`, + headers: { 'kbn-xsrf': 'delete-signals' }, + }); + } }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index bb009f34b02d6..fbd4c4145e8ff 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { esArchiverLoadEmptyKibana } from './es_archiver'; + const primaryButton = 0; /** @@ -60,3 +62,8 @@ export const reload = (afterReload: () => void) => { cy.contains('a', 'Security'); afterReload(); }; + +export const cleanKibana = () => { + cy.exec(`curl -XDELETE "${Cypress.env('ELASTICSEARCH_URL')}/.kibana\*" -k`); + esArchiverLoadEmptyKibana(); +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts index ea721d0257d45..00a5e84e73448 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts @@ -3,40 +3,65 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { v4 as generateUUID } from 'uuid'; // @ts-ignore import minimist from 'minimist'; import { KbnClient, ToolingLog } from '@kbn/dev-utils'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../lists/common/constants'; -import { TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants'; -import { ExceptionListItemSchema } from '../../../../lists/common/schemas/response'; +import bluebird from 'bluebird'; +import { basename } from 'path'; +import { TRUSTED_APPS_CREATE_API, TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants'; +import { NewTrustedApp, OperatingSystem, TrustedApp } from '../../../common/endpoint/types'; -interface RunOptions { - count?: number; -} - -const logger = new ToolingLog({ level: 'info', writeTo: process.stdout }); +const defaultLogger = new ToolingLog({ level: 'info', writeTo: process.stdout }); const separator = '----------------------------------------'; export const cli = async () => { - const options: RunOptions = minimist(process.argv.slice(2), { + const cliDefaults = { + string: ['kibana'], default: { count: 10, + kibana: 'http://elastic:changeme@localhost:5601', }, - }) as RunOptions; - logger.write(`${separator} + }; + const options: RunOptions = minimist(process.argv.slice(2), cliDefaults); + + if ('help' in options) { + defaultLogger.write(` +node ${basename(process.argv[1])} [options] + +Options:${Object.keys(cliDefaults.default).reduce((out, option) => { + // @ts-ignore + return `${out}\n --${option}=${cliDefaults.default[option]}`; + }, '')} +`); + return; + } + + const runLogger = createRunLogger(); + + defaultLogger.write(`${separator} Loading ${options.count} Trusted App Entries`); - await run(options); - logger.write(`Done! + await run({ + ...options, + logger: runLogger, + }); + defaultLogger.write(` +Done! ${separator}`); }; -export const run: (options?: RunOptions) => Promise = async ({ +interface RunOptions { + count?: number; + kibana?: string; + logger?: ToolingLog; +} +export const run: (options?: RunOptions) => Promise = async ({ count = 10, + kibana = 'http://elastic:changeme@localhost:5601', + logger = defaultLogger, }: RunOptions = {}) => { const kbnClient = new KbnClient({ log: logger, - url: 'http://elastic:changeme@localhost:5601', + url: kibana, }); // touch the Trusted Apps List so it can be created @@ -45,46 +70,46 @@ export const run: (options?: RunOptions) => Promise = path: TRUSTED_APPS_LIST_API, }); - return Promise.all( - Array.from({ length: count }, () => { - return kbnClient - .request({ + return bluebird.map( + Array.from({ length: count }), + () => + kbnClient + .request({ method: 'POST', - path: '/api/exception_lists/items', + path: TRUSTED_APPS_CREATE_API, body: generateTrustedAppEntry(), }) - .then((item) => (item as unknown) as ExceptionListItemSchema); - }) + .then(({ data }) => { + logger.write(data.id); + return data; + }), + { concurrency: 10 } ); }; interface GenerateTrustedAppEntryOptions { - os?: 'windows' | 'macos' | 'linux'; + os?: OperatingSystem; name?: string; } - const generateTrustedAppEntry: (options?: GenerateTrustedAppEntryOptions) => object = ({ - os = 'windows', - name = `Sample Endpoint Trusted App Entry ${Date.now()}`, -} = {}) => { + os = randomOperatingSystem(), + name = randomName(), +} = {}): NewTrustedApp => { return { - list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, - item_id: `generator_endpoint_trusted_apps_${generateUUID()}`, - os_types: [os], - tags: ['user added string for a tag', 'malware'], - type: 'simple', - description: 'This is a sample agnostic endpoint trusted app entry', + description: `Generator says we trust ${name}`, name, - namespace_type: 'agnostic', + os, entries: [ { - field: 'actingProcess.file.signer', + // @ts-ignore + field: 'process.hash.*', operator: 'included', type: 'match', - value: 'Elastic, N.V.', + value: '1234234659af249ddf3e40864e9fb241', }, { - field: 'actingProcess.file.path', + // @ts-ignore + field: 'process.executable.caseless', operator: 'included', type: 'match', value: '/one/two/three', @@ -92,3 +117,72 @@ const generateTrustedAppEntry: (options?: GenerateTrustedAppEntryOptions) => obj ], }; }; + +const randomN = (max: number): number => Math.floor(Math.random() * max); + +const randomName = (() => { + const names = [ + 'Symantec Endpoint Security', + 'Bitdefender GravityZone', + 'Malwarebytes', + 'Sophos Intercept X', + 'Webroot Business Endpoint Protection', + 'ESET Endpoint Security', + 'FortiClient', + 'Kaspersky Endpoint Security', + 'Trend Micro Apex One', + 'CylancePROTECT', + 'VIPRE', + 'Norton', + 'McAfee Endpoint Security', + 'AVG AntiVirus', + 'CrowdStrike Falcon', + 'Avast Business Antivirus', + 'Avira Antivirus', + 'Cisco AMP for Endpoints', + 'Eset Endpoint Antivirus', + 'VMware Carbon Black', + 'Palo Alto Networks Traps', + 'Trend Micro', + 'SentinelOne', + 'Panda Security for Desktops', + 'Microsoft Defender ATP', + ]; + const count = names.length; + + return () => names[randomN(count)]; +})(); + +const randomOperatingSystem = (() => { + const osKeys = Object.keys(OperatingSystem) as Array; + const count = osKeys.length; + + return () => OperatingSystem[osKeys[randomN(count)]]; +})(); + +const createRunLogger = () => { + let groupCount = 1; + let itemCount = 0; + + return new ToolingLog({ + level: 'info', + writeTo: { + write: (msg: string) => { + process.stdout.write('.'); + itemCount++; + + if (itemCount === 5) { + itemCount = 0; + + if (groupCount === 5) { + process.stdout.write('\n'); + groupCount = 1; + } else { + process.stdout.write(' '); + groupCount++; + } + } + }, + }, + }); +}; diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index a8999fd065e75..e520a7c8f8e04 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -52,12 +52,14 @@ export default function ({ getService }: FtrProviderContext) { await deleteComposableIndexTemplate(name); }; - const assertDataStreamStorageSizeExists = (storageSize: string) => { - // Storage size of a document doesn't like it would be deterministic (could vary depending + const assertDataStreamStorageSizeExists = (storageSize: string, storageSizeBytes: number) => { + // Storage size of a document doesn't look like it would be deterministic (could vary depending // on how ES, Lucene, and the file system interact), so we'll just assert its presence and // type. expect(storageSize).to.be.ok(); expect(typeof storageSize).to.be('string'); + expect(storageSizeBytes).to.be.ok(); + expect(typeof storageSizeBytes).to.be('number'); }; describe('Data streams', function () { @@ -120,10 +122,9 @@ export default function ({ getService }: FtrProviderContext) { expect(testDataStream).to.be.ok(); // ES determines these values so we'll just echo them back. - const { name: indexName, uuid } = testDataStream!.indices[0]; - const { storageSize, ...dataStreamWithoutStorageSize } = testDataStream!; - assertDataStreamStorageSizeExists(storageSize); + const { storageSize, storageSizeBytes, ...dataStreamWithoutStorageSize } = testDataStream!; + assertDataStreamStorageSizeExists(storageSize, storageSizeBytes); expect(dataStreamWithoutStorageSize).to.eql({ name: testDataStreamName, @@ -153,8 +154,8 @@ export default function ({ getService }: FtrProviderContext) { // ES determines these values so we'll just echo them back. const { name: indexName, uuid } = dataStream.indices[0]; - const { storageSize, ...dataStreamWithoutStorageSize } = dataStream; - assertDataStreamStorageSizeExists(storageSize); + const { storageSize, storageSizeBytes, ...dataStreamWithoutStorageSize } = dataStream; + assertDataStreamStorageSizeExists(storageSize, storageSizeBytes); expect(dataStreamWithoutStorageSize).to.eql({ name: testDataStreamName, diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts index a11f8ca3947c5..63a20cdaca234 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts @@ -235,5 +235,42 @@ export default ({ getService }: FtrProviderContext) => { expect(body.message).to.eql('Forbidden'); }); }); + + describe('GetDataFrameAnalyticsIdMap', () => { + it('should return a map of objects leading up to analytics job id', async () => { + const { body } = await supertest + .get(`/api/ml/data_frame/analytics/map/${jobId}_1`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body).to.have.keys('elements', 'details', 'error'); + // Index node, 2 job nodes (with same source index), and 2 edge nodes to connect them + expect(body.elements.length).to.eql(5); + + for (const detailsId in body.details) { + if (detailsId.includes('analytics')) { + expect(body.details[detailsId]).to.have.keys('id', 'source', 'dest'); + } else if (detailsId.includes('index')) { + const indexId = detailsId.replace('-index', ''); + expect(body.details[detailsId][indexId]).to.have.keys('aliases', 'mappings'); + } + } + }); + + it('should return empty results and an error message if the job does not exist', async () => { + const { body } = await supertest + .get(`/api/ml/data_frame/analytics/map/${jobId}_fake`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.elements.length).to.eql(0); + expect(body.details).to.eql({}); + expect(body.error).to.eql(`No known job with id '${jobId}_fake'`); + + expect(body).to.have.keys('elements', 'details', 'error'); + }); + }); }); }; diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index a1a1a3916ef7f..1cd32c55509ed 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -16,7 +16,6 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr const config = getService('config'); const esArchiver = getService('esArchiver'); - await esArchiver.load('empty_kibana'); await esArchiver.load('auditbeat'); await withProcRunner(log, async (procs) => { diff --git a/yarn.lock b/yarn.lock index 032d6c4238567..4dbfa610be6c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10798,10 +10798,10 @@ cypress-promise@^1.1.0: resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA== -cypress@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.0.1.tgz#86857ca2f527c3723575737deab42fd8f2a209df" - integrity sha512-3xtQZ0YM65soLgKQUgn2wg2IbWsM6A2yBg6L4RF31mZHr5LNKdO2/9sgiwxEVMKu2C2m6+IQ75zHP41kZP5rPg== +cypress@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.1.0.tgz#af2596cb110aa98eaf75fef3d8ab379ca0ff2413" + integrity sha512-uQnSxRcZ6hkf9R5cr8KpRBTzN88QZwLIImbf5DWa5RIxH6o5Gpff58EcjiYhAR8/8p9SGv7O6SRygq4H+k0Qpw== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5"