diff --git a/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx b/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx index 47c1d8b478c2..e13788b8f7f0 100644 --- a/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx @@ -205,6 +205,10 @@ export const useDiscoverInTimelineActions = ( } else { // If no saved search exists. Create a new saved search instance and associate it with the timeline. try { + // Make sure we're not creating a saved search while a previous creation call is in progress + if (status !== 'idle') { + return; + } dispatch( timelineActions.startTimelineSaving({ id: TimelineId.active, @@ -218,7 +222,7 @@ export const useDiscoverInTimelineActions = ( const responseIsEmpty = !response || !response?.id; if (responseIsEmpty) { throw new Error('Response is empty'); - } else if (!savedSearchId && !responseIsEmpty && status !== 'loading') { + } else if (!savedSearchId && !responseIsEmpty) { dispatch( timelineActions.updateSavedSearchId({ id: TimelineId.active, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts index 351cb601c481..903ca0c087ed 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts @@ -6,9 +6,11 @@ */ import { visitWithTimeRange } from '../../../../tasks/navigation'; -import { TIMELINE_TITLE } from '../../../../screens/timeline'; import { BASIC_TABLE_LOADING } from '../../../../screens/common'; -import { goToSavedObjectSettings } from '../../../../tasks/stack_management'; +import { + clickSavedObjectTagsFilter, + goToSavedObjectSettings, +} from '../../../../tasks/stack_management'; import { navigateFromKibanaCollapsibleTo, openKibanaNavigation, @@ -31,9 +33,9 @@ import { import { updateDateRangeInLocalDatePickers } from '../../../../tasks/date_picker'; import { login } from '../../../../tasks/login'; import { - addDescriptionToTimeline, addNameToTimelineAndSave, createNewTimeline, + createTimelineOptionsPopoverBottomBar, goToEsqlTab, openTimelineById, openTimelineFromSettings, @@ -43,15 +45,10 @@ import { STACK_MANAGEMENT_PAGE } from '../../../../screens/kibana_navigation'; import { GET_SAVED_OBJECTS_TAGS_OPTION, SAVED_OBJECTS_ROW_TITLES, - SAVED_OBJECTS_TAGS_FILTER, } from '../../../../screens/common/stack_management'; const INITIAL_START_DATE = 'Jan 18, 2021 @ 20:33:29.186'; const INITIAL_END_DATE = 'Jan 19, 2024 @ 20:33:29.186'; -const SAVED_SEARCH_UPDATE_REQ = 'SAVED_SEARCH_UPDATE_REQ'; -const SAVED_SEARCH_UPDATE_WITH_DESCRIPTION = 'SAVED_SEARCH_UPDATE_WITH_DESCRIPTION'; -const SAVED_SEARCH_CREATE_REQ = 'SAVED_SEARCH_CREATE_REQ'; -const SAVED_SEARCH_GET_REQ = 'SAVED_SEARCH_GET_REQ'; const TIMELINE_REQ_WITH_SAVED_SEARCH = 'TIMELINE_REQ_WITH_SAVED_SEARCH'; const TIMELINE_PATCH_REQ = 'TIMELINE_PATCH_REQ'; @@ -59,59 +56,37 @@ const TIMELINE_RESPONSE_SAVED_OBJECT_ID_PATH = 'response.body.data.persistTimeline.timeline.savedObjectId'; const esqlQuery = 'from auditbeat-* | where ecs.version == "8.0.0"'; -// FLAKY: https://github.com/elastic/kibana/issues/168745 -describe.skip( +const handleIntercepts = () => { + cy.intercept('PATCH', '/api/timeline', (req) => { + if (req.body.hasOwnProperty('timeline') && req.body.timeline.savedSearchId === null) { + req.alias = TIMELINE_PATCH_REQ; + } + }); + cy.intercept('PATCH', '/api/timeline', (req) => { + if (req.body.hasOwnProperty('timeline') && req.body.timeline.savedSearchId !== null) { + req.alias = TIMELINE_REQ_WITH_SAVED_SEARCH; + } + }); +}; + +describe( 'Discover Timeline State Integration', { tags: ['@ess', '@brokenInServerless'], - // ESQL and test involving STACK_MANAGEMENT_PAGE are broken in serverless }, () => { beforeEach(() => { - cy.intercept('PATCH', '/api/timeline', (req) => { - if (req.body.hasOwnProperty('timeline') && req.body.timeline.savedSearchId === null) { - req.alias = TIMELINE_PATCH_REQ; - } - }); - cy.intercept('PATCH', '/api/timeline', (req) => { - if (req.body.hasOwnProperty('timeline') && req.body.timeline.savedSearchId !== null) { - req.alias = TIMELINE_REQ_WITH_SAVED_SEARCH; - } - }); - cy.intercept('POST', '/api/content_management/rpc/get', (req) => { - if (req.body.hasOwnProperty('contentTypeId') && req.body.contentTypeId === 'search') { - req.alias = SAVED_SEARCH_GET_REQ; - } - }); - cy.intercept('POST', '/api/content_management/rpc/create', (req) => { - if (req.body.hasOwnProperty('contentTypeId') && req.body.contentTypeId === 'search') { - req.alias = SAVED_SEARCH_CREATE_REQ; - } - }); - - cy.intercept('POST', '/api/content_management/rpc/update', (req) => { - if (req.body.hasOwnProperty('contentTypeId') && req.body.contentTypeId === 'search') { - req.alias = SAVED_SEARCH_UPDATE_REQ; - } - }); - cy.intercept('POST', '/api/content_management/rpc/update', (req) => { - if ( - req.body.hasOwnProperty('data') && - req.body.data.hasOwnProperty('description') && - req.body.data.description.length > 0 - ) { - req.alias = SAVED_SEARCH_UPDATE_WITH_DESCRIPTION; - } - }); login(); visitWithTimeRange(ALERTS_URL); - createNewTimeline(); + createTimelineOptionsPopoverBottomBar(); goToEsqlTab(); updateDateRangeInLocalDatePickers(DISCOVER_CONTAINER, INITIAL_START_DATE, INITIAL_END_DATE); + handleIntercepts(); }); - context('save/restore', () => { - it('should be able create an empty timeline with default discover state', () => { + + describe('ESQL tab state', () => { + it('should be able create an empty timeline with default esql tab state', () => { addNameToTimelineAndSave('Timerange timeline'); createNewTimeline(); goToEsqlTab(); @@ -120,7 +95,7 @@ describe.skip( `Last 15 minutes` ); }); - it('should save/restore discover dataview/timerange/filter/query/columns when saving/resoring timeline', () => { + it('should save/restore esql tab dataview/timerange/filter/query/columns when saving/resoring timeline', () => { const timelineSuffix = Date.now(); const timelineName = `DataView timeline-${timelineSuffix}`; const column1 = 'event.category'; @@ -151,7 +126,7 @@ describe.skip( ); }); }); - it('should save/restore discover dataview/timerange/filter/query/columns when timeline is opened via url', () => { + it('should save/restore esql tab dataview/timerange/filter/query/columns when timeline is opened via url', () => { const timelineSuffix = Date.now(); const timelineName = `DataView timeline-${timelineSuffix}`; const column1 = 'event.category'; @@ -177,7 +152,7 @@ describe.skip( ); }); }); - it('should save/restore discover ES|QL when saving timeline', () => { + it('should save/restore esql tab ES|QL when saving timeline', () => { const timelineSuffix = Date.now(); const timelineName = `ES|QL timeline-${timelineSuffix}`; addNameToTimelineAndSave(timelineName); @@ -196,60 +171,51 @@ describe.skip( }); }); }); - /* - * skipping because it is @brokenInServerless and this cypress tag was somehow not working - * so skipping this test both in ess and serverless. - * - * Raised issue: https://github.com/elastic/kibana/issues/165913 - * - * */ - context.skip('saved search tags', () => { - it('should save discover saved search with `Security Solution` tag', () => { + + describe('Discover saved search state for ESQL tab', () => { + it('should save esql tab saved search with `Security Solution` tag', () => { const timelineSuffix = Date.now(); const timelineName = `SavedObject timeline-${timelineSuffix}`; addDiscoverEsqlQuery(esqlQuery); addNameToTimelineAndSave(timelineName); cy.wait(`@${TIMELINE_REQ_WITH_SAVED_SEARCH}`); + cy.get(LOADING_INDICATOR).should('not.exist'); openKibanaNavigation(); navigateFromKibanaCollapsibleTo(STACK_MANAGEMENT_PAGE); cy.get(LOADING_INDICATOR).should('not.exist'); goToSavedObjectSettings(); cy.get(LOADING_INDICATOR).should('not.exist'); - cy.get(SAVED_OBJECTS_TAGS_FILTER).trigger('click'); + clickSavedObjectTagsFilter(); cy.get(GET_SAVED_OBJECTS_TAGS_OPTION('Security_Solution')).trigger('click'); cy.get(BASIC_TABLE_LOADING).should('not.exist'); cy.get(SAVED_OBJECTS_ROW_TITLES).should( 'contain.text', - `Saved Search for timeline - ${timelineName}` + `Saved search for timeline - ${timelineName}` ); }); - }); - context('saved search', () => { + it('should rename the saved search on timeline rename', () => { - const timelineSuffix = Date.now(); - const timelineName = `Rename timeline-${timelineSuffix}`; + const initialTimelineSuffix = Date.now(); + const initialTimelineName = `Timeline-${initialTimelineSuffix}`; addDiscoverEsqlQuery(esqlQuery); - - addNameToTimelineAndSave(timelineName); - cy.wait(`@${TIMELINE_PATCH_REQ}`) - .its(TIMELINE_RESPONSE_SAVED_OBJECT_ID_PATH) - .then((timelineId) => { - cy.wait(`@${SAVED_SEARCH_UPDATE_REQ}`); - cy.wait(`@${TIMELINE_REQ_WITH_SAVED_SEARCH}`); - // create an empty timeline - createNewTimeline(); - // switch to old timeline - openTimelineFromSettings(); - openTimelineById(timelineId); - cy.get(TIMELINE_TITLE).should('have.text', timelineName); - const timelineDesc = 'Timeline Description with Saved Seach'; - addDescriptionToTimeline(timelineDesc); - cy.wait(`@${SAVED_SEARCH_UPDATE_WITH_DESCRIPTION}`, { - timeout: 30000, - }).then((interception) => { - expect(interception.request.body.data.description).eq(timelineDesc); - }); - }); + addNameToTimelineAndSave(initialTimelineName); + cy.get(LOADING_INDICATOR).should('not.exist'); + const timelineSuffix = Date.now(); + const renamedTimelineName = `Rename timeline-${timelineSuffix}`; + addNameToTimelineAndSave(renamedTimelineName); + cy.wait(`@${TIMELINE_REQ_WITH_SAVED_SEARCH}`); + openKibanaNavigation(); + navigateFromKibanaCollapsibleTo(STACK_MANAGEMENT_PAGE); + cy.get(LOADING_INDICATOR).should('not.exist'); + goToSavedObjectSettings(); + cy.get(LOADING_INDICATOR).should('not.exist'); + clickSavedObjectTagsFilter(); + cy.get(GET_SAVED_OBJECTS_TAGS_OPTION('Security_Solution')).trigger('click'); + cy.get(BASIC_TABLE_LOADING).should('not.exist'); + cy.get(SAVED_OBJECTS_ROW_TITLES).should( + 'contain.text', + `Saved search for timeline - ${renamedTimelineName}` + ); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts index 21a20a7027a1..ff0bbac6866c 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts @@ -85,7 +85,7 @@ export const waitForTableToLoad = () => { export const waitForTabToBeLoaded = (tabId: string) => { recurse( - () => cy.get(tabId).should('be.visible').click(), + () => cy.get(tabId).click(), ($el) => expect($el).to.have.class('euiTab-isSelected'), { delay: 500, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts b/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts index 95fd6d858321..f3609596e288 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts @@ -60,7 +60,7 @@ export const addDiscoverEsqlQuery = (esqlQuery: string) => { selectCurrentDiscoverEsqlQuery(DISCOVER_ESQL_EDITABLE_INPUT); cy.get(DISCOVER_ESQL_EDITABLE_INPUT).type(`${esqlQuery}`); cy.get(DISCOVER_ESQL_EDITABLE_INPUT).blur(); - cy.get(GET_LOCAL_SEARCH_BAR_SUBMIT_BUTTON(DISCOVER_CONTAINER)).realClick(); + cy.get(GET_LOCAL_SEARCH_BAR_SUBMIT_BUTTON(DISCOVER_CONTAINER)).click(); }; export const convertEditorNonBreakingSpaceToSpace = (str: string) => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/stack_management.ts b/x-pack/test/security_solution_cypress/cypress/tasks/stack_management.ts index 4c53443c13ae..9bc0f6360c88 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/stack_management.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/stack_management.ts @@ -5,10 +5,15 @@ * 2.0. */ -import { SAVED_OBJECTS_SETTINGS } from '../screens/common/stack_management'; +import { + SAVED_OBJECTS_SETTINGS, + SAVED_OBJECTS_TAGS_FILTER, +} from '../screens/common/stack_management'; export const goToSavedObjectSettings = () => { - cy.get(SAVED_OBJECTS_SETTINGS).scrollIntoView(); - cy.get(SAVED_OBJECTS_SETTINGS).should('be.visible').focus(); - cy.get(SAVED_OBJECTS_SETTINGS).should('be.visible').click(); + cy.get(SAVED_OBJECTS_SETTINGS).click(); +}; + +export const clickSavedObjectTagsFilter = () => { + cy.get(SAVED_OBJECTS_TAGS_FILTER).trigger('click'); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts b/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts index d8543ec852c1..40ee5c4f2996 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts @@ -288,7 +288,7 @@ export const attachTimelineToExistingCase = () => { const clickIdHoverActionOverflowButton = () => { cy.get(ID_HOVER_ACTION_OVERFLOW_BTN).should('exist'); - cy.get(ID_HOVER_ACTION_OVERFLOW_BTN).click({ force: true }); + cy.get(ID_HOVER_ACTION_OVERFLOW_BTN).click(); }; export const clickIdToggleField = () => { @@ -311,19 +311,31 @@ export const createNewTimeline = () => { }; export const openCreateTimelineOptionsPopover = () => { - cy.get(NEW_TIMELINE_ACTION).filter(':visible').should('be.visible').click(); + cy.get(NEW_TIMELINE_ACTION).filter(':visible').click(); +}; + +export const createTimelineOptionsPopoverBottomBar = () => { + recurse( + () => { + cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click(); + return cy.get(CREATE_NEW_TIMELINE).eq(0); + }, + (sub) => sub.is(':visible') + ); + + cy.get(CREATE_NEW_TIMELINE).eq(0).click(); }; export const createTimelineTemplateOptionsPopoverBottomBar = () => { recurse( () => { - cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').should('be.visible').click(); + cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click(); return cy.get(CREATE_NEW_TIMELINE_TEMPLATE).eq(0); }, (sub) => sub.is(':visible') ); - cy.get(CREATE_NEW_TIMELINE_TEMPLATE).eq(0).should('be.visible').click(); + cy.get(CREATE_NEW_TIMELINE_TEMPLATE).eq(0).click(); }; export const createNewTimelineTemplate = () => { @@ -341,7 +353,7 @@ export const executeTimelineSearch = (query: string) => { }; export const expandFirstTimelineEventDetails = () => { - cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true }); + cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click(); }; /** @@ -375,7 +387,7 @@ export const openTimelineFieldsBrowser = () => { export const openTimelineInspectButton = () => { cy.get(TIMELINE_INSPECT_BUTTON).should('not.be.disabled'); - cy.get(TIMELINE_INSPECT_BUTTON).click({ force: true }); + cy.get(TIMELINE_INSPECT_BUTTON).click(); }; export const openTimelineFromSettings = () => { @@ -417,7 +429,7 @@ export const populateTimeline = () => { const clickTimestampHoverActionOverflowButton = () => { cy.get(TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN).should('exist'); - cy.get(TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN).click({ force: true }); + cy.get(TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN).click(); }; export const clickTimestampToggleField = () => { @@ -425,7 +437,7 @@ export const clickTimestampToggleField = () => { cy.get(TIMESTAMP_TOGGLE_FIELD).should('exist'); - cy.get(TIMESTAMP_TOGGLE_FIELD).click({ force: true }); + cy.get(TIMESTAMP_TOGGLE_FIELD).click(); }; export const removeColumn = (columnName: string) => { @@ -436,7 +448,7 @@ export const removeColumn = (columnName: string) => { }; export const resetFields = () => { - cy.get(RESET_FIELDS).click({ force: true }); + cy.get(RESET_FIELDS).click(); }; export const selectCase = (caseId: string) => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts b/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts index 338017829e9f..019971104973 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts @@ -31,17 +31,15 @@ export const expandNotes = () => { export const importTimeline = (timeline: string) => { cy.get(IMPORT_TIMELINE_BTN).click(); - cy.get(INPUT_FILE).click({ force: true }); + cy.get(INPUT_FILE).click(); cy.get(INPUT_FILE).attachFile(timeline); cy.get(INPUT_FILE).trigger('change'); - cy.get(IMPORT_BTN).last().click({ force: true }); + cy.get(IMPORT_BTN).last().click(); cy.get(INPUT_FILE).should('not.exist'); }; export const openTimeline = (id?: string) => { - cy.get(id ? TIMELINE(id) : TIMELINE_NAME) - .should('be.visible') - .click(); + cy.get(id ? TIMELINE(id) : TIMELINE_NAME).click(); }; export const waitForTimelinesPanelToBeLoaded = () => {