From 07d1ec8d7dc40ba511767d1a2a89fd50a4b1aa04 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 10 Aug 2022 20:49:22 +0200 Subject: [PATCH] [SecuritySolution][Bug] Fix to add empty values to timeline (#138510) Fixes: #118846 Issue: `Add to Timeline` action was not working for empty values. https://user-images.githubusercontent.com/61860752/142180967-972a0438-154e-47c7-b058-be3abc4ac353.mp4 Solution: Empty values can be added in timeline with condition of `NOT EXISTS` and hence on `Add to Timeline` click, data provider is modified to add `NOT EXISTS` condition to the timeline. Please see demo below: https://user-images.githubusercontent.com/7485038/183914875-0b3c7e5f-7e12-40f2-a0c2-8b773434480b.mov --- .../investigate_in_timeline.spec.ts | 34 +++++++++++- .../cypress/screens/timeline.ts | 15 ++++++ .../security_solution/cypress/tasks/alerts.ts | 10 +++- .../cypress/tasks/timeline.ts | 5 ++ .../lib/cell_actions/add_to_timeline.tsx | 53 +++++++++++++------ 5 files changed, 97 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts index 5f52041e75d173..5d8099c84d5a6b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts @@ -6,13 +6,23 @@ */ import { getNewRule } from '../../objects/rule'; -import { PROVIDER_BADGE } from '../../screens/timeline'; +import { + ALERT_TABLE_FILE_NAME_HEADER, + ALERT_TABLE_FILE_NAME_VALUES, + ALERT_TABLE_SEVERITY_VALUES, + PROVIDER_BADGE, +} from '../../screens/timeline'; -import { investigateFirstAlertInTimeline } from '../../tasks/alerts'; +import { + addAlertPropertyToTimeline, + investigateFirstAlertInTimeline, + scrollAlertTableColumnIntoView, +} from '../../tasks/alerts'; import { createCustomRuleEnabled } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { login, visit } from '../../tasks/login'; +import { openActiveTimeline } from '../../tasks/timeline'; import { ALERTS_URL } from '../../urls/navigation'; @@ -37,4 +47,24 @@ describe('Alerts timeline', () => { cy.get(PROVIDER_BADGE).filter(':visible').should('have.text', eventId); }); }); + + it('Add a non-empty property to default timeline', () => { + cy.get(ALERT_TABLE_SEVERITY_VALUES) + .first() + .invoke('text') + .then((severityVal) => { + addAlertPropertyToTimeline(ALERT_TABLE_SEVERITY_VALUES, 0); + openActiveTimeline(); + cy.get(PROVIDER_BADGE) + .first() + .should('have.text', `kibana.alert.severity: "${severityVal}"`); + }); + }); + + it('Add an empty property to default timeline', () => { + scrollAlertTableColumnIntoView(ALERT_TABLE_FILE_NAME_HEADER); + addAlertPropertyToTimeline(ALERT_TABLE_FILE_NAME_VALUES, 0); + openActiveTimeline(); + cy.get(PROVIDER_BADGE).first().should('have.text', 'NOT file.name exists'); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 231c24b3127f3c..1c6e41da02c1d8 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -267,3 +267,18 @@ export const USER_KPI = '[data-test-subj="siem-timeline-user-kpi"]'; export const EDIT_TIMELINE_BTN = '[data-test-subj="save-timeline-button-icon"]'; export const EDIT_TIMELINE_TOOLTIP = '[data-test-subj="save-timeline-btn-tooltip"]'; + +export const ALERT_TABLE_SEVERITY_VALUES = + '[data-test-subj="formatted-field-kibana.alert.severity"]'; + +export const ALERT_TABLE_FILE_NAME_HEADER = '[data-gridcell-column-id="file.name"]'; + +export const ALERT_TABLE_FILE_NAME_VALUES = + '[data-gridcell-column-id="file.name"][data-test-subj="dataGridRowCell"]'; // empty column for the test data + +export const ALERT_TABLE_CELL_ACTIONS_ADD_TO_TIMELINE = '[data-test-subj="add-to-timeline"]'; + +export const ACTIVE_TIMELINE_BOTTOM_BAR = + '[data-test-subj="flyoutBottomBar"] .active-timeline-button'; + +export const DATA_GRID_BODY = '[data-test-subj=body-data-grid] .euiDataGrid__virtualized'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts index 94a4529549a6dd..16f8568bf7a82e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts @@ -26,7 +26,10 @@ import { TIMELINE_CONTEXT_MENU_BTN, } from '../screens/alerts'; import { REFRESH_BUTTON } from '../screens/security_header'; -import { TIMELINE_COLUMN_SPINNER } from '../screens/timeline'; +import { + ALERT_TABLE_CELL_ACTIONS_ADD_TO_TIMELINE, + TIMELINE_COLUMN_SPINNER, +} from '../screens/timeline'; import { UPDATE_ENRICHMENT_RANGE_BUTTON, ENRICHMENT_QUERY_END_INPUT, @@ -152,6 +155,11 @@ export const investigateFirstAlertInTimeline = () => { cy.get(SEND_ALERT_TO_TIMELINE_BTN).first().click({ force: true }); }; +export const addAlertPropertyToTimeline = (propertySelector: string, rowIndex: number) => { + cy.get(propertySelector).eq(rowIndex).trigger('mouseover'); + cy.get(ALERT_TABLE_CELL_ACTIONS_ADD_TO_TIMELINE).first().click({ force: true }); +}; + export const waitForAlerts = () => { cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 5403c4f95bf54d..01d10c627af85f 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -69,6 +69,7 @@ import { TIMELINE_TAB_CONTENT_EQL, TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN, TIMELINE_DATA_PROVIDER_FIELD_INPUT, + ACTIVE_TIMELINE_BOTTOM_BAR, } from '../screens/timeline'; import { REFRESH_BUTTON, TIMELINE } from '../screens/timelines'; @@ -296,6 +297,10 @@ export const openTimelineById = (timelineId: string): Cypress.Chainable { + cy.get(ACTIVE_TIMELINE_BOTTOM_BAR).click({ force: true }); +}; + export const pinFirstEvent = (): Cypress.Chainable> => { return cy.get(PIN_EVENT).first().click({ force: true }); }; diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx index 4e944072fefef4..54956f63045761 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx @@ -12,7 +12,10 @@ import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common/search_str import type { DataProvider } from '@kbn/timelines-plugin/common/types'; import { getPageRowIndex } from '@kbn/timelines-plugin/public'; import { useGetMappedNonEcsValue } from '../../../timelines/components/timeline/body/data_driven_columns'; -import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { + EXISTS_OPERATOR, + IS_OPERATOR, +} from '../../../timelines/components/timeline/data_providers/data_provider'; import { escapeDataProviderId } from '../../components/drag_and_drop/helpers'; import { EmptyComponent, useKibanaServices } from './helpers'; @@ -41,23 +44,39 @@ export const getAddToTimelineCellAction = ({ [timelines] ); - const dataProvider: DataProvider[] = useMemo( - () => - value?.map((x) => ({ - and: [], - enabled: true, - id: `${escapeDataProviderId(columnId)}-row-${rowIndex}-col-${columnId}-val-${x}`, - name: x, - excluded: false, - kqlQuery: '', - queryMatch: { - field: columnId, - value: x, - operator: IS_OPERATOR, + const dataProvider: DataProvider[] = useMemo(() => { + const queryIdPrefix = `${escapeDataProviderId(columnId)}-row-${rowIndex}-col-${columnId}`; + if (!value) { + return [ + { + and: [], + enabled: true, + kqlQuery: '', + id: `${queryIdPrefix}`, + name: '', + excluded: true, + queryMatch: { + field: columnId, + value: '', + operator: EXISTS_OPERATOR, + }, }, - })) ?? [], - [columnId, rowIndex, value] - ); + ]; + } + return value.map((x) => ({ + and: [], + enabled: true, + excluded: false, + kqlQuery: '', + id: `${queryIdPrefix}-val-${x}`, + name: x, + queryMatch: { + field: columnId, + value: x, + operator: IS_OPERATOR, + }, + })); + }, [columnId, rowIndex, value]); const addToTimelineProps = useMemo(() => { return { Component,