diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index 02514511e7560..59c4bb2221b0a 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerting/kibana.json @@ -5,6 +5,6 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "alerting"], - "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions"], + "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions", "eventLog"], "optionalPlugins": ["usageCollection", "spaces", "security"] } diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index ec0ed4b761205..74a1f2349180e 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -9,6 +9,7 @@ import { coreMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../../plugins/licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; +import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -30,6 +31,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), } as any ); @@ -67,6 +69,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), } as any ); @@ -109,6 +112,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), } as any ); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index b0d06d4aeeb74..90e274df3a5ee 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -56,6 +56,15 @@ import { import { Services } from './types'; import { registerAlertsUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; +import { IEventLogger, IEventLogService } from '../../event_log/server'; + +const EVENT_LOG_PROVIDER = 'alerting'; +export const EVENT_LOG_ACTIONS = { + execute: 'execute', + executeAction: 'execute-action', + newInstance: 'new-instance', + resolvedInstance: 'resolved-instance', +}; export interface PluginSetupContract { registerType: AlertTypeRegistry['register']; @@ -73,6 +82,7 @@ export interface AlertingPluginsSetup { licensing: LicensingPluginSetup; spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; + eventLog: IEventLogService; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; @@ -93,6 +103,7 @@ export class AlertingPlugin { private readonly alertsClientFactory: AlertsClientFactory; private readonly telemetryLogger: Logger; private readonly kibanaIndex: Promise; + private eventLogger?: IEventLogger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); @@ -133,6 +144,11 @@ export class AlertingPlugin { ]), }); + plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); + this.eventLogger = plugins.eventLog.getLogger({ + event: { provider: EVENT_LOG_PROVIDER }, + }); + const alertTypeRegistry = new AlertTypeRegistry({ taskManager: plugins.taskManager, taskRunnerFactory: this.taskRunnerFactory, @@ -211,6 +227,7 @@ export class AlertingPlugin { actionsPlugin: plugins.actions, encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, getBasePath: this.getBasePath, + eventLogger: this.eventLogger!, }); scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 5bd8382f0a4b2..8d037a1ecee91 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -8,6 +8,7 @@ import { AlertType } from '../types'; import { createExecutionHandler } from './create_execution_handler'; import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; +import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; const alertType: AlertType = { id: 'test', @@ -31,6 +32,7 @@ const createExecutionHandlerParams = { getBasePath: jest.fn().mockReturnValue(undefined), alertType, logger: loggingServiceMock.create().get(), + eventLogger: eventLoggerMock.create(), actions: [ { id: '1', @@ -75,6 +77,37 @@ test('calls actionsPlugin.execute per selected action', async () => { }, ] `); + + const eventLogger = createExecutionHandlerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-action", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "2", + }, + "namespace": "default", + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + Object { + "id": "1", + "type": "action", + }, + ], + }, + "message": "alert: test:1: 'name-of-alert' instanceId: '2' scheduled actionGroup: 'default' action: test:1", + }, + ], + ] + `); }); test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => { diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 5d14f4adc709e..de06c8bbb374a 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -9,6 +9,8 @@ import { AlertAction, State, Context, AlertType } from '../types'; import { Logger } from '../../../../../src/core/server'; import { transformActionParams } from './transform_action_params'; import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; +import { IEventLogger, IEvent } from '../../../event_log/server'; +import { EVENT_LOG_ACTIONS } from '../plugin'; interface CreateExecutionHandlerOptions { alertId: string; @@ -20,6 +22,7 @@ interface CreateExecutionHandlerOptions { apiKey: string | null; alertType: AlertType; logger: Logger; + eventLogger: IEventLogger; } interface ExecutionHandlerOptions { @@ -39,6 +42,7 @@ export function createExecutionHandler({ spaceId, apiKey, alertType, + eventLogger, }: CreateExecutionHandlerOptions) { const alertTypeActionGroups = new Set(pluck(alertType.actionGroups, 'id')); return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => { @@ -63,19 +67,42 @@ export function createExecutionHandler({ }), }; }); + + const alertLabel = `${alertType.id}:${alertId}: '${alertName}'`; + for (const action of actions) { - if (actionsPlugin.isActionTypeEnabled(action.actionTypeId)) { - await actionsPlugin.execute({ - id: action.id, - params: action.params, - spaceId, - apiKey, - }); - } else { + if (!actionsPlugin.isActionTypeEnabled(action.actionTypeId)) { logger.warn( `Alert "${alertId}" skipped scheduling action "${action.id}" because it is disabled` ); + continue; } + + // TODO would be nice to add the action name here, but it's not available + const actionLabel = `${action.actionTypeId}:${action.id}`; + await actionsPlugin.execute({ + id: action.id, + params: action.params, + spaceId, + apiKey, + }); + + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.executeAction }, + kibana: { + alerting: { + instance_id: alertInstanceId, + }, + namespace: spaceId, + saved_objects: [ + { type: 'alert', id: alertId }, + { type: 'action', id: action.id }, + ], + }, + }; + + event.message = `alert: ${alertLabel} instanceId: '${alertInstanceId}' scheduled actionGroup: '${actionGroup}' action: ${actionLabel}`; + eventLogger.logEvent(event); } }; } diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 5f4669f64f09d..520f8d5c99b16 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -14,6 +14,8 @@ import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_o import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; import { actionsMock } from '../../../actions/server/mocks'; +import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; +import { IEventLogger } from '../../../event_log/server'; const alertType = { id: 'test', @@ -59,6 +61,7 @@ describe('Task Runner', () => { const taskRunnerFactoryInitializerParams: jest.Mocked & { actionsPlugin: jest.Mocked; + eventLogger: jest.Mocked; } = { getServices: jest.fn().mockReturnValue(services), actionsPlugin: actionsMock.createStart(), @@ -66,6 +69,7 @@ describe('Task Runner', () => { logger: loggingServiceMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), + eventLogger: eventLoggerMock.create(), }; const mockedAlertTypeSavedObject = { @@ -156,6 +160,26 @@ describe('Task Runner', () => { expect(call.services.alertInstanceFactory).toBeTruthy(); expect(call.services.callCluster).toBeTruthy(); expect(call.services).toBeTruthy(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(eventLogger.logEvent.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + } + `); }); test('actionsPlugin.execute is called per alert instance that is scheduled', async () => { @@ -194,6 +218,74 @@ describe('Task Runner', () => { }, ] `); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + Array [ + Object { + "event": Object { + "action": "new-instance", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "1", + }, + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "test:1: 'alert-name' created new instance: '1'", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute-action", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "1", + }, + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + Object { + "id": "1", + "type": "action", + }, + ], + }, + "message": "alert: test:1: 'alert-name' instanceId: '1' scheduled actionGroup: 'default' action: undefined:1", + }, + ], + ] + `); }); test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { @@ -241,6 +333,50 @@ describe('Task Runner', () => { }, } `); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + Array [ + Object { + "event": Object { + "action": "resolved-instance", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "2", + }, + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "test:1: 'alert-name' resolved instance: '2'", + }, + ], + ] + `); }); test('validates params before executing the alert type', async () => { @@ -410,6 +546,33 @@ describe('Task Runner', () => { }, } `); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "error": Object { + "message": "OMG", + }, + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert execution failure: test:1: 'alert-name'", + }, + ], + ] + `); }); test('recovers gracefully when the Alert Task Runner throws an exception when fetching the encrypted attributes', async () => { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 42768a80a4ccf..2ba56396279ea 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pick, mapValues, omit } from 'lodash'; +import { pick, mapValues, omit, without } from 'lodash'; import { Logger, SavedObject } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; @@ -24,6 +24,8 @@ import { import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type'; import { taskInstanceToAlertTaskInstance } from './alert_task_instance'; import { AlertInstances } from '../alert_instance/alert_instance'; +import { EVENT_LOG_ACTIONS } from '../plugin'; +import { IEvent, IEventLogger } from '../../../event_log/server'; const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' }; @@ -124,6 +126,7 @@ export class TaskRunner { actions: actionsWithIds, spaceId, alertType: this.alertType, + eventLogger: this.context.eventLogger, }); } @@ -165,29 +168,61 @@ export class TaskRunner { rawAlertInstance => new AlertInstance(rawAlertInstance) ); - const updatedAlertTypeState = await this.alertType.executor({ - alertId, - services: { - ...services, - alertInstanceFactory: createAlertInstanceFactory(alertInstances), - }, - params, - state: alertTypeState, - startedAt: this.taskInstance.startedAt!, - previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, - spaceId, - namespace, - name, - tags, - createdBy, - updatedBy, - }); + const originalAlertInstanceIds = Object.keys(alertInstances); + const eventLogger = this.context.eventLogger; + const alertLabel = `${this.alertType.id}:${alertId}: '${name}'`; + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.execute }, + kibana: { namespace, saved_objects: [{ type: 'alert', id: alertId }] }, + }; + eventLogger.startTiming(event); + + let updatedAlertTypeState: void | Record; + try { + updatedAlertTypeState = await this.alertType.executor({ + alertId, + services: { + ...services, + alertInstanceFactory: createAlertInstanceFactory(alertInstances), + }, + params, + state: alertTypeState, + startedAt: this.taskInstance.startedAt!, + previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, + spaceId, + namespace, + name, + tags, + createdBy, + updatedBy, + }); + } catch (err) { + eventLogger.stopTiming(event); + event.message = `alert execution failure: ${alertLabel}`; + event.error = event.error || {}; + event.error.message = err.message; + eventLogger.logEvent(event); + throw err; + } + + eventLogger.stopTiming(event); + event.message = `alert executed: ${alertLabel}`; + eventLogger.logEvent(event); // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object const instancesWithScheduledActions = pick( alertInstances, (alertInstance: AlertInstance) => alertInstance.hasScheduledActions() ); + const currentAlertInstanceIds = Object.keys(instancesWithScheduledActions); + generateNewAndResolvedInstanceEvents({ + eventLogger, + originalAlertInstanceIds, + currentAlertInstanceIds, + alertId, + alertLabel, + namespace, + }); if (!muteAll) { const enabledAlertInstances = omit( @@ -313,6 +348,48 @@ export class TaskRunner { } } +interface GenerateNewAndResolvedInstanceEventsParams { + eventLogger: IEventLogger; + originalAlertInstanceIds: string[]; + currentAlertInstanceIds: string[]; + alertId: string; + alertLabel: string; + namespace: string | undefined; +} + +function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInstanceEventsParams) { + const { currentAlertInstanceIds, originalAlertInstanceIds } = params; + const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); + const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); + + for (const id of newIds) { + const message = `${params.alertLabel} created new instance: '${id}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.newInstance, message); + } + + for (const id of resolvedIds) { + const message = `${params.alertLabel} resolved instance: '${id}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message); + } + + function logInstanceEvent(id: string, action: string, message: string) { + const event: IEvent = { + event: { + action, + }, + kibana: { + namespace: params.namespace, + alerting: { + instance_id: id, + }, + saved_objects: [{ type: 'alert', id: params.alertId }], + }, + message, + }; + params.eventLogger.logEvent(event); + } +} + /** * If an error is thrown, wrap it in an AlertTaskRunResult * so that we can treat each field independantly diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index fc34cacba2818..1d220f97f127a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -10,6 +10,7 @@ import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; +import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; const alertType = { id: 'test', @@ -62,6 +63,7 @@ describe('Task Runner Factory', () => { logger: loggingServiceMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), + eventLogger: eventLoggerMock.create(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index 3bad4e475ff49..b58db8c74f7bb 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -14,11 +14,13 @@ import { SpaceIdToNamespaceFunction, } from '../types'; import { TaskRunner } from './task_runner'; +import { IEventLogger } from '../../../event_log/server'; export interface TaskRunnerContext { logger: Logger; getServices: GetServicesFunction; actionsPlugin: ActionsPluginStartContract; + eventLogger: IEventLogger; encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index d0e4652c2828e..ab1b4096d17f2 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -55,6 +55,12 @@ "user": { "properties": { "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, "ignore_above": 1024, "type": "keyword" } @@ -70,6 +76,14 @@ "type": "keyword", "ignore_above": 1024 }, + "alerting": { + "properties": { + "instance_id": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, "saved_objects": { "properties": { "store": { diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index a040ede891bfd..b731093b33b06 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -18,7 +18,7 @@ type DeepPartial = { [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial; }; -export const ECS_VERSION = '1.3.1'; +export const ECS_VERSION = '1.5.0'; // types and config-schema describing the es structures export type IValidatedEvent = TypeOf; @@ -57,6 +57,11 @@ export const EventSchema = schema.maybe( schema.object({ server_uuid: ecsString(), namespace: ecsString(), + alerting: schema.maybe( + schema.object({ + instance_id: ecsString(), + }) + ), saved_objects: schema.maybe( schema.arrayOf( schema.object({ diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 43fd0c78183a1..9e721b06ec335 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -11,6 +11,15 @@ exports.EcsKibanaExtensionsMappings = { type: 'keyword', ignore_above: 1024, }, + // alerting specific fields + alerting: { + properties: { + instance_id: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, // relevant kibana space namespace: { type: 'keyword', @@ -53,6 +62,7 @@ exports.EcsEventLogProperties = [ 'user.name', 'kibana.server_uuid', 'kibana.namespace', + 'kibana.alerting.instance_id', 'kibana.saved_objects.store', 'kibana.saved_objects.id', 'kibana.saved_objects.name',