diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts index 8e5a606a91017..72fe68fa9cf59 100644 --- a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts @@ -22,6 +22,7 @@ import { ALERT_RULE_NAME, ALERT_RULE_PARAMETERS, ALERT_RULE_PRODUCER, + ALERT_RULE_REVISION, ALERT_RULE_TAGS, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, @@ -112,6 +113,11 @@ export const alertFieldMap = { array: false, required: true, }, + [ALERT_RULE_REVISION]: { + type: 'long', + array: false, + required: true, + }, [ALERT_RULE_TAGS]: { type: 'keyword', array: true, diff --git a/packages/kbn-expandable-flyout/src/types.ts b/packages/kbn-expandable-flyout/src/types.ts index f526832810900..2413fbb56ca7d 100644 --- a/packages/kbn-expandable-flyout/src/types.ts +++ b/packages/kbn-expandable-flyout/src/types.ts @@ -39,5 +39,5 @@ export interface Panel { /** * Width used when rendering the panel */ - width: number; // TODO remove this, the width shouldn't be a property of a panel, but handled at the flyout level + width: number; // TODO remove this, see https://github.com/elastic/security-team/issues/6247 } diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index 34b04116b9522..e996acbd9fb6c 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -82,6 +82,9 @@ const ALERT_RULE_PARAMETERS = `${ALERT_RULE_NAMESPACE}.parameters` as const; // kibana.alert.rule.producer - rule type producer for rule that generated this alert const ALERT_RULE_PRODUCER = `${ALERT_RULE_NAMESPACE}.producer` as const; +// kibana.alert.rule.revision - current revision of the rule that generated this alert +const ALERT_RULE_REVISION = `${ALERT_RULE_NAMESPACE}.revision` as const; + // kibana.alert.rule.tags - rule tags for rule that generated this alert const ALERT_RULE_TAGS = `${ALERT_RULE_NAMESPACE}.tags` as const; @@ -113,6 +116,7 @@ const fields = { ALERT_RULE_NAME, ALERT_RULE_PARAMETERS, ALERT_RULE_PRODUCER, + ALERT_RULE_REVISION, ALERT_RULE_TAGS, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, @@ -148,6 +152,7 @@ export { ALERT_RULE_NAME, ALERT_RULE_PARAMETERS, ALERT_RULE_PRODUCER, + ALERT_RULE_REVISION, ALERT_RULE_TAGS, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index ff3d9aeecd79f..4456bd363a016 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -254,6 +254,9 @@ describe('mappingFromFieldMap', () => { producer: { type: 'keyword', }, + revision: { + type: 'long', + }, rule_type_id: { type: 'keyword', }, diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 8b2eeba976898..9b014d538cb96 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -187,6 +187,7 @@ export type SanitizedRuleConfig = Pick< | 'throttle' | 'notifyWhen' | 'muteAll' + | 'revision' | 'snoozeSchedule' > & { producer: string; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 55a470dc8e4a3..45d22a8fbf2ac 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -260,6 +260,7 @@ export class TaskRunner< enabled, actions, muteAll, + revision, snoozeSchedule, } = rule; const { @@ -379,6 +380,7 @@ export class TaskRunner< tags, consumer, producer: ruleType.producer, + revision, ruleTypeId: rule.alertTypeId, ruleTypeName: ruleType.name, enabled, diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index 63b0c81108a47..de3ef3cf68d0c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -75,6 +75,7 @@ const mockOptions = { throttle: null, notifyWhen: null, producer: '', + revision: 0, ruleTypeId: '', ruleTypeName: '', muteAll: false, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index f363b5baeabf1..a24a02b2df5e8 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -113,6 +113,7 @@ const mockOptions = { throttle: null, notifyWhen: null, producer: '', + revision: 0, ruleTypeId: '', ruleTypeName: '', muteAll: false, diff --git a/x-pack/plugins/observability/public/components/alerts_flyout.test.tsx b/x-pack/plugins/observability/public/components/alerts_flyout.test.tsx index 96716ab593200..84d3046d40b3c 100644 --- a/x-pack/plugins/observability/public/components/alerts_flyout.test.tsx +++ b/x-pack/plugins/observability/public/components/alerts_flyout.test.tsx @@ -67,6 +67,7 @@ const activeAlert: TopAlert = { 'kibana.alert.rule.producer': 'logs', 'kibana.alert.rule.consumer': 'logs', 'kibana.alert.rule.category': 'Log threshold', + 'kibana.alert.rule.revision': 0, 'kibana.alert.start': '2021-09-02T12:54:09.674Z', 'kibana.alert.rule.rule_type_id': 'logs.alert.document.count', 'event.action': 'active', @@ -97,6 +98,7 @@ const recoveredAlert: TopAlert = { 'kibana.alert.rule.producer': 'infrastructure', 'kibana.alert.rule.consumer': 'infrastructure', 'kibana.alert.rule.category': 'Inventory', + 'kibana.alert.rule.revision': 0, 'kibana.alert.start': '2021-09-02T13:05:36.699Z', 'kibana.alert.rule.rule_type_id': 'metrics.alert.inventory.threshold', 'event.action': 'close', diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.test.ts b/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.test.ts index bcf851e32c4d3..479a77f7e5bc6 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.test.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.test.ts @@ -27,6 +27,7 @@ describe('useFetchAlertDetail', () => { 'kibana.alert.rule.execution.uuid': 'e62c418d-734d-47e7-bbeb-e6f182f5fb45', 'kibana.alert.rule.name': 'A super rule', 'kibana.alert.rule.producer': 'infrastructure', + 'kibana.alert.rule.revision': 0, 'kibana.alert.rule.rule_type_id': 'metrics.alert.threshold', 'kibana.alert.rule.uuid': '69411af0-82a2-11ec-8139-c1568734434e', 'kibana.space_ids': ['default'], @@ -124,6 +125,7 @@ describe('useFetchAlertDetail', () => { "kibana.alert.rule.execution.uuid": "e62c418d-734d-47e7-bbeb-e6f182f5fb45", "kibana.alert.rule.name": "A super rule", "kibana.alert.rule.producer": "infrastructure", + "kibana.alert.rule.revision": 0, "kibana.alert.rule.rule_type_id": "metrics.alert.threshold", "kibana.alert.rule.tags": Array [], "kibana.alert.rule.uuid": "69411af0-82a2-11ec-8139-c1568734434e", diff --git a/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts b/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts index 91dfb68c7f641..e35c888d46fc9 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts +++ b/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts @@ -15,6 +15,7 @@ import { ALERT_RULE_CONSUMER, ALERT_RULE_NAME, ALERT_RULE_PRODUCER, + ALERT_RULE_REVISION, ALERT_RULE_TAGS, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, @@ -46,6 +47,7 @@ export const alert: TopAlert = { [ALERT_RULE_PRODUCER]: 'logs', [ALERT_RULE_CONSUMER]: 'logs', [ALERT_RULE_CATEGORY]: 'Log threshold', + [ALERT_RULE_REVISION]: 0, [ALERT_START]: '2021-09-02T12:54:09.674Z', [ALERT_RULE_TYPE_ID]: 'logs.alert.document.count', [EVENT_ACTION]: 'active', diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index 7c2cc7c2a02af..581c77670a153 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -163,6 +163,11 @@ it('matches snapshot', () => { "required": false, "type": "keyword", }, + "kibana.alert.rule.revision": Object { + "array": false, + "required": true, + "type": "long", + }, "kibana.alert.rule.rule_id": Object { "array": false, "required": false, diff --git a/x-pack/plugins/rule_registry/common/parse_technical_fields.test.ts b/x-pack/plugins/rule_registry/common/parse_technical_fields.test.ts index c7a90cb57ead5..faa1747cc2a9b 100644 --- a/x-pack/plugins/rule_registry/common/parse_technical_fields.test.ts +++ b/x-pack/plugins/rule_registry/common/parse_technical_fields.test.ts @@ -24,6 +24,7 @@ describe('parseTechnicalFields', () => { 'kibana.alert.rule.rule_type_id': ['metrics.alert.threshold'], 'event.action': ['active'], 'kibana.alert.rule.name': ['Uptime'], + 'kibana.alert.rule.revision': 0, 'kibana.alert.uuid': ['f31f5726-3c47-4c88-bc42-4e1fbde17e34'], 'kibana.space_ids': ['default'], 'kibana.version': ['8.1.0'], @@ -102,6 +103,7 @@ describe('parseTechnicalFields', () => { 'kibana.alert.rule.category': ['Metric threshold'], 'kibana.alert.rule.rule_type_id': ['metrics.alert.threshold'], 'kibana.alert.rule.name': ['Uptime'], + 'kibana.alert.rule.revision': 0, 'kibana.alert.uuid': ['f31f5726-3c47-4c88-bc42-4e1fbde17e34'], 'kibana.space_ids': ['default'], }; diff --git a/x-pack/plugins/rule_registry/common/schemas/8.8.0/index.ts b/x-pack/plugins/rule_registry/common/schemas/8.8.0/index.ts new file mode 100644 index 0000000000000..6ed240f589113 --- /dev/null +++ b/x-pack/plugins/rule_registry/common/schemas/8.8.0/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ALERT_RULE_REVISION } from '@kbn/rule-data-utils'; +import { CommonAlertFields870 } from '../8.7.0'; + +/* DO NOT MODIFY THIS SCHEMA TO ADD NEW FIELDS. These types represent the alerts that shipped in 8.8.0. +Any changes to these types should be bug fixes so the types more accurately represent the alerts from 8.8.0. + +If you are adding new fields for a new release of Kibana, create a new sibling folder to this one +for the version to be released and add the field(s) to the schema in that folder. + +Then, update `../index.ts` to import from the new folder that has the latest schemas, add the +new schemas to the union of all alert schemas, and re-export the new schemas as the `*Latest` schemas. +*/ + +export interface RevisionField880 { + [ALERT_RULE_REVISION]: number; +} + +export type CommonAlertFields880 = CommonAlertFields870 & RevisionField880; + +export type CommonAlertFieldName880 = keyof CommonAlertFields880; + +export type AlertWithCommonFields880<T> = T & CommonAlertFields880; diff --git a/x-pack/plugins/rule_registry/common/schemas/index.ts b/x-pack/plugins/rule_registry/common/schemas/index.ts index 9a19794b6c4ce..95b0bf6914aaf 100644 --- a/x-pack/plugins/rule_registry/common/schemas/index.ts +++ b/x-pack/plugins/rule_registry/common/schemas/index.ts @@ -6,19 +6,22 @@ */ import type { - CommonAlertFieldName870, + AlertWithSuppressionFields870, + SuppressionFields870, CommonAlertIdFieldName870, - CommonAlertFields870, - AlertWithCommonFields870, } from './8.7.0'; -import type { AlertWithSuppressionFields870, SuppressionFields870 } from './8.7.0'; +import type { + AlertWithCommonFields880, + CommonAlertFieldName880, + CommonAlertFields880, +} from './8.8.0'; export type { AlertWithSuppressionFields870 as AlertWithSuppressionFieldsLatest, SuppressionFields870 as SuppressionFieldsLatest, - CommonAlertFieldName870 as CommonAlertFieldNameLatest, + CommonAlertFieldName880 as CommonAlertFieldNameLatest, CommonAlertIdFieldName870 as CommonAlertIdFieldNameLatest, - CommonAlertFields870 as CommonAlertFieldsLatest, - AlertWithCommonFields870 as AlertWithCommonFieldsLatest, + CommonAlertFields880 as CommonAlertFieldsLatest, + AlertWithCommonFields880 as AlertWithCommonFieldsLatest, }; diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts index d6485eea8e89d..809a5167870a1 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts @@ -11,6 +11,7 @@ import { ALERT_RULE_CONSUMER, ALERT_RULE_NAME, ALERT_RULE_PRODUCER, + ALERT_RULE_REVISION, ALERT_RISK_SCORE, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, @@ -36,6 +37,7 @@ const getMockAlert = (): ParsedTechnicalFields & ParsedExperimentalFields => ({ [ALERT_RULE_CONSUMER]: 'apm', [ALERT_RULE_NAME]: 'Check error rate', [ALERT_RULE_PRODUCER]: 'apm', + [ALERT_RULE_REVISION]: 0, [ALERT_RISK_SCORE]: 20, [ALERT_RULE_TYPE_ID]: 'fake-rule-type-id', [ALERT_RULE_UUID]: 'fake-rule-uuid', diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 7b312ccf5cef2..787fd17c3d74f 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -115,6 +115,7 @@ function createRule(shouldWriteAlerts: boolean = true) { name: 'name', notifyWhen: 'onActionGroupChange', producer: 'producer', + revision: 0, ruleTypeId: 'ruleTypeId', ruleTypeName: 'ruleTypeName', schedule: { @@ -255,6 +256,7 @@ describe('createLifecycleRuleTypeFactory', () => { "threshold": 1, }, "kibana.alert.rule.producer": "producer", + "kibana.alert.rule.revision": 0, "kibana.alert.rule.rule_type_id": "ruleTypeId", "kibana.alert.rule.tags": Array [ "tags", @@ -291,6 +293,7 @@ describe('createLifecycleRuleTypeFactory', () => { "threshold": 1, }, "kibana.alert.rule.producer": "producer", + "kibana.alert.rule.revision": 0, "kibana.alert.rule.rule_type_id": "ruleTypeId", "kibana.alert.rule.tags": Array [ "tags", diff --git a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts index 5d7d105571c84..477fe83352693 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts @@ -17,6 +17,7 @@ import { ALERT_RULE_TAGS, TIMESTAMP, ALERT_RULE_PARAMETERS, + ALERT_RULE_REVISION, } from '@kbn/rule-data-utils'; import { RuleExecutorOptions } from '@kbn/alerting-plugin/server'; @@ -32,6 +33,7 @@ export const getCommonAlertFields = ( [ALERT_RULE_EXECUTION_UUID]: options.executionId, [ALERT_RULE_NAME]: options.rule.name, [ALERT_RULE_PRODUCER]: options.rule.producer, + [ALERT_RULE_REVISION]: options.rule.revision, [ALERT_RULE_TYPE_ID]: options.rule.ruleTypeId, [ALERT_RULE_UUID]: options.rule.id, [SPACE_IDS]: [options.spaceId], diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts index 9e22d49ef135b..58b370847de5f 100644 --- a/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts +++ b/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts @@ -66,6 +66,7 @@ export const createDefaultAlertExecutorOptions = < createdAt, updatedAt, notifyWhen: null, + revision: 0, ruleTypeId: 'RULE_TYPE_ID', ruleTypeName: 'RULE_TYPE_NAME', muteAll: false, diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_header.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_header.cy.ts new file mode 100644 index 0000000000000..bf790b15ecd8e --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_header.cy.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { upperFirst } from 'lodash'; +import { + DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE, + DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE, + DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY, + DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE, + DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE, +} from '../../../screens/document_expandable_flyout'; +import { expandFirstAlertExpandableFlyout } from '../../../tasks/document_expandable_flyout'; +import { cleanKibana } from '../../../tasks/common'; +import { login, visit } from '../../../tasks/login'; +import { createRule } from '../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../objects/rule'; +import { ALERTS_URL } from '../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; + +// Skipping these for now as the feature is protected behind a feature flag set to false by default +// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 +describe.skip( + 'Alert details expandable flyout right panel header', + { testIsolation: false }, + () => { + const rule = getNewRule(); + + before(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + }); + + it('should display correct title in header', () => { + cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE) + .should('be.visible') + .and('have.text', rule.name); + }); + + it('should display risk score in header', () => { + cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE) + .should('be.visible') + .and('have.text', rule.risk_score); + }); + + it('should display severity in header', () => { + cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE) + .should('be.visible') + .and('have.text', upperFirst(rule.severity)); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 1fc306b3d4ad4..06242709b52cc 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { upperFirst } from 'lodash'; import { DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE, @@ -16,15 +15,18 @@ import { DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE, - DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS, } from '../../../screens/document_expandable_flyout'; import { expandFirstAlertExpandableFlyout, openOverviewTab, + toggleOverviewTabDescriptionSection, } from '../../../tasks/document_expandable_flyout'; import { cleanKibana } from '../../../tasks/common'; import { login, visit } from '../../../tasks/login'; @@ -51,74 +53,105 @@ describe.skip( openOverviewTab(); }); - it('should display correct title in header', () => { - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_TITLE) - .should('be.visible') - .and('have.text', rule.name); - }); - - it('should display risk score in header', () => { - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_RISK_SCORE_VALUE) - .should('be.visible') - .and('have.text', rule.risk_score); - }); - - it('should display severity in header', () => { - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_OVERVIEW_TAB_HEADER_SEVERITY_VALUE) - .should('be.visible') - .and('have.text', upperFirst(rule.severity)); - }); - - it('should display mitre attack', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) - .should('be.visible') - // @ts-ignore - .and('contain.text', rule.threat[0].framework); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS) - .should('be.visible') - // @ts-ignore - .and('contain.text', rule.threat[0].technique[0].name) - // @ts-ignore - .and('contain.text', rule.threat[0].tactic.name); - }); + describe('description section', () => { + it('should display description section header and content', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER) + .should('be.visible') + .and('have.text', 'Description'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT).should( + 'be.visible' + ); + }); - it('should display highlighted fields', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON) + it('should display document description and expand button', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) .should('be.visible') - .click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) + .and('have.text', 'Rule description'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS) .should('be.visible') - .and('have.text', 'Highlighted fields'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should( - 'be.visible' + .and('have.text', rule.description); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON) + .should('be.visible') + .and('have.text', 'Expand'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON).click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON).should( + 'have.text', + 'Collapse' ); + }); - // close highlighted fields to reset the view for next test - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON) + it('should display reason', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) + .should('be.visible') + .and('have.text', 'Alert reason'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) .should('be.visible') - .click(); + .and('contain.text', rule.name); }); - }); - it('should navigate to table tab when clicking on highlighted fields view button', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS).within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON) + it('should display mitre attack', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS).should('be.visible'); + }); + + it('should display mitre attack', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) .should('be.visible') - .click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK) + // @ts-ignore + .and('contain.text', rule.threat[0].framework); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS) .should('be.visible') - .click(); + // @ts-ignore + .and('contain.text', rule.threat[0].technique[0].name) + // @ts-ignore + .and('contain.text', rule.threat[0].tactic.name); }); + }); - // the table component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that scrolls to a specific element in the table - // (in the middle of it vertically) to ensure Cypress finds it - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); + describe('investigation section', () => { + before(() => { + toggleOverviewTabDescriptionSection(); + }); + + it('should display highlighted fields', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS) + .scrollIntoView() + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON) + .should('be.visible') + .click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) + .should('be.visible') + .and('have.text', 'Highlighted fields'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should( + 'be.visible' + ); + + // close highlighted fields to reset the view for next test + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON) + .should('be.visible') + .click(); + }); + }); + it('should navigate to table tab when clicking on highlighted fields view button', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS) + .scrollIntoView() + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON) + .should('be.visible') + .click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK) + .should('be.visible') + .click(); + }); + + // the table component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that scrolls to a specific element in the table + // (in the middle of it vertically) to ensure Cypress finds it + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); + }); }); } ); diff --git a/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts b/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts index e9936a88e6e04..6c567e109bb40 100644 --- a/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts +++ b/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts @@ -35,6 +35,11 @@ import { } from '../../public/flyout/right/tabs/test_ids'; import { COLLAPSE_DETAILS_BUTTON_TEST_ID, + DESCRIPTION_DETAILS_TEST_ID, + DESCRIPTION_EXPAND_BUTTON_TEST_ID, + DESCRIPTION_SECTION_CONTENT_TEST_ID, + DESCRIPTION_SECTION_HEADER_TEST_ID, + DESCRIPTION_TITLE_TEST_ID, EXPAND_DETAILS_BUTTON_TEST_ID, FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID, FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID, @@ -48,6 +53,8 @@ import { HIGHLIGHTED_FIELDS_TEST_ID, MITRE_ATTACK_DETAILS_TEST_ID, MITRE_ATTACK_TITLE_TEST_ID, + REASON_DETAILS_TEST_ID, + REASON_TITLE_TEST_ID, } from '../../public/flyout/right/components/test_ids'; import { getClassSelector, getDataTestSubjectSelector } from '../helpers/common'; @@ -106,6 +113,21 @@ export const DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB_CONTENT = getDataTestSubjectSel /* Overview tab */ +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER = + getDataTestSubjectSelector(DESCRIPTION_SECTION_HEADER_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_CONTENT = + getDataTestSubjectSelector(DESCRIPTION_SECTION_CONTENT_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE = + getDataTestSubjectSelector(DESCRIPTION_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS = getDataTestSubjectSelector( + DESCRIPTION_DETAILS_TEST_ID +); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_EXPAND_BUTTON = + getDataTestSubjectSelector(DESCRIPTION_EXPAND_BUTTON_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE = + getDataTestSubjectSelector(REASON_TITLE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS = + getDataTestSubjectSelector(REASON_DETAILS_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE = getDataTestSubjectSelector( MITRE_ATTACK_TITLE_TEST_ID ); diff --git a/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts b/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts index e66d1117c6603..7c25557fea42a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts @@ -14,6 +14,7 @@ import { DOCUMENT_DETAILS_FLYOUT_INVESTIGATIONS_TAB, DOCUMENT_DETAILS_FLYOUT_JSON_TAB, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CLEAR_FILTER, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER, @@ -60,6 +61,15 @@ export const scrollWithinDocumentDetailsExpandableFlyoutRightSection = (x: numbe export const openOverviewTab = () => cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).should('be.visible').click(); +/** + * Toggle the Overview tab description section in the document details expandable flyout right section + */ +export const toggleOverviewTabDescriptionSection = () => + cy + .get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_SECTION_HEADER) + .should('be.visible') + .click(); + /** * Open the Table tab in the document details expandable flyout right section */ diff --git a/x-pack/plugins/security_solution/public/flyout/index.tsx b/x-pack/plugins/security_solution/public/flyout/index.tsx index dc1d6d09d8ee7..dec91855c2628 100644 --- a/x-pack/plugins/security_solution/public/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/index.tsx @@ -14,6 +14,11 @@ import type { LeftPanelProps } from './left'; import { LeftPanel, LeftPanelKey } from './left'; import { LeftPanelProvider } from './left/context'; +// TODO these should be replaced by a more dynamic solution +// see https://github.com/elastic/security-team/issues/6247 +export const RIGHT_SECTION_WIDTH = 500; +export const LEFT_SECTION_WIDTH = 1000; + /** * List of all panels that will be used within the document details expandable flyout. * This needs to be passed to the expandable flyout registeredPanels property. @@ -21,7 +26,7 @@ import { LeftPanelProvider } from './left/context'; export const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] = [ { key: RightPanelKey, - width: 500, + width: RIGHT_SECTION_WIDTH, component: (props) => ( <RightPanelProvider {...(props as RightPanelProps).params}> <RightPanel path={props.path as RightPanelProps['path']} /> @@ -30,7 +35,7 @@ export const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredP }, { key: LeftPanelKey, - width: 1000, + width: LEFT_SECTION_WIDTH, component: (props) => ( <LeftPanelProvider {...(props as LeftPanelProps).params}> <LeftPanel path={props.path as LeftPanelProps['path']} /> diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.stories.tsx new file mode 100644 index 0000000000000..5588fcbc123bc --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.stories.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import type { Story } from '@storybook/react'; +import { RIGHT_SECTION_WIDTH } from '../..'; +import { Description } from './description'; +import { RightPanelContext } from '../context'; + +const PADDING = 24; +const WIDTH = RIGHT_SECTION_WIDTH - 2 * PADDING; + +const ruleUuid = { + category: 'kibana', + field: 'kibana.alert.rule.uuid', + values: ['123'], + originalValue: ['123'], + isObjectArray: false, +}; +const ruleDescription = { + category: 'kibana', + field: 'kibana.alert.rule.description', + values: [ + 'This is a very long description of the rule. In theory. this description is long enough that it should be cut off when displayed in collapsed mode.', + ], + originalValue: ['description'], + isObjectArray: false, +}; + +export default { + component: Description, + title: 'Flyout/Description', +}; + +export const RuleExpand: Story<void> = () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], + } as unknown as RightPanelContext; + + return ( + <RightPanelContext.Provider value={panelContextValue}> + <div + css={css` + width: ${WIDTH}px; // this mimics the current 500 width of the right panel + `} + > + <Description /> + </div> + </RightPanelContext.Provider> + ); +}; + +export const RuleCollapse: Story<void> = () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], + } as unknown as RightPanelContext; + + return ( + <RightPanelContext.Provider value={panelContextValue}> + <div + css={css` + width: ${WIDTH}px; // this mimics the current 500 width of the right panel + `} + > + <Description expanded={true} /> + </div> + </RightPanelContext.Provider> + ); +}; + +export const Document: Story<void> = () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ + { + category: 'kibana', + field: 'kibana.alert.rule.description', + values: ['This is a description for the document.'], + originalValue: ['description'], + isObjectArray: false, + }, + ], + } as unknown as RightPanelContext; + + return ( + <RightPanelContext.Provider value={panelContextValue}> + <div + css={css` + width: ${WIDTH}px; // this mimics the current 500 width of the right panel + `} + > + <Description /> + </div> + </RightPanelContext.Provider> + ); +}; + +export const EmptyDescription: Story<void> = () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ + ruleUuid, + { + category: 'kibana', + field: 'kibana.alert.rule.description', + values: [''], + originalValue: ['description'], + isObjectArray: false, + }, + ], + } as unknown as RightPanelContext; + return ( + <RightPanelContext.Provider value={panelContextValue}> + <div + css={css` + width: ${WIDTH}px; // this mimics the current 500 width of the right panel + `} + > + <Description expanded={true} /> + </div> + </RightPanelContext.Provider> + ); +}; + +export const Empty: Story<void> = () => { + const panelContextValue = {} as unknown as RightPanelContext; + return ( + <RightPanelContext.Provider value={panelContextValue}> + <div + css={css` + width: ${WIDTH}px; // this mimics the current 500 width of the right panel + `} + > + <Description expanded={true} /> + </div> + </RightPanelContext.Provider> + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx new file mode 100644 index 0000000000000..f378a58b14076 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { DESCRIPTION_EXPAND_BUTTON_TEST_ID, DESCRIPTION_TITLE_TEST_ID } from './test_ids'; +import { + DOCUMENT_DESCRIPTION_COLLAPSE_BUTTON, + DOCUMENT_DESCRIPTION_EXPAND_BUTTON, + DOCUMENT_DESCRIPTION_TITLE, + RULE_DESCRIPTION_TITLE, +} from './translations'; +import { Description } from './description'; +import { RightPanelContext } from '../context'; + +const ruleUuid = { + category: 'kibana', + field: 'kibana.alert.rule.uuid', + values: ['123'], + originalValue: ['123'], + isObjectArray: false, +}; +const ruleDescription = { + category: 'kibana', + field: 'kibana.alert.rule.description', + values: [ + 'This is a very long description of the rule. In theory. this description is long enough that it should be cut off when displayed in collapsed mode.', + ], + originalValue: ['description'], + isObjectArray: false, +}; + +describe('<Description />', () => { + it('should render the component collapsed', () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], + } as unknown as RightPanelContext; + + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Description /> + </RightPanelContext.Provider> + ); + + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(RULE_DESCRIPTION_TITLE); + expect(getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID)).toHaveTextContent( + DOCUMENT_DESCRIPTION_EXPAND_BUTTON + ); + }); + + it('should render the component expanded', () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], + } as unknown as RightPanelContext; + + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Description expanded={true} /> + </RightPanelContext.Provider> + ); + + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(RULE_DESCRIPTION_TITLE); + expect(getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID)).toHaveTextContent( + DOCUMENT_DESCRIPTION_COLLAPSE_BUTTON + ); + }); + + it('should render expand and collapse when clicking on the button', () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], + } as unknown as RightPanelContext; + + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Description /> + </RightPanelContext.Provider> + ); + + expect(getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID)).toHaveTextContent( + DOCUMENT_DESCRIPTION_EXPAND_BUTTON + ); + getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID).click(); + expect(getByTestId(DESCRIPTION_EXPAND_BUTTON_TEST_ID)).toHaveTextContent( + DOCUMENT_DESCRIPTION_COLLAPSE_BUTTON + ); + }); + + it('should render document title if document is not an alert', () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [ruleDescription], + } as unknown as RightPanelContext; + + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Description /> + </RightPanelContext.Provider> + ); + + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(DOCUMENT_DESCRIPTION_TITLE); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx new file mode 100644 index 0000000000000..b233f3b4597ce --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import type { VFC } from 'react'; +import React, { useState } from 'react'; +import { css } from '@emotion/react'; +import { useRightPanelContext } from '../context'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; +import { + DESCRIPTION_DETAILS_TEST_ID, + DESCRIPTION_EXPAND_BUTTON_TEST_ID, + DESCRIPTION_TITLE_TEST_ID, +} from './test_ids'; +import { + DOCUMENT_DESCRIPTION_COLLAPSE_BUTTON, + DOCUMENT_DESCRIPTION_EXPAND_BUTTON, + DOCUMENT_DESCRIPTION_TITLE, + RULE_DESCRIPTION_TITLE, +} from './translations'; + +export interface DescriptionProps { + /** + * Boolean to allow the component to be expanded or collapsed on first render + */ + expanded?: boolean; +} + +/** + * Displays the description of a document. + * If the document is an alert we show the rule description. If the document is of another type, we show -. + * By default, the text is truncated to only shows 2 lines. + * The Expand/Collapse button allows the user to see the whole description. + */ +export const Description: VFC<DescriptionProps> = ({ expanded = false }) => { + const [isExpanded, setIsExpanded] = useState(expanded); + + const { dataFormattedForFieldBrowser } = useRightPanelContext(); + const { isAlert, ruleDescription } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + + if (!dataFormattedForFieldBrowser) { + return null; + } + + const hasRuleDescription = ruleDescription && ruleDescription.length > 0; + + // TODO look into hiding the expand/collapse button if the description is short + // see https://github.com/elastic/security-team/issues/6248 + + return ( + <EuiFlexGroup direction="column" gutterSize="s"> + <EuiFlexItem data-test-subj={DESCRIPTION_TITLE_TEST_ID}> + <EuiTitle size="xxs"> + <h5>{isAlert ? RULE_DESCRIPTION_TITLE : DOCUMENT_DESCRIPTION_TITLE}</h5> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup direction="column" gutterSize="xs" alignItems="flexStart"> + <EuiFlexItem + data-test-subj={DESCRIPTION_DETAILS_TEST_ID} + css={css` + word-break: break-word; + ${!isExpanded && + ` + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} + `} + > + {hasRuleDescription ? ruleDescription : '-'} + </EuiFlexItem> + {hasRuleDescription ? ( + <EuiFlexItem grow={false}> + <EuiButtonEmpty + size="s" + onClick={() => setIsExpanded((preIsExpanded) => !preIsExpanded)} + data-test-subj={DESCRIPTION_EXPAND_BUTTON_TEST_ID} + > + {isExpanded + ? DOCUMENT_DESCRIPTION_COLLAPSE_BUTTON + : DOCUMENT_DESCRIPTION_EXPAND_BUTTON} + </EuiButtonEmpty> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +Description.displayName = 'Description'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description_section.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description_section.stories.tsx new file mode 100644 index 0000000000000..c38125459df43 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description_section.stories.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Story } from '@storybook/react'; +import { mockDataFormattedForFieldBrowser, mockSearchHit } from '../mocks/mock_context'; +import { RightPanelContext } from '../context'; +import { DescriptionSection } from './description_section'; + +const panelContextValue = { + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, + searchHit: mockSearchHit, +} as unknown as RightPanelContext; + +export default { + component: DescriptionSection, + title: 'Flyout/DescriptionSection', +}; + +export const Expand: Story<void> = () => { + return ( + <RightPanelContext.Provider value={panelContextValue}> + <DescriptionSection /> + </RightPanelContext.Provider> + ); +}; + +export const Collapse: Story<void> = () => { + return ( + <RightPanelContext.Provider value={panelContextValue}> + <DescriptionSection expanded={false} /> + </RightPanelContext.Provider> + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description_section.test.tsx new file mode 100644 index 0000000000000..9b3506d563816 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description_section.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + DESCRIPTION_SECTION_CONTENT_TEST_ID, + DESCRIPTION_SECTION_HEADER_TEST_ID, +} from './test_ids'; +import { DescriptionSection } from './description_section'; +import { RightPanelContext } from '../context'; + +const panelContextValue = {} as unknown as RightPanelContext; + +describe('<DescriptionSection />', () => { + it('should render the component collapsed', () => { + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <DescriptionSection /> + </RightPanelContext.Provider> + ); + + expect(getByTestId(DESCRIPTION_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); + }); + + it('should render the component expanded', () => { + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <DescriptionSection expanded={true} /> + </RightPanelContext.Provider> + ); + + expect(getByTestId(DESCRIPTION_SECTION_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DESCRIPTION_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); + }); + + it('should expand the component when clicking on the arrow on header', () => { + const { getByTestId } = render( + <RightPanelContext.Provider value={panelContextValue}> + <DescriptionSection /> + </RightPanelContext.Provider> + ); + + getByTestId(DESCRIPTION_SECTION_HEADER_TEST_ID).click(); + expect(getByTestId(DESCRIPTION_SECTION_CONTENT_TEST_ID)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description_section.tsx new file mode 100644 index 0000000000000..3fd98c597e024 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description_section.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiSpacer } from '@elastic/eui'; +import type { VFC } from 'react'; +import React from 'react'; +import { ExpandableSection } from './expandable_section'; +import { DESCRIPTION_SECTION_TEST_ID } from './test_ids'; +import { DESCRIPTION_TITLE } from './translations'; +import { Description } from './description'; +import { Reason } from './reason'; +import { MitreAttack } from './mitre_attack'; + +export interface DescriptionSectionProps { + /** + * Boolean to allow the component to be expanded or collapsed on first render + */ + expanded?: boolean; +} + +/** + * Most top section of the overview tab. It contains the description, reason and mitre attack information (for a document of type alert). + */ +export const DescriptionSection: VFC<DescriptionSectionProps> = ({ expanded = true }) => { + return ( + <ExpandableSection + expanded={expanded} + title={DESCRIPTION_TITLE} + data-test-subj={DESCRIPTION_SECTION_TEST_ID} + > + <Description /> + <EuiSpacer size="m" /> + <Reason /> + <EuiSpacer size="m" /> + <MitreAttack /> + </ExpandableSection> + ); +}; + +DescriptionSection.displayName = 'DescriptionSection'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx new file mode 100644 index 0000000000000..64411fbe69513 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.stories.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Story } from '@storybook/react'; +import { ExpandableSection } from './expandable_section'; + +const title = 'title'; +const children = <div>{'content'}</div>; + +export default { + component: ExpandableSection, + title: 'Flyout/ExpandableSection', +}; + +export const Expand: Story<void> = () => { + return ( + <ExpandableSection expanded={false} title={title}> + {children} + </ExpandableSection> + ); +}; + +export const Collapse: Story<void> = () => { + return ( + <ExpandableSection expanded={true} title={title}> + {children} + </ExpandableSection> + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx new file mode 100644 index 0000000000000..e0d08e260f944 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { CONTENT_TEST_ID, ExpandableSection, HEADER_TEST_ID } from './expandable_section'; + +const title = 'title'; +const children = <div>{'content'}</div>; +const testId = 'test'; +const headerTestId = testId + HEADER_TEST_ID; +const contentTestId = testId + CONTENT_TEST_ID; + +describe('<ExpandableSection />', () => { + it('should render the component collapsed', () => { + const { getByTestId } = render( + <ExpandableSection expanded={false} title={title} data-test-subj={testId}> + {children} + </ExpandableSection> + ); + + expect(getByTestId(headerTestId)).toBeInTheDocument(); + }); + + it('should render the component expanded', () => { + const { getByTestId } = render( + <ExpandableSection expanded={true} title={title} data-test-subj={testId}> + {children} + </ExpandableSection> + ); + + expect(getByTestId(headerTestId)).toBeInTheDocument(); + expect(getByTestId(contentTestId)).toBeInTheDocument(); + }); + + it('should expand the component when clicking on the arrow on header', () => { + const { getByTestId } = render( + <ExpandableSection expanded={false} title={title} data-test-subj={testId}> + {children} + </ExpandableSection> + ); + + getByTestId(headerTestId).click(); + expect(getByTestId(contentTestId)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx new file mode 100644 index 0000000000000..4cb7595798fef --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/expandable_section.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiAccordion, EuiFlexGroup, EuiSpacer, EuiTitle, useGeneratedHtmlId } from '@elastic/eui'; +import type { VFC } from 'react'; +import React from 'react'; + +export const HEADER_TEST_ID = 'Header'; +export const CONTENT_TEST_ID = 'Content'; + +export interface DescriptionSectionProps { + /** + * Boolean to allow the component to be expanded or collapsed on first render + */ + expanded: boolean; + /** + * Title value to render in the header of the accordion + */ + title: string; + /** + * React component to render in the expandable section of the accordion + */ + children: React.ReactNode; + /** + * Prefix data-test-subj to use for the header and expandable section of the accordion + */ + ['data-test-subj']?: string; +} + +/** + * Component used to render multiple sections in the Overview tab + * - Description + * - Investigation + * - Visualizations + * - Insights + */ +export const ExpandableSection: VFC<DescriptionSectionProps> = ({ + expanded, + title, + children, + 'data-test-subj': dataTestSub, +}) => { + const accordionId = useGeneratedHtmlId({ prefix: 'accordion' }); + + const headerDataTestSub = dataTestSub + HEADER_TEST_ID; + const contentDataTestSub = dataTestSub + CONTENT_TEST_ID; + + const header = ( + <EuiTitle size="xs" data-test-subj={headerDataTestSub}> + <h4>{title}</h4> + </EuiTitle> + ); + + return ( + <EuiAccordion id={accordionId} buttonContent={header} initialIsOpen={expanded}> + <EuiSpacer size="m" /> + <EuiFlexGroup gutterSize="none" direction="column" data-test-subj={contentDataTestSub}> + {children} + </EuiFlexGroup> + </EuiAccordion> + ); +}; + +ExpandableSection.displayName = 'ExpandableSection'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx index 5846303ff6dab..4724b6f9a9c49 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx @@ -97,6 +97,31 @@ describe('<HighlightedFields />', () => { </ThemeProvider> ); + getByTestId(HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID).click(); + expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument(); + }); + + it('should navigate to table tab when clicking on the link button', () => { + const flyoutContextValue = { + openRightPanel: jest.fn(), + } as unknown as ExpandableFlyoutContext; + const panelContextValue = { + eventId: 'eventId', + indexName: 'indexName', + dataFormattedForFieldBrowser: [], + browserFields: {}, + } as unknown as RightPanelContext; + + const { getByTestId } = render( + <ThemeProvider theme={mockTheme}> + <ExpandableFlyoutContext.Provider value={flyoutContextValue}> + <RightPanelContext.Provider value={panelContextValue}> + <HighlightedFields /> + </RightPanelContext.Provider> + </ExpandableFlyoutContext.Provider> + </ThemeProvider> + ); + getByTestId(HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID).click(); getByTestId(HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK).click(); expect(flyoutContextValue.openRightPanel).toHaveBeenCalledWith({ @@ -107,7 +132,6 @@ describe('<HighlightedFields />', () => { indexName: panelContextValue.indexName, }, }); - expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument(); }); it('should render empty component if dataFormattedForFieldBrowser is null', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx index 38e9dc6a1f122..c983b59864d7b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.stories.tsx @@ -7,6 +7,7 @@ import React from 'react'; import type { Story } from '@storybook/react'; +import { mockSearchHit } from '../mocks/mock_context'; import { RightPanelContext } from '../context'; import { MitreAttack } from './mitre_attack'; @@ -16,33 +17,7 @@ export default { }; export const Default: Story<void> = () => { - const contextValue = { - searchHit: { - fields: { - 'kibana.alert.rule.parameters': [ - { - threat: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: '123', - reference: 'https://attack.mitre.org/tactics/123', - name: 'Tactic', - }, - technique: [ - { - id: '456', - reference: 'https://attack.mitre.org/techniques/456', - name: 'Technique', - }, - ], - }, - ], - }, - ], - }, - }, - } as unknown as RightPanelContext; + const contextValue = { searchHit: mockSearchHit } as unknown as RightPanelContext; return ( <RightPanelContext.Provider value={contextValue}> diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx index c51bad7315d43..f0bc9bd993f66 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.test.tsx @@ -10,36 +10,11 @@ import { render } from '@testing-library/react'; import { MitreAttack } from './mitre_attack'; import { RightPanelContext } from '../context'; import { MITRE_ATTACK_DETAILS_TEST_ID, MITRE_ATTACK_TITLE_TEST_ID } from './test_ids'; +import { mockSearchHit } from '../mocks/mock_context'; describe('<MitreAttack />', () => { it('should render mitre attack information', () => { - const contextValue = { - searchHit: { - fields: { - 'kibana.alert.rule.parameters': [ - { - threat: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: '123', - reference: 'https://attack.mitre.org/tactics/123', - name: 'Tactic', - }, - technique: [ - { - id: '456', - reference: 'https://attack.mitre.org/techniques/456', - name: 'Technique', - }, - ], - }, - ], - }, - ], - }, - }, - } as unknown as RightPanelContext; + const contextValue = { searchHit: mockSearchHit } as unknown as RightPanelContext; const { getByTestId } = render( <RightPanelContext.Provider value={contextValue}> diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.stories.tsx new file mode 100644 index 0000000000000..a86b078f9f81a --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.stories.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Story } from '@storybook/react'; +import { Reason } from './reason'; +import { RightPanelContext } from '../context'; + +export default { + component: Reason, + title: 'Flyout/Reason', +}; + +// TODO to get this working, we need to spent some time getting all the foundation items for storybook +// (ReduxStoreProvider, CellActionsProvider...) similarly to how it was done for the TestProvidersComponent +// see ticket https://github.com/elastic/security-team/issues/6223 +// export const Default: Story<void> = () => { +// const panelContextValue = { +// dataAsNestedObject: mockDataAsNestedObject, +// dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, +// } as unknown as RightPanelContext; +// +// return ( +// <RightPanelContext.Provider value={panelContextValue}> +// <Reason /> +// </RightPanelContext.Provider> +// ); +// }; + +export const Empty: Story<void> = () => { + const panelContextValue = { + dataFormattedForFieldBrowser: {}, + } as unknown as RightPanelContext; + + return ( + <RightPanelContext.Provider value={panelContextValue}> + <Reason /> + </RightPanelContext.Provider> + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx new file mode 100644 index 0000000000000..d2a7c90011d68 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { REASON_TITLE_TEST_ID } from './test_ids'; +import { Reason } from './reason'; +import { RightPanelContext } from '../context'; +import { mockDataAsNestedObject, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; +import { euiDarkVars } from '@kbn/ui-theme'; +import { ThemeProvider } from 'styled-components'; + +describe('<Reason />', () => { + it('should render the component', () => { + const panelContextValue = { + dataAsNestedObject: mockDataAsNestedObject, + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, + } as unknown as RightPanelContext; + + const { getByTestId } = render( + <ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}> + <RightPanelContext.Provider value={panelContextValue}> + <Reason /> + </RightPanelContext.Provider> + </ThemeProvider> + ); + + expect(getByTestId(REASON_TITLE_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null if dataFormattedForFieldBrowser is null', () => { + const panelContextValue = { + dataAsNestedObject: {}, + } as unknown as RightPanelContext; + + const { baseElement } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Reason /> + </RightPanelContext.Provider> + ); + + expect(baseElement).toMatchInlineSnapshot(` + <body> + <div /> + </body> + `); + }); + + it('should render null if dataAsNestedObject is null', () => { + const panelContextValue = { + dataFormattedForFieldBrowser: [], + } as unknown as RightPanelContext; + + const { baseElement } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Reason /> + </RightPanelContext.Provider> + ); + + expect(baseElement).toMatchInlineSnapshot(` + <body> + <div /> + </body> + `); + }); + it('should render null if renderer is null', () => { + const panelContextValue = { + dataAsNestedObject: {}, + dataFormattedForFieldBrowser: [], + } as unknown as RightPanelContext; + + const { baseElement } = render( + <RightPanelContext.Provider value={panelContextValue}> + <Reason /> + </RightPanelContext.Provider> + ); + + expect(baseElement).toMatchInlineSnapshot(` + <body> + <div /> + </body> + `); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx new file mode 100644 index 0000000000000..b6633ac42c46b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FC } from 'react'; +import React, { useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { REASON_DETAILS_TEST_ID, REASON_TITLE_TEST_ID } from './test_ids'; +import { ALERT_REASON_TITLE, DOCUMENT_REASON_TITLE } from './translations'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; +import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; +import { getRowRenderer } from '../../../timelines/components/timeline/body/renderers/get_row_renderer'; +import { useRightPanelContext } from '../context'; + +/** + * Displays the information provided by the rowRenderer. Supports multiple types of documents. + */ +export const Reason: FC = () => { + const { dataAsNestedObject, dataFormattedForFieldBrowser } = useRightPanelContext(); + const { isAlert } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + + const renderer = useMemo( + () => + dataAsNestedObject != null + ? getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }) + : null, + [dataAsNestedObject] + ); + + if (!dataFormattedForFieldBrowser || !dataAsNestedObject || !renderer) { + return null; + } + + return ( + <EuiFlexGroup direction="column" gutterSize="s"> + <EuiFlexItem data-test-subj={REASON_TITLE_TEST_ID}> + <EuiTitle size="xxs"> + <h5>{isAlert ? ALERT_REASON_TITLE : DOCUMENT_REASON_TITLE}</h5> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem data-test-subj={REASON_DETAILS_TEST_ID}> + {renderer.renderRow({ + contextId: 'event-details', + data: dataAsNestedObject, + isDraggable: false, + scopeId: 'global', + })} + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +Reason.displayName = 'Reason'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts index a3ca8a12265b5..4a6ceabae57e3 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts @@ -5,11 +5,24 @@ * 2.0. */ +import { CONTENT_TEST_ID, HEADER_TEST_ID } from './expandable_section'; + export const FLYOUT_HEADER_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutHeaderTitle'; export const EXPAND_DETAILS_BUTTON_TEST_ID = 'securitySolutionDocumentDetailsFlyoutHeaderExpandDetailButton'; export const COLLAPSE_DETAILS_BUTTON_TEST_ID = 'securitySolutionDocumentDetailsFlyoutHeaderCollapseDetailButton'; +export const DESCRIPTION_SECTION_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutDescriptionSection'; +export const DESCRIPTION_SECTION_HEADER_TEST_ID = DESCRIPTION_SECTION_TEST_ID + HEADER_TEST_ID; +export const DESCRIPTION_SECTION_CONTENT_TEST_ID = DESCRIPTION_SECTION_TEST_ID + CONTENT_TEST_ID; +export const DESCRIPTION_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutDescriptionTitle'; +export const DESCRIPTION_DETAILS_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutDescriptionDetails'; +export const DESCRIPTION_EXPAND_BUTTON_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutDescriptionExpandButton'; +export const REASON_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutReasonTitle'; +export const REASON_DETAILS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutReasonDetails'; export const MITRE_ATTACK_TITLE_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackTitle'; export const MITRE_ATTACK_DETAILS_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackDetails'; export const FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID = diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts index 4ae41a5abcf4c..3d3a21b7faa0b 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts @@ -36,6 +36,53 @@ export const RISK_SCORE_TITLE = i18n.translate( } ); +export const DESCRIPTION_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.descriptionTitle', + { + defaultMessage: 'Description', + } +); + +export const RULE_DESCRIPTION_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.ruleDescriptionTitle', + { + defaultMessage: 'Rule description', + } +); + +export const DOCUMENT_DESCRIPTION_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.documentDescriptionTitle', + { + defaultMessage: 'Document description', + } +); +export const DOCUMENT_DESCRIPTION_EXPAND_BUTTON = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.documentDescriptionExpandButton', + { + defaultMessage: 'Expand', + } +); +export const DOCUMENT_DESCRIPTION_COLLAPSE_BUTTON = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.documentDescriptionCollapseButton', + { + defaultMessage: 'Collapse', + } +); + +export const ALERT_REASON_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.alertReasonTitle', + { + defaultMessage: 'Alert reason', + } +); + +export const DOCUMENT_REASON_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.documentReasonTitle', + { + defaultMessage: 'Document reason', + } +); + export const HIGHLIGHTED_FIELDS_TITLE = i18n.translate( 'xpack.securitySolution.flyout.documentDetails.highlightedFieldsTitle', { defaultMessage: 'Highlighted fields' } diff --git a/x-pack/plugins/security_solution/public/flyout/right/context.tsx b/x-pack/plugins/security_solution/public/flyout/right/context.tsx index afbb8e200c198..ce2a2df466715 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/context.tsx @@ -10,6 +10,7 @@ import { css } from '@emotion/react'; import React, { createContext, useContext, useMemo } from 'react'; import type { SearchHit } from '@kbn/es-types'; import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { useTimelineEventsDetails } from '../../timelines/containers/details'; import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; import { useSpaceId } from '../../common/hooks/use_space_id'; @@ -33,6 +34,10 @@ export interface RightPanelContext { * An object containing fields by type */ browserFields: BrowserFields | null; + /** + * An object with top level fields from the ECS object + */ + dataAsNestedObject: Ecs | null; /** * An array of field objects with category and value */ @@ -42,7 +47,7 @@ export interface RightPanelContext { */ searchHit: SearchHit<object> | undefined; /** - * + * Retrieves searchHit values for the provided field */ getFieldsData: (field: string) => unknown | unknown[]; } @@ -65,12 +70,13 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid ? SourcererScopeName.detections : SourcererScopeName.default; const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, dataFormattedForFieldBrowser, searchHit] = useTimelineEventsDetails({ - indexName: eventIndex, - eventId: id ?? '', - runtimeMappings: sourcererDataView.runtimeMappings, - skip: !id, - }); + const [loading, dataFormattedForFieldBrowser, searchHit, dataAsNestedObject] = + useTimelineEventsDetails({ + indexName: eventIndex, + eventId: id ?? '', + runtimeMappings: sourcererDataView.runtimeMappings, + skip: !id, + }); const getFieldsData = useGetFieldsData(searchHit?.fields); const contextValue = useMemo( @@ -80,6 +86,7 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid eventId: id, indexName, browserFields: sourcererDataView.browserFields, + dataAsNestedObject: dataAsNestedObject as unknown as Ecs, dataFormattedForFieldBrowser, searchHit: searchHit as SearchHit<object>, getFieldsData, @@ -89,6 +96,7 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid id, indexName, sourcererDataView.browserFields, + dataAsNestedObject, dataFormattedForFieldBrowser, searchHit, getFieldsData, diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts index d87a230429fd9..6765589b668da 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts @@ -24,23 +24,21 @@ export const mockGetFieldsData = (field: string): string[] => { }; /** - * Mock an array that will allow rendering a correct header: - * - rule name - * - timestamp + * Mock an array of fields for an alert */ export const mockDataFormattedForFieldBrowser = [ { category: 'kibana', field: 'kibana.alert.rule.uuid', - values: ['123'], - originalValue: ['123'], + values: ['rule-uuid'], + originalValue: ['rule-uuid'], isObjectArray: false, }, { category: 'kibana', field: 'kibana.alert.rule.name', - values: ['test'], - originalValue: ['test'], + values: ['rule-name'], + originalValue: ['rule-name'], isObjectArray: false, }, { @@ -50,4 +48,66 @@ export const mockDataFormattedForFieldBrowser = [ originalValue: ['2023-01-01T01:01:01.000Z'], isObjectArray: false, }, + { + category: 'kibana', + field: 'kibana.alert.rule.description', + values: ['rule-description'], + originalValue: ['rule-description'], + isObjectArray: false, + }, ]; + +/** + * Mock an object of nested properties for an alert + */ +export const mockDataAsNestedObject = { + _id: '123', + '@timestamp': ['2023-01-01T01:01:01.000Z'], + event: { + category: ['malware'], + kind: ['signal'], + }, + host: { + name: ['host-name'], + }, + kibana: { + alert: { + rule: { + name: ['rule-name'], + }, + severity: ['low'], + }, + }, + process: { + name: ['process-name'], + }, +}; + +/** + * Mock the document result of the search for an alert + */ +export const mockSearchHit = { + fields: { + 'kibana.alert.rule.parameters': [ + { + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: '123', + reference: 'https://attack.mitre.org/tactics/123', + name: 'Tactic', + }, + technique: [ + { + id: '456', + reference: 'https://attack.mitre.org/techniques/456', + name: 'Technique', + }, + ], + }, + ], + }, + ], + }, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx index 0161f198f097f..d5da888c153fb 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx @@ -7,9 +7,9 @@ import type { FC } from 'react'; import React, { memo } from 'react'; -import { EuiPanel } from '@elastic/eui'; +import { EuiHorizontalRule, EuiPanel } from '@elastic/eui'; +import { DescriptionSection } from '../components/description_section'; import { HighlightedFields } from '../components/highlighted_fields'; -import { MitreAttack } from '../components/mitre_attack'; /** * Overview view displayed in the document details expandable flyout right section @@ -17,7 +17,8 @@ import { MitreAttack } from '../components/mitre_attack'; export const OverviewTab: FC = memo(() => { return ( <> - <MitreAttack /> + <DescriptionSection /> + <EuiHorizontalRule margin="l" /> <EuiPanel hasBorder hasShadow={false}> <HighlightedFields /> </EuiPanel> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx index f91a9eed0d165..5691c14d25b6c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx @@ -20,6 +20,7 @@ export interface GetBasicDataFromDetailsData { ruleName: string; timestamp: string; data: TimelineEventsDetailsItem[] | null; + ruleDescription: string; } export const useBasicDataFromDetailsData = ( @@ -32,6 +33,11 @@ export const useBasicDataFromDetailsData = ( [data] ); + const ruleDescription = useMemo( + () => getFieldValue({ category: 'kibana', field: 'kibana.alert.rule.description' }, data), + [data] + ); + const alertId = useMemo(() => getFieldValue({ category: '_id', field: '_id' }, data), [data]); const agentId = useMemo( @@ -64,8 +70,9 @@ export const useBasicDataFromDetailsData = ( ruleName, timestamp, data, + ruleDescription, }), - [agentId, alertId, hostName, isAlert, ruleName, timestamp, userName, data] + [agentId, alertId, hostName, isAlert, ruleName, timestamp, userName, data, ruleDescription] ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts index 04a892427774e..a73561b7a34ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts @@ -53,6 +53,7 @@ describe('legacyRules_notification_alert_type', () => { tags: [], consumer: 'foo', producer: 'foo', + revision: 0, ruleTypeId: 'ruleType', ruleTypeName: 'Name of rule', enabled: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index 25e971eadbeae..469d25e4b1c3f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -223,6 +223,7 @@ export const previewRulesRoute = async ( createdAt: new Date(), createdBy: username ?? 'preview-created-by', producer: 'preview-producer', + revision: 0, ruleTypeId, ruleTypeName, updatedAt: new Date(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts index 7c12639d7efdf..10a213a5d8b2f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts @@ -216,6 +216,7 @@ export const getRuleConfigMock = (type: string = 'rule-type'): SanitizedRuleConf ruleTypeId: `${type}-id`, ruleTypeName: type, muteAll: false, + revision: 0, snoozeSchedule: [], }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts index 5cdc73d4015b4..8e71a4dce49aa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts @@ -33,6 +33,7 @@ import { ALERT_RULE_NAME, ALERT_RULE_PARAMETERS, ALERT_RULE_PRODUCER, + ALERT_RULE_REVISION, ALERT_RULE_TAGS, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, @@ -71,6 +72,7 @@ describe('searchAfterAndBulkCreate', () => { [ALERT_RULE_EXECUTION_UUID]: '97e8f53a-4971-4935-bb54-9b8f86930cc7', [ALERT_RULE_NAME]: 'rule-name', [ALERT_RULE_PRODUCER]: 'siem', + [ALERT_RULE_REVISION]: 0, [ALERT_RULE_TYPE_ID]: 'siem.queryRule', [ALERT_RULE_UUID]: '2e051244-b3c6-4779-a241-e1b4f0beceb9', [SPACE_IDS]: ['default'], diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts index aa0b5d8a4d1fc..66d96c36d2eb9 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts @@ -716,6 +716,7 @@ async function invokeExecutor({ ruleTypeId: '', ruleTypeName: '', enabled: true, + revision: 0, schedule: { interval: '1h', }, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts index 3875f52dbfe52..11a02beddc96d 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts @@ -206,6 +206,7 @@ describe('ruleType', () => { ruleTypeId: '', ruleTypeName: '', enabled: true, + revision: 0, schedule: { interval: '1h', }, @@ -272,6 +273,7 @@ describe('ruleType', () => { ruleTypeId: '', ruleTypeName: '', enabled: true, + revision: 0, schedule: { interval: '1h', }, @@ -338,6 +340,7 @@ describe('ruleType', () => { ruleTypeId: '', ruleTypeName: '', enabled: true, + revision: 0, schedule: { interval: '1h', }, @@ -403,6 +406,7 @@ describe('ruleType', () => { ruleTypeId: '', ruleTypeName: '', enabled: true, + revision: 0, schedule: { interval: '1h', }, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts index e97c0df7bf1df..4b8962b4771cc 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts @@ -84,6 +84,7 @@ export default function alertTests({ getService }: FtrProviderContext) { }; }), producer: 'alertsFixture', + revision: 0, ruleTypeId: 'test.always-firing', ruleTypeName: 'Test: Always Firing', muteAll: false, @@ -432,6 +433,7 @@ instanceStateValue: true }; }), producer: 'alertsFixture', + revision: 1, ruleTypeId: 'test.always-firing', ruleTypeName: 'Test: Always Firing', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts index f50e0be3c03c5..b90bdd4202d42 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts @@ -123,6 +123,7 @@ export function alertTests({ getService }: FtrProviderContext, space: Space) { }; }), producer: 'alertsFixture', + revision: 0, ruleTypeId: 'test.always-firing', ruleTypeName: 'Test: Always Firing', muteAll: false, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts index 64da1c06fe666..7af3e0c00ed02 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts @@ -368,6 +368,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.rule.threat': [], 'kibana.alert.rule.to': 'now', 'kibana.alert.rule.references': [], + 'kibana.alert.rule.revision': 0, 'kibana.alert.rule.version': 1, 'kibana.alert.rule.exceptions_list': [], 'kibana.alert.rule.immutable': false, @@ -528,6 +529,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.rule.threat': [], 'kibana.alert.rule.to': 'now', 'kibana.alert.rule.references': [], + 'kibana.alert.rule.revision': 0, 'kibana.alert.rule.version': 1, 'kibana.alert.rule.exceptions_list': [], 'kibana.alert.rule.immutable': false, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts index 88976b8ad3b18..4a2aad16f70df 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts @@ -210,6 +210,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.rule.interval': '5m', 'kibana.alert.rule.max_signals': 100, 'kibana.alert.rule.references': [], + 'kibana.alert.rule.revision': 0, 'kibana.alert.rule.risk_score_mapping': [], 'kibana.alert.rule.rule_id': 'rule-1', 'kibana.alert.rule.severity_mapping': [],