diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/alert.test.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/alert.test.tsx new file mode 100644 index 0000000000000..bdffe3c50c4a4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/comment/alert.test.tsx @@ -0,0 +1,138 @@ +/* + * 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 { CommentResponseAlertsType } from '../../../../common/api'; +import { SnakeToCamelCase } from '../../../../common/types'; +import { getRuleId, getRuleName } from './alert'; +import { Ecs } from '../../../containers/types'; + +describe('rule getters', () => { + describe.each([ + ['getRuleId', getRuleId], + ['getRuleName', getRuleName], + ])('%s null checks', (name, funcToExec) => { + it('returns null if the comment field is an empty string', () => { + const comment = { + rule: { + id: '', + name: '', + }, + } as unknown as SnakeToCamelCase; + + expect(funcToExec(comment)).toBeNull(); + }); + + it('returns null if the comment field is an empty string in an array', () => { + const comment = { + rule: { + id: [''], + name: [''], + }, + } as unknown as SnakeToCamelCase; + + expect(funcToExec(comment)).toBeNull(); + }); + + it('returns null if the comment does not have a rule field', () => { + const comment = {} as unknown as SnakeToCamelCase; + + expect(funcToExec(comment)).toBeNull(); + }); + + it('returns null if the signals and alert field is an empty string', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + signal: { rule: { id: '', name: '' } }, + kibana: { alert: { rule: { uuid: '', name: '' } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toBeNull(); + }); + }); + + describe.each([ + ['getRuleId', getRuleId, '1'], + ['getRuleName', getRuleName, 'Rule name1'], + ])('%s', (name, funcToExec, expectedResult) => { + it('returns the first entry in the comment field', () => { + const comment = { + rule: { + id: ['1', '2'], + name: ['Rule name1', 'Rule name2'], + }, + } as unknown as SnakeToCamelCase; + + expect(funcToExec(comment)).toEqual(expectedResult); + }); + + it('returns signal field', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { signal: { rule: { id: '1', name: 'Rule name1' } } } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + + it('returns kibana alert field', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + kibana: { alert: { rule: { uuid: '1', name: 'Rule name1' } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + + it('returns signal field even when kibana alert field is defined', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + signal: { rule: { id: '1', name: 'Rule name1' } }, + kibana: { alert: { rule: { uuid: 'rule id1', name: 'other rule name1' } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + + it('returns the first entry in the signals field', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + signal: { rule: { id: '1', name: 'Rule name1' } }, + kibana: { alert: { rule: { uuid: 'rule id1', name: 'other rule name1' } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + + it('returns the alert field if the signals field is an empty string', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + signal: { rule: { id: '', name: '' } }, + kibana: { alert: { rule: { uuid: '1', name: 'Rule name1' } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + + it('returns the alert field if the signals field is an empty string in an array', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + signal: { rule: { id: [''], name: [''] } }, + kibana: { alert: { rule: { uuid: '1', name: 'Rule name1' } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + + it('returns the alert field first item if the signals field is an empty string in an array', () => { + const comment = {} as unknown as SnakeToCamelCase; + const alert = { + signal: { rule: { id: [''], name: [''] } }, + kibana: { alert: { rule: { uuid: ['1', '2'], name: ['Rule name1', 'Rule name2'] } } }, + } as unknown as Ecs; + + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx index 0341af259e402..91752261d1471 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx @@ -18,6 +18,7 @@ import { UserActionUsernameWithAvatar } from '../avatar_username'; import { AlertCommentEvent } from './alert_event'; import { UserActionCopyLink } from '../copy_link'; import { UserActionShowAlert } from './show_alert'; +import { Ecs } from '../../../containers/types'; type BuilderArgs = Pick< UserActionBuilderArgs, @@ -29,9 +30,6 @@ type BuilderArgs = Pick< | 'onShowAlertDetails' > & { comment: SnakeToCamelCase }; -const getFirstItem = (items: string | string[]) => - Array.isArray(items) ? (items.length > 0 ? items[0] : '') : items; - export const createAlertAttachmentUserActionBuilder = ({ userAction, comment, @@ -42,24 +40,16 @@ export const createAlertAttachmentUserActionBuilder = ({ onShowAlertDetails, }: BuilderArgs): ReturnType => ({ build: () => { - const alertId = getFirstItem(comment.alertId); - const alertIndex = getFirstItem(comment.index); + const alertId = getNonEmptyField(comment.alertId); + const alertIndex = getNonEmptyField(comment.index); - if (isEmpty(alertId)) { + if (!alertId || !alertIndex) { return []; } - const ruleId = - comment?.rule?.id ?? - alertData[alertId]?.signal?.rule?.id?.[0] ?? - get(alertData[alertId], ALERT_RULE_UUID)[0] ?? - null; - - const ruleName = - comment?.rule?.name ?? - alertData[alertId]?.signal?.rule?.name?.[0] ?? - get(alertData[alertId], ALERT_RULE_NAME)[0] ?? - null; + const alertField: Ecs | undefined = alertData[alertId]; + const ruleId = getRuleId(comment, alertField); + const ruleName = getRuleName(comment, alertField); return [ { @@ -104,3 +94,51 @@ export const createAlertAttachmentUserActionBuilder = ({ ]; }, }); + +const getFirstItem = (items?: string | string[] | null): string | null => { + return Array.isArray(items) ? items[0] : items ?? null; +}; + +export const getRuleId = (comment: BuilderArgs['comment'], alertData?: Ecs): string | null => + getRuleField({ + commentRuleField: comment?.rule?.id, + alertData, + signalRuleFieldPath: 'signal.rule.id', + kibanaAlertFieldPath: ALERT_RULE_UUID, + }); + +export const getRuleName = (comment: BuilderArgs['comment'], alertData?: Ecs): string | null => + getRuleField({ + commentRuleField: comment?.rule?.name, + alertData, + signalRuleFieldPath: 'signal.rule.name', + kibanaAlertFieldPath: ALERT_RULE_NAME, + }); + +const getRuleField = ({ + commentRuleField, + alertData, + signalRuleFieldPath, + kibanaAlertFieldPath, +}: { + commentRuleField: string | string[] | null | undefined; + alertData: Ecs | undefined; + signalRuleFieldPath: string; + kibanaAlertFieldPath: string; +}): string | null => { + const field = + getNonEmptyField(commentRuleField) ?? + getNonEmptyField(get(alertData, signalRuleFieldPath)) ?? + getNonEmptyField(get(alertData, kibanaAlertFieldPath)); + + return field; +}; + +function getNonEmptyField(field: string | string[] | undefined | null): string | null { + const firstItem = getFirstItem(field); + if (firstItem == null || isEmpty(firstItem)) { + return null; + } + + return firstItem; +} diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts index 1983ae0db51d3..77a9382f12389 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts @@ -9,12 +9,14 @@ import { createCommentsMigrations, mergeMigrationFunctionMaps, migrateByValueLensVisualizations, + removeRuleInformation, stringifyCommentWithoutTrailingNewline, } from './comments'; import { getLensVisualizations, parseCommentString, } from '../../../common/utils/markdown_plugins/utils'; +import { CommentType } from '../../../common/api'; import { savedObjectsServiceMock } from '../../../../../../src/core/server/mocks'; import { makeLensEmbeddableFactory } from '../../../../lens/server/embeddable/make_lens_embeddable_factory'; @@ -396,4 +398,79 @@ describe('comments migrations', () => { ); }); }); + + describe('removeRuleInformation', () => { + it('does not modify non-alert comment', () => { + const doc = { + id: '123', + attributes: { + type: 'user', + }, + type: 'abc', + references: [], + }; + + expect(removeRuleInformation(doc)).toEqual(doc); + }); + + it('sets the rule fields to null', () => { + const doc = { + id: '123', + type: 'abc', + attributes: { + type: CommentType.alert, + rule: { + id: '123', + name: 'hello', + }, + }, + }; + + expect(removeRuleInformation(doc)).toEqual({ + ...doc, + attributes: { ...doc.attributes, rule: { id: null, name: null } }, + references: [], + }); + }); + + it('sets the rule fields to null for a generated alert', () => { + const doc = { + id: '123', + type: 'abc', + attributes: { + type: CommentType.generatedAlert, + rule: { + id: '123', + name: 'hello', + }, + }, + }; + + expect(removeRuleInformation(doc)).toEqual({ + ...doc, + attributes: { ...doc.attributes, rule: { id: null, name: null } }, + references: [], + }); + }); + + it('preserves the references field', () => { + const doc = { + id: '123', + type: 'abc', + attributes: { + type: CommentType.alert, + rule: { + id: '123', + name: 'hello', + }, + }, + references: [{ id: '123', name: 'hi', type: 'awesome' }], + }; + + expect(removeRuleInformation(doc)).toEqual({ + ...doc, + attributes: { ...doc.attributes, rule: { id: null, name: null } }, + }); + }); + }); }); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts index 0af9db13fce40..5ab1dab784f56 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts @@ -98,6 +98,15 @@ export const createCommentsMigrations = ( ): SavedObjectSanitizedDoc => { return addOwnerToSO(doc); }, + /* + * This is to fix the issue here: https://github.com/elastic/kibana/issues/123089 + * Instead of migrating the rule information in the references array which was risky for 8.0 + * we decided to remove the information since the UI will do the look up for the rule information if + * the backend returns it as null. + * + * The downside is it incurs extra query overhead. + **/ + '8.0.0': removeRuleInformation, }; return mergeMigrationFunctionMaps(commentsMigrations, embeddableMigrations); @@ -175,3 +184,29 @@ export const mergeMigrationFunctionMaps = ( return mergeWith({ ...obj1 }, obj2, customizer); }; + +export const removeRuleInformation = ( + doc: SavedObjectUnsanitizedDoc> +): SavedObjectSanitizedDoc => { + if ( + doc.attributes.type === CommentType.alert || + doc.attributes.type === CommentType.generatedAlert + ) { + return { + ...doc, + attributes: { + ...doc.attributes, + rule: { + id: null, + name: null, + }, + }, + references: doc.references ?? [], + }; + } + + return { + ...doc, + references: doc.references ?? [], + }; +}; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/alerts.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/alerts.test.ts new file mode 100644 index 0000000000000..77059d5b3c338 --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/alerts.test.ts @@ -0,0 +1,181 @@ +/* + * 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 { + SavedObject, + SavedObjectMigrationContext, + SavedObjectsMigrationLogger, +} from 'kibana/server'; +import { cloneDeep, omit } from 'lodash'; +import { migrationMocks } from 'src/core/server/mocks'; +import { removeRuleInformation } from './alerts'; + +describe('alert user actions', () => { + describe('removeRuleInformation', () => { + let context: jest.Mocked; + + beforeEach(() => { + context = migrationMocks.createContext(); + }); + + it('does not modify non-alert user action', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['description'], + }, + type: 'abc', + references: [], + }; + + expect(removeRuleInformation(doc, context)).toEqual(doc); + }); + + it('does not modify the document when it fails to decode the new_value field', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + new_value: '{"type":"alert",', + }, + type: 'abc', + references: [], + }; + + expect(removeRuleInformation(doc, context)).toEqual(doc); + + const log = context.log as jest.Mocked; + expect(log.error.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to migrate user action alerts with doc id: 123 version: 8.0.0 error: Unexpected end of JSON input", + Object { + "migrations": Object { + "userAction": Object { + "id": "123", + }, + }, + }, + ] + `); + }); + + it('does not modify the document when new_value is null', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + new_value: null, + }, + type: 'abc', + references: [], + }; + + expect(removeRuleInformation(doc, context)).toEqual(doc); + }); + + it('does not modify the document when new_value is undefined', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + }, + type: 'abc', + references: [], + }; + + expect(removeRuleInformation(doc, context)).toEqual(doc); + }); + + it('does not modify the document when the comment type is not an alert', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + new_value: + '{"type":"not_an_alert","alertId":"4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb","index":".internal.alerts-security.alerts-default-000001","rule":{"id":"43104810-7875-11ec-abc6-6f72e72f6004","name":"A rule"},"owner":"securitySolution"}', + }, + type: 'abc', + references: [], + }; + + expect(removeRuleInformation(doc, context)).toEqual(doc); + }); + + it('sets the rule fields to null', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + new_value: + '{"type":"alert","alertId":"4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb","index":".internal.alerts-security.alerts-default-000001","rule":{"id":"43104810-7875-11ec-abc6-6f72e72f6004","name":"A rule"},"owner":"securitySolution"}', + }, + type: 'abc', + references: [], + }; + + const newDoc = removeRuleInformation(doc, context) as SavedObject<{ new_value: string }>; + ensureRuleFieldsAreNull(newDoc); + + expect(docWithoutNewValue(newDoc)).toEqual(docWithoutNewValue(doc)); + }); + + it('sets the rule fields to null for a generated alert', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + new_value: + '{"type":"generated_alert","alertId":"4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb","index":".internal.alerts-security.alerts-default-000001","rule":{"id":"43104810-7875-11ec-abc6-6f72e72f6004","name":"A rule"},"owner":"securitySolution"}', + }, + type: 'abc', + references: [], + }; + + const newDoc = removeRuleInformation(doc, context) as SavedObject<{ new_value: string }>; + ensureRuleFieldsAreNull(newDoc); + + expect(docWithoutNewValue(newDoc)).toEqual(docWithoutNewValue(doc)); + }); + + it('preserves the references field', () => { + const doc = { + id: '123', + attributes: { + action: 'create', + action_field: ['comment'], + new_value: + '{"type":"generated_alert","alertId":"4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb","index":".internal.alerts-security.alerts-default-000001","rule":{"id":"43104810-7875-11ec-abc6-6f72e72f6004","name":"A rule"},"owner":"securitySolution"}', + }, + type: 'abc', + references: [{ id: '123', name: 'hi', type: 'awesome' }], + }; + + const newDoc = removeRuleInformation(doc, context) as SavedObject<{ new_value: string }>; + ensureRuleFieldsAreNull(newDoc); + + expect(docWithoutNewValue(newDoc)).toEqual(docWithoutNewValue(doc)); + }); + }); +}); + +const docWithoutNewValue = (doc: {}) => { + const copyOfDoc = cloneDeep(doc); + return omit(copyOfDoc, 'attributes.new_value'); +}; + +const ensureRuleFieldsAreNull = (doc: SavedObject<{ new_value: string }>) => { + const decodedNewValue = JSON.parse(doc.attributes.new_value); + + expect(decodedNewValue.rule).toEqual({ id: null, name: null }); +}; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/alerts.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/alerts.ts new file mode 100644 index 0000000000000..b2a825b80fd6e --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/alerts.ts @@ -0,0 +1,99 @@ +/* + * 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 { + SavedObjectMigrationContext, + SavedObjectSanitizedDoc, + SavedObjectUnsanitizedDoc, +} from 'kibana/server'; +import { CommentRequestAlertType, CommentType } from '../../../../common/api'; +import { logError } from '../utils'; +import { UserActionVersion800 } from './types'; + +/* eslint-disable @typescript-eslint/naming-convention */ + +export function removeRuleInformation( + doc: SavedObjectUnsanitizedDoc, + context: SavedObjectMigrationContext +): SavedObjectSanitizedDoc { + const originalDocWithReferences = { ...doc, references: doc.references ?? [] }; + + try { + const { new_value, action, action_field } = doc.attributes; + + const decodedNewValueData = decodeNewValue(new_value); + + if (!isAlertUserAction(action, action_field, decodedNewValueData)) { + return originalDocWithReferences; + } + + const encodedValue = JSON.stringify({ + ...decodedNewValueData, + rule: { + id: null, + name: null, + }, + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + new_value: encodedValue, + }, + references: doc.references ?? [], + }; + } catch (error) { + logError({ + id: doc.id, + context, + error, + docType: 'user action alerts', + docKey: 'userAction', + }); + + return originalDocWithReferences; + } +} + +function decodeNewValue(data?: string | null): unknown | null { + if (data === undefined || data === null) { + return null; + } + + return JSON.parse(data); +} + +function isAlertUserAction( + action?: string, + actionFields?: string[], + newValue?: unknown | null +): newValue is AlertCommentOptional { + return isCreateComment(action, actionFields) && isAlertObject(newValue); +} + +function isCreateComment(action?: string, actionFields?: string[]): boolean { + return ( + action === 'create' && + actionFields !== null && + actionFields !== undefined && + actionFields.includes('comment') + ); +} + +type AlertCommentOptional = Partial; + +function isAlertObject(data?: unknown | null): boolean { + const unsafeAlertData = data as AlertCommentOptional; + + return ( + unsafeAlertData !== undefined && + unsafeAlertData !== null && + (unsafeAlertData.type === CommentType.generatedAlert || + unsafeAlertData.type === CommentType.alert) + ); +} diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/connector_id.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/connector_id.ts index 1c61b7162a062..00261cd578239 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/connector_id.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/connector_id.ts @@ -28,7 +28,7 @@ import { } from '../../../common/constants'; import { getNoneCaseConnector } from '../../../common/utils'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../../actions/server'; -import { UserActionUnmigratedConnectorDocument } from './types'; +import { UserActionVersion800 } from './types'; import { logError } from '../utils'; import { USER_ACTION_OLD_ID_REF_NAME, USER_ACTION_OLD_PUSH_ID_REF_NAME } from './constants'; @@ -306,7 +306,7 @@ export function isConnectorUserAction(action?: string, actionFields?: string[]): } export function formatDocumentWithConnectorReferences( - doc: SavedObjectUnsanitizedDoc + doc: SavedObjectUnsanitizedDoc ): SavedObjectSanitizedDoc { const { new_value, old_value, action, action_field, ...restAttributes } = doc.attributes; const { references = [] } = doc; @@ -342,7 +342,7 @@ export function formatDocumentWithConnectorReferences( // 8.1.0 migration util functions export function userActionsConnectorIdMigration( - doc: SavedObjectUnsanitizedDoc, + doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext ): SavedObjectSanitizedDoc { const originalDocWithReferences = { ...doc, references: doc.references ?? [] }; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts index 7288b68483b87..d977e470a7fe3 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts @@ -14,6 +14,7 @@ import { } from '../../../../../../../src/core/server'; import { ConnectorTypes } from '../../../../common/api'; +import { removeRuleInformation } from './alerts'; import { userActionsConnectorIdMigration } from './connector_id'; import { payloadMigration } from './payload'; import { UserActions } from './types'; @@ -63,5 +64,6 @@ export const userActionsMigrations = { return addOwnerToSO(doc); }, '7.16.0': userActionsConnectorIdMigration, + '8.0.0': removeRuleInformation, '8.1.0': payloadMigration, }; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/types.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/types.ts index 65f566143b0ee..0c905dc784b11 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/types.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/types.ts @@ -15,7 +15,7 @@ export interface UserActions { owner: string; } -export interface UserActionUnmigratedConnectorDocument { +export interface UserActionVersion800 { action?: string; action_field?: string[]; new_value?: string | null; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts index 67e30987fabac..0711b78159d07 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts @@ -11,12 +11,15 @@ import { CASES_URL, SECURITY_SOLUTION_OWNER, } from '../../../../../../plugins/cases/common/constants'; -import { getComment } from '../../../../common/lib/utils'; +import { deleteAllCaseItems, getComment } from '../../../../common/lib/utils'; +import { CommentResponseAlertsType } from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const es = getService('es'); + const kibanaServer = getService('kibanaServer'); describe('migrations', () => { describe('7.11.0', () => { @@ -59,5 +62,42 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(comment.owner).to.be(SECURITY_SOLUTION_OWNER); }); }); + + describe('8.0.0', () => { + before(async () => { + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json' + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json' + ); + await deleteAllCaseItems(es); + }); + + it('removes the rule information from alert attachments', async () => { + const comment = (await getComment({ + supertest, + caseId: 'e49ad6e0-cf9d-11eb-a603-13e7747d215c', + commentId: 'ee59cdd0-cf9d-11eb-a603-13e7747d215c', + })) as CommentResponseAlertsType; + + expect(comment).to.have.property('rule'); + expect(comment.rule.id).to.be(null); + expect(comment.rule.name).to.be(null); + }); + + it('does not modify non-alert attachments', async () => { + const comment = (await getComment({ + supertest, + caseId: 'e49ad6e0-cf9d-11eb-a603-13e7747d215c', + commentId: 'ae59cdd0-cf9d-11eb-a603-13e7747d215c', + })) as CommentResponseAlertsType; + + expect(comment).to.not.have.property('rule'); + }); + }); }); } diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts index 62d1ba17ff4db..782af13ca6b5f 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts @@ -12,7 +12,7 @@ import { SECURITY_SOLUTION_OWNER, } from '../../../../../../plugins/cases/common/constants'; import { deleteAllCaseItems, getCaseUserActions } from '../../../../common/lib/utils'; -import { CaseUserActionsResponse } from '../../../../../../plugins/cases/common/api'; +import { CaseUserActionsResponse, CommentType } from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { @@ -661,6 +661,53 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); describe('8.0.0', () => { + before(async () => { + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json' + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json' + ); + await deleteAllCaseItems(es); + }); + + it('removes the rule information from alert user action', async () => { + const userActions = await getCaseUserActions({ + supertest, + caseID: 'e49ad6e0-cf9d-11eb-a603-13e7747d215c', + }); + + const userAction = getUserActionById(userActions, 'a5509250-cf9d-11eb-a603-13e7747d215c')!; + + expect(userAction.payload.comment.type).to.be(CommentType.alert); + expect(userAction.payload.comment.alertId).to.be( + '4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb' + ); + expect(userAction.payload.comment.index).to.be( + '.internal.alerts-security.alerts-default-000001' + ); + expect(userAction.payload.comment.rule.id).to.be(null); + expect(userAction.payload.comment.rule.name).to.be(null); + }); + + it('does not modify non-alert attachments', async () => { + const userActions = await getCaseUserActions({ + supertest, + caseID: 'e49ad6e0-cf9d-11eb-a603-13e7747d215c', + }); + + const userAction = getUserActionById(userActions, 'e5509250-cf9d-11eb-a603-13e7747d215c')!; + + expect(userAction.payload).to.not.have.property('rule'); + expect(userAction.payload.status).to.be('open'); + expect(userAction.payload.title).to.be('A case'); + }); + }); + + describe('8.1.0', () => { const CASE_ID = '5257a000-5e7d-11ec-9ee9-cd64f0b77b3c'; before(async () => { diff --git a/x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json b/x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json new file mode 100644 index 0000000000000..e7976cb49938d --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/cases/7.13.2/alerts.json @@ -0,0 +1,202 @@ +{ + "attributes": { + "closed_at": null, + "closed_by": null, + "connector": { + "fields": [ + { + "key": "issueType", + "value": "10002" + }, + { + "key": "parent", + "value": null + }, + { + "key": "priority", + "value": null + } + ], + "id": "d68508f0-cf9d-11eb-a603-13e7747d215c", + "name": "Test Jira", + "type": ".jira" + }, + "created_at": "2021-06-17T18:57:41.682Z", + "created_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + }, + "description": "asdf", + "external_service": { + "connector_id": "d68508f0-cf9d-11eb-a603-13e7747d215c", + "connector_name": "Test Jira", + "external_id": "10106", + "external_title": "TPN-99", + "external_url": "https://cases-testing.atlassian.net/browse/TPN-99", + "pushed_at": "2021-06-17T18:57:45.524Z", + "pushed_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + } + }, + "settings": { + "syncAlerts": true + }, + "status": "open", + "tags": [ + "some tag" + ], + "title": "A case", + "type": "individual", + "updated_at": "2021-06-17T18:57:58.037Z", + "updated_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + } + }, + "id": "e49ad6e0-cf9d-11eb-a603-13e7747d215c", + "coreMigrationVersion": "7.13.2", + "migrationVersion": { + "cases": "7.12.0" + }, + "references": [ + ], + "type": "cases", + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "attributes": { + "associationType": "case", + "alertId": "123", + "index": "index", + "rule": {"id": "id", "name": "name"}, + "created_at": "2021-06-17T18:57:58.037Z", + "created_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + }, + "pushed_at": null, + "pushed_by": null, + "type": "alert", + "updated_at": null, + "updated_by": null + }, + "coreMigrationVersion": "7.13.2", + "migrationVersion": { + "cases-comments": "7.12.0" + }, + "id": "ee59cdd0-cf9d-11eb-a603-13e7747d215c", + "references": [ + { + "id": "e49ad6e0-cf9d-11eb-a603-13e7747d215c", + "name": "associated-cases", + "type": "cases" + } + ], + "type": "cases-comments", + "updated_at": "2021-06-17T18:57:58.087Z" +} + +{ + "attributes": { + "associationType": "case", + "comment": "a comment", + "created_at": "2021-06-17T18:57:58.037Z", + "created_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + }, + "pushed_at": null, + "pushed_by": null, + "type": "user", + "updated_at": null, + "updated_by": null + }, + "coreMigrationVersion": "7.13.2", + "migrationVersion": { + "cases-comments": "7.12.0" + }, + "id": "ae59cdd0-cf9d-11eb-a603-13e7747d215c", + "references": [ + { + "id": "e49ad6e0-cf9d-11eb-a603-13e7747d215c", + "name": "associated-cases", + "type": "cases" + } + ], + "type": "cases-comments", + "updated_at": "2021-06-17T18:57:58.087Z" +} + +{ + "attributes": { + "action": "create", + "action_at": "2021-06-17T18:57:41.682Z", + "action_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + }, + "action_field": [ + "description", + "status", + "tags", + "title", + "connector", + "settings" + ], + "new_value": "{\"type\":\"individual\",\"title\":\"A case\",\"tags\":[\"some tag\"],\"description\":\"asdf\",\"connector\":{\"id\":\"d68508f0-cf9d-11eb-a603-13e7747d215c\",\"name\":\"Test Jira\",\"type\":\".jira\",\"fields\":{\"issueType\":\"10002\",\"parent\":null,\"priority\":null}},\"settings\":{\"syncAlerts\":true}}", + "old_value": null + }, + "coreMigrationVersion": "7.13.2", + "migrationVersion": { + "cases-user-actions": "7.10.0" + }, + "id": "e5509250-cf9d-11eb-a603-13e7747d215c", + "references": [ + { + "id": "e49ad6e0-cf9d-11eb-a603-13e7747d215c", + "name": "associated-cases", + "type": "cases" + } + ], + "type": "cases-user-actions", + "updated_at": "2021-06-17T18:57:42.925Z" +} + +{ + "attributes": { + "action": "create", + "action_at": "2021-06-17T18:57:41.682Z", + "action_by": { + "email": null, + "full_name": "j@j.com", + "username": "711621466" + }, + "action_field": [ + "comment" + ], + "new_value": "{\"type\":\"alert\",\"alertId\":\"4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb\",\"index\":\".internal.alerts-security.alerts-default-000001\",\"rule\":{\"id\":\"43104810-7875-11ec-abc6-6f72e72f6004\",\"name\":\"A rule\"}}", + "old_value": null + }, + "coreMigrationVersion": "7.13.2", + "migrationVersion": { + "cases-user-actions": "7.10.0" + }, + "id": "a5509250-cf9d-11eb-a603-13e7747d215c", + "references": [ + { + "id": "e49ad6e0-cf9d-11eb-a603-13e7747d215c", + "name": "associated-cases", + "type": "cases" + } + ], + "type": "cases-user-actions", + "updated_at": "2021-06-17T18:57:42.925Z" +}