From 261a2a6d237bf95c1314a86a5c20d70bdd02c472 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Tue, 18 Jan 2022 11:48:24 -0500 Subject: [PATCH] Addressing PR feedback --- .../cases/common/utils/user_actions.ts | 9 + .../user_action_tree/helpers.test.tsx | 195 +++++++++--------- .../components/user_action_tree/helpers.tsx | 6 +- .../migrations/user_actions.test.ts | 157 +++++++++++++- .../migrations/user_actions.ts | 96 ++++++++- .../tests/common/user_actions/migrations.ts | 52 ++++- .../kbn_archiver/cases/7.13.2/alerts.json | 67 ++++++ 7 files changed, 477 insertions(+), 105 deletions(-) diff --git a/x-pack/plugins/cases/common/utils/user_actions.ts b/x-pack/plugins/cases/common/utils/user_actions.ts index 7de0d7066eaed..ef8d2bb425e7d 100644 --- a/x-pack/plugins/cases/common/utils/user_actions.ts +++ b/x-pack/plugins/cases/common/utils/user_actions.ts @@ -16,3 +16,12 @@ export function isUpdateConnector(action?: string, actionFields?: string[]): boo export function isPush(action?: string, actionFields?: string[]): boolean { return action === 'push-to-service' && actionFields != null && actionFields.includes('pushed'); } + +export function isCreateComment(action?: string, actionFields?: string[]): boolean { + return ( + action === 'create' && + actionFields !== null && + actionFields !== undefined && + actionFields.includes('comment') + ); +} diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx index 953cb05f188ab..fa4f8147c031f 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx @@ -249,121 +249,130 @@ describe('User action tree helpers', () => { }); }); - describe.each([ - ['getRuleId', getRuleId], - ['getRuleName', getRuleName], - ])('%s', (name, funcToExec) => { - it('returns the first entry in the comment field', () => { - const comment = { - rule: { - id: ['1', '2'], - name: ['1', '2'], - }, - } as unknown as CommentRequestAlertType; - - expect(funcToExec(comment)).toEqual('1'); - }); + 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 CommentRequestAlertType; + + expect(funcToExec(comment)).toBeNull(); + }); - it('returns null if the comment field is an empty string', () => { - const comment = { - rule: { - id: '', - name: '', - }, - } as unknown as CommentRequestAlertType; + it('returns null if the comment field is an empty string in an array', () => { + const comment = { + rule: { + id: [''], + name: [''], + }, + } as unknown as CommentRequestAlertType; - expect(funcToExec(comment)).toBeNull(); - }); + 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 CommentRequestAlertType; + it('returns null if the comment does not have a rule field', () => { + const comment = {} as unknown as CommentRequestAlertType; - expect(funcToExec(comment)).toBeNull(); - }); + expect(funcToExec(comment)).toBeNull(); + }); - it('returns null if the comment does not have a rule field', () => { - const comment = {} as unknown as CommentRequestAlertType; + it('returns null if the signals and alert field is an empty string', () => { + const comment = {} as unknown as CommentRequestAlertType; + const alert = { + signal: { rule: { id: '', name: '' } }, + kibana: { alert: { rule: { uuid: '', name: '' } } }, + } as unknown as Ecs; - expect(funcToExec(comment)).toBeNull(); + expect(funcToExec(comment, alert)).toBeNull(); + }); }); - it('returns signal field', () => { - const comment = {} as unknown as CommentRequestAlertType; - const alert = { signal: { rule: { id: '1', name: '1' } } } as unknown as Ecs; - - expect(funcToExec(comment, alert)).toEqual('1'); - }); + 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 CommentRequestAlertType; + + expect(funcToExec(comment)).toEqual(expectedResult); + }); - it('returns kibana alert field', () => { - const comment = {} as unknown as CommentRequestAlertType; - const alert = { kibana: { alert: { rule: { uuid: '1', name: '1' } } } } as unknown as Ecs; + it('returns signal field', () => { + const comment = {} as unknown as CommentRequestAlertType; + const alert = { signal: { rule: { id: '1', name: 'Rule name1' } } } as unknown as Ecs; - expect(funcToExec(comment, alert)).toEqual('1'); - }); + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); - it('returns signal field even when kibana alert field is defined', () => { - const comment = {} as unknown as CommentRequestAlertType; - const alert = { - signal: { rule: { id: 'signal', name: 'signal' } }, - kibana: { alert: { rule: { uuid: '1', name: '1' } } }, - } as unknown as Ecs; + it('returns kibana alert field', () => { + const comment = {} as unknown as CommentRequestAlertType; + const alert = { + kibana: { alert: { rule: { uuid: '1', name: 'Rule name1' } } }, + } as unknown as Ecs; - expect(funcToExec(comment, alert)).toEqual('signal'); - }); + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); - it('returns the first entry in the signals field', () => { - const comment = {} as unknown as CommentRequestAlertType; - const alert = { - signal: { rule: { id: ['signal1', 'signal2'], name: ['signal1', 'signal2'] } }, - kibana: { alert: { rule: { uuid: '1', name: '1' } } }, - } as unknown as Ecs; + it('returns signal field even when kibana alert field is defined', () => { + const comment = {} as unknown as CommentRequestAlertType; + 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('signal1'); - }); + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); - it('returns the alert field if the signals field is an empty string', () => { - const comment = {} as unknown as CommentRequestAlertType; - const alert = { - signal: { rule: { id: '', name: '' } }, - kibana: { alert: { rule: { uuid: '1', name: '1' } } }, - } as unknown as Ecs; + it('returns the first entry in the signals field', () => { + const comment = {} as unknown as CommentRequestAlertType; + 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('1'); - }); + 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 CommentRequestAlertType; - const alert = { - signal: { rule: { id: [''], name: [''] } }, - kibana: { alert: { rule: { uuid: '1', name: '1' } } }, - } as unknown as Ecs; + it('returns the alert field if the signals field is an empty string', () => { + const comment = {} as unknown as CommentRequestAlertType; + const alert = { + signal: { rule: { id: '', name: '' } }, + kibana: { alert: { rule: { uuid: '1', name: 'Rule name1' } } }, + } as unknown as Ecs; - expect(funcToExec(comment, alert)).toEqual('1'); - }); + 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 CommentRequestAlertType; - const alert = { - signal: { rule: { id: [''], name: [''] } }, - kibana: { alert: { rule: { uuid: ['1', '2'], name: ['1', '2'] } } }, - } as unknown as Ecs; + it('returns the alert field if the signals field is an empty string in an array', () => { + const comment = {} as unknown as CommentRequestAlertType; + const alert = { + signal: { rule: { id: [''], name: [''] } }, + kibana: { alert: { rule: { uuid: '1', name: 'Rule name1' } } }, + } as unknown as Ecs; - expect(funcToExec(comment, alert)).toEqual('1'); - }); + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); - it('returns null if the signals and alert field is an empty string', () => { - const comment = {} as unknown as CommentRequestAlertType; - const alert = { - signal: { rule: { id: '', name: '' } }, - kibana: { alert: { rule: { uuid: '', name: '' } } }, - } as unknown as Ecs; + it('returns the alert field first item if the signals field is an empty string in an array', () => { + const comment = {} as unknown as CommentRequestAlertType; + 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)).toBeNull(); + expect(funcToExec(comment, alert)).toEqual(expectedResult); + }); }); }); }); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx index c37d6913c44dd..cd13d33bf8dd4 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx @@ -463,11 +463,7 @@ export interface Alert { } export const getFirstItem = (items?: string | string[] | null): string | null => { - if (items == null) { - return null; - } - - return Array.isArray(items) ? (items.length > 0 ? items[0] : null) : items; + return Array.isArray(items) ? items[0] : items ?? null; }; export const getRuleId = (comment: CommentRequestAlertType, alertData?: Ecs): string | null => diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts index 3d0cff814b7d4..e232615fac07d 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts @@ -20,7 +20,7 @@ import { createExternalService, createJiraConnector, } from '../../services/test_utils'; -import { userActionsConnectorIdMigration } from './user_actions'; +import { removeRuleInformation, userActionsConnectorIdMigration } from './user_actions'; const create_7_14_0_userAction = ( params: { @@ -600,4 +600,159 @@ describe('user action migrations', () => { }); }); }); + + 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: [], + }; + + expect(removeRuleInformation(doc, context)).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', + 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: [], + }; + + expect(removeRuleInformation(doc, context)).toEqual({ + ...doc, + attributes: { ...doc.attributes, rule: { id: null, name: null } }, + references: [], + }); + }); + + 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' }], + }; + + expect(removeRuleInformation(doc, context)).toEqual({ + ...doc, + attributes: { ...doc.attributes, rule: { id: null, name: null } }, + }); + }); + }); }); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts index 4d8395eb189fc..ee73ba6e92591 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts @@ -13,8 +13,13 @@ import { SavedObjectSanitizedDoc, SavedObjectMigrationContext, } from '../../../../../../src/core/server'; -import { isPush, isUpdateConnector, isCreateConnector } from '../../../common/utils/user_actions'; -import { ConnectorTypes } from '../../../common/api'; +import { + isPush, + isUpdateConnector, + isCreateConnector, + isCreateComment, +} from '../../../common/utils/user_actions'; +import { CommentRequestAlertType, CommentType, ConnectorTypes } from '../../../common/api'; import { extractConnectorIdFromJson } from '../../services/user_actions/transform'; import { UserActionFieldType } from '../../services/user_actions/types'; @@ -26,7 +31,11 @@ interface UserActions { old_value: string; } -interface UserActionUnmigratedConnectorDocument { +/** + * An interface for the values we need from a json blob style user action to determine what type of + * user action it is. + */ +interface TypedAndValueUserAction { action?: string; action_field?: string[]; new_value?: string | null; @@ -34,7 +43,7 @@ interface UserActionUnmigratedConnectorDocument { } export function userActionsConnectorIdMigration( - doc: SavedObjectUnsanitizedDoc, + doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext ): SavedObjectSanitizedDoc { const originalDocWithReferences = { ...doc, references: doc.references ?? [] }; @@ -67,7 +76,7 @@ function isConnectorUserAction(action?: string, actionFields?: string[]): boolea } function formatDocumentWithConnectorReferences( - doc: SavedObjectUnsanitizedDoc + doc: SavedObjectUnsanitizedDoc ): SavedObjectSanitizedDoc { const { new_value, old_value, action, action_field, ...restAttributes } = doc.attributes; const { references = [] } = doc; @@ -101,6 +110,74 @@ function formatDocumentWithConnectorReferences( }; } +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; + } + + return { + ...doc, + attributes: { + ...doc.attributes, + rule: { + id: null, + name: null, + }, + }, + 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 +): boolean { + return isCreateComment(action, actionFields) && isAlertObject(newValue); +} + +type AlertCommentOptional = Partial; + +function isAlertObject(data?: unknown | null): data is AlertCommentOptional { + const unsafeAlertData = data as AlertCommentOptional; + + return ( + unsafeAlertData !== undefined && + unsafeAlertData !== null && + (unsafeAlertData.type === CommentType.generatedAlert || + unsafeAlertData.type === CommentType.alert) + ); +} + export const userActionsMigrations = { '7.10.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => { const { action_field, new_value, old_value, ...restAttributes } = doc.attributes; @@ -146,4 +223,13 @@ export const userActionsMigrations = { return addOwnerToSO(doc); }, '7.16.0': userActionsConnectorIdMigration, + /* + * 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, }; diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts index 2dc4f740a6819..253413117ce70 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts @@ -11,16 +11,19 @@ import { CASES_URL, SECURITY_SOLUTION_OWNER, } from '../../../../../../plugins/cases/common/constants'; -import { getCaseUserActions } from '../../../../common/lib/utils'; +import { deleteAllCaseItems, getCaseUserActions } from '../../../../common/lib/utils'; import { CaseUserActionResponse, CaseUserActionsResponse, + CommentType, } 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.10.0', () => { @@ -215,6 +218,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')!; + + const newValDecoded = JSON.parse(userAction.new_value!); + expect(newValDecoded.type).to.be(CommentType.alert); + expect(newValDecoded.alertId).to.be( + '4eb4cd05b85bc65c7b9f22b776e0136f970f7538eb0d1b2e6e8c7d35b2e875cb' + ); + expect(newValDecoded.index).to.be('.internal.alerts-security.alerts-default-000001'); + expect(newValDecoded.rule.id).to.be(null); + expect(newValDecoded.rule.index).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')!; + + const newValDecoded = JSON.parse(userAction.new_value!); + expect(newValDecoded).to.not.have.property('rule'); + expect(newValDecoded.type).to.be('individual'); + expect(newValDecoded.title).to.be('A case'); + }); + }); }); } 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 index b5c8b0e78bcad..e7976cb49938d 100644 --- 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 @@ -133,3 +133,70 @@ "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" +}