From 0913844d265247676aad2421608772886e6437ad Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 17 Aug 2021 13:02:26 -0400 Subject: [PATCH 01/14] Extracting saved object references before saving action_task_params saved object --- .../server/create_execute_function.test.ts | 127 +++++++++++++- .../actions/server/create_execute_function.ts | 32 +++- ..._references_for_action_task_params.test.ts | 155 ++++++++++++++++++ ...ct_so_references_for_action_task_params.ts | 53 ++++++ x-pack/plugins/actions/server/lib/index.ts | 1 + .../server/lib/related_saved_objects.ts | 22 +-- 6 files changed, 366 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts create mode 100644 x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index ee8064d2aadc5..f02b7b8229050 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -72,11 +72,19 @@ describe('execute()', () => { expect(savedObjectsClient.create).toHaveBeenCalledWith( 'action_task_params', { - actionId: '123', + actionId: 'actionRef', params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), }, - {} + { + references: [ + { + id: '123', + name: 'actionRef', + type: 'action', + }, + ], + } ); expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('123', 'mock-action', { notifyUsage: true, @@ -123,19 +131,32 @@ describe('execute()', () => { expect(savedObjectsClient.create).toHaveBeenCalledWith( 'action_task_params', { - actionId: '123', + actionId: 'actionRef', params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [ { - id: 'some-id', + ref: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', }, ], }, - {} + { + references: [ + { + id: '123', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + } ); }); @@ -214,6 +235,102 @@ describe('execute()', () => { ); }); + test('schedules the action with all given parameters with a preconfigured action and relatedSavedObjects', async () => { + const executeFn = createExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + actionTypeRegistry: actionTypeRegistryMock.create(), + isESOCanEncrypt: true, + preconfiguredActions: [ + { + id: '123', + actionTypeId: 'mock-action-preconfigured', + config: {}, + isPreconfigured: true, + name: 'x', + secrets: {}, + }, + ], + }); + const source = { type: 'alert', id: uuid.v4() }; + + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: 'action', + attributes: { + actionTypeId: 'mock-action', + }, + references: [], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '234', + type: 'action_task_params', + attributes: {}, + references: [], + }); + await executeFn(savedObjectsClient, { + id: '123', + params: { baz: false }, + spaceId: 'default', + apiKey: Buffer.from('123:abc').toString('base64'), + source: asSavedObjectExecutionSource(source), + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }); + expect(mockTaskManager.schedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:mock-action-preconfigured", + }, + ] + `); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalledWith( + 'action_task_params', + { + actionId: '123', + params: { baz: false }, + apiKey: Buffer.from('123:abc').toString('base64'), + relatedSavedObjects: [ + { + ref: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + { + references: [ + { + id: source.id, + name: 'source', + type: source.type, + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + } + ); + }); + test('throws when passing isESOCanEncrypt with false as a value', async () => { const executeFn = createExecutionEnqueuerFunction({ taskManager: mockTaskManager, diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index bcad5f20d9ba7..0401082cbf138 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -15,7 +15,7 @@ import { } from './types'; import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; -import { isSavedObjectExecutionSource } from './lib'; +import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; import { RelatedSavedObjects } from './lib/related_saved_objects'; interface CreateExecuteFunctionOptions { @@ -53,7 +53,11 @@ export function createExecutionEnqueuerFunction({ ); } - const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const { action, isPreconfigured } = await getAction( + unsecuredSavedObjectsClient, + preconfiguredActions, + id + ); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -61,15 +65,25 @@ export function createExecutionEnqueuerFunction({ actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); } + // Get saved object references from action ID and relatedSavedObjects + const { actionIdOrRef, references, relatedSavedObjectRefs } = extractSavedObjectReferences( + id, + isPreconfigured, + relatedSavedObjects + ); + const executionSourceReference = executionSourceAsSavedObjectReferences(source); + const actionTaskParamsRecord = await unsecuredSavedObjectsClient.create( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, { - actionId: id, + actionId: actionIdOrRef, params, apiKey, - relatedSavedObjects, + relatedSavedObjects: relatedSavedObjectRefs, }, - executionSourceAsSavedObjectReferences(source) + { + references: [...(executionSourceReference.references ?? []), ...(references ?? [])], + } ); await taskManager.schedule({ @@ -93,7 +107,7 @@ export function createEphemeralExecutionEnqueuerFunction({ unsecuredSavedObjectsClient: SavedObjectsClientContract, { id, params, spaceId, source, apiKey }: ExecuteOptions ): Promise { - const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const { action } = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -148,12 +162,12 @@ async function getAction( unsecuredSavedObjectsClient: SavedObjectsClientContract, preconfiguredActions: PreConfiguredAction[], actionId: string -): Promise { +): Promise<{ action: PreConfiguredAction | RawAction; isPreconfigured: boolean }> { const pcAction = preconfiguredActions.find((action) => action.id === actionId); if (pcAction) { - return pcAction; + return { action: pcAction, isPreconfigured: true }; } const { attributes } = await unsecuredSavedObjectsClient.get('action', actionId); - return attributes; + return { action: attributes, isPreconfigured: false }; } diff --git a/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts b/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts new file mode 100644 index 0000000000000..7800803bd20ef --- /dev/null +++ b/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts @@ -0,0 +1,155 @@ +/* + * 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 { extractSavedObjectReferences } from './extract_so_references_for_action_task_params'; + +describe('extractSavedObjectReferences()', () => { + test('correctly extracts action id into references array', () => { + expect(extractSavedObjectReferences('my-action-id', false)).toEqual({ + actionIdOrRef: 'actionRef', + references: [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('correctly extracts related saved object into references array', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', false, relatedSavedObjects)).toEqual({ + actionIdOrRef: 'actionRef', + references: [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectRefs: [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly skips extracting action id into references array if action is preconfigured', () => { + expect(extractSavedObjectReferences('my-action-id', true)).toEqual({ + actionIdOrRef: 'my-action-id', + references: [], + }); + }); + + test('correctly extracts related saved object into references array if action is preconfigured', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', true, relatedSavedObjects)).toEqual({ + actionIdOrRef: 'my-action-id', + references: [ + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectRefs: [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts b/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts new file mode 100644 index 0000000000000..ead64c8b4c31d --- /dev/null +++ b/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts @@ -0,0 +1,53 @@ +/* + * 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 { SavedObjectReference } from 'src/core/server/types'; +import { RelatedSavedObjectRef, RelatedSavedObjects } from './related_saved_objects'; + +const ACTION_REF_NAME = `actionRef`; + +export function extractSavedObjectReferences( + actionId: string, + isPreconfigured: boolean, + relatedSavedObjects?: RelatedSavedObjects +): { + actionIdOrRef: string; + references: SavedObjectReference[]; + relatedSavedObjectRefs?: RelatedSavedObjectRef[]; +} { + const references: SavedObjectReference[] = []; + const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; + + // Add action saved object to reference if it is not preconfigured + if (!isPreconfigured) { + references.push({ + id: actionId, + name: ACTION_REF_NAME, + type: 'action', + }); + } + + // Add related saved objects, if any + (relatedSavedObjects ?? []).forEach((relatedSavedObject, index) => { + const { id, ...restRelatedSavedObject } = relatedSavedObject; + relatedSavedObjectRefs.push({ + ...restRelatedSavedObject, + ref: `related_${relatedSavedObject.type}_${index}`, + }); + references.push({ + id: relatedSavedObject.id, + name: `related_${relatedSavedObject.type}_${index}`, + type: relatedSavedObject.type, + }); + }); + + return { + actionIdOrRef: isPreconfigured ? actionId : ACTION_REF_NAME, + references, + ...(relatedSavedObjects ? { relatedSavedObjectRefs } : {}), + }; +} diff --git a/x-pack/plugins/actions/server/lib/index.ts b/x-pack/plugins/actions/server/lib/index.ts index fba47f9a0f995..92a99bf0361c5 100644 --- a/x-pack/plugins/actions/server/lib/index.ts +++ b/x-pack/plugins/actions/server/lib/index.ts @@ -13,6 +13,7 @@ export { ILicenseState, LicenseState } from './license_state'; export { verifyApiAccess } from './verify_api_access'; export { getActionTypeFeatureUsageName } from './get_action_type_feature_usage_name'; export { spaceIdToNamespace } from './space_id_to_namespace'; +export { extractSavedObjectReferences } from './extract_so_references_for_action_task_params'; export { ActionTypeDisabledError, ActionTypeDisabledReason, diff --git a/x-pack/plugins/actions/server/lib/related_saved_objects.ts b/x-pack/plugins/actions/server/lib/related_saved_objects.ts index 160587a3a9a8b..915580005d3fc 100644 --- a/x-pack/plugins/actions/server/lib/related_saved_objects.ts +++ b/x-pack/plugins/actions/server/lib/related_saved_objects.ts @@ -9,17 +9,19 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '../../../../../src/core/server'; export type RelatedSavedObjects = TypeOf; +export type RelatedSavedObjectRef = Omit, 'id'> & { + ref: string; +}; -const RelatedSavedObjectsSchema = schema.arrayOf( - schema.object({ - namespace: schema.maybe(schema.string({ minLength: 1 })), - id: schema.string({ minLength: 1 }), - type: schema.string({ minLength: 1 }), - // optional; for SO types like action/alert that have type id's - typeId: schema.maybe(schema.string({ minLength: 1 })), - }), - { defaultValue: [] } -); +const RelatedSavedObjectSchema = schema.object({ + namespace: schema.maybe(schema.string({ minLength: 1 })), + id: schema.string({ minLength: 1 }), + type: schema.string({ minLength: 1 }), + // optional; for SO types like action/alert that have type id's + typeId: schema.maybe(schema.string({ minLength: 1 })), +}); + +const RelatedSavedObjectsSchema = schema.arrayOf(RelatedSavedObjectSchema, { defaultValue: [] }); export function validatedRelatedSavedObjects(logger: Logger, data: unknown): RelatedSavedObjects { try { From 7d743595d4ca5c5a9e3dc5cebab6761b42c9cc2e Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 17 Aug 2021 14:15:32 -0400 Subject: [PATCH 02/14] Injecting saved object ids from references when reading action_task_param --- .../lib/action_task_params_utils.test.ts | 361 ++++++++++++++++++ ..._params.ts => action_task_params_utils.ts} | 28 +- ..._references_for_action_task_params.test.ts | 155 -------- x-pack/plugins/actions/server/lib/index.ts | 5 +- .../server/lib/task_runner_factory.test.ts | 199 +++++++++- .../actions/server/lib/task_runner_factory.ts | 24 +- 6 files changed, 593 insertions(+), 179 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts rename x-pack/plugins/actions/server/lib/{extract_so_references_for_action_task_params.ts => action_task_params_utils.ts} (58%) delete mode 100644 x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts new file mode 100644 index 0000000000000..b6bcb400f3c1e --- /dev/null +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts @@ -0,0 +1,361 @@ +/* + * 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 { + extractSavedObjectReferences, + injectSavedObjectReferences, +} from './action_task_params_utils'; + +describe('extractSavedObjectReferences()', () => { + test('correctly extracts action id into references array', () => { + expect(extractSavedObjectReferences('my-action-id', false)).toEqual({ + actionIdOrRef: 'actionRef', + references: [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('correctly extracts related saved object into references array', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', false, relatedSavedObjects)).toEqual({ + actionIdOrRef: 'actionRef', + references: [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectRefs: [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly skips extracting action id into references array if action is preconfigured', () => { + expect(extractSavedObjectReferences('my-action-id', true)).toEqual({ + actionIdOrRef: 'my-action-id', + references: [], + }); + }); + + test('correctly extracts related saved object into references array if action is preconfigured', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', true, relatedSavedObjects)).toEqual({ + actionIdOrRef: 'my-action-id', + references: [ + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectRefs: [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); +}); + +describe('injectSavedObjectReferences()', () => { + test('correctly returns action id from references array', () => { + expect( + injectSavedObjectReferences([ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + ]) + ).toEqual({ actionId: 'my-action-id' }); + }); + + test('correctly returns undefined if no action id in references array', () => { + expect(injectSavedObjectReferences([])).toEqual({}); + }); + + test('correctly injects related saved object ids from references array', () => { + expect( + injectSavedObjectReferences( + [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + actionId: 'my-action-id', + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly injects related saved object ids from references array when no actionRef exists', () => { + expect( + injectSavedObjectReferences( + [ + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly skips missing related saved object ids in references array', () => { + expect( + injectSavedObjectReferences( + [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + actionId: 'my-action-id', + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts similarity index 58% rename from x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts rename to x-pack/plugins/actions/server/lib/action_task_params_utils.ts index ead64c8b4c31d..b34593e15f11a 100644 --- a/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { SavedObjectReference } from 'src/core/server/types'; +import { omit } from 'lodash'; +import { SavedObjectAttribute, SavedObjectReference } from 'src/core/server/types'; import { RelatedSavedObjectRef, RelatedSavedObjects } from './related_saved_objects'; -const ACTION_REF_NAME = `actionRef`; +export const ACTION_REF_NAME = `actionRef`; export function extractSavedObjectReferences( actionId: string, @@ -51,3 +52,26 @@ export function extractSavedObjectReferences( ...(relatedSavedObjects ? { relatedSavedObjectRefs } : {}), }; } + +export function injectSavedObjectReferences( + references: SavedObjectReference[], + relatedSavedObjectRefs?: RelatedSavedObjectRef[] +): { actionId?: string; relatedSavedObjects?: SavedObjectAttribute } { + references = references ?? []; + + // Look for for the action id + const action = references.find((ref) => ref.name === ACTION_REF_NAME); + + const relatedSavedObjects = (relatedSavedObjectRefs ?? []).flatMap((relatedSavedObjectRef) => { + const reference = references.find((ref) => ref.name === relatedSavedObjectRef.ref); + + // These are used to provide context in the event log so we will not throw an error + // if it is not found because we don't want to block the action execution + return reference ? [{ ...omit(relatedSavedObjectRef, 'ref'), id: reference.id }] : []; + }); + + return { + ...(action ? { actionId: action.id } : {}), + ...(relatedSavedObjectRefs ? { relatedSavedObjects } : {}), + }; +} diff --git a/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts b/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts deleted file mode 100644 index 7800803bd20ef..0000000000000 --- a/x-pack/plugins/actions/server/lib/extract_so_references_for_action_task_params.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 { extractSavedObjectReferences } from './extract_so_references_for_action_task_params'; - -describe('extractSavedObjectReferences()', () => { - test('correctly extracts action id into references array', () => { - expect(extractSavedObjectReferences('my-action-id', false)).toEqual({ - actionIdOrRef: 'actionRef', - references: [ - { - id: 'my-action-id', - name: 'actionRef', - type: 'action', - }, - ], - }); - }); - - test('correctly extracts related saved object into references array', () => { - const relatedSavedObjects = [ - { - id: 'abc', - type: 'alert', - typeId: 'ruleTypeA', - }, - { - id: 'def', - type: 'action', - typeId: 'connectorTypeB', - }, - { - id: 'xyz', - type: 'alert', - typeId: 'ruleTypeB', - namespace: 'custom', - }, - ]; - - expect(extractSavedObjectReferences('my-action-id', false, relatedSavedObjects)).toEqual({ - actionIdOrRef: 'actionRef', - references: [ - { - id: 'my-action-id', - name: 'actionRef', - type: 'action', - }, - { - id: 'abc', - name: 'related_alert_0', - type: 'alert', - }, - { - id: 'def', - name: 'related_action_1', - type: 'action', - }, - { - id: 'xyz', - name: 'related_alert_2', - type: 'alert', - }, - ], - relatedSavedObjectRefs: [ - { - ref: 'related_alert_0', - type: 'alert', - typeId: 'ruleTypeA', - }, - { - ref: 'related_action_1', - type: 'action', - typeId: 'connectorTypeB', - }, - { - ref: 'related_alert_2', - type: 'alert', - typeId: 'ruleTypeB', - namespace: 'custom', - }, - ], - }); - }); - - test('correctly skips extracting action id into references array if action is preconfigured', () => { - expect(extractSavedObjectReferences('my-action-id', true)).toEqual({ - actionIdOrRef: 'my-action-id', - references: [], - }); - }); - - test('correctly extracts related saved object into references array if action is preconfigured', () => { - const relatedSavedObjects = [ - { - id: 'abc', - type: 'alert', - typeId: 'ruleTypeA', - }, - { - id: 'def', - type: 'action', - typeId: 'connectorTypeB', - }, - { - id: 'xyz', - type: 'alert', - typeId: 'ruleTypeB', - namespace: 'custom', - }, - ]; - - expect(extractSavedObjectReferences('my-action-id', true, relatedSavedObjects)).toEqual({ - actionIdOrRef: 'my-action-id', - references: [ - { - id: 'abc', - name: 'related_alert_0', - type: 'alert', - }, - { - id: 'def', - name: 'related_action_1', - type: 'action', - }, - { - id: 'xyz', - name: 'related_alert_2', - type: 'alert', - }, - ], - relatedSavedObjectRefs: [ - { - ref: 'related_alert_0', - type: 'alert', - typeId: 'ruleTypeA', - }, - { - ref: 'related_action_1', - type: 'action', - typeId: 'connectorTypeB', - }, - { - ref: 'related_alert_2', - type: 'alert', - typeId: 'ruleTypeB', - namespace: 'custom', - }, - ], - }); - }); -}); diff --git a/x-pack/plugins/actions/server/lib/index.ts b/x-pack/plugins/actions/server/lib/index.ts index 92a99bf0361c5..c47325c19fad9 100644 --- a/x-pack/plugins/actions/server/lib/index.ts +++ b/x-pack/plugins/actions/server/lib/index.ts @@ -13,7 +13,10 @@ export { ILicenseState, LicenseState } from './license_state'; export { verifyApiAccess } from './verify_api_access'; export { getActionTypeFeatureUsageName } from './get_action_type_feature_usage_name'; export { spaceIdToNamespace } from './space_id_to_namespace'; -export { extractSavedObjectReferences } from './extract_so_references_for_action_task_params'; +export { + extractSavedObjectReferences, + injectSavedObjectReferences, +} from './action_task_params_utils'; export { ActionTypeDisabledError, ActionTypeDisabledReason, diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 722ba08a26258..1671e644aa4b9 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -97,7 +97,7 @@ test(`throws an error if factory is already initialized`, () => { ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); }); -test('executes the task by calling the executor with proper parameters', async () => { +test('executes the task by calling the executor with proper parameters with no actionRef', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -146,6 +146,61 @@ test('executes the task by calling the executor with proper parameters', async ( ); }); +test('executes the task by calling the executor with proper parameters when actionId is stored in references', async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: mockedTaskInstance, + }); + + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); + spaceIdToNamespace.mockReturnValueOnce('namespace-test'); + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: 'actionRef', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toBeUndefined(); + expect(spaceIdToNamespace).toHaveBeenCalledWith('test'); + expect( + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser + ).toHaveBeenCalledWith('action_task_params', '3', { namespace: 'namespace-test' }); + + expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ + actionId: '2', + isEphemeral: false, + params: { baz: true }, + relatedSavedObjects: [], + request: expect.objectContaining({ + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + }), + taskInfo: { + scheduled: new Date(), + }, + }); + + const [executeParams] = mockedActionExecutor.execute.mock.calls[0]; + expect(taskRunnerFactoryInitializerParams.basePathService.set).toHaveBeenCalledWith( + executeParams.request, + '/s/test' + ); +}); + test('cleans up action_task_params object', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, @@ -157,11 +212,17 @@ test('cleans up action_task_params object', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); await taskRunner.run(); @@ -180,11 +241,17 @@ test('runs successfully when cleanup fails and logs the error', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); services.savedObjectsClient.delete.mockRejectedValueOnce(new Error('Fail')); @@ -205,11 +272,17 @@ test('throws an error with suggested retry logic when return status is error', a id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'error', @@ -240,11 +313,17 @@ test('uses API key when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); await taskRunner.run(); @@ -283,12 +362,23 @@ test('uses relatedSavedObjects when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ id: 'some-id', type: 'some-type' }], + relatedSavedObjects: [{ ref: 'related_some-type_0', type: 'some-type' }], }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], }); await taskRunner.run(); @@ -326,12 +416,71 @@ test('sanitizes invalid relatedSavedObjects when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ Xid: 'some-id', type: 'some-type' }], + relatedSavedObjects: [{ Xid: 'related_some-type_0', type: 'some-type' }], }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + + await taskRunner.run(); + expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ + actionId: '2', + isEphemeral: false, + params: { baz: true }, + request: expect.objectContaining({ + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + }), + relatedSavedObjects: [], + taskInfo: { + scheduled: new Date(), + }, + }); +}); + +test('sanitizes invalid relatedSavedObject refs when provided', async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: mockedTaskInstance, + }); + + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); + spaceIdToNamespace.mockReturnValueOnce('namespace-test'); + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: 'actionRef', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + relatedSavedObjects: [{ ref: 'invalid-related_some-type_0', type: 'some-type' }], + }, + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], }); await taskRunner.run(); @@ -363,10 +512,16 @@ test(`doesn't use API key when not provided`, async () => { id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); await taskRunner.run(); @@ -400,11 +555,17 @@ test(`throws an error when license doesn't support the action type`, async () => id: '3', type: 'action_task_params', attributes: { - actionId: '2', + actionId: 'actionRef', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); mockedActionExecutor.execute.mockImplementation(() => { throw new ActionTypeDisabledError('Fail', 'license_invalid'); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 2354ea55eded6..984a7356aac2f 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -33,7 +33,8 @@ import { } from '../types'; import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; import { asSavedObjectExecutionSource } from './action_execution_source'; -import { validatedRelatedSavedObjects } from './related_saved_objects'; +import { RelatedSavedObjectRef, validatedRelatedSavedObjects } from './related_saved_objects'; +import { injectSavedObjectReferences } from './action_task_params_utils'; export interface TaskRunnerContext { logger: Logger; @@ -178,11 +179,30 @@ async function getActionTaskParams( const { spaceId } = executorParams; const namespace = spaceIdToNamespace(spaceId); if (isPersistedActionTask(executorParams)) { - return encryptedSavedObjectsClient.getDecryptedAsInternalUser( + const actionTask = await encryptedSavedObjectsClient.getDecryptedAsInternalUser( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, executorParams.actionTaskParamsId, { namespace } ); + + const { + attributes: { relatedSavedObjects }, + references, + } = actionTask; + + const { actionId, relatedSavedObjects: relatedSOs } = injectSavedObjectReferences( + references, + relatedSavedObjects as RelatedSavedObjectRef[] + ); + + return { + ...actionTask, + attributes: { + ...actionTask.attributes, + ...(actionId ? { actionId } : {}), + ...(relatedSavedObjects ? { relatedSavedObjects: relatedSOs } : {}), + }, + }; } else { return { attributes: executorParams.taskParams, references: executorParams.references ?? [] }; } From c46c87f80190469a07cbc45ae5b2a1c9918fe129 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 17 Aug 2021 15:26:20 -0400 Subject: [PATCH 03/14] Adding migration --- .../action_task_params_migrations.ts | 124 ++++++++++++++++++ ...ons.test.ts => actions_migrations.test.ts} | 20 +-- .../{migrations.ts => actions_migrations.ts} | 2 +- .../actions/server/saved_objects/index.ts | 6 +- 4 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts rename x-pack/plugins/actions/server/saved_objects/{migrations.test.ts => actions_migrations.test.ts} (88%) rename x-pack/plugins/actions/server/saved_objects/{migrations.ts => actions_migrations.ts} (99%) diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts new file mode 100644 index 0000000000000..bafc63708cc5b --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -0,0 +1,124 @@ +/* + * 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 { + LogMeta, + SavedObjectMigrationMap, + SavedObjectUnsanitizedDoc, + SavedObjectMigrationFn, + SavedObjectMigrationContext, + SavedObjectReference, +} from '../../../../../src/core/server'; +import { ActionTaskParams } from '../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; +import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server'; +import { RelatedSavedObjectRef, RelatedSavedObjects } from '../lib/related_saved_objects'; + +interface ActionTaskParamsLogMeta extends LogMeta { + migrations: { actionTaskParamDocument: SavedObjectUnsanitizedDoc }; +} + +type ActionTaskParamMigration = ( + doc: SavedObjectUnsanitizedDoc +) => SavedObjectUnsanitizedDoc; + +function createEsoMigration( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + isMigrationNeededPredicate: IsMigrationNeededPredicate, + migrationFunc: ActionTaskParamMigration +) { + return encryptedSavedObjects.createMigration({ + isMigrationNeededPredicate, + migration: migrationFunc, + shouldMigrateIfDecryptionFails: true, // shouldMigrateIfDecryptionFails flag that applies the migration to undecrypted document if decryption fails + }); +} + +export function getActionTaskParamsMigrations( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +): SavedObjectMigrationMap { + const migrationActionTaskParamsSixteen = createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(useSavedObjectReferences) + ); + + return { + '7.16.0': executeMigrationWithErrorHandling(migrationActionTaskParamsSixteen, '7.16.0'), + }; +} + +function executeMigrationWithErrorHandling( + migrationFunc: SavedObjectMigrationFn, + version: string +) { + return ( + doc: SavedObjectUnsanitizedDoc, + context: SavedObjectMigrationContext + ) => { + try { + return migrationFunc(doc, context); + } catch (ex) { + context.log.error( + `encryptedSavedObject ${version} migration failed for action task param ${doc.id} with error: ${ex.message}`, + { + migrations: { + actionTaskParamDocument: doc, + }, + } + ); + throw ex; + } + }; +} + +function useSavedObjectReferences( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { actionId, relatedSavedObjects }, + references, + } = doc; + + const newReferences: SavedObjectReference[] = []; + const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; + + // No way to differentiate preconfigured from non-preconfigured action here + newReferences.push({ + id: actionId, + name: 'actionRef', + type: 'action', + }); + + // Add related saved objects, if any + ((relatedSavedObjects as RelatedSavedObjects) ?? []).forEach((relatedSavedObject, index) => { + const { id, ...restRelatedSavedObject } = relatedSavedObject; + relatedSavedObjectRefs.push({ + ...restRelatedSavedObject, + ref: `related_${relatedSavedObject.type}_${index}`, + }); + newReferences.push({ + id: relatedSavedObject.id, + name: `related_${relatedSavedObject.type}_${index}`, + type: relatedSavedObject.type, + }); + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + actionId: 'actionRef', + }, + references: [...(references ?? []), ...(newReferences ?? [])], + }; +} + +function pipeMigrations(...migrations: ActionTaskParamMigration[]): ActionTaskParamMigration { + return (doc: SavedObjectUnsanitizedDoc) => + migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc); +} diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts similarity index 88% rename from x-pack/plugins/actions/server/saved_objects/migrations.test.ts rename to x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts index beaea76756113..bc0e59279abc1 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts @@ -6,7 +6,7 @@ */ import uuid from 'uuid'; -import { getMigrations } from './migrations'; +import { getActionsMigrations } from './actions_migrations'; import { RawAction } from '../types'; import { SavedObjectUnsanitizedDoc } from 'kibana/server'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; @@ -23,7 +23,7 @@ describe('successful migrations', () => { describe('7.10.0', () => { test('add hasAuth config property for .email actions', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; + const migration710 = getActionsMigrations(encryptedSavedObjectsSetup)['7.10.0']; const action = getMockDataForEmail({}); const migratedAction = migration710(action, context); expect(migratedAction.attributes.config).toEqual({ @@ -41,7 +41,7 @@ describe('successful migrations', () => { }); test('rename cases configuration object', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; + const migration710 = getActionsMigrations(encryptedSavedObjectsSetup)['7.10.0']; const action = getCasesMockData({}); const migratedAction = migration710(action, context); expect(migratedAction.attributes.config).toEqual({ @@ -61,7 +61,7 @@ describe('successful migrations', () => { describe('7.11.0', () => { test('add hasAuth = true for .webhook actions with user and password', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockDataForWebhook({}, true); expect(migration711(action, context)).toMatchObject({ ...action, @@ -75,7 +75,7 @@ describe('successful migrations', () => { }); test('add hasAuth = false for .webhook actions without user and password', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockDataForWebhook({}, false); expect(migration711(action, context)).toMatchObject({ ...action, @@ -88,7 +88,7 @@ describe('successful migrations', () => { }); }); test('remove cases mapping object', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockData({ config: { incidentConfiguration: { mapping: [] }, isCaseOwned: true, another: 'value' }, }); @@ -106,7 +106,7 @@ describe('successful migrations', () => { describe('7.14.0', () => { test('add isMissingSecrets property for actions', () => { - const migration714 = getMigrations(encryptedSavedObjectsSetup)['7.14.0']; + const migration714 = getActionsMigrations(encryptedSavedObjectsSetup)['7.14.0']; const action = getMockData({ isMissingSecrets: undefined }); const migratedAction = migration714(action, context); expect(migratedAction).toEqual({ @@ -130,7 +130,7 @@ describe('handles errors during migrations', () => { describe('7.10.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; + const migration710 = getActionsMigrations(encryptedSavedObjectsSetup)['7.10.0']; const action = getMockDataForEmail({}); expect(() => { migration710(action, context); @@ -148,7 +148,7 @@ describe('handles errors during migrations', () => { describe('7.11.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockDataForEmail({}); expect(() => { migration711(action, context); @@ -166,7 +166,7 @@ describe('handles errors during migrations', () => { describe('7.14.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration714 = getMigrations(encryptedSavedObjectsSetup)['7.14.0']; + const migration714 = getActionsMigrations(encryptedSavedObjectsSetup)['7.14.0']; const action = getMockDataForEmail({}); expect(() => { migration714(action, context); diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts similarity index 99% rename from x-pack/plugins/actions/server/saved_objects/migrations.ts rename to x-pack/plugins/actions/server/saved_objects/actions_migrations.ts index de15de7b15e23..a72565e00ef7b 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts @@ -36,7 +36,7 @@ function createEsoMigration( }); } -export function getMigrations( +export function getActionsMigrations( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ): SavedObjectMigrationMap { const migrationActionsTen = createEsoMigration( diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index c4ce38c857151..de81b218ef743 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -13,7 +13,8 @@ import type { } from 'kibana/server'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; import mappings from './mappings.json'; -import { getMigrations } from './migrations'; +import { getActionsMigrations } from './actions_migrations'; +import { getActionTaskParamsMigrations } from './action_task_params_migrations'; import { RawAction } from '../types'; import { getImportWarnings } from './get_import_warnings'; import { transformConnectorsForExport } from './transform_connectors_for_export'; @@ -35,7 +36,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'single', mappings: mappings.action as SavedObjectsTypeMappingDefinition, - migrations: getMigrations(encryptedSavedObjects), + migrations: getActionsMigrations(encryptedSavedObjects), management: { defaultSearchField: 'name', importableAndExportable: true, @@ -71,6 +72,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'single', mappings: mappings.action_task_params as SavedObjectsTypeMappingDefinition, + migrations: getActionTaskParamsMigrations(encryptedSavedObjects), excludeOnUpgrade: async ({ readonlyEsClient }) => { const oldestIdleActionTask = await getOldestIdleActionTask( readonlyEsClient, From 4088232e63dca15bd4d9d9a34784d17a211f5ce5 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 11:23:19 -0400 Subject: [PATCH 04/14] Adding unit test for migrations --- .../action_task_params_migrations.test.ts | 327 ++++++++++++++++++ .../action_task_params_migrations.ts | 1 + 2 files changed, 328 insertions(+) create mode 100644 x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts new file mode 100644 index 0000000000000..b5bcbf3bfa3e7 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts @@ -0,0 +1,327 @@ +/* + * 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 uuid from 'uuid'; +import { getActionTaskParamsMigrations } from './action_task_params_migrations'; +import { ActionTaskParams } from '../types'; +import { SavedObjectReference, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; +import { migrationMocks } from 'src/core/server/mocks'; + +const context = migrationMocks.createContext(); +const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); + +describe('successful migrations', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation(({ migration }) => migration); + }); + + describe('7.16.0', () => { + test('moves actionId to references array', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData(); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('handles empty relatedSavedObjects array', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData({ relatedSavedObjects: [] }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + relatedSavedObjects: [], + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('moves actionId and relatedSavedObjects to references array', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData({ + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + relatedSavedObjects: [ + { + ref: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + }); + + test('moves actionId and multiple relatedSavedObjects to references array', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData({ + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + { + id: 'another-id', + type: 'another-type', + typeId: 'another-typeId', + }, + ], + }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + relatedSavedObjects: [ + { + ref: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + { + ref: 'related_another-type_1', + type: 'another-type', + typeId: 'another-typeId', + }, + ], + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + { + id: 'another-id', + name: 'related_another-type_1', + type: 'another-type', + }, + ], + }); + }); + + test('does not overwrite existing references', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData( + { + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + ] + ); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + relatedSavedObjects: [ + { + ref: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + references: [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + }); + + test('does not overwrite existing references if relatedSavedObjects is undefined', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData({}, [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + ]); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + }, + references: [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('does not overwrite existing references if relatedSavedObjects is empty', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData({ relatedSavedObjects: [] }, [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + ]); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + actionId: 'actionRef', + relatedSavedObjects: [], + }, + references: [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + }); +}); + +describe('handles errors during migrations', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation(() => () => { + throw new Error(`Can't migrate!`); + }); + }); + + describe('7.16.0 throws if migration fails', () => { + test('should show the proper exception', () => { + const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const actionTaskParam = getMockData(); + expect(() => { + migration716(actionTaskParam, context); + }).toThrowError(`Can't migrate!`); + expect(context.log.error).toHaveBeenCalledWith( + `encryptedSavedObject 7.16.0 migration failed for action task param ${actionTaskParam.id} with error: Can't migrate!`, + { + migrations: { + actionTaskParamDocument: actionTaskParam, + }, + } + ); + }); + }); +}); + +function getMockData( + overwrites: Record = {}, + referencesOverwrites: SavedObjectReference[] = [] +): SavedObjectUnsanitizedDoc { + return { + attributes: { + actionId: uuid.v4(), + params: {}, + ...overwrites, + }, + references: [...referencesOverwrites], + id: uuid.v4(), + type: 'action_task_param', + }; +} diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts index bafc63708cc5b..5cbf17fd55df7 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -113,6 +113,7 @@ function useSavedObjectReferences( attributes: { ...doc.attributes, actionId: 'actionRef', + ...(relatedSavedObjects ? { relatedSavedObjects: relatedSavedObjectRefs } : {}), }, references: [...(references ?? []), ...(newReferences ?? [])], }; From e21c15b657268689ee315f9fd3e46996cb7aff90 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 14:05:05 -0400 Subject: [PATCH 05/14] Not differentiating between preconfigured or not --- .../server/create_execute_function.test.ts | 14 +++- .../actions/server/create_execute_function.ts | 19 ++--- .../lib/action_task_params_utils.test.ts | 75 +------------------ .../server/lib/action_task_params_utils.ts | 17 ++--- .../server/lib/task_runner_factory.test.ts | 28 +++---- .../action_task_params_migrations.test.ts | 17 +---- .../action_task_params_migrations.ts | 2 - 7 files changed, 44 insertions(+), 128 deletions(-) diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index f02b7b8229050..5f82a021eed9b 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -72,7 +72,7 @@ describe('execute()', () => { expect(savedObjectsClient.create).toHaveBeenCalledWith( 'action_task_params', { - actionId: 'actionRef', + actionId: '123', params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), }, @@ -131,7 +131,7 @@ describe('execute()', () => { expect(savedObjectsClient.create).toHaveBeenCalledWith( 'action_task_params', { - actionId: 'actionRef', + actionId: '123', params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [ @@ -230,6 +230,11 @@ describe('execute()', () => { name: 'source', type: source.type, }, + { + id: '123', + name: 'actionRef', + type: 'action', + }, ], } ); @@ -321,6 +326,11 @@ describe('execute()', () => { name: 'source', type: source.type, }, + { + id: '123', + name: 'actionRef', + type: 'action', + }, { id: 'some-id', name: 'related_some-type_0', diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 0401082cbf138..d267516e50f47 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -53,11 +53,7 @@ export function createExecutionEnqueuerFunction({ ); } - const { action, isPreconfigured } = await getAction( - unsecuredSavedObjectsClient, - preconfiguredActions, - id - ); + const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -66,9 +62,8 @@ export function createExecutionEnqueuerFunction({ } // Get saved object references from action ID and relatedSavedObjects - const { actionIdOrRef, references, relatedSavedObjectRefs } = extractSavedObjectReferences( + const { references, relatedSavedObjectRefs } = extractSavedObjectReferences( id, - isPreconfigured, relatedSavedObjects ); const executionSourceReference = executionSourceAsSavedObjectReferences(source); @@ -76,7 +71,7 @@ export function createExecutionEnqueuerFunction({ const actionTaskParamsRecord = await unsecuredSavedObjectsClient.create( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, { - actionId: actionIdOrRef, + actionId: id, params, apiKey, relatedSavedObjects: relatedSavedObjectRefs, @@ -107,7 +102,7 @@ export function createEphemeralExecutionEnqueuerFunction({ unsecuredSavedObjectsClient: SavedObjectsClientContract, { id, params, spaceId, source, apiKey }: ExecuteOptions ): Promise { - const { action } = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -162,12 +157,12 @@ async function getAction( unsecuredSavedObjectsClient: SavedObjectsClientContract, preconfiguredActions: PreConfiguredAction[], actionId: string -): Promise<{ action: PreConfiguredAction | RawAction; isPreconfigured: boolean }> { +): Promise { const pcAction = preconfiguredActions.find((action) => action.id === actionId); if (pcAction) { - return { action: pcAction, isPreconfigured: true }; + return pcAction; } const { attributes } = await unsecuredSavedObjectsClient.get('action', actionId); - return { action: attributes, isPreconfigured: false }; + return attributes; } diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts index b6bcb400f3c1e..e892a19c1057f 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts @@ -12,8 +12,7 @@ import { describe('extractSavedObjectReferences()', () => { test('correctly extracts action id into references array', () => { - expect(extractSavedObjectReferences('my-action-id', false)).toEqual({ - actionIdOrRef: 'actionRef', + expect(extractSavedObjectReferences('my-action-id')).toEqual({ references: [ { id: 'my-action-id', @@ -44,8 +43,7 @@ describe('extractSavedObjectReferences()', () => { }, ]; - expect(extractSavedObjectReferences('my-action-id', false, relatedSavedObjects)).toEqual({ - actionIdOrRef: 'actionRef', + expect(extractSavedObjectReferences('my-action-id', relatedSavedObjects)).toEqual({ references: [ { id: 'my-action-id', @@ -88,73 +86,6 @@ describe('extractSavedObjectReferences()', () => { ], }); }); - - test('correctly skips extracting action id into references array if action is preconfigured', () => { - expect(extractSavedObjectReferences('my-action-id', true)).toEqual({ - actionIdOrRef: 'my-action-id', - references: [], - }); - }); - - test('correctly extracts related saved object into references array if action is preconfigured', () => { - const relatedSavedObjects = [ - { - id: 'abc', - type: 'alert', - typeId: 'ruleTypeA', - }, - { - id: 'def', - type: 'action', - typeId: 'connectorTypeB', - }, - { - id: 'xyz', - type: 'alert', - typeId: 'ruleTypeB', - namespace: 'custom', - }, - ]; - - expect(extractSavedObjectReferences('my-action-id', true, relatedSavedObjects)).toEqual({ - actionIdOrRef: 'my-action-id', - references: [ - { - id: 'abc', - name: 'related_alert_0', - type: 'alert', - }, - { - id: 'def', - name: 'related_action_1', - type: 'action', - }, - { - id: 'xyz', - name: 'related_alert_2', - type: 'alert', - }, - ], - relatedSavedObjectRefs: [ - { - ref: 'related_alert_0', - type: 'alert', - typeId: 'ruleTypeA', - }, - { - ref: 'related_action_1', - type: 'action', - typeId: 'connectorTypeB', - }, - { - ref: 'related_alert_2', - type: 'alert', - typeId: 'ruleTypeB', - namespace: 'custom', - }, - ], - }); - }); }); describe('injectSavedObjectReferences()', () => { @@ -241,7 +172,7 @@ describe('injectSavedObjectReferences()', () => { }); }); - test('correctly injects related saved object ids from references array when no actionRef exists', () => { + test('correctly injects related saved object ids from references array if no actionRef', () => { expect( injectSavedObjectReferences( [ diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts index b34593e15f11a..d4d164022c13a 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts @@ -13,24 +13,20 @@ export const ACTION_REF_NAME = `actionRef`; export function extractSavedObjectReferences( actionId: string, - isPreconfigured: boolean, relatedSavedObjects?: RelatedSavedObjects ): { - actionIdOrRef: string; references: SavedObjectReference[]; relatedSavedObjectRefs?: RelatedSavedObjectRef[]; } { const references: SavedObjectReference[] = []; const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; - // Add action saved object to reference if it is not preconfigured - if (!isPreconfigured) { - references.push({ - id: actionId, - name: ACTION_REF_NAME, - type: 'action', - }); - } + // Add action saved object to reference + references.push({ + id: actionId, + name: ACTION_REF_NAME, + type: 'action', + }); // Add related saved objects, if any (relatedSavedObjects ?? []).forEach((relatedSavedObject, index) => { @@ -47,7 +43,6 @@ export function extractSavedObjectReferences( }); return { - actionIdOrRef: isPreconfigured ? actionId : ACTION_REF_NAME, references, ...(relatedSavedObjects ? { relatedSavedObjectRefs } : {}), }; diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 1671e644aa4b9..53f35c0f95a99 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -97,7 +97,7 @@ test(`throws an error if factory is already initialized`, () => { ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); }); -test('executes the task by calling the executor with proper parameters with no actionRef', async () => { +test('executes the task by calling the executor with proper parameters, using given actionId when no actionRef in references', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -146,7 +146,7 @@ test('executes the task by calling the executor with proper parameters with no a ); }); -test('executes the task by calling the executor with proper parameters when actionId is stored in references', async () => { +test('executes the task by calling the executor with proper parameters, using stored actionId when actionRef is in references', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -157,13 +157,13 @@ test('executes the task by calling the executor with proper parameters when acti id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, references: [ { - id: '2', + id: '9', name: 'actionRef', type: 'action', }, @@ -179,7 +179,7 @@ test('executes the task by calling the executor with proper parameters when acti ).toHaveBeenCalledWith('action_task_params', '3', { namespace: 'namespace-test' }); expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ - actionId: '2', + actionId: '9', isEphemeral: false, params: { baz: true }, relatedSavedObjects: [], @@ -212,7 +212,7 @@ test('cleans up action_task_params object', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, @@ -241,7 +241,7 @@ test('runs successfully when cleanup fails and logs the error', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, @@ -272,7 +272,7 @@ test('throws an error with suggested retry logic when return status is error', a id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, @@ -313,7 +313,7 @@ test('uses API key when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, @@ -362,7 +362,7 @@ test('uses relatedSavedObjects when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [{ ref: 'related_some-type_0', type: 'some-type' }], @@ -416,7 +416,7 @@ test('sanitizes invalid relatedSavedObjects when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [{ Xid: 'related_some-type_0', type: 'some-type' }], @@ -464,7 +464,7 @@ test('sanitizes invalid relatedSavedObject refs when provided', async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [{ ref: 'invalid-related_some-type_0', type: 'some-type' }], @@ -512,7 +512,7 @@ test(`doesn't use API key when not provided`, async () => { id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, }, references: [ @@ -555,7 +555,7 @@ test(`throws an error when license doesn't support the action type`, async () => id: '3', type: 'action_task_params', attributes: { - actionId: 'actionRef', + actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts index b5bcbf3bfa3e7..63e96dd4ea55c 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts @@ -22,16 +22,12 @@ describe('successful migrations', () => { }); describe('7.16.0', () => { - test('moves actionId to references array', () => { + test('adds actionId to references array', () => { const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; const actionTaskParam = getMockData(); const migratedActionTaskParam = migration716(actionTaskParam, context); expect(migratedActionTaskParam).toEqual({ ...actionTaskParam, - attributes: { - ...actionTaskParam.attributes, - actionId: 'actionRef', - }, references: [ { id: actionTaskParam.attributes.actionId, @@ -50,7 +46,6 @@ describe('successful migrations', () => { ...actionTaskParam, attributes: { ...actionTaskParam.attributes, - actionId: 'actionRef', relatedSavedObjects: [], }, references: [ @@ -63,7 +58,7 @@ describe('successful migrations', () => { }); }); - test('moves actionId and relatedSavedObjects to references array', () => { + test('adds actionId and relatedSavedObjects to references array', () => { const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; const actionTaskParam = getMockData({ relatedSavedObjects: [ @@ -80,7 +75,6 @@ describe('successful migrations', () => { ...actionTaskParam, attributes: { ...actionTaskParam.attributes, - actionId: 'actionRef', relatedSavedObjects: [ { ref: 'related_some-type_0', @@ -127,7 +121,6 @@ describe('successful migrations', () => { ...actionTaskParam, attributes: { ...actionTaskParam.attributes, - actionId: 'actionRef', relatedSavedObjects: [ { ref: 'related_some-type_0', @@ -188,7 +181,6 @@ describe('successful migrations', () => { ...actionTaskParam, attributes: { ...actionTaskParam.attributes, - actionId: 'actionRef', relatedSavedObjects: [ { ref: 'related_some-type_0', @@ -230,10 +222,6 @@ describe('successful migrations', () => { const migratedActionTaskParam = migration716(actionTaskParam, context); expect(migratedActionTaskParam).toEqual({ ...actionTaskParam, - attributes: { - ...actionTaskParam.attributes, - actionId: 'actionRef', - }, references: [ { id: 'existing-ref-id', @@ -263,7 +251,6 @@ describe('successful migrations', () => { ...actionTaskParam, attributes: { ...actionTaskParam.attributes, - actionId: 'actionRef', relatedSavedObjects: [], }, references: [ diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts index 5cbf17fd55df7..5ef1d2f2a2997 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -87,7 +87,6 @@ function useSavedObjectReferences( const newReferences: SavedObjectReference[] = []; const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; - // No way to differentiate preconfigured from non-preconfigured action here newReferences.push({ id: actionId, name: 'actionRef', @@ -112,7 +111,6 @@ function useSavedObjectReferences( ...doc, attributes: { ...doc.attributes, - actionId: 'actionRef', ...(relatedSavedObjects ? { relatedSavedObjects: relatedSavedObjectRefs } : {}), }, references: [...(references ?? []), ...(newReferences ?? [])], From 0ff73d0f29b79b8decf0e1d89c19422683ce5ed6 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 16:20:40 -0400 Subject: [PATCH 06/14] Adding functional test for migration --- .../tests/action_task_params/index.ts | 19 + .../tests/action_task_params/migrations.ts | 59 + .../spaces_only/tests/index.ts | 1 + .../es_archives/action_task_params/data.json | 63 + .../action_task_params/mappings.json | 2572 +++++++++++++++++ 5 files changed, 2714 insertions(+) create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts create mode 100644 x-pack/test/functional/es_archives/action_task_params/data.json create mode 100644 x-pack/test/functional/es_archives/action_task_params/mappings.json diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts new file mode 100644 index 0000000000000..115358c4bce3a --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { buildUp, tearDown } from '..'; + +// eslint-disable-next-line import/no-default-export +export default function actionTaskParamsTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Action Task Params', () => { + before(async () => buildUp(getService)); + after(async () => tearDown(getService)); + + loadTestFile(require.resolve('./migrations')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts new file mode 100644 index 0000000000000..bdbafc4ccf49b --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts @@ -0,0 +1,59 @@ +/* + * 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 expect from '@kbn/expect'; +import { SavedObject } from 'src/core/server'; +import { ActionTaskParams } from '../../../../../plugins/actions/server/types'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function createGetTests({ getService }: FtrProviderContext) { + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('migrations', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/action_task_params'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/action_task_params'); + }); + + it('7.16.0 migrates action_task_params to use references array', async () => { + const responses = await Promise.all( + [ + 'action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed', + 'action_task_params:0205a520-0054-11ec-917b-f7aa317691ed', + ].map((id) => + es.get>({ + index: '.kibana', + id, + }) + ) + ); + responses.forEach((response) => { + expect(response.statusCode).to.eql(200); + const actionTaskParams = (response.body._source as any) + ?.action_task_params as ActionTaskParams; + const actionId = actionTaskParams.actionId; + const relatedSavedObjects = actionTaskParams.relatedSavedObjects as unknown[]; + const references = response.body._source?.references ?? []; + // Should have 'actionRef' reference + expect(references.find((ref) => ref.name === 'actionRef')).to.eql({ + name: 'actionRef', + id: actionId, + type: 'action', + }); + // Should have reference entry for each relatedSavedObject entry + (relatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { + expect(references.find((ref) => ref.name === relatedSavedObject.ref)).not.to.be(null); + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts index 8b0addcba26e9..88e5e0740789f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts @@ -15,6 +15,7 @@ export default function alertingApiIntegrationTests({ loadTestFile }: FtrProvide loadTestFile(require.resolve('./actions')); loadTestFile(require.resolve('./alerting')); + loadTestFile(require.resolve('./action_task_params')); }); } diff --git a/x-pack/test/functional/es_archives/action_task_params/data.json b/x-pack/test/functional/es_archives/action_task_params/data.json new file mode 100644 index 0000000000000..757d2c703ed32 --- /dev/null +++ b/x-pack/test/functional/es_archives/action_task_params/data.json @@ -0,0 +1,63 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "id": "action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed", + "source": { + "type": "action_task_params", + "action_task_params" : { + "actionId" : "918da460-0052-11ec-917b-f7aa317691ed", + "params" : { + "level" : "info", + "message" : "yo yo" + }, + "relatedSavedObjects" : [ + { + "type" : "alert", + "typeId" : "example.always-firing", + "id" : "b6db0cd0-0052-11ec-917b-f7aa317691ed" + } + ] + }, + "references" : [ + { + "name" : "source", + "id" : "b6db0cd0-0052-11ec-917b-f7aa317691ed", + "type" : "alert" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "id": "action_task_params:0205a520-0054-11ec-917b-f7aa317691ed", + "source": { + "type": "action_task_params", + "action_task_params" : { + "actionId" : "preconfigured-server-log", + "params" : { + "level" : "info", + "message" : "hi hi" + }, + "relatedSavedObjects" : [ + { + "type" : "alert", + "typeId" : "example.always-firing", + "id" : "b50bfcb0-0053-11ec-917b-f7aa317691ed" + } + ] + }, + "references" : [ + { + "name" : "source", + "id" : "b50bfcb0-0053-11ec-917b-f7aa317691ed", + "type" : "alert" + } + ] + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/action_task_params/mappings.json b/x-pack/test/functional/es_archives/action_task_params/mappings.json new file mode 100644 index 0000000000000..d0eb35fa3b157 --- /dev/null +++ b/x-pack/test/functional/es_archives/action_task_params/mappings.json @@ -0,0 +1,2572 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "isMissingSecrets": { + "type": "boolean" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + }, + "relatedSavedObjects": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + }, + "artifacts": { + "type": "nested", + "properties": { + "policyId": { + "type": "keyword", + "index": false + }, + "artifactId": { + "type": "keyword", + "index": false + } + } + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} From 291e1096aecc9cbad4c68a6e799fcdfd43ee4e95 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 19:15:44 -0400 Subject: [PATCH 07/14] Skip extracting action id if action is preconfigured --- .../server/create_execute_function.test.ts | 10 --- .../actions/server/create_execute_function.ts | 15 ++-- .../lib/action_task_params_utils.test.ts | 69 ++++++++++++++++++- .../server/lib/action_task_params_utils.ts | 15 ++-- 4 files changed, 86 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index 5f82a021eed9b..c343ad85e6242 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -230,11 +230,6 @@ describe('execute()', () => { name: 'source', type: source.type, }, - { - id: '123', - name: 'actionRef', - type: 'action', - }, ], } ); @@ -326,11 +321,6 @@ describe('execute()', () => { name: 'source', type: source.type, }, - { - id: '123', - name: 'actionRef', - type: 'action', - }, { id: 'some-id', name: 'related_some-type_0', diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index d267516e50f47..72325b94fac8f 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -53,7 +53,11 @@ export function createExecutionEnqueuerFunction({ ); } - const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const { action, isPreconfigured } = await getAction( + unsecuredSavedObjectsClient, + preconfiguredActions, + id + ); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -64,6 +68,7 @@ export function createExecutionEnqueuerFunction({ // Get saved object references from action ID and relatedSavedObjects const { references, relatedSavedObjectRefs } = extractSavedObjectReferences( id, + isPreconfigured, relatedSavedObjects ); const executionSourceReference = executionSourceAsSavedObjectReferences(source); @@ -102,7 +107,7 @@ export function createEphemeralExecutionEnqueuerFunction({ unsecuredSavedObjectsClient: SavedObjectsClientContract, { id, params, spaceId, source, apiKey }: ExecuteOptions ): Promise { - const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const { action } = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -157,12 +162,12 @@ async function getAction( unsecuredSavedObjectsClient: SavedObjectsClientContract, preconfiguredActions: PreConfiguredAction[], actionId: string -): Promise { +): Promise<{ action: PreConfiguredAction | RawAction; isPreconfigured: boolean }> { const pcAction = preconfiguredActions.find((action) => action.id === actionId); if (pcAction) { - return pcAction; + return { action: pcAction, isPreconfigured: true }; } const { attributes } = await unsecuredSavedObjectsClient.get('action', actionId); - return attributes; + return { action: attributes, isPreconfigured: false }; } diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts index e892a19c1057f..610d7bdef194e 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts @@ -12,7 +12,7 @@ import { describe('extractSavedObjectReferences()', () => { test('correctly extracts action id into references array', () => { - expect(extractSavedObjectReferences('my-action-id')).toEqual({ + expect(extractSavedObjectReferences('my-action-id', false)).toEqual({ references: [ { id: 'my-action-id', @@ -43,7 +43,7 @@ describe('extractSavedObjectReferences()', () => { }, ]; - expect(extractSavedObjectReferences('my-action-id', relatedSavedObjects)).toEqual({ + expect(extractSavedObjectReferences('my-action-id', false, relatedSavedObjects)).toEqual({ references: [ { id: 'my-action-id', @@ -86,6 +86,71 @@ describe('extractSavedObjectReferences()', () => { ], }); }); + + test('correctly skips extracting action id if action is preconfigured', () => { + expect(extractSavedObjectReferences('my-action-id', true)).toEqual({ + references: [], + }); + }); + + test('correctly extracts related saved object into references array if isPreconfigured is true', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', true, relatedSavedObjects)).toEqual({ + references: [ + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectRefs: [ + { + ref: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + ref: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + ref: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); }); describe('injectSavedObjectReferences()', () => { diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts index d4d164022c13a..68571e769923c 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts @@ -13,6 +13,7 @@ export const ACTION_REF_NAME = `actionRef`; export function extractSavedObjectReferences( actionId: string, + isPreconfigured: boolean, relatedSavedObjects?: RelatedSavedObjects ): { references: SavedObjectReference[]; @@ -21,12 +22,14 @@ export function extractSavedObjectReferences( const references: SavedObjectReference[] = []; const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; - // Add action saved object to reference - references.push({ - id: actionId, - name: ACTION_REF_NAME, - type: 'action', - }); + // Add action saved object to reference if it is not preconfigured + if (!isPreconfigured) { + references.push({ + id: actionId, + name: ACTION_REF_NAME, + type: 'action', + }); + } // Add related saved objects, if any (relatedSavedObjects ?? []).forEach((relatedSavedObject, index) => { From e18e5d3f461c78f0ac980931fda684e3b3941a12 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 20:13:43 -0400 Subject: [PATCH 08/14] Only migrating action task params for non preconfigured connectors --- x-pack/plugins/actions/server/plugin.ts | 3 +- .../action_task_params_migrations.test.ts | 68 ++++++++++++++++--- .../action_task_params_migrations.ts | 15 +++- .../actions/server/saved_objects/index.ts | 7 +- 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 9e22816056618..e5c81f6320f51 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -229,7 +229,8 @@ export class ActionsPlugin implements Plugin { beforeEach(() => { jest.resetAllMocks(); @@ -23,7 +37,10 @@ describe('successful migrations', () => { describe('7.16.0', () => { test('adds actionId to references array', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData(); const migratedActionTaskParam = migration716(actionTaskParam, context); expect(migratedActionTaskParam).toEqual({ @@ -39,7 +56,10 @@ describe('successful migrations', () => { }); test('handles empty relatedSavedObjects array', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData({ relatedSavedObjects: [] }); const migratedActionTaskParam = migration716(actionTaskParam, context); expect(migratedActionTaskParam).toEqual({ @@ -59,7 +79,10 @@ describe('successful migrations', () => { }); test('adds actionId and relatedSavedObjects to references array', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData({ relatedSavedObjects: [ { @@ -100,7 +123,10 @@ describe('successful migrations', () => { }); test('moves actionId and multiple relatedSavedObjects to references array', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData({ relatedSavedObjects: [ { @@ -156,7 +182,10 @@ describe('successful migrations', () => { }); test('does not overwrite existing references', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData( { relatedSavedObjects: [ @@ -211,7 +240,10 @@ describe('successful migrations', () => { }); test('does not overwrite existing references if relatedSavedObjects is undefined', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData({}, [ { id: 'existing-ref-id', @@ -238,7 +270,10 @@ describe('successful migrations', () => { }); test('does not overwrite existing references if relatedSavedObjects is empty', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData({ relatedSavedObjects: [] }, [ { id: 'existing-ref-id', @@ -280,7 +315,10 @@ describe('handles errors during migrations', () => { describe('7.16.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration716 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup)['7.16.0']; + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; const actionTaskParam = getMockData(); expect(() => { migration716(actionTaskParam, context); @@ -297,6 +335,18 @@ describe('handles errors during migrations', () => { }); }); +describe('isPreconfiguredAction()', () => { + test('returns true if actionId is preconfigured action', () => { + expect( + isPreconfiguredAction(getMockData({ actionId: 'my-slack1' }), preconfiguredActions) + ).toEqual(true); + }); + + test('returns false if actionId is not preconfigured action', () => { + expect(isPreconfiguredAction(getMockData(), preconfiguredActions)).toEqual(false); + }); +}); + function getMockData( overwrites: Record = {}, referencesOverwrites: SavedObjectReference[] = [] diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts index 5ef1d2f2a2997..7350b87e76a8d 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -13,7 +13,7 @@ import { SavedObjectMigrationContext, SavedObjectReference, } from '../../../../../src/core/server'; -import { ActionTaskParams } from '../types'; +import { ActionTaskParams, PreConfiguredAction } from '../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server'; import { RelatedSavedObjectRef, RelatedSavedObjects } from '../lib/related_saved_objects'; @@ -39,11 +39,13 @@ function createEsoMigration( } export function getActionTaskParamsMigrations( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + preconfiguredActions: PreConfiguredAction[] ): SavedObjectMigrationMap { const migrationActionTaskParamsSixteen = createEsoMigration( encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => true, + (doc): doc is SavedObjectUnsanitizedDoc => + !isPreconfiguredAction(doc, preconfiguredActions), pipeMigrations(useSavedObjectReferences) ); @@ -76,6 +78,13 @@ function executeMigrationWithErrorHandling( }; } +export function isPreconfiguredAction( + doc: SavedObjectUnsanitizedDoc, + preconfiguredActions: PreConfiguredAction[] +): boolean { + return !!preconfiguredActions.find((action) => action.id === doc.attributes.actionId); +} + function useSavedObjectReferences( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc { diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index de81b218ef743..71ec92645b249 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -15,7 +15,7 @@ import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objec import mappings from './mappings.json'; import { getActionsMigrations } from './actions_migrations'; import { getActionTaskParamsMigrations } from './action_task_params_migrations'; -import { RawAction } from '../types'; +import { PreConfiguredAction, RawAction } from '../types'; import { getImportWarnings } from './get_import_warnings'; import { transformConnectorsForExport } from './transform_connectors_for_export'; import { ActionTypeRegistry } from '../action_type_registry'; @@ -29,7 +29,8 @@ export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, actionTypeRegistry: ActionTypeRegistry, - taskManagerIndex: string + taskManagerIndex: string, + preconfiguredActions: PreConfiguredAction[] ) { savedObjects.registerType({ name: ACTION_SAVED_OBJECT_TYPE, @@ -72,7 +73,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'single', mappings: mappings.action_task_params as SavedObjectsTypeMappingDefinition, - migrations: getActionTaskParamsMigrations(encryptedSavedObjects), + migrations: getActionTaskParamsMigrations(encryptedSavedObjects, preconfiguredActions), excludeOnUpgrade: async ({ readonlyEsClient }) => { const oldestIdleActionTask = await getOldestIdleActionTask( readonlyEsClient, From 73ccd07796229f44ea0d44653cdc7387f5f14234 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 20:50:44 -0400 Subject: [PATCH 09/14] Simplifying related saved objects --- .../server/create_execute_function.test.ts | 4 +- .../actions/server/create_execute_function.ts | 4 +- .../lib/action_task_params_utils.test.ts | 91 ++++++++++++++----- .../server/lib/action_task_params_utils.ts | 26 +++--- .../server/lib/related_saved_objects.ts | 22 ++--- .../server/lib/task_runner_factory.test.ts | 26 +++--- .../actions/server/lib/task_runner_factory.ts | 12 +-- 7 files changed, 114 insertions(+), 71 deletions(-) diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index c343ad85e6242..f31916458e59c 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -136,7 +136,7 @@ describe('execute()', () => { apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [ { - ref: 'related_some-type_0', + id: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', @@ -307,7 +307,7 @@ describe('execute()', () => { apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [ { - ref: 'related_some-type_0', + id: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 72325b94fac8f..e1f6a06be6c94 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -66,7 +66,7 @@ export function createExecutionEnqueuerFunction({ } // Get saved object references from action ID and relatedSavedObjects - const { references, relatedSavedObjectRefs } = extractSavedObjectReferences( + const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( id, isPreconfigured, relatedSavedObjects @@ -79,7 +79,7 @@ export function createExecutionEnqueuerFunction({ actionId: id, params, apiKey, - relatedSavedObjects: relatedSavedObjectRefs, + relatedSavedObjects: relatedSavedObjectWithRefs, }, { references: [...(executionSourceReference.references ?? []), ...(references ?? [])], diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts index 610d7bdef194e..98a425ff6fd39 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts @@ -66,19 +66,19 @@ describe('extractSavedObjectReferences()', () => { type: 'alert', }, ], - relatedSavedObjectRefs: [ + relatedSavedObjectWithRefs: [ { - ref: 'related_alert_0', + id: 'related_alert_0', type: 'alert', typeId: 'ruleTypeA', }, { - ref: 'related_action_1', + id: 'related_action_1', type: 'action', typeId: 'connectorTypeB', }, { - ref: 'related_alert_2', + id: 'related_alert_2', type: 'alert', typeId: 'ruleTypeB', namespace: 'custom', @@ -131,19 +131,19 @@ describe('extractSavedObjectReferences()', () => { type: 'alert', }, ], - relatedSavedObjectRefs: [ + relatedSavedObjectWithRefs: [ { - ref: 'related_alert_0', + id: 'related_alert_0', type: 'alert', typeId: 'ruleTypeA', }, { - ref: 'related_action_1', + id: 'related_action_1', type: 'action', typeId: 'connectorTypeB', }, { - ref: 'related_alert_2', + id: 'related_alert_2', type: 'alert', typeId: 'ruleTypeB', namespace: 'custom', @@ -197,17 +197,17 @@ describe('injectSavedObjectReferences()', () => { ], [ { - ref: 'related_alert_0', + id: 'related_alert_0', type: 'alert', typeId: 'ruleTypeA', }, { - ref: 'related_action_1', + id: 'related_action_1', type: 'action', typeId: 'connectorTypeB', }, { - ref: 'related_alert_2', + id: 'related_alert_2', type: 'alert', typeId: 'ruleTypeB', namespace: 'custom', @@ -259,17 +259,17 @@ describe('injectSavedObjectReferences()', () => { ], [ { - ref: 'related_alert_0', + id: 'related_alert_0', type: 'alert', typeId: 'ruleTypeA', }, { - ref: 'related_action_1', + id: 'related_action_1', type: 'action', typeId: 'connectorTypeB', }, { - ref: 'related_alert_2', + id: 'related_alert_2', type: 'alert', typeId: 'ruleTypeB', namespace: 'custom', @@ -298,7 +298,52 @@ describe('injectSavedObjectReferences()', () => { }); }); - test('correctly skips missing related saved object ids in references array', () => { + test('correctly keeps related saved object ids if references array is empty', () => { + expect( + injectSavedObjectReferences( + [], + [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly skips injecting missing related saved object ids in references array', () => { expect( injectSavedObjectReferences( [ @@ -312,25 +357,20 @@ describe('injectSavedObjectReferences()', () => { name: 'related_alert_0', type: 'alert', }, - { - id: 'xyz', - name: 'related_alert_2', - type: 'alert', - }, ], [ { - ref: 'related_alert_0', + id: 'related_alert_0', type: 'alert', typeId: 'ruleTypeA', }, { - ref: 'related_action_1', + id: 'def', type: 'action', typeId: 'connectorTypeB', }, { - ref: 'related_alert_2', + id: 'xyz', type: 'alert', typeId: 'ruleTypeB', namespace: 'custom', @@ -345,6 +385,11 @@ describe('injectSavedObjectReferences()', () => { type: 'alert', typeId: 'ruleTypeA', }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, { id: 'xyz', type: 'alert', diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts index 68571e769923c..9a47993e94d63 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { omit } from 'lodash'; import { SavedObjectAttribute, SavedObjectReference } from 'src/core/server/types'; -import { RelatedSavedObjectRef, RelatedSavedObjects } from './related_saved_objects'; +import { RelatedSavedObjects } from './related_saved_objects'; export const ACTION_REF_NAME = `actionRef`; @@ -17,10 +16,10 @@ export function extractSavedObjectReferences( relatedSavedObjects?: RelatedSavedObjects ): { references: SavedObjectReference[]; - relatedSavedObjectRefs?: RelatedSavedObjectRef[]; + relatedSavedObjectWithRefs?: RelatedSavedObjects; } { const references: SavedObjectReference[] = []; - const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; + const relatedSavedObjectWithRefs: RelatedSavedObjects = []; // Add action saved object to reference if it is not preconfigured if (!isPreconfigured) { @@ -33,10 +32,9 @@ export function extractSavedObjectReferences( // Add related saved objects, if any (relatedSavedObjects ?? []).forEach((relatedSavedObject, index) => { - const { id, ...restRelatedSavedObject } = relatedSavedObject; - relatedSavedObjectRefs.push({ - ...restRelatedSavedObject, - ref: `related_${relatedSavedObject.type}_${index}`, + relatedSavedObjectWithRefs.push({ + ...relatedSavedObject, + id: `related_${relatedSavedObject.type}_${index}`, }); references.push({ id: relatedSavedObject.id, @@ -47,29 +45,29 @@ export function extractSavedObjectReferences( return { references, - ...(relatedSavedObjects ? { relatedSavedObjectRefs } : {}), + ...(relatedSavedObjects ? { relatedSavedObjectWithRefs } : {}), }; } export function injectSavedObjectReferences( references: SavedObjectReference[], - relatedSavedObjectRefs?: RelatedSavedObjectRef[] + relatedSavedObjects?: RelatedSavedObjects ): { actionId?: string; relatedSavedObjects?: SavedObjectAttribute } { references = references ?? []; // Look for for the action id const action = references.find((ref) => ref.name === ACTION_REF_NAME); - const relatedSavedObjects = (relatedSavedObjectRefs ?? []).flatMap((relatedSavedObjectRef) => { - const reference = references.find((ref) => ref.name === relatedSavedObjectRef.ref); + const injectedRelatedSavedObjects = (relatedSavedObjects ?? []).flatMap((relatedSavedObject) => { + const reference = references.find((ref) => ref.name === relatedSavedObject.id); // These are used to provide context in the event log so we will not throw an error // if it is not found because we don't want to block the action execution - return reference ? [{ ...omit(relatedSavedObjectRef, 'ref'), id: reference.id }] : []; + return reference ? [{ ...relatedSavedObject, id: reference.id }] : [relatedSavedObject]; }); return { ...(action ? { actionId: action.id } : {}), - ...(relatedSavedObjectRefs ? { relatedSavedObjects } : {}), + ...(relatedSavedObjects ? { relatedSavedObjects: injectedRelatedSavedObjects } : {}), }; } diff --git a/x-pack/plugins/actions/server/lib/related_saved_objects.ts b/x-pack/plugins/actions/server/lib/related_saved_objects.ts index 915580005d3fc..160587a3a9a8b 100644 --- a/x-pack/plugins/actions/server/lib/related_saved_objects.ts +++ b/x-pack/plugins/actions/server/lib/related_saved_objects.ts @@ -9,19 +9,17 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '../../../../../src/core/server'; export type RelatedSavedObjects = TypeOf; -export type RelatedSavedObjectRef = Omit, 'id'> & { - ref: string; -}; -const RelatedSavedObjectSchema = schema.object({ - namespace: schema.maybe(schema.string({ minLength: 1 })), - id: schema.string({ minLength: 1 }), - type: schema.string({ minLength: 1 }), - // optional; for SO types like action/alert that have type id's - typeId: schema.maybe(schema.string({ minLength: 1 })), -}); - -const RelatedSavedObjectsSchema = schema.arrayOf(RelatedSavedObjectSchema, { defaultValue: [] }); +const RelatedSavedObjectsSchema = schema.arrayOf( + schema.object({ + namespace: schema.maybe(schema.string({ minLength: 1 })), + id: schema.string({ minLength: 1 }), + type: schema.string({ minLength: 1 }), + // optional; for SO types like action/alert that have type id's + typeId: schema.maybe(schema.string({ minLength: 1 })), + }), + { defaultValue: [] } +); export function validatedRelatedSavedObjects(logger: Logger, data: unknown): RelatedSavedObjects { try { diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 53f35c0f95a99..cff92f874e0ef 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -351,7 +351,7 @@ test('uses API key when provided', async () => { ); }); -test('uses relatedSavedObjects when provided', async () => { +test('uses relatedSavedObjects merged with references when provided', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -365,7 +365,7 @@ test('uses relatedSavedObjects when provided', async () => { actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ ref: 'related_some-type_0', type: 'some-type' }], + relatedSavedObjects: [{ id: 'related_some-type_0', type: 'some-type' }], }, references: [ { @@ -405,7 +405,7 @@ test('uses relatedSavedObjects when provided', async () => { }); }); -test('sanitizes invalid relatedSavedObjects when provided', async () => { +test('uses relatedSavedObjects as is when references are empty', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -419,7 +419,7 @@ test('sanitizes invalid relatedSavedObjects when provided', async () => { actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ Xid: 'related_some-type_0', type: 'some-type' }], + relatedSavedObjects: [{ id: 'abc', type: 'some-type', namespace: 'yo' }], }, references: [ { @@ -427,33 +427,35 @@ test('sanitizes invalid relatedSavedObjects when provided', async () => { name: 'actionRef', type: 'action', }, - { - id: 'some-id', - name: 'related_some-type_0', - type: 'some-type', - }, ], }); await taskRunner.run(); + expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ actionId: '2', isEphemeral: false, params: { baz: true }, + relatedSavedObjects: [ + { + id: 'abc', + type: 'some-type', + namespace: 'yo', + }, + ], request: expect.objectContaining({ headers: { // base64 encoded "123:abc" authorization: 'ApiKey MTIzOmFiYw==', }, }), - relatedSavedObjects: [], taskInfo: { scheduled: new Date(), }, }); }); -test('sanitizes invalid relatedSavedObject refs when provided', async () => { +test('sanitizes invalid relatedSavedObjects when provided', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -467,7 +469,7 @@ test('sanitizes invalid relatedSavedObject refs when provided', async () => { actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ ref: 'invalid-related_some-type_0', type: 'some-type' }], + relatedSavedObjects: [{ Xid: 'related_some-type_0', type: 'some-type' }], }, references: [ { diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 984a7356aac2f..45ae6c1d5fae9 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -33,7 +33,7 @@ import { } from '../types'; import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; import { asSavedObjectExecutionSource } from './action_execution_source'; -import { RelatedSavedObjectRef, validatedRelatedSavedObjects } from './related_saved_objects'; +import { RelatedSavedObjects, validatedRelatedSavedObjects } from './related_saved_objects'; import { injectSavedObjectReferences } from './action_task_params_utils'; export interface TaskRunnerContext { @@ -190,17 +190,17 @@ async function getActionTaskParams( references, } = actionTask; - const { actionId, relatedSavedObjects: relatedSOs } = injectSavedObjectReferences( - references, - relatedSavedObjects as RelatedSavedObjectRef[] - ); + const { + actionId, + relatedSavedObjects: injectedRelatedSavedObjects, + } = injectSavedObjectReferences(references, relatedSavedObjects as RelatedSavedObjects); return { ...actionTask, attributes: { ...actionTask.attributes, ...(actionId ? { actionId } : {}), - ...(relatedSavedObjects ? { relatedSavedObjects: relatedSOs } : {}), + ...(relatedSavedObjects ? { relatedSavedObjects: injectedRelatedSavedObjects } : {}), }, }; } else { From 5bd8ed41537c23b8e7184227aaf5b3d253e0a177 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 18 Aug 2021 20:55:12 -0400 Subject: [PATCH 10/14] Fixing functional test --- .../spaces_only/tests/action_task_params/migrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts index bdbafc4ccf49b..a480eee5f1b1c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts @@ -51,7 +51,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); // Should have reference entry for each relatedSavedObject entry (relatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { - expect(references.find((ref) => ref.name === relatedSavedObject.ref)).not.to.be(null); + expect(references.find((ref) => ref.name === relatedSavedObject.id)).not.to.be(null); }); }); }); From 930cdc60decc591ce9f13594a209c4bcae1c8df7 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 19 Aug 2021 07:41:51 -0400 Subject: [PATCH 11/14] Fixing migration --- .../saved_objects/action_task_params_migrations.test.ts | 8 ++++---- .../saved_objects/action_task_params_migrations.ts | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts index 738990296f8de..9d9f2e7128193 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts @@ -100,7 +100,7 @@ describe('successful migrations', () => { ...actionTaskParam.attributes, relatedSavedObjects: [ { - ref: 'related_some-type_0', + id: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', @@ -149,13 +149,13 @@ describe('successful migrations', () => { ...actionTaskParam.attributes, relatedSavedObjects: [ { - ref: 'related_some-type_0', + id: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', }, { - ref: 'related_another-type_1', + id: 'related_another-type_1', type: 'another-type', typeId: 'another-typeId', }, @@ -212,7 +212,7 @@ describe('successful migrations', () => { ...actionTaskParam.attributes, relatedSavedObjects: [ { - ref: 'related_some-type_0', + id: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts index 7350b87e76a8d..ebd65d79f24fd 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -16,7 +16,7 @@ import { import { ActionTaskParams, PreConfiguredAction } from '../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server'; -import { RelatedSavedObjectRef, RelatedSavedObjects } from '../lib/related_saved_objects'; +import { RelatedSavedObjects } from '../lib/related_saved_objects'; interface ActionTaskParamsLogMeta extends LogMeta { migrations: { actionTaskParamDocument: SavedObjectUnsanitizedDoc }; @@ -94,7 +94,7 @@ function useSavedObjectReferences( } = doc; const newReferences: SavedObjectReference[] = []; - const relatedSavedObjectRefs: RelatedSavedObjectRef[] = []; + const relatedSavedObjectRefs: RelatedSavedObjects = []; newReferences.push({ id: actionId, @@ -104,10 +104,9 @@ function useSavedObjectReferences( // Add related saved objects, if any ((relatedSavedObjects as RelatedSavedObjects) ?? []).forEach((relatedSavedObject, index) => { - const { id, ...restRelatedSavedObject } = relatedSavedObject; relatedSavedObjectRefs.push({ - ...restRelatedSavedObject, - ref: `related_${relatedSavedObject.type}_${index}`, + ...relatedSavedObject, + id: `related_${relatedSavedObject.type}_${index}`, }); newReferences.push({ id: relatedSavedObject.id, From 91d5577ddd84413860c7b5e28e339ade65efcfd9 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 19 Aug 2021 09:42:35 -0400 Subject: [PATCH 12/14] Javascript is sometimes magical --- .../action_task_params_migrations.test.ts | 57 ++++++++++++++++++- .../action_task_params_migrations.ts | 26 ++++++--- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts index 9d9f2e7128193..ceea9f3cff18f 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts @@ -36,7 +36,7 @@ describe('successful migrations', () => { }); describe('7.16.0', () => { - test('adds actionId to references array', () => { + test('adds actionId to references array if actionId is not preconfigured', () => { const migration716 = getActionTaskParamsMigrations( encryptedSavedObjectsSetup, preconfiguredActions @@ -55,6 +55,19 @@ describe('successful migrations', () => { }); }); + test('does not add actionId to references array if actionId is preconfigured', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ actionId: 'my-slack1' }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + references: [], + }); + }); + test('handles empty relatedSavedObjects array', () => { const migration716 = getActionTaskParamsMigrations( encryptedSavedObjectsSetup, @@ -122,7 +135,47 @@ describe('successful migrations', () => { }); }); - test('moves actionId and multiple relatedSavedObjects to references array', () => { + test('only adds relatedSavedObjects to references array if action is preconfigured', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ + actionId: 'my-slack1', + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + references: [ + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + }); + + test('adds actionId and multiple relatedSavedObjects to references array', () => { const migration716 = getActionTaskParamsMigrations( encryptedSavedObjectsSetup, preconfiguredActions diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts index ebd65d79f24fd..3612642160443 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -44,9 +44,8 @@ export function getActionTaskParamsMigrations( ): SavedObjectMigrationMap { const migrationActionTaskParamsSixteen = createEsoMigration( encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => - !isPreconfiguredAction(doc, preconfiguredActions), - pipeMigrations(useSavedObjectReferences) + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(getUseSavedObjectReferencesFn(preconfiguredActions)) ); return { @@ -85,8 +84,15 @@ export function isPreconfiguredAction( return !!preconfiguredActions.find((action) => action.id === doc.attributes.actionId); } +function getUseSavedObjectReferencesFn(preconfiguredActions: PreConfiguredAction[]) { + return (doc: SavedObjectUnsanitizedDoc) => { + return useSavedObjectReferences(doc, preconfiguredActions); + }; +} + function useSavedObjectReferences( - doc: SavedObjectUnsanitizedDoc + doc: SavedObjectUnsanitizedDoc, + preconfiguredActions: PreConfiguredAction[] ): SavedObjectUnsanitizedDoc { const { attributes: { actionId, relatedSavedObjects }, @@ -96,11 +102,13 @@ function useSavedObjectReferences( const newReferences: SavedObjectReference[] = []; const relatedSavedObjectRefs: RelatedSavedObjects = []; - newReferences.push({ - id: actionId, - name: 'actionRef', - type: 'action', - }); + if (!isPreconfiguredAction(doc, preconfiguredActions)) { + newReferences.push({ + id: actionId, + name: 'actionRef', + type: 'action', + }); + } // Add related saved objects, if any ((relatedSavedObjects as RelatedSavedObjects) ?? []).forEach((relatedSavedObject, index) => { From 5f909107b658be9a11b3b15320b4e289b25fee7c Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 19 Aug 2021 10:14:17 -0400 Subject: [PATCH 13/14] Updating functional test --- .../tests/action_task_params/migrations.ts | 87 +++++++++++++------ .../es_archives/action_task_params/data.json | 2 +- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts index a480eee5f1b1c..0c0b62b6cb529 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { SavedObject } from 'src/core/server'; +import { SavedObject, SavedObjectReference } from 'src/core/server'; import { ActionTaskParams } from '../../../../../plugins/actions/server/types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -25,35 +25,66 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); it('7.16.0 migrates action_task_params to use references array', async () => { - const responses = await Promise.all( - [ - 'action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed', - 'action_task_params:0205a520-0054-11ec-917b-f7aa317691ed', - ].map((id) => - es.get>({ - index: '.kibana', - id, - }) - ) + // Inspect migration of non-preconfigured connector ID + const response = await es.get>({ + index: '.kibana', + id: 'action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed', + }); + expect(response.statusCode).to.eql(200); + const { actionId, relatedSavedObjects, references } = getActionIdAndRelatedSavedObjects( + response.body._source ); - responses.forEach((response) => { - expect(response.statusCode).to.eql(200); - const actionTaskParams = (response.body._source as any) - ?.action_task_params as ActionTaskParams; - const actionId = actionTaskParams.actionId; - const relatedSavedObjects = actionTaskParams.relatedSavedObjects as unknown[]; - const references = response.body._source?.references ?? []; - // Should have 'actionRef' reference - expect(references.find((ref) => ref.name === 'actionRef')).to.eql({ - name: 'actionRef', - id: actionId, - type: 'action', - }); - // Should have reference entry for each relatedSavedObject entry - (relatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { - expect(references.find((ref) => ref.name === relatedSavedObject.id)).not.to.be(null); - }); + + expect(references.find((ref: SavedObjectReference) => ref.name === 'actionRef')).to.eql({ + name: 'actionRef', + id: actionId, + type: 'action', + }); + + // Should have reference entry for each relatedSavedObject entry + (relatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { + expect( + references.find((ref: SavedObjectReference) => ref.name === relatedSavedObject.id) + ).not.to.be(undefined); + }); + + // Inspect migration of preconfigured connector ID + const preconfiguredConnectorResponse = await es.get>({ + index: '.kibana', + id: 'action_task_params:0205a520-0054-11ec-917b-f7aa317691ed', + }); + expect(preconfiguredConnectorResponse.statusCode).to.eql(200); + + const { + relatedSavedObjects: preconfiguredRelatedSavedObjects, + references: preconfiguredReferences, + } = getActionIdAndRelatedSavedObjects(preconfiguredConnectorResponse.body._source); + + expect( + preconfiguredReferences.find((ref: SavedObjectReference) => ref.name === 'actionRef') + ).to.eql(undefined); + + // Should have reference entry for each relatedSavedObject entry + (preconfiguredRelatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { + expect( + preconfiguredReferences.find( + (ref: SavedObjectReference) => ref.name === relatedSavedObject.id + ) + ).not.to.be(undefined); }); }); }); + + function getActionIdAndRelatedSavedObjects(responseSource: any) { + if (!responseSource) { + return {}; + } + + const actionTaskParams = (responseSource as any)?.action_task_params as ActionTaskParams; + const actionId = actionTaskParams.actionId; + const relatedSavedObjects = actionTaskParams.relatedSavedObjects as unknown[]; + const references = responseSource?.references ?? []; + + return { actionId, relatedSavedObjects, references }; + } } diff --git a/x-pack/test/functional/es_archives/action_task_params/data.json b/x-pack/test/functional/es_archives/action_task_params/data.json index 757d2c703ed32..158faa429d888 100644 --- a/x-pack/test/functional/es_archives/action_task_params/data.json +++ b/x-pack/test/functional/es_archives/action_task_params/data.json @@ -38,7 +38,7 @@ "source": { "type": "action_task_params", "action_task_params" : { - "actionId" : "preconfigured-server-log", + "actionId" : "my-slack1", "params" : { "level" : "info", "message" : "hi hi" From 105e57e4a9cff11ab9bbb34aae6831bb0530d1b3 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 23 Aug 2021 16:33:30 -0400 Subject: [PATCH 14/14] PR feedback --- .../actions/server/create_execute_function.ts | 10 +++++++++- .../server/lib/action_task_params_utils.ts | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index e1f6a06be6c94..de15a1e0ca446 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -73,6 +73,14 @@ export function createExecutionEnqueuerFunction({ ); const executionSourceReference = executionSourceAsSavedObjectReferences(source); + const taskReferences = []; + if (executionSourceReference.references) { + taskReferences.push(...executionSourceReference.references); + } + if (references) { + taskReferences.push(...references); + } + const actionTaskParamsRecord = await unsecuredSavedObjectsClient.create( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, { @@ -82,7 +90,7 @@ export function createExecutionEnqueuerFunction({ relatedSavedObjects: relatedSavedObjectWithRefs, }, { - references: [...(executionSourceReference.references ?? []), ...(references ?? [])], + references: taskReferences, } ); diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts index 9a47993e94d63..a4b1afb9497dc 100644 --- a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts @@ -61,13 +61,20 @@ export function injectSavedObjectReferences( const injectedRelatedSavedObjects = (relatedSavedObjects ?? []).flatMap((relatedSavedObject) => { const reference = references.find((ref) => ref.name === relatedSavedObject.id); - // These are used to provide context in the event log so we will not throw an error - // if it is not found because we don't want to block the action execution + // relatedSavedObjects are used only in the event log document that is written during + // action execution. Because they are not critical to the actual execution of the action + // we will not throw an error if no reference is found matching this related saved object return reference ? [{ ...relatedSavedObject, id: reference.id }] : [relatedSavedObject]; }); - return { - ...(action ? { actionId: action.id } : {}), - ...(relatedSavedObjects ? { relatedSavedObjects: injectedRelatedSavedObjects } : {}), - }; + const result: { actionId?: string; relatedSavedObjects?: SavedObjectAttribute } = {}; + if (action) { + result.actionId = action.id; + } + + if (relatedSavedObjects) { + result.relatedSavedObjects = injectedRelatedSavedObjects; + } + + return result; }