From 113181b6df1f04d6712790a52c48952f608510ba Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Mon, 6 Apr 2020 10:24:02 -0400 Subject: [PATCH 01/27] [Alerting] write event log entries for alert execution and it's actions (#61706) resolves https://github.com/elastic/kibana/issues/55636 Writes eventLog events for alert executions, and the actions executed from that alert execution. --- x-pack/plugins/alerting/kibana.json | 2 +- x-pack/plugins/alerting/server/plugin.test.ts | 4 + x-pack/plugins/alerting/server/plugin.ts | 17 ++ .../create_execution_handler.test.ts | 33 ++++ .../task_runner/create_execution_handler.ts | 43 ++++- .../server/task_runner/task_runner.test.ts | 163 ++++++++++++++++++ .../server/task_runner/task_runner.ts | 113 ++++++++++-- .../task_runner/task_runner_factory.test.ts | 2 + .../server/task_runner/task_runner_factory.ts | 2 + .../plugins/event_log/generated/mappings.json | 14 ++ x-pack/plugins/event_log/generated/schemas.ts | 7 +- x-pack/plugins/event_log/scripts/mappings.js | 10 ++ 12 files changed, 382 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index 02514511e756..59c4bb2221b0 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 ec0ed4b76120..74a1f2349180 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 b0d06d4aeeb7..90e274df3a5e 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 5bd8382f0a4b..8d037a1ecee9 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 5d14f4adc709..de06c8bbb374 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 5f4669f64f09..520f8d5c99b1 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 42768a80a4cc..2ba56396279e 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 fc34cacba281..1d220f97f127 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 3bad4e475ff4..b58db8c74f7b 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 d0e4652c2828..ab1b4096d17f 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 a040ede891bf..b731093b33b0 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 43fd0c78183a..9e721b06ec33 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', From fa661c0f9a39df75ca7a865854b7e099de7c4932 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 6 Apr 2020 17:51:27 +0300 Subject: [PATCH 02/27] match_all query disappears when typed into Lucene query bar (#62194) * match_all query disappears when typed into Lucene query bar Closes: #52115 * add migrations for searh savedobject type Co-authored-by: Elastic Machine --- .../kibana/migrations/migrations.js | 6 +- .../public/dashboard/migrations/index.ts | 1 + .../migrate_match_all_query.test.ts | 52 +++++++++++++++++ .../migrations/migrate_match_all_query.ts | 56 +++++++++++++++++++ .../data/public/query/lib/to_user.test.ts | 14 ++--- src/plugins/data/public/query/lib/to_user.ts | 3 - .../saved_objects/search_migrations.test.ts | 32 +++++++++++ .../server/saved_objects/search_migrations.ts | 36 ++++++++++++ .../visualization_migrations.test.ts | 26 +++++++++ .../saved_objects/visualization_migrations.ts | 37 +++++++++++- 10 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index d37887c640b9..029dbde555a4 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -18,7 +18,10 @@ */ import { get } from 'lodash'; -import { migrations730 as dashboardMigrations730 } from '../public/dashboard/migrations'; +import { + migrateMatchAllQuery, + migrations730 as dashboardMigrations730, +} from '../public/dashboard/migrations'; function migrateIndexPattern(doc) { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -60,6 +63,7 @@ function migrateIndexPattern(doc) { export const migrations = { dashboard: { + '6.7.2': migrateMatchAllQuery, '7.0.0': doc => { // Set new "references" attribute doc.references = doc.references || []; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts index da2542e854c3..f333ce97d120 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts @@ -18,3 +18,4 @@ */ export { migrations730 } from './migrations_730'; +export { migrateMatchAllQuery } from './migrate_match_all_query'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts new file mode 100644 index 000000000000..8a91c422eed3 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { migrateMatchAllQuery } from './migrate_match_all_query'; +import { SavedObjectMigrationContext, SavedObjectMigrationFn } from 'kibana/server'; + +const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; + +describe('migrate match_all query', () => { + test('should migrate obsolete match_all query', () => { + const migratedDoc = migrateMatchAllQuery( + { + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: { + match_all: {}, + }, + }), + }, + }, + } as Parameters[0], + savedObjectMigrationContext + ); + + const migratedSearchSource = JSON.parse( + migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + + expect(migratedSearchSource).toEqual({ + query: { + query: '', + language: 'kuery', + }, + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts new file mode 100644 index 000000000000..707aae9e5d4a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectMigrationFn } from 'kibana/server'; +import { get } from 'lodash'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../../../../plugins/data/common'; + +export const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + + if (searchSourceJSON) { + let searchSource: any; + + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (searchSource.query?.match_all) { + return { + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + ...searchSource, + query: { + query: '', + language: DEFAULT_QUERY_LANGUAGE, + }, + }), + }, + }, + }; + } + } + + return doc; +}; diff --git a/src/plugins/data/public/query/lib/to_user.test.ts b/src/plugins/data/public/query/lib/to_user.test.ts index d13afa251ecb..74373ca0d7de 100644 --- a/src/plugins/data/public/query/lib/to_user.test.ts +++ b/src/plugins/data/public/query/lib/to_user.test.ts @@ -19,27 +19,27 @@ import { toUser } from '../'; -describe('user input helpers', function() { - describe('model presentation formatter', function() { - it('should present objects as strings', function() { +describe('user input helpers', () => { + describe('model presentation formatter', () => { + test('should present objects as strings', () => { expect(toUser({ foo: 'bar' })).toBe('{"foo":"bar"}'); }); - it('should present query_string queries as strings', function() { + test('should present query_string queries as strings', () => { expect(toUser({ query_string: { query: 'lucene query string' } })).toBe( 'lucene query string' ); }); - it('should present query_string queries without a query as an empty string', function() { + test('should present query_string queries without a query as an empty string', () => { expect(toUser({ query_string: {} })).toBe(''); }); - it('should present string as strings', function() { + test('should present string as strings', () => { expect(toUser('foo')).toBe('foo'); }); - it('should present numbers as strings', function() { + test('should present numbers as strings', () => { expect(toUser(400)).toBe('400'); }); }); diff --git a/src/plugins/data/public/query/lib/to_user.ts b/src/plugins/data/public/query/lib/to_user.ts index 1fdb2d8ed03d..1a364534d93f 100644 --- a/src/plugins/data/public/query/lib/to_user.ts +++ b/src/plugins/data/public/query/lib/to_user.ts @@ -27,9 +27,6 @@ export function toUser(text: { [key: string]: any } | string | number): string { return ''; } if (typeof text === 'object') { - if (text.match_all) { - return ''; - } if (text.query_string) { return toUser(text.query_string.query); } diff --git a/src/plugins/data/server/saved_objects/search_migrations.test.ts b/src/plugins/data/server/saved_objects/search_migrations.test.ts index 7fdf2e14aefe..f9b4af7d6d2b 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/data/server/saved_objects/search_migrations.test.ts @@ -23,6 +23,38 @@ import { searchSavedObjectTypeMigrations } from './search_migrations'; const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; describe('migration search', () => { + describe('6.7.2', () => { + const migrationFn = searchSavedObjectTypeMigrations['6.7.2']; + + it('should migrate obsolete match_all query', () => { + const migratedDoc = migrationFn( + { + type: 'search', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: { + match_all: {}, + }, + }), + }, + }, + }, + savedObjectMigrationContext + ); + const migratedSearchSource = JSON.parse( + migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + + expect(migratedSearchSource).toEqual({ + query: { + query: '', + language: 'kuery', + }, + }); + }); + }); + describe('7.0.0', () => { const migrationFn = searchSavedObjectTypeMigrations['7.0.0']; diff --git a/src/plugins/data/server/saved_objects/search_migrations.ts b/src/plugins/data/server/saved_objects/search_migrations.ts index db545e52ce17..45fa5e11e2a3 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.ts +++ b/src/plugins/data/server/saved_objects/search_migrations.ts @@ -19,6 +19,41 @@ import { flow, get } from 'lodash'; import { SavedObjectMigrationFn } from 'kibana/server'; +import { DEFAULT_QUERY_LANGUAGE } from '../../common'; + +const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + + if (searchSourceJSON) { + let searchSource: any; + + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (searchSource.query?.match_all) { + return { + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + ...searchSource, + query: { + query: '', + language: DEFAULT_QUERY_LANGUAGE, + }, + }), + }, + }, + }; + } + } + + return doc; +}; const migrateIndexPattern: SavedObjectMigrationFn = doc => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -87,6 +122,7 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = doc => { }; export const searchSavedObjectTypeMigrations = { + '6.7.2': flow(migrateMatchAllQuery), '7.0.0': flow(setNewReferences), '7.4.0': flow(migrateSearchSortToNestedArray), }; diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 02c114bad4e7..c7f245e59551 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -150,6 +150,32 @@ describe('migration visualization', () => { expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); expect(aggs[2]).not.toHaveProperty('params.time_zone'); }); + + it('should migrate obsolete match_all query', () => { + const migratedDoc = migrate({ + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: { + match_all: {}, + }, + }), + }, + }, + }); + const migratedSearchSource = JSON.parse( + migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + + expect(migratedSearchSource).toEqual({ + query: { + query: '', + language: 'kuery', + }, + }); + }); }); }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 9ee355cbb23c..db87006dde3e 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -19,6 +19,7 @@ import { SavedObjectMigrationFn } from 'kibana/server'; import { cloneDeep, get, omit, has, flow } from 'lodash'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; const migrateIndexPattern: SavedObjectMigrationFn = doc => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -539,6 +540,40 @@ const migrateTableSplits: SavedObjectMigrationFn = doc => { } }; +const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + + if (searchSourceJSON) { + let searchSource: any; + + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (searchSource.query?.match_all) { + return { + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + ...searchSource, + query: { + query: '', + language: DEFAULT_QUERY_LANGUAGE, + }, + }), + }, + }, + }; + } + } + + return doc; +}; + export const visualizationSavedObjectTypeMigrations = { /** * We need to have this migration twice, once with a version prior to 7.0.0 once with a version @@ -550,7 +585,7 @@ export const visualizationSavedObjectTypeMigrations = { * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 * only contained the 6.7.2 migration and not the 7.0.1 migration. */ - '6.7.2': flow(removeDateHistogramTimeZones), + '6.7.2': flow(migrateMatchAllQuery, removeDateHistogramTimeZones), '7.0.0': flow( addDocReferences, migrateIndexPattern, From ba446f39002cc28c01964af67723a00194bd4176 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 6 Apr 2020 11:11:25 -0400 Subject: [PATCH 03/27] [Uptime] Default uptime alert type and disable changing type (#62028) * Default uptime alert type and disable changing type. * Update functional test to handle new UI flow. * Fix type error. Co-authored-by: Elastic Machine --- x-pack/legacy/plugins/uptime/public/uptime_app.tsx | 5 ++++- x-pack/test/functional/page_objects/uptime_page.ts | 6 +++++- x-pack/test/functional/services/uptime/alerts.ts | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index fa2998532d14..dafb20dc9c32 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -104,7 +104,10 @@ const Application = (props: UptimeAppProps) => {
- +
diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index fcf2b77dbd62..39c3c46adddb 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -105,6 +105,7 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo alertTags, alertThrottleInterval, alertTimerangeSelection, + alertType, filters, }: { alertName: string; @@ -113,11 +114,14 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo alertThrottleInterval: string; alertNumTimes: string; alertTimerangeSelection: string; + alertType?: string; filters?: string; }) { const { setKueryBarText } = commonService; await alerts.openFlyout(); - await alerts.openMonitorStatusAlertType(); + if (alertType) { + await alerts.openMonitorStatusAlertType(alertType); + } await alerts.setAlertName(alertName); await alerts.setAlertTags(alertTags); await alerts.setAlertInterval(alertInterval); diff --git a/x-pack/test/functional/services/uptime/alerts.ts b/x-pack/test/functional/services/uptime/alerts.ts index 5ee444adec82..3a8193ff3d32 100644 --- a/x-pack/test/functional/services/uptime/alerts.ts +++ b/x-pack/test/functional/services/uptime/alerts.ts @@ -15,8 +15,8 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) { await testSubjects.click('xpack.uptime.alertsPopover.toggleButton', 5000); await testSubjects.click('xpack.uptime.toggleAlertFlyout', 5000); }, - async openMonitorStatusAlertType() { - return testSubjects.click('xpack.uptime.alerts.monitorStatus-SelectOption', 5000); + async openMonitorStatusAlertType(alertType: string) { + return testSubjects.click(`xpack.uptime.alerts.${alertType}-SelectOption`, 5000); }, async setAlertTags(tags: string[]) { for (let i = 0; i < tags.length; i += 1) { From cde0b73cb12c0f19fd2e7753711f946b7829aeaf Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 6 Apr 2020 17:24:21 +0200 Subject: [PATCH 04/27] [Uptime] Removed unnecessary filter from Monitor List Fetch (#61958) * removed unnecessary filter * update condition * added a unit test for mix state * fix types * fix type * updated test * update * updates test * updates test Co-authored-by: Elastic Machine --- .../search/refine_potential_matches.ts | 3 +- .../{get_all_pings.js => get_all_pings.ts} | 3 +- .../uptime/graphql/{index.js => index.ts} | 4 +- .../apis/uptime/graphql/monitor_states.ts | 66 ++++++++++++++++++- .../apis/uptime/{index.js => index.ts} | 4 +- ..._collectors.js => telemetry_collectors.ts} | 4 +- 6 files changed, 78 insertions(+), 6 deletions(-) rename x-pack/test/api_integration/apis/uptime/{get_all_pings.js => get_all_pings.ts} (95%) rename x-pack/test/api_integration/apis/uptime/graphql/{index.js => index.ts} (82%) rename x-pack/test/api_integration/apis/uptime/{index.js => index.ts} (81%) rename x-pack/test/api_integration/apis/uptime/{telemetry_collectors.js => telemetry_collectors.ts} (83%) diff --git a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts index 7d69ff6751f0..218eb2f121a8 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts @@ -68,7 +68,8 @@ const fullyMatchingIds = async ( const status = topSource.summary.down > 0 ? 'down' : 'up'; // This monitor doesn't match, so just skip ahead and don't add it to the output - if (queryContext.statusFilter && queryContext.statusFilter !== status) { + // Only skip in case of up statusFilter, for a monitor to be up, all checks should be up + if (queryContext?.statusFilter === 'up' && queryContext.statusFilter !== status) { continue MonitorLoop; } diff --git a/x-pack/test/api_integration/apis/uptime/get_all_pings.js b/x-pack/test/api_integration/apis/uptime/get_all_pings.ts similarity index 95% rename from x-pack/test/api_integration/apis/uptime/get_all_pings.js rename to x-pack/test/api_integration/apis/uptime/get_all_pings.ts index dcbc9389b1da..666986e7008b 100644 --- a/x-pack/test/api_integration/apis/uptime/get_all_pings.js +++ b/x-pack/test/api_integration/apis/uptime/get_all_pings.ts @@ -7,8 +7,9 @@ import moment from 'moment'; import expect from '@kbn/expect'; import { PINGS_DATE_RANGE_START, PINGS_DATE_RANGE_END } from './constants'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function({ getService }) { +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/uptime/graphql/index.js b/x-pack/test/api_integration/apis/uptime/graphql/index.ts similarity index 82% rename from x-pack/test/api_integration/apis/uptime/graphql/index.js rename to x-pack/test/api_integration/apis/uptime/graphql/index.ts index ee22974d4717..2e0b5e2eea2a 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/index.js +++ b/x-pack/test/api_integration/apis/uptime/graphql/index.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function({ loadTestFile }) { +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { describe('graphql', () => { // each of these test files imports a GQL query from // the uptime app and runs it against the live HTTP server, diff --git a/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts b/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts index a293426195d2..216560583249 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts @@ -5,10 +5,11 @@ */ import expect from '@kbn/expect'; -import { monitorStatesQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_states_query'; import { expectFixtureEql } from './helpers/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { makeChecksWithStatus } from './helpers/make_checks'; +import { monitorStatesQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_states_query'; +import { MonitorSummary } from '../../../../../legacy/plugins/uptime/common/graphql/types'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -116,6 +117,7 @@ export default function({ getService }: FtrProviderContext) { } return d; }); + dateRangeEnd = new Date().toISOString(); nonSummaryIp = checks[0][0].monitor.ip; }); @@ -177,5 +179,67 @@ export default function({ getService }: FtrProviderContext) { }); }); }); + + describe(' test status filter', async () => { + const upMonitorId = 'up-test-id'; + const downMonitorId = 'down-test-id'; + const mixMonitorId = 'mix-test-id'; + before('generate three monitors with up, down, mix state', async () => { + await getService('esArchiver').load('uptime/blank'); + + const es = getService('legacyEs'); + + const observer = { + geo: { + name: 'US-East', + location: '40.7128, -74.0060', + }, + }; + + // Generating three monitors each with two geo locations, + // One in a down state , + // One in an up state, + // One in a mix state + + dateRangeStart = new Date().toISOString(); + + await makeChecksWithStatus(es, upMonitorId, 1, 4, 1, {}, 'up'); + await makeChecksWithStatus(es, upMonitorId, 1, 4, 1, { observer }, 'up'); + + await makeChecksWithStatus(es, downMonitorId, 1, 4, 1, {}, 'down'); + await makeChecksWithStatus(es, downMonitorId, 1, 4, 1, { observer }, 'down'); + + await makeChecksWithStatus(es, mixMonitorId, 1, 4, 1, {}, 'up'); + await makeChecksWithStatus(es, mixMonitorId, 1, 4, 1, { observer }, 'down'); + + dateRangeEnd = new Date().toISOString(); + }); + + after('unload heartbeat index', () => getService('esArchiver').unload('uptime/blank')); + + it('should return all monitor when no status filter', async () => { + const { monitorStates } = await getMonitorStates({}); + expect(monitorStates.summaries.length).to.eql(3); + // Summaries are by default sorted by monitor names + expect( + monitorStates.summaries.map((summary: MonitorSummary) => summary.monitor_id) + ).to.eql([downMonitorId, mixMonitorId, upMonitorId]); + }); + + it('should return a monitor with mix state if check status filter is down', async () => { + const { monitorStates } = await getMonitorStates({ statusFilter: 'down' }); + expect(monitorStates.summaries.length).to.eql(2); + monitorStates.summaries.forEach((summary: MonitorSummary) => { + expect(summary.monitor_id).to.not.eql(upMonitorId); + }); + }); + + it('should not return a monitor with mix state if check status filter is up', async () => { + const { monitorStates } = await getMonitorStates({ statusFilter: 'up' }); + + expect(monitorStates.summaries.length).to.eql(1); + expect(monitorStates.summaries[0].monitor_id).to.eql(upMonitorId); + }); + }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/index.js b/x-pack/test/api_integration/apis/uptime/index.ts similarity index 81% rename from x-pack/test/api_integration/apis/uptime/index.js rename to x-pack/test/api_integration/apis/uptime/index.ts index 0b18f14cd7da..a21db08d58c4 100644 --- a/x-pack/test/api_integration/apis/uptime/index.js +++ b/x-pack/test/api_integration/apis/uptime/index.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function({ getService, loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService, loadTestFile }: FtrProviderContext) { const es = getService('legacyEs'); describe('uptime', () => { diff --git a/x-pack/test/api_integration/apis/uptime/telemetry_collectors.js b/x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts similarity index 83% rename from x-pack/test/api_integration/apis/uptime/telemetry_collectors.js rename to x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts index eb4e3dc00290..e33c6120557b 100644 --- a/x-pack/test/api_integration/apis/uptime/telemetry_collectors.js +++ b/x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function({ getService }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('telemetry collectors', () => { From 6da7c00b5db79df491cea9fdb5a17f79f167a2ea Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Mon, 6 Apr 2020 11:49:34 -0400 Subject: [PATCH 05/27] Remove the action_value_click action in canvas (#62215) Co-authored-by: Elastic Machine --- .../plugins/canvas/public/application.tsx | 34 ++++++++++++++++++- x-pack/legacy/plugins/canvas/public/legacy.ts | 1 + .../legacy/plugins/canvas/public/plugin.tsx | 4 ++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index e26157aadebc..79b3918fef99 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -26,9 +26,24 @@ import { getDocumentationLinks } from './lib/documentation_links'; import { HelpMenu } from './components/help_menu/help_menu'; import { createStore } from './store'; +import { VALUE_CLICK_TRIGGER, ActionByType } from '../../../../../src/plugins/ui_actions/public'; +/* eslint-disable */ +import { ACTION_VALUE_CLICK } from '../../../../../src/plugins/data/public/actions/value_click_action'; +/* eslint-enable */ + import { CapabilitiesStrings } from '../i18n'; const { ReadOnlyBadge: strings } = CapabilitiesStrings; +let restoreAction: ActionByType | undefined; +const emptyAction = { + id: 'empty-action', + type: '', + getDisplayName: () => 'empty action', + getIconType: () => undefined, + isCompatible: async () => true, + execute: async () => undefined, +} as ActionByType; + export const renderApp = ( coreStart: CoreStart, plugins: CanvasStartDeps, @@ -94,13 +109,30 @@ export const initializeCanvas = async ( }, }); + // TODO: We need this to disable the filtering modal from popping up in lens embeds until + // they honor the disableTriggers parameter + const action = startPlugins.uiActions.getAction(ACTION_VALUE_CLICK); + + if (action) { + restoreAction = action; + + startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, action.id); + startPlugins.uiActions.attachAction(VALUE_CLICK_TRIGGER, emptyAction); + } + return canvasStore; }; -export const teardownCanvas = (coreStart: CoreStart) => { +export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDeps) => { destroyRegistries(); resetInterpreter(); + startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, emptyAction.id); + if (restoreAction) { + startPlugins.uiActions.attachAction(VALUE_CLICK_TRIGGER, restoreAction); + restoreAction = undefined; + } + coreStart.chrome.setBadge(undefined); coreStart.chrome.setHelpExtension(undefined); }; diff --git a/x-pack/legacy/plugins/canvas/public/legacy.ts b/x-pack/legacy/plugins/canvas/public/legacy.ts index 9bccc958f726..a6caa1985325 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy.ts @@ -27,6 +27,7 @@ const shimSetupPlugins: CanvasSetupDeps = { const shimStartPlugins: CanvasStartDeps = { ...npStart.plugins, expressions: npStart.plugins.expressions, + uiActions: npStart.plugins.uiActions, __LEGACY: { // ToDo: Copy directly into canvas absoluteToParsedUrl, diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index f4a3aed28a0a..d9e5e6b4b084 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -10,6 +10,7 @@ import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { initLoadingIndicator } from './lib/loading_indicator'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; // @ts-ignore untyped local import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; @@ -31,6 +32,7 @@ export interface CanvasSetupDeps { export interface CanvasStartDeps { expressions: ExpressionsStart; + uiActions: UiActionsStart; __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; formatMsg: any; @@ -70,7 +72,7 @@ export class CanvasPlugin return () => { unmount(); - teardownCanvas(coreStart); + teardownCanvas(coreStart, depsStart); }; }, }); From 1e92dcf1f33ddb9af5ea794a4612a0c940edee49 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 6 Apr 2020 17:55:27 +0200 Subject: [PATCH 06/27] migrate saved object, reset validation counter and fix typo attribute (#62442) --- .../core_plugins/vis_type_timeseries/index.ts | 14 - .../validation_telemetry/saved_object_type.ts | 45 ++ .../validation_telemetry_service.ts | 2 + .../visualization_migrations.test.ts | 539 ++++++++++-------- .../saved_objects/visualization_migrations.ts | 33 ++ 5 files changed, 390 insertions(+), 243 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/index.ts b/src/legacy/core_plugins/vis_type_timeseries/index.ts index 3ad8ba3a31c1..596fd5b581a7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/index.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/index.ts @@ -31,20 +31,6 @@ const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPlu styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars: server => ({}), - mappings: { - 'tsvb-validation-telemetry': { - properties: { - failedRequests: { - type: 'long', - }, - }, - }, - }, - savedObjectSchemas: { - 'tsvb-validation-telemetry': { - isNamespaceAgnostic: true, - }, - }, }, config(Joi: any) { return Joi.object({ diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts new file mode 100644 index 000000000000..77b49e824334 --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { flow } from 'lodash'; +import { SavedObjectMigrationFn, SavedObjectsType } from 'kibana/server'; + +const resetCount: SavedObjectMigrationFn = doc => ({ + ...doc, + attributes: { + ...doc.attributes, + failedRequests: 0, + }, +}); + +export const tsvbTelemetrySavedObjectType: SavedObjectsType = { + name: 'tsvb-validation-telemetry', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + failedRequests: { + type: 'long', + }, + }, + }, + migrations: { + '7.7.0': flow(resetCount), + }, +}; diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index e49664265b8b..779d9441df2f 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -19,6 +19,7 @@ import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../usage_collection/server'; +import { tsvbTelemetrySavedObjectType } from './saved_object_type'; export interface ValidationTelemetryServiceSetup { logFailedValidation: () => void; @@ -36,6 +37,7 @@ export class ValidationTelemetryService implements Plugin { this.kibanaIndex = config.kibana.index; }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index c7f245e59551..26f8278cd3d4 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -207,13 +207,13 @@ describe('migration visualization', () => { }, }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{}", - }, - "references": Array [], -} -`); + Object { + "attributes": Object { + "visState": "{}", + }, + "references": Array [], + } + `); }); it('skips errors when searchSourceJSON is null', () => { @@ -231,25 +231,25 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips errors when searchSourceJSON is undefined', () => { @@ -267,25 +267,25 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips error when searchSourceJSON is not a string', () => { @@ -302,25 +302,25 @@ Object { }; expect(migrate(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips error when searchSourceJSON is invalid json', () => { @@ -337,25 +337,25 @@ Object { }; expect(migrate(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips error when "index" and "filter" is missing from searchSourceJSON', () => { @@ -373,25 +373,25 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('extracts "index" attribute from doc', () => { @@ -409,30 +409,30 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('extracts index patterns from the filter', () => { @@ -457,30 +457,30 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "my-index", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('extracts index patterns from controls', () => { @@ -508,22 +508,22 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "control_0_index_pattern", - "type": "index-pattern", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "foo": true, + "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "control_0_index_pattern", + "type": "index-pattern", + }, + ], + "type": "visualization", + } + `); }); it('skips extracting savedSearchId when missing', () => { @@ -539,17 +539,17 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], + } + `); }); it('extract savedSearchId from doc', () => { @@ -566,24 +566,24 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + } + `); }); it('delete savedSearchId when empty string in doc', () => { @@ -600,17 +600,17 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], + } + `); }); it('should return a new object if vis is table and has multiple split aggs', () => { @@ -930,12 +930,12 @@ Object { }, }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", - }, -} -`); + Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", + }, + } + `); }); it('migrates type = gauge verticalSplit: false to alignment: horizontal', () => { @@ -946,12 +946,12 @@ Object { }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", - }, -} -`); + Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", + }, + } + `); }); it('doesnt migrate type = gauge containing invalid visState object, adds message to log', () => { @@ -962,18 +962,18 @@ Object { }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\"}", - }, -} -`); + Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\"}", + }, + } + `); expect(logMsgArr).toMatchInlineSnapshot(` -Array [ - "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", - "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", -] -`); + Array [ + "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", + "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", + ] + `); }); describe('filters agg query migration', () => { @@ -1379,4 +1379,85 @@ Array [ expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); }); }); + + describe('7.7.0 tsvb opperator typo migration', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.7.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + const generateDoc = (params: any) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + + it('should remove the misspelled opperator key if it exists', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [], + gauge_color_rules: [ + { + value: 0, + id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', + gauge: 'rgba(69,39,217,1)', + opperator: 'lt', + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(migratedParams.gauge_color_rules[0]).toMatchInlineSnapshot(` + Object { + "gauge": "rgba(69,39,217,1)", + "id": "020e3d50-75a6-11ea-8f61-71579ff7f64d", + "opperator": "lt", + "value": 0, + } + `); + }); + + it('should not change color rules with the correct spelling', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [], + gauge_color_rules: [ + { + value: 0, + id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', + gauge: 'rgba(69,39,217,1)', + opperator: 'lt', + }, + { + value: 0, + id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', + gauge: 'rgba(69,39,217,1)', + operator: 'lt', + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(migratedParams.gauge_color_rules[1]).toEqual(params.gauge_color_rules[1]); + }); + }); }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index db87006dde3e..80783e41863e 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -99,6 +99,38 @@ const migratePercentileRankAggregation: SavedObjectMigrationFn = doc => { return doc; }; +// [TSVB] Remove stale opperator key +const migrateOperatorKeyTypo: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + if (visState && visState.type === 'metrics') { + const gaugeColorRules: any[] = get(visState, 'params.gauge_color_rules') || []; + + gaugeColorRules.forEach(colorRule => { + if (colorRule.opperator) { + delete colorRule.opperator; + } + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + return doc; +}; + // Migrate date histogram aggregation (remove customInterval) const migrateDateHistogramAggregation: SavedObjectMigrationFn = doc => { const visStateJSON = get(doc, 'attributes.visState'); @@ -606,4 +638,5 @@ export const visualizationSavedObjectTypeMigrations = { ), '7.3.1': flow(migrateFiltersAggQueryStringQueries), '7.4.2': flow(transformSplitFiltersStringToQueryObject), + '7.7.0': flow(migrateOperatorKeyTypo), }; From 70ae8aa6f1418beff1aeb853dce0415814d0a890 Mon Sep 17 00:00:00 2001 From: Michael Marcialis Date: Mon, 6 Apr 2020 12:10:20 -0400 Subject: [PATCH 07/27] add ownFocus prop to help menu (#62492) --- src/core/public/chrome/ui/header/header_help_menu.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx index 80a45b4c61f0..1023a561a0fe 100644 --- a/src/core/public/chrome/ui/header/header_help_menu.tsx +++ b/src/core/public/chrome/ui/header/header_help_menu.tsx @@ -314,13 +314,14 @@ class HeaderHelpMenuUI extends Component { return ( // @ts-ignore repositionOnScroll doesn't exist in EuiPopover From e86cb4208058bc68eb49b3780ea304521c4106b7 Mon Sep 17 00:00:00 2001 From: Michael Marcialis Date: Mon, 6 Apr 2020 12:15:13 -0400 Subject: [PATCH 08/27] [Core UI] Add missing alt attributes to add data tutorial logos (#62393) * add empty title prop for data tutorial page icons * switch alt attribute to title prop * update snapshots * enabling skipped a11y tests --- .../components/__snapshots__/synopsis.test.js.snap | 2 +- .../home/public/application/components/synopsis.js | 4 ++-- .../tutorial/__snapshots__/introduction.test.js.snap | 7 ++----- .../application/components/tutorial/introduction.js | 11 ++++++++--- x-pack/test/accessibility/apps/home.ts | 6 ++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap index 594d67d9c8eb..d757d6a8b730 100644 --- a/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap @@ -9,8 +9,8 @@ exports[`props iconType 1`] = ` href="link_to_item" icon={ } diff --git a/src/plugins/home/public/application/components/synopsis.js b/src/plugins/home/public/application/components/synopsis.js index f43c377b4e5b..b6fa85db2bfe 100644 --- a/src/plugins/home/public/application/components/synopsis.js +++ b/src/plugins/home/public/application/components/synopsis.js @@ -35,9 +35,9 @@ export function Synopsis({ }) { let optionalImg; if (iconUrl) { - optionalImg = ; + optionalImg = ; } else if (iconType) { - optionalImg = ; + optionalImg = ; } const classes = classNames('homSynopsis__card', { diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap index b35545787e4a..410d29a42cac 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap @@ -15,7 +15,6 @@ exports[`props exportedFieldsUrl 1`] = ` >

Great tutorial -  

@@ -56,6 +55,7 @@ exports[`props iconType 1`] = ` > @@ -67,7 +67,6 @@ exports[`props iconType 1`] = ` >

Great tutorial -  

@@ -97,7 +96,7 @@ exports[`props isBeta 1`] = ` >

Great tutorial -   +   @@ -130,7 +129,6 @@ exports[`props previewUrl 1`] = ` >

Great tutorial -  

@@ -169,7 +167,6 @@ exports[`render 1`] = ` >

Great tutorial -  

diff --git a/src/plugins/home/public/application/components/tutorial/introduction.js b/src/plugins/home/public/application/components/tutorial/introduction.js index bc5f30622f1a..c36d150c42d3 100644 --- a/src/plugins/home/public/application/components/tutorial/introduction.js +++ b/src/plugins/home/public/application/components/tutorial/introduction.js @@ -76,7 +76,7 @@ function IntroductionUI({ if (iconType) { icon = ( - + ); } @@ -99,8 +99,13 @@ function IntroductionUI({

- {title}   - {betaBadge} + {title} + {betaBadge && ( + <> +   + {betaBadge} + + )}

diff --git a/x-pack/test/accessibility/apps/home.ts b/x-pack/test/accessibility/apps/home.ts index f40976f09f9c..463673d966c2 100644 --- a/x-pack/test/accessibility/apps/home.ts +++ b/x-pack/test/accessibility/apps/home.ts @@ -52,14 +52,12 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); - // issue - logo images are missing alt -text https://github.com/elastic/kibana/issues/62239 - it.skip('click on ActiveMQ logs panel to open tutorial meets a11y requirements', async () => { + it('click on ActiveMQ logs panel to open tutorial meets a11y requirements', async () => { await PageObjects.home.clickOnLogsTutorial(); await a11y.testAppSnapshot(); }); - // https://github.com/elastic/kibana/issues/62239 - it.skip('click on cloud tutorial meets a11y requirements', async () => { + it('click on cloud tutorial meets a11y requirements', async () => { await PageObjects.home.clickOnCloudTutorial(); await a11y.testAppSnapshot(); }); From 23755f95fa82e9f910b0646330fb222a142167bf Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 6 Apr 2020 11:15:52 -0500 Subject: [PATCH 09/27] [DOCS] Collapses content in Kibana and APM APIs (#62201) * Collapses content in Kibana and APM APIs * Update docs/api/role-management/put.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/copy_saved_objects.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/copy_saved_objects.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/copy_saved_objects.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/copy_saved_objects.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/copy_saved_objects.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/copy_saved_objects.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley * Update docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc Co-Authored-By: Lisa Cawley Co-authored-by: Lisa Cawley --- docs/api/role-management/put.asciidoc | 11 +++-- .../copy_saved_objects.asciidoc | 29 +++++++++++-- ...olve_copy_saved_objects_conflicts.asciidoc | 42 ++++++++++++++++++- docs/apm/api.asciidoc | 15 +++++++ 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/docs/api/role-management/put.asciidoc b/docs/api/role-management/put.asciidoc index 59e6bc8d37ee..993d75780c87 100644 --- a/docs/api/role-management/put.asciidoc +++ b/docs/api/role-management/put.asciidoc @@ -17,6 +17,7 @@ experimental[] Create a new {kib} role, or update the attributes of an existing To use the create or update role API, you must have the `manage_security` cluster privilege. +[role="child_attributes"] [[role-management-api-response-body]] ==== Request body @@ -29,8 +30,11 @@ To use the create or update role API, you must have the `manage_security` cluste {ref}/defining-roles.html[Defining roles]. `kibana`:: - (list) Objects that specify the <> for the role: - + (list) Objects that specify the <> for the role. ++ +.Properties of `kibana` +[%collapsible%open] +===== `base` ::: (Optional, list) A base privilege. When specified, the base must be `["all"]` or `["read"]`. When the `base` privilege is specified, you are unable to use the `feature` section. @@ -45,6 +49,7 @@ To use the create or update role API, you must have the `manage_security` cluste `spaces` ::: (list) The spaces to apply the privileges to. To grant access to all spaces, set to `["*"]`, or omit the value. +===== [[role-management-api-put-response-codes]] ==== Response code @@ -52,7 +57,7 @@ To use the create or update role API, you must have the `manage_security` cluste `204`:: Indicates a successful call. -===== Examples +==== Examples Grant access to various features in all spaces: diff --git a/docs/api/spaces-management/copy_saved_objects.asciidoc b/docs/api/spaces-management/copy_saved_objects.asciidoc index e23a137485b2..4822e7f62430 100644 --- a/docs/api/spaces-management/copy_saved_objects.asciidoc +++ b/docs/api/spaces-management/copy_saved_objects.asciidoc @@ -26,6 +26,7 @@ You can request to overwrite any objects that already exist in the target space `space_id`:: (Optional, string) The ID of the space that contains the saved objects you want to copy. When `space_id` is unspecified in the URL, the default space is used. +[role="child_attributes"] [[spaces-api-copy-saved-objects-request-body]] ==== {api-request-body-title} @@ -34,10 +35,16 @@ You can request to overwrite any objects that already exist in the target space `objects`:: (Required, object array) The saved objects to copy. ++ +.Properties of `objects` +[%collapsible%open] +===== `type`::: (Required, string) The saved object type. + `id`::: (Required, string) The saved object ID. +===== `includeReferences`:: (Optional, boolean) When set to `true`, all saved objects related to the specified saved objects will also be copied into the target spaces. The default value is `false`. @@ -45,27 +52,43 @@ You can request to overwrite any objects that already exist in the target space `overwrite`:: (Optional, boolean) When set to `true`, all conflicts are automatically overidden. When a saved object with a matching `type` and `id` exists in the target space, that version is replaced with the version from the source space. The default value is `false`. - +[role="child_attributes"] [[spaces-api-copy-saved-objects-response-body]] ==== {api-response-body-title} ``:: (object) An object that describes the result of the copy operation for the space. Includes the dynamic keys in the response. ++ +.Properties of `` +[%collapsible%open] +===== `success`::: (boolean) The copy operation was successful. When set to `false`, some objects may have been copied. For additional information, refer to the `successCount` and `errors` properties. + `successCount`::: (number) The number of objects that successfully copied. + `errors`::: - (Optional, array) The errors that occurred during the copy operation. When errors are reported, the `success` flag is set to `false`.v + (Optional, array) The errors that occurred during the copy operation. When errors are reported, the `success` flag is set to `false`. ++ +.Properties of `errors` +[%collapsible%open] +====== `id`:::: (string) The saved object ID that failed to copy. `type`:::: (string) The type of saved object that failed to copy. `error`:::: (object) The error that caused the copy operation to fail. ++ +.Properties of `error` +[%collapsible%open] +======= `type`::::: (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. Errors marked as `conflict` may be resolved by using the <>. - +======= +====== +===== [[spaces-api-copy-saved-objects-example]] ==== {api-examples-title} diff --git a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc index 8e874bb9f94e..7f35dc3834f0 100644 --- a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc +++ b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc @@ -25,51 +25,89 @@ Execute the <>, w `space_id`:: (Optional, string) The ID of the space that contains the saved objects you want to copy. When `space_id` is unspecified in the URL, the default space is used. The `space_id` must be the same value used during the failed <> operation. +[role="child_attributes"] [[spaces-api-resolve-copy-saved-objects-conflicts-request-body]] ==== {api-request-body-title} `objects`:: (Required, object array) The saved objects to copy. The `objects` must be the same values used during the failed <> operation. ++ +.Properties of `objects` +[%collapsible%open] +===== `type`::: (Required, string) The saved object type. + `id`::: (Required, string) The saved object ID. +===== `includeReferences`:: (Optional, boolean) When set to `true`, all saved objects related to the specified saved objects are copied into the target spaces. The `includeReferences` must be the same values used during the failed <> operation. The default value is `false`. `retries`:: (Required, object) The retry operations to attempt. Object keys represent the target space IDs. ++ +.Properties of `retries` +[%collapsible%open] +===== ``::: (Required, array) The errors to resolve for the specified ``. ++ + +.Properties of `` +[%collapsible%open] +====== `type`:::: (Required, string) The saved object type. `id`:::: (Required, string) The saved object ID. `overwrite`:::: (Required, boolean) When set to `true`, the saved object from the source space (desigated by the <>) overwrites the conflicting object in the destination space. When set to `false`, this does nothing. +====== +===== - +[role="child_attributes"] [[spaces-api-resolve-copy-saved-objects-conflicts-response-body]] ==== {api-response-body-title} ``:: (object) An object that describes the result of the copy operation for the space. Includes the dynamic keys in the response. ++ +.Properties of `` +[%collapsible%open] +===== `success`::: (boolean) The copy operation was successful. When set to `false`, some objects may have been copied. For additional information, refer to the `successCount` and `errors` properties. + `successCount`::: (number) The number of objects that successfully copied. + `errors`::: (Optional, array) The errors that occurred during the copy operation. When errors are reported, the `success` flag is set to `false`. ++ + +.Properties of `errors` +[%collapsible%open] +====== `id`:::: (string) The saved object ID that failed to copy. + `type`:::: (string) The type of saved object that failed to copy. + `error`:::: (object) The error that caused the copy operation to fail. ++ + +.Properties of `error` +[%collapsible%open] +======= `type`::::: (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. - +======= +====== +===== [[spaces-api-resolve-copy-saved-objects-conflicts-example]] ==== {api-examples-title} diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index b520cc46bef8..76d898ba0cb1 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -38,17 +38,22 @@ The following Agent configuration APIs are available: `PUT /api/apm/settings/agent-configuration` +[role="child_attributes"] [[apm-update-config-req-body]] ===== Request body `service`:: (required, object) Service identifying the configuration to create or update. +.Properties of `service` +[%collapsible%open] +====== `name` ::: (required, string) Name of service `environment` ::: (optional, string) Environment of service +====== `settings`:: (required) Key/value object with settings and their corresponding value. @@ -90,16 +95,21 @@ PUT /api/apm/settings/agent-configuration `DELETE /api/apm/settings/agent-configuration` +[role="child_attributes"] [[apm-delete-config-req-body]] ===== Request body `service`:: (required, object) Service identifying the configuration to delete +.Properties of `service` +[%collapsible%open] +====== `name` ::: (required, string) Name of service `environment` ::: (optional, string) Environment of service +====== [[apm-delete-config-example]] @@ -201,17 +211,22 @@ GET /api/apm/settings/agent-configuration `POST /api/apm/settings/agent-configuration/search` +[role="child_attributes"] [[apm-search-config-req-body]] ===== Request body `service`:: (required, object) Service identifying the configuration. +.Properties of `service` +[%collapsible%open] +====== `name` ::: (required, string) Name of service `environment` ::: (optional, string) Environment of service +====== `etag`:: (required) etag is sent by the agent to indicate the etag of the last successfully applied configuration. If the etag matches an existing configuration its `applied_by_agent` property will be set to `true`. Every time a configuration is edited `applied_by_agent` is reset to `false`. From 0abd7aa43fac41f8541d5337a5f793ab2f34fb6b Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Mon, 6 Apr 2020 16:18:38 +0000 Subject: [PATCH 10/27] simplify new index pattern button click method (#62451) * simplify new index pattern button click method * replace method name to match previous commit Co-authored-by: Elastic Machine --- .../apps/management/_index_pattern_create_delete.js | 2 +- test/functional/page_objects/settings_page.ts | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js index a74620b696d1..616e2297b2f5 100644 --- a/test/functional/apps/management/_index_pattern_create_delete.js +++ b/test/functional/apps/management/_index_pattern_create_delete.js @@ -42,7 +42,7 @@ export default function({ getService, getPageObjects }) { describe('special character handling', () => { it('should handle special charaters in template input', async () => { - await PageObjects.settings.clickOptionalAddNewButton(); + await PageObjects.settings.clickAddNewIndexPatternButton(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.settings.setIndexPatternField({ indexPatternName: '❤️', diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 3f6036f58f0a..6dcd017335c8 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -326,7 +326,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider await PageObjects.header.waitUntilLoadingHasFinished(); await this.clickKibanaIndexPatterns(); await PageObjects.header.waitUntilLoadingHasFinished(); - await this.clickOptionalAddNewButton(); + await this.clickAddNewIndexPatternButton(); if (!isStandardIndexPattern) { await this.clickCreateNewRollupButton(); } @@ -356,11 +356,8 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider return await this.getIndexPatternIdFromUrl(); } - // adding a method to check if the create index pattern button is visible when more than 1 index pattern is present - async clickOptionalAddNewButton() { - if (await testSubjects.isDisplayed('createIndexPatternButton')) { - await testSubjects.click('createIndexPatternButton'); - } + async clickAddNewIndexPatternButton() { + await testSubjects.click('createIndexPatternButton'); } async clickCreateNewRollupButton() { From 9bab4ef746a380ca13d7ba0e611a6a30116c21aa Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 6 Apr 2020 18:36:22 +0200 Subject: [PATCH 11/27] Move ownerships of home, dev tools and discover (#62612) * move ownership * fix es-ui ownership --- .github/CODEOWNERS | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index da85fb986ae0..3ae01b079d37 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,18 +15,20 @@ /src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app -# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon -/src/plugins/home/public @elastic/kibana-app -/src/plugins/home/server/*.ts @elastic/kibana-app -/src/plugins/home/server/services/ @elastic/kibana-app -# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon -/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/home/*.scss @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app -/src/plugins/dev_tools/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app +/src/plugins/discover/ @elastic/kibana-app + +# Core UI +# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon +/src/plugins/home/public @elastic/kibana-core-ui +/src/plugins/home/server/*.ts @elastic/kibana-core-ui +/src/plugins/home/server/services/ @elastic/kibana-core-ui +# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon +/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/*.scss @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui # App Architecture /examples/url_generators_examples/ @elastic/kibana-app-arch @@ -175,6 +177,7 @@ **/*.scss @elastic/kibana-design # Elasticsearch UI +/src/plugins/dev_tools/ @elastic/es-ui /src/plugins/console/ @elastic/es-ui /src/plugins/es_ui_shared/ @elastic/es-ui /x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui From 5c8dda265636529c2ba54ed915dac88ff21fbd05 Mon Sep 17 00:00:00 2001 From: marshallmain <55718608+marshallmain@users.noreply.github.com> Date: Mon, 6 Apr 2020 12:41:49 -0400 Subject: [PATCH 12/27] [Endpoint] Add pipeline to generator that redirect alerts to alert index (#62512) * add ingest pipeline to generator script * make alert index name configurable * move pipeline name to constant * update setupOnly flag help text Co-authored-by: Elastic Machine --- .../endpoint/scripts/alert_mapping.json | 2375 +++++++++++++++++ .../{mapping.json => event_mapping.json} | 3 +- .../endpoint/scripts/resolver_generator.ts | 81 +- 3 files changed, 2445 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/endpoint/scripts/alert_mapping.json rename x-pack/plugins/endpoint/scripts/{mapping.json => event_mapping.json} (99%) diff --git a/x-pack/plugins/endpoint/scripts/alert_mapping.json b/x-pack/plugins/endpoint/scripts/alert_mapping.json new file mode 100644 index 000000000000..a21e48b4bc95 --- /dev/null +++ b/x-pack/plugins/endpoint/scripts/alert_mapping.json @@ -0,0 +1,2375 @@ +{ + "mappings": { + "_meta": { + "version": "1.5.0-dev" + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "mutable_state": { + "properties": { + "triage_status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "compile_time": { + "type": "date" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mapped_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "mapped_size": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "endpoint": { + "properties": { + "artifact": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "policy": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "entry_modified": { + "type": "double" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "macro": { + "properties": { + "code_page": { + "type": "long" + }, + "collection": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + }, + "type": "object" + }, + "errors": { + "properties": { + "count": { + "type": "long" + }, + "error_type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "file_extension": { + "type": "long" + }, + "project_file": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + }, + "type": "object" + }, + "stream": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_code_size": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "temp_file_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_percent": { + "type": "double" + }, + "cwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "env_variables": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handles": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory_percent": { + "type": "double" + }, + "memory_region": { + "properties": { + "allocation_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocation_protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram": { + "properties": { + "histogram_array": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_resolution": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "length": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "permission": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "unbacked_on_disk": { + "type": "boolean" + } + }, + "type": "nested" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "num_threads": { + "type": "long" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "phys_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "services": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "type": "long" + }, + "short_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "call_stack": { + "properties": { + "instruction_pointer": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_section": { + "properties": { + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rva": { + "ignore_above": 1024, + "type": "keyword" + }, + "symbol_info": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty_device": { + "properties": { + "major_number": { + "type": "integer" + }, + "minor_number": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "target": { + "properties": { + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "compile_time": { + "type": "date" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mapped_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "mapped_size": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_percent": { + "type": "double" + }, + "cwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "env_variables": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handles": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory_percent": { + "type": "double" + }, + "memory_region": { + "properties": { + "allocation_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocation_protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram": { + "properties": { + "histogram_array": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_resolution": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "length": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "permission": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "unbacked_on_disk": { + "type": "boolean" + } + }, + "type": "nested" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "num_threads": { + "type": "long" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "phys_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "services": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "type": "long" + }, + "short_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "call_stack": { + "properties": { + "instruction_pointer": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_section": { + "properties": { + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rva": { + "ignore_above": 1024, + "type": "keyword" + }, + "symbol_info": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty_device": { + "properties": { + "major_number": { + "type": "integer" + }, + "minor_number": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": 10000 + } + }, + "refresh_interval": "5s" + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/endpoint/scripts/mapping.json b/x-pack/plugins/endpoint/scripts/event_mapping.json similarity index 99% rename from x-pack/plugins/endpoint/scripts/mapping.json rename to x-pack/plugins/endpoint/scripts/event_mapping.json index 5878e01b52a4..59d1ed17852b 100644 --- a/x-pack/plugins/endpoint/scripts/mapping.json +++ b/x-pack/plugins/endpoint/scripts/event_mapping.json @@ -2361,7 +2361,8 @@ "limit": 10000 } }, - "refresh_interval": "5s" + "refresh_interval": "5s", + "default_pipeline": "endpoint-event-pipeline" } } } \ No newline at end of file diff --git a/x-pack/plugins/endpoint/scripts/resolver_generator.ts b/x-pack/plugins/endpoint/scripts/resolver_generator.ts index aebf92eff6cb..333846bde6ce 100644 --- a/x-pack/plugins/endpoint/scripts/resolver_generator.ts +++ b/x-pack/plugins/endpoint/scripts/resolver_generator.ts @@ -8,7 +8,8 @@ import seedrandom from 'seedrandom'; import { Client, ClientOptions } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { EndpointDocGenerator, Event } from '../common/generate_data'; -import { default as mapping } from './mapping.json'; +import { default as eventMapping } from './event_mapping.json'; +import { default as alertMapping } from './alert_mapping.json'; main(); @@ -25,6 +26,12 @@ async function main() { default: 'http://localhost:9200', type: 'string', }, + alertIndex: { + alias: 'ai', + describe: 'index to store alerts in', + default: '.alerts-endpoint-000001', + type: 'string', + }, eventIndex: { alias: 'ei', describe: 'index to store events in', @@ -95,7 +102,16 @@ async function main() { type: 'boolean', default: false, }, + setupOnly: { + alias: 'so', + describe: + 'Run only the index and pipeline creation then exit. This is intended to be used to set up the Endpoint App for use with the real Elastic Endpoint.', + type: 'boolean', + default: false, + }, }).argv; + const pipelineName = 'endpoint-event-pipeline'; + eventMapping.settings.index.default_pipeline = pipelineName; const clientOptions: ClientOptions = { node: argv.node, }; @@ -107,7 +123,7 @@ async function main() { if (argv.delete) { try { await client.indices.delete({ - index: [argv.eventIndex, argv.metadataIndex], + index: [argv.eventIndex, argv.metadataIndex, argv.alertIndex], }); } catch (err) { if (err instanceof ResponseError && err.statusCode !== 404) { @@ -117,21 +133,42 @@ async function main() { } } } + + const pipeline = { + description: 'redirects alerts to their own index', + processors: [ + { + set: { + field: '_index', + value: argv.alertIndex, + if: "ctx.event.kind == 'alert'", + }, + }, + { + set: { + field: 'mutable_state.triage_status', + value: 'open', + }, + }, + ], + }; try { - await client.indices.create({ - index: argv.eventIndex, - body: mapping, + await client.ingest.putPipeline({ + id: pipelineName, + body: pipeline, }); } catch (err) { - if ( - err instanceof ResponseError && - err.body.error.type !== 'resource_already_exists_exception' - ) { - // eslint-disable-next-line no-console - console.log(err.body); - process.exit(1); - } + // eslint-disable-next-line no-console + console.log(err); + process.exit(1); } + + await createIndex(client, argv.alertIndex, alertMapping); + await createIndex(client, argv.eventIndex, eventMapping); + if (argv.setupOnly) { + process.exit(0); + } + let seed = argv.seed; if (!seed) { seed = Math.random().toString(); @@ -183,3 +220,21 @@ async function main() { } } } + +async function createIndex(client: Client, index: string, mapping: any) { + try { + await client.indices.create({ + index, + body: mapping, + }); + } catch (err) { + if ( + err instanceof ResponseError && + err.body.error.type !== 'resource_already_exists_exception' + ) { + // eslint-disable-next-line no-console + console.log(err.body); + process.exit(1); + } + } +} From 2cd86a4c838c81c17a7c66e5b4379b9ffa72f7fe Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Mon, 6 Apr 2020 18:48:18 +0200 Subject: [PATCH 13/27] [EPM] Refactor expandFields() (#62180) * Do not modify input array in expandFields() * Add unit tests for processFields() Co-authored-by: Elastic Machine --- .../server/services/epm/fields/field.test.ts | 100 ++++++++++++++++++ .../server/services/epm/fields/field.ts | 30 +++--- 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts index 929f2518ee74..e3aef6077dbc 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts @@ -80,3 +80,103 @@ describe('getField searches recursively for nested field in fields given an arra expect(getField(searchFields, ['2', '2-2', '2-2-1'])?.name).toBe('2-2-1'); }); }); + +describe('processFields', () => { + const flattenedFields = [ + { + name: 'a.a', + type: 'text', + }, + { + name: 'a.b', + type: 'text', + }, + ]; + const expandedFields = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ]; + test('correctly expands flattened fields', () => { + expect(JSON.stringify(processFields(flattenedFields))).toEqual(JSON.stringify(expandedFields)); + }); + test('leaves expanded fields unchanged', () => { + expect(JSON.stringify(processFields(expandedFields))).toEqual(JSON.stringify(expandedFields)); + }); + + const mixedFieldsA = [ + { + name: 'a.a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ]; + + const mixedFieldsB = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a.a', + type: 'text', + }, + { + name: 'a.b', + type: 'text', + }, + ], + }, + ]; + + const mixedFieldsExpanded = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ], + }, + ]; + test('correctly expands a mix of expanded and flattened fields', () => { + expect(JSON.stringify(processFields(mixedFieldsA))).toEqual( + JSON.stringify(mixedFieldsExpanded) + ); + expect(JSON.stringify(processFields(mixedFieldsB))).toEqual( + JSON.stringify(mixedFieldsExpanded) + ); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts index 4a1a84baf659..810896bb5038 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts @@ -52,13 +52,12 @@ export type Fields = Field[]; * expandFields takes the given fields read from yaml and expands them. * There are dotted fields in the field.yml like `foo.bar`. These should * be stored as an field within a 'group' field. - * - * Note: This function modifies the passed fields array. */ -export function expandFields(fields: Fields) { +export function expandFields(fields: Fields): Fields { + const newFields: Fields = []; + fields.forEach((field, key) => { const fieldName = field.name; - // If the field name contains a dot, it means we need to // - take the first part of the name // - create a field of type 'group' with this first part @@ -71,30 +70,29 @@ export function expandFields(fields: Fields) { const groupFieldName = nameParts[0]; // Put back together the parts again for the new field name - const restFieldName = nameParts.slice(1).join('.'); + const nestedFieldName = nameParts.slice(1).join('.'); // keep all properties of the original field, but give it the shortened name - field.name = restFieldName; + const nestedField = { ...field, name: nestedFieldName }; // create a new field of type group with the original field in the fields array const groupField: Field = { name: groupFieldName, type: 'group', - fields: [field], + fields: expandFields([nestedField]), }; - // check child fields further down the tree - if (groupField.fields) { - expandFields(groupField.fields); - } // Replace the original field in the array with the new one - fields[key] = groupField; + newFields.push(groupField); } else { // even if this field doesn't have dots to expand, its child fields further down the tree might - if (field.fields) { - expandFields(field.fields); + const newField = { ...field }; + if (newField.fields) { + newField.fields = expandFields(newField.fields); } + newFields.push(newField); } }); + return newFields; } /** * dedupFields takes the given fields and merges sibling fields with the @@ -180,8 +178,8 @@ export const getField = (fields: Fields, pathNames: string[]): Field | undefined }; export function processFields(fields: Fields): Fields { - expandFields(fields); - const dedupedFields = dedupFields(fields); + const expandedFields = expandFields(fields); + const dedupedFields = dedupFields(expandedFields); return validateFields(dedupedFields, dedupedFields); } From 4bdbe7356d10d87fb7cf38b8fe529c63cb2a316f Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 6 Apr 2020 09:49:23 -0700 Subject: [PATCH 14/27] Remove ES-UI as code owner of Transform app. (#62556) --- .github/CODEOWNERS | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3ae01b079d37..feaf47e45fd6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -87,9 +87,8 @@ /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/ml.ts @elastic/ml-ui -# ML team owns the transform plugin, ES team added here for visibility -# because the plugin lives in Kibana's Elasticsearch management section. -/x-pack/plugins/transform/ @elastic/ml-ui @elastic/es-ui +# ML team owns and maintains the transform plugin despite it living in the Elasticsearch management section. +/x-pack/plugins/transform/ @elastic/ml-ui /x-pack/test/functional/apps/transform/ @elastic/ml-ui /x-pack/test/functional/services/transform_ui/ @elastic/ml-ui /x-pack/test/functional/services/transform.ts @elastic/ml-ui From e7a4ca261b17418e32567b0f69fd842ffc989318 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 6 Apr 2020 18:02:58 +0100 Subject: [PATCH 15/27] [Event Log] adds query support to the Event Log (#62015) * added Start api on Event Log plugin * added empty skeleton for Event Log FTs * added functional test to public find events api * added test for pagination * fixed unit tests * added support for date ranges * removed unused code * replaces valdiation typing * Revert "replaces valdiation typing" This reverts commit 711c098e9b2a8329c58c9674fc99de23842884d1. * replaces match with term * added sorting * fixed saved objects nested query * updated plugin FTs path * Update x-pack/plugins/encrypted_saved_objects/README.md Co-Authored-By: Aleh Zasypkin * Update x-pack/plugins/encrypted_saved_objects/README.md Co-Authored-By: Aleh Zasypkin * remofed validation from tests * fixed typos Co-authored-by: Elastic Machine Co-authored-by: Aleh Zasypkin --- package.json | 3 +- test/scripts/jenkins_xpack_build_kibana.sh | 1 + .../plugins/encrypted_saved_objects/README.md | 4 +- x-pack/plugins/event_log/common/index.ts | 7 + .../server/es/cluster_client_adapter.mock.ts | 1 + .../server/es/cluster_client_adapter.test.ts | 229 ++++++++++++++ .../server/es/cluster_client_adapter.ts | 91 ++++++ .../event_log/server/event_log_client.mock.ts | 18 ++ .../event_log/server/event_log_client.test.ts | 292 ++++++++++++++++++ .../event_log/server/event_log_client.ts | 86 ++++++ .../server/event_log_start_service.mock.ts | 18 ++ .../server/event_log_start_service.test.ts | 59 ++++ .../server/event_log_start_service.ts | 48 +++ x-pack/plugins/event_log/server/index.ts | 2 +- x-pack/plugins/event_log/server/mocks.ts | 5 +- x-pack/plugins/event_log/server/plugin.ts | 42 ++- .../server/routes/_mock_handler_arguments.ts | 70 +++++ .../event_log/server/routes/find.test.ts | 98 ++++++ .../plugins/event_log/server/routes/find.ts | 50 +++ .../plugins/event_log/server/routes/index.ts | 7 + x-pack/plugins/event_log/server/types.ts | 23 ++ x-pack/plugins/task_manager/server/README.md | 4 +- x-pack/scripts/functional_tests.js | 2 +- .../{config.js => config.ts} | 11 +- .../plugins/event_log/kibana.json | 9 + .../plugins/event_log/package.json | 15 + .../plugins/event_log/server/index.ts | 11 + .../plugins/event_log/server/plugin.ts | 98 ++++++ .../test_suites/event_log/index.ts | 15 + .../event_log/public_api_integration.ts | 236 ++++++++++++++ .../event_log/service_api_integration.ts | 11 + 31 files changed, 1552 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/event_log/common/index.ts create mode 100644 x-pack/plugins/event_log/server/event_log_client.mock.ts create mode 100644 x-pack/plugins/event_log/server/event_log_client.test.ts create mode 100644 x-pack/plugins/event_log/server/event_log_client.ts create mode 100644 x-pack/plugins/event_log/server/event_log_start_service.mock.ts create mode 100644 x-pack/plugins/event_log/server/event_log_start_service.test.ts create mode 100644 x-pack/plugins/event_log/server/event_log_start_service.ts create mode 100644 x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts create mode 100644 x-pack/plugins/event_log/server/routes/find.test.ts create mode 100644 x-pack/plugins/event_log/server/routes/find.ts create mode 100644 x-pack/plugins/event_log/server/routes/index.ts rename x-pack/test/plugin_api_integration/{config.js => config.ts} (77%) create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/kibana.json create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/package.json create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/event_log/index.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts diff --git a/package.json b/package.json index 46e0b9adfea2..e807cd4d9519 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,8 @@ "examples/*", "test/plugin_functional/plugins/*", "test/interpreter_functional/plugins/*", - "x-pack/test/functional_with_es_ssl/fixtures/plugins/*" + "x-pack/test/functional_with_es_ssl/fixtures/plugins/*", + "x-pack/test/plugin_api_integration/plugins/*" ], "nohoist": [ "**/@types/*", diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 777d98080e40..962d2794f712 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -7,6 +7,7 @@ echo " -> building kibana platform plugins" node scripts/build_kibana_platform_plugins \ --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ --verbose; # doesn't persist, also set in kibanaPipeline.groovy diff --git a/x-pack/plugins/encrypted_saved_objects/README.md b/x-pack/plugins/encrypted_saved_objects/README.md index a35298987007..6085b52d392a 100644 --- a/x-pack/plugins/encrypted_saved_objects/README.md +++ b/x-pack/plugins/encrypted_saved_objects/README.md @@ -100,10 +100,10 @@ $ node scripts/jest.js In one shell, from `kibana-root-folder/x-pack`: ```bash -$ node scripts/functional_tests_server.js --config test/plugin_api_integration/config.js +$ node scripts/functional_tests_server.js --config test/encrypted_saved_objects_api_integration/config.ts ``` In another shell, from `kibana-root-folder/x-pack`: ```bash -$ node ../scripts/functional_test_runner.js --config test/plugin_api_integration/config.js --grep="{TEST_NAME}" +$ node ../scripts/functional_test_runner.js --config test/encrypted_saved_objects_api_integration/config.ts --grep="{TEST_NAME}" ``` diff --git a/x-pack/plugins/event_log/common/index.ts b/x-pack/plugins/event_log/common/index.ts new file mode 100644 index 000000000000..3ee274916c12 --- /dev/null +++ b/x-pack/plugins/event_log/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const BASE_EVENT_LOG_API_PATH = '/api/event_log'; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts index 87e8fb0f521a..bd57958b0cb8 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts @@ -15,6 +15,7 @@ const createClusterClientMock = () => { createIndexTemplate: jest.fn(), doesAliasExist: jest.fn(), createIndex: jest.fn(), + queryEventsBySavedObject: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index b61196439ee4..ae26d7a7ece0 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -7,6 +7,8 @@ import { ClusterClient, Logger } from '../../../../../src/core/server'; import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { ClusterClientAdapter, IClusterClientAdapter } from './cluster_client_adapter'; +import moment from 'moment'; +import { findOptionsSchema } from '../event_log_client'; type EsClusterClient = Pick, 'callAsInternalUser' | 'asScoped'>; @@ -195,3 +197,230 @@ describe('createIndex', () => { await clusterClientAdapter.createIndex('foo'); }); }); + +describe('queryEventsBySavedObject', () => { + const DEFAULT_OPTIONS = findOptionsSchema.validate({}); + + test('should call cluster with proper arguments', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + DEFAULT_OPTIONS + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + from: 0, + size: 10, + sort: { 'event.start': { order: 'asc' } }, + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + ], + }, + }, + }, + }); + }); + + test('should call cluster with sort', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, sort_field: 'event.end', sort_order: 'desc' } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + sort: { 'event.end': { order: 'desc' } }, + }, + }); + }); + + test('supports open ended date', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, start } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + { + range: { + 'event.start': { + gte: start, + }, + }, + }, + ], + }, + }, + }, + }); + }); + + test('supports optional date range', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + const end = moment() + .add(1, 'days') + .toISOString(); + + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, start, end } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + { + range: { + 'event.start': { + gte: start, + }, + }, + }, + { + range: { + 'event.end': { + lte: end, + }, + }, + }, + ], + }, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index d585fd4f539b..36bc94edfca4 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { reject, isUndefined } from 'lodash'; import { Logger, ClusterClient } from '../../../../../src/core/server'; +import { IEvent } from '../types'; +import { FindOptionsType } from '../event_log_client'; export type EsClusterClient = Pick; export type IClusterClientAdapter = PublicMethodsOf; @@ -14,6 +17,13 @@ export interface ConstructorOpts { clusterClient: EsClusterClient; } +export interface QueryEventsBySavedObjectResult { + page: number; + per_page: number; + total: number; + data: IEvent[]; +} + export class ClusterClientAdapter { private readonly logger: Logger; private readonly clusterClient: EsClusterClient; @@ -107,6 +117,87 @@ export class ClusterClientAdapter { } } + public async queryEventsBySavedObject( + index: string, + type: string, + id: string, + { page, per_page: perPage, start, end, sort_field, sort_order }: FindOptionsType + ): Promise { + try { + const { + hits: { + hits, + total: { value: total }, + }, + } = await this.callEs('search', { + index, + body: { + size: perPage, + from: (page - 1) * perPage, + sort: { [sort_field]: { order: sort_order } }, + query: { + bool: { + must: reject( + [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: type, + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: id, + }, + }, + }, + ], + }, + }, + }, + }, + start && { + range: { + 'event.start': { + gte: start, + }, + }, + }, + end && { + range: { + 'event.end': { + lte: end, + }, + }, + }, + ], + isUndefined + ), + }, + }, + }, + }); + return { + page, + per_page: perPage, + total, + data: hits.map((hit: any) => hit._source) as IEvent[], + }; + } catch (err) { + throw new Error( + `querying for Event Log by for type "${type}" and id "${id}" failed with: ${err.message}` + ); + } + } + private async callEs(operation: string, body?: any): Promise { try { this.debug(`callEs(${operation}) calls:`, body); diff --git a/x-pack/plugins/event_log/server/event_log_client.mock.ts b/x-pack/plugins/event_log/server/event_log_client.mock.ts new file mode 100644 index 000000000000..31cab802555d --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.mock.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEventLogClient } from './types'; + +const createEventLogClientMock = () => { + const mock: jest.Mocked = { + findEventsBySavedObject: jest.fn(), + }; + return mock; +}; + +export const eventLogClientMock = { + create: createEventLogClientMock, +}; diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts new file mode 100644 index 000000000000..6d4c9b67abc1 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -0,0 +1,292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EventLogClient } from './event_log_client'; +import { contextMock } from './es/context.mock'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { merge } from 'lodash'; +import moment from 'moment'; + +describe('EventLogStart', () => { + describe('findEventsBySavedObject', () => { + test('verifies that the user can access the specified saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id'); + + expect(savedObjectsClient.get).toHaveBeenCalledWith('saved-object-type', 'saved-object-id'); + }); + + test('throws when the user doesnt have permission to access the specified saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockRejectedValue(new Error('Fail')); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id') + ).rejects.toMatchInlineSnapshot(`[Error: Fail]`); + }); + + test('fetches all event that reference the saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + const expectedEvents = [ + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '1', + }, + ], + }, + }), + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '2', + }, + ], + }, + }), + ]; + + const result = { + page: 0, + per_page: 10, + total: expectedEvents.length, + data: expectedEvents, + }; + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); + + expect( + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id') + ).toEqual(result); + + expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( + esContext.esNames.alias, + 'saved-object-type', + 'saved-object-id', + { + page: 1, + per_page: 10, + sort_field: 'event.start', + sort_order: 'asc', + } + ); + }); + + test('fetches all events in time frame that reference the saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + const expectedEvents = [ + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '1', + }, + ], + }, + }), + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '2', + }, + ], + }, + }), + ]; + + const result = { + page: 0, + per_page: 10, + total: expectedEvents.length, + data: expectedEvents, + }; + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + const end = moment() + .add(1, 'days') + .toISOString(); + + expect( + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + start, + end, + }) + ).toEqual(result); + + expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( + esContext.esNames.alias, + 'saved-object-type', + 'saved-object-id', + { + page: 1, + per_page: 10, + sort_field: 'event.start', + sort_order: 'asc', + start, + end, + } + ); + }); + + test('validates that the start date is valid', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({ + page: 0, + per_page: 0, + total: 0, + data: [], + }); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + start: 'not a date string', + }) + ).rejects.toMatchInlineSnapshot(`[Error: [start]: Invalid Date]`); + }); + + test('validates that the end date is valid', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({ + page: 0, + per_page: 0, + total: 0, + data: [], + }); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + end: 'not a date string', + }) + ).rejects.toMatchInlineSnapshot(`[Error: [end]: Invalid Date]`); + }); + }); +}); + +function fakeEvent(overrides = {}) { + return merge( + { + event: { + provider: 'actions', + action: 'execute', + start: '2020-03-30T14:55:47.054Z', + end: '2020-03-30T14:55:47.055Z', + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'action', + id: '968f1b82-0414-4a10-becc-56b6473e4a29', + }, + ], + server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d', + }, + message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger', + '@timestamp': '2020-03-30T14:55:47.055Z', + ecs: { + version: '1.3.1', + }, + }, + overrides + ); +} diff --git a/x-pack/plugins/event_log/server/event_log_client.ts b/x-pack/plugins/event_log/server/event_log_client.ts new file mode 100644 index 000000000000..765f0895f8e0 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { ClusterClient, SavedObjectsClientContract } from 'src/core/server'; + +import { schema, TypeOf } from '@kbn/config-schema'; +import { EsContext } from './es'; +import { IEventLogClient } from './types'; +import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +interface EventLogServiceCtorParams { + esContext: EsContext; + savedObjectsClient: SavedObjectsClientContract; +} + +const optionalDateFieldSchema = schema.maybe( + schema.string({ + validate(value) { + if (isNaN(Date.parse(value))) { + return 'Invalid Date'; + } + }, + }) +); + +export const findOptionsSchema = schema.object({ + per_page: schema.number({ defaultValue: 10, min: 0 }), + page: schema.number({ defaultValue: 1, min: 1 }), + start: optionalDateFieldSchema, + end: optionalDateFieldSchema, + sort_field: schema.oneOf( + [ + schema.literal('event.start'), + schema.literal('event.end'), + schema.literal('event.provider'), + schema.literal('event.duration'), + schema.literal('event.action'), + schema.literal('message'), + ], + { + defaultValue: 'event.start', + } + ), + sort_order: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), +}); +// page & perPage are required, other fields are optional +// using schema.maybe allows us to set undefined, but not to make the field optional +export type FindOptionsType = Pick< + TypeOf, + 'page' | 'per_page' | 'sort_field' | 'sort_order' +> & + Partial>; + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogClient implements IEventLogClient { + private esContext: EsContext; + private savedObjectsClient: SavedObjectsClientContract; + + constructor({ esContext, savedObjectsClient }: EventLogServiceCtorParams) { + this.esContext = esContext; + this.savedObjectsClient = savedObjectsClient; + } + + async findEventsBySavedObject( + type: string, + id: string, + options?: Partial + ): Promise { + // verify the user has the required permissions to view this saved object + await this.savedObjectsClient.get(type, id); + return await this.esContext.esAdapter.queryEventsBySavedObject( + this.esContext.esNames.alias, + type, + id, + findOptionsSchema.validate(options ?? {}) + ); + } +} diff --git a/x-pack/plugins/event_log/server/event_log_start_service.mock.ts b/x-pack/plugins/event_log/server/event_log_start_service.mock.ts new file mode 100644 index 000000000000..e99ec777b473 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.mock.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEventLogClientService } from './types'; + +const createEventLogServiceMock = () => { + const mock: jest.Mocked = { + getClient: jest.fn(), + }; + return mock; +}; + +export const eventLogStartServiceMock = { + create: createEventLogServiceMock, +}; diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts new file mode 100644 index 000000000000..a8d75bc6c2e5 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EventLogClientService } from './event_log_start_service'; +import { contextMock } from './es/context.mock'; +import { KibanaRequest } from 'kibana/server'; +import { savedObjectsServiceMock } from 'src/core/server/saved_objects/saved_objects_service.mock'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; + +jest.mock('./event_log_client'); + +describe('EventLogClientService', () => { + const esContext = contextMock.create(); + + describe('getClient', () => { + test('creates a client with a scoped SavedObjects client', () => { + const savedObjectsService = savedObjectsServiceMock.createStartContract(); + const request = fakeRequest(); + + const eventLogStartService = new EventLogClientService({ + esContext, + savedObjectsService, + }); + + eventLogStartService.getClient(request); + + expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request); + + const [{ value: savedObjectsClient }] = savedObjectsService.getScopedClient.mock.results; + + expect(jest.requireMock('./event_log_client').EventLogClient).toHaveBeenCalledWith({ + esContext, + savedObjectsClient, + }); + }); + }); +}); + +function fakeRequest(): KibanaRequest { + const savedObjectsClient = savedObjectsClientMock.create(); + return { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: () => savedObjectsClient, + } as any; +} diff --git a/x-pack/plugins/event_log/server/event_log_start_service.ts b/x-pack/plugins/event_log/server/event_log_start_service.ts new file mode 100644 index 000000000000..5938f7a2e614 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { Observable } from 'rxjs'; +import { + ClusterClient, + KibanaRequest, + SavedObjectsServiceStart, + SavedObjectsClientContract, +} from 'src/core/server'; + +import { EsContext } from './es'; +import { IEventLogClientService } from './types'; +import { EventLogClient } from './event_log_client'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +interface EventLogServiceCtorParams { + esContext: EsContext; + savedObjectsService: SavedObjectsServiceStart; +} + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogClientService implements IEventLogClientService { + private esContext: EsContext; + private savedObjectsService: SavedObjectsServiceStart; + + constructor({ esContext, savedObjectsService }: EventLogServiceCtorParams) { + this.esContext = esContext; + this.savedObjectsService = savedObjectsService; + } + + getClient( + request: KibanaRequest, + savedObjectsClient: SavedObjectsClientContract = this.savedObjectsService.getScopedClient( + request + ) + ) { + return new EventLogClient({ + esContext: this.esContext, + savedObjectsClient, + }); + } +} diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts index 81a56faa4996..b7fa25cb6eb9 100644 --- a/x-pack/plugins/event_log/server/index.ts +++ b/x-pack/plugins/event_log/server/index.ts @@ -8,6 +8,6 @@ import { PluginInitializerContext } from 'src/core/server'; import { ConfigSchema } from './types'; import { Plugin } from './plugin'; -export { IEventLogService, IEventLogger, IEvent } from './types'; +export { IEventLogService, IEventLogger, IEventLogClientService, IEvent } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (context: PluginInitializerContext) => new Plugin(context); diff --git a/x-pack/plugins/event_log/server/mocks.ts b/x-pack/plugins/event_log/server/mocks.ts index aad6cf3e2456..2f632a52d2f3 100644 --- a/x-pack/plugins/event_log/server/mocks.ts +++ b/x-pack/plugins/event_log/server/mocks.ts @@ -5,8 +5,9 @@ */ import { eventLogServiceMock } from './event_log_service.mock'; +import { eventLogStartServiceMock } from './event_log_start_service.mock'; -export { eventLogServiceMock }; +export { eventLogServiceMock, eventLogStartServiceMock }; export { eventLoggerMock } from './event_logger.mock'; const createSetupMock = () => { @@ -14,7 +15,7 @@ const createSetupMock = () => { }; const createStartMock = () => { - return undefined; + return eventLogStartServiceMock.create(); }; export const eventLogMock = { diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index fdb08b2d090a..2cc41354b4fb 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -14,11 +14,21 @@ import { PluginInitializerContext, ClusterClient, SharedGlobalConfig, + IContextProvider, + RequestHandler, } from 'src/core/server'; -import { IEventLogConfig, IEventLogService, IEventLogger, IEventLogConfig$ } from './types'; +import { + IEventLogConfig, + IEventLogService, + IEventLogger, + IEventLogConfig$, + IEventLogClientService, +} from './types'; +import { findRoute } from './routes'; import { EventLogService } from './event_log_service'; import { createEsContext, EsContext } from './es'; +import { EventLogClientService } from './event_log_start_service'; export type PluginClusterClient = Pick; @@ -29,13 +39,14 @@ const ACTIONS = { stopping: 'stopping', }; -export class Plugin implements CorePlugin { +export class Plugin implements CorePlugin { private readonly config$: IEventLogConfig$; private systemLogger: Logger; private eventLogService?: IEventLogService; private esContext?: EsContext; private eventLogger?: IEventLogger; private globalConfig$: Observable; + private eventLogClientService?: EventLogClientService; constructor(private readonly context: PluginInitializerContext) { this.systemLogger = this.context.logger.get(); @@ -71,10 +82,17 @@ export class Plugin implements CorePlugin { event: { provider: PROVIDER }, }); + core.http.registerRouteHandlerContext('eventLog', this.createRouteHandlerContext()); + + // Routes + const router = core.http.createRouter(); + // Register routes + findRoute(router); + return this.eventLogService; } - async start(core: CoreStart) { + async start(core: CoreStart): Promise { this.systemLogger.debug('starting plugin'); if (!this.esContext) throw new Error('esContext not initialized'); @@ -91,8 +109,26 @@ export class Plugin implements CorePlugin { event: { action: ACTIONS.starting }, message: 'eventLog starting', }); + + this.eventLogClientService = new EventLogClientService({ + esContext: this.esContext, + savedObjectsService: core.savedObjects, + }); + return this.eventLogClientService; } + private createRouteHandlerContext = (): IContextProvider< + RequestHandler, + 'eventLog' + > => { + return async (context, request) => { + return { + getEventLogClient: () => + this.eventLogClientService!.getClient(request, context.core.savedObjects.client), + }; + }; + }; + stop() { this.systemLogger.debug('stopping plugin'); diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts new file mode 100644 index 000000000000..6640683bf600 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { identity, merge } from 'lodash'; +import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { IEventLogClient } from '../types'; + +export function mockHandlerArguments( + eventLogClient: IEventLogClient, + req: any, + res?: Array> +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { + return [ + ({ + eventLog: { + getEventLogClient() { + return eventLogClient; + }, + }, + } as unknown) as RequestHandlerContext, + req as KibanaRequest, + mockResponseFactory(res), + ]; +} + +export const mockResponseFactory = (resToMock: Array> = []) => { + const factory: jest.Mocked = httpServerMock.createResponseFactory(); + resToMock.forEach((key: string) => { + if (key in factory) { + Object.defineProperty(factory, key, { + value: jest.fn(identity), + }); + } + }); + return (factory as unknown) as KibanaResponseFactory; +}; + +export function fakeEvent(overrides = {}) { + return merge( + { + event: { + provider: 'actions', + action: 'execute', + start: '2020-03-30T14:55:47.054Z', + end: '2020-03-30T14:55:47.055Z', + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'action', + id: '968f1b82-0414-4a10-becc-56b6473e4a29', + }, + ], + server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d', + }, + message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger', + '@timestamp': '2020-03-30T14:55:47.055Z', + ecs: { + version: '1.3.1', + }, + }, + overrides + ); +} diff --git a/x-pack/plugins/event_log/server/routes/find.test.ts b/x-pack/plugins/event_log/server/routes/find.test.ts new file mode 100644 index 000000000000..844a84dc117a --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/find.test.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { findRoute } from './find'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockHandlerArguments, fakeEvent } from './_mock_handler_arguments'; +import { eventLogClientMock } from '../event_log_client.mock'; + +const eventLogClient = eventLogClientMock.create(); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('find', () => { + it('finds events with proper parameters', async () => { + const router: RouterMock = mockRouter.create(); + + findRoute(router); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/event_log/{type}/{id}/_find"`); + + const events = [fakeEvent(), fakeEvent()]; + const result = { + page: 0, + per_page: 10, + total: events.length, + data: events, + }; + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(result); + + const [context, req, res] = mockHandlerArguments( + eventLogClient, + { + params: { id: '1', type: 'action' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); + + const [type, id] = eventLogClient.findEventsBySavedObject.mock.calls[0]; + expect(type).toEqual(`action`); + expect(id).toEqual(`1`); + + expect(res.ok).toHaveBeenCalledWith({ + body: result, + }); + }); + + it('supports optional pagination parameters', async () => { + const router: RouterMock = mockRouter.create(); + + findRoute(router); + + const [, handler] = router.get.mock.calls[0]; + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce({ + page: 0, + per_page: 10, + total: 0, + data: [], + }); + + const [context, req, res] = mockHandlerArguments( + eventLogClient, + { + params: { id: '1', type: 'action' }, + query: { page: 3, per_page: 10 }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); + + const [type, id, options] = eventLogClient.findEventsBySavedObject.mock.calls[0]; + expect(type).toEqual(`action`); + expect(id).toEqual(`1`); + expect(options).toMatchObject({}); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + page: 0, + per_page: 10, + total: 0, + data: [], + }, + }); + }); +}); diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts new file mode 100644 index 000000000000..cb170e50fb44 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { BASE_EVENT_LOG_API_PATH } from '../../common'; +import { findOptionsSchema, FindOptionsType } from '../event_log_client'; + +const paramSchema = schema.object({ + type: schema.string(), + id: schema.string(), +}); + +export const findRoute = (router: IRouter) => { + router.get( + { + path: `${BASE_EVENT_LOG_API_PATH}/{type}/{id}/_find`, + validate: { + params: paramSchema, + query: findOptionsSchema, + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, FindOptionsType, any, any>, + res: KibanaResponseFactory + ): Promise> { + if (!context.eventLog) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for eventLog' }); + } + const eventLogClient = context.eventLog.getEventLogClient(); + const { + params: { id, type }, + query, + } = req; + return res.ok({ + body: await eventLogClient.findEventsBySavedObject(type, id, query), + }); + }) + ); +}; diff --git a/x-pack/plugins/event_log/server/routes/index.ts b/x-pack/plugins/event_log/server/routes/index.ts new file mode 100644 index 000000000000..85d9b3e0db8c --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { findRoute } from './find'; diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index f606bb2be6c6..baf53ef44791 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -8,7 +8,10 @@ import { Observable } from 'rxjs'; import { schema, TypeOf } from '@kbn/config-schema'; export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/schemas'; +import { KibanaRequest } from 'kibana/server'; import { IEvent } from '../generated/schemas'; +import { FindOptionsType } from './event_log_client'; +import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -19,6 +22,14 @@ export const ConfigSchema = schema.object({ export type IEventLogConfig = TypeOf; export type IEventLogConfig$ = Observable>; +declare module 'src/core/server' { + interface RequestHandlerContext { + eventLog?: { + getEventLogClient: () => IEventLogClient; + }; + } +} + // the object exposed by plugin.setup() export interface IEventLogService { isEnabled(): boolean; @@ -31,6 +42,18 @@ export interface IEventLogService { getLogger(properties: IEvent): IEventLogger; } +export interface IEventLogClientService { + getClient(request: KibanaRequest): IEventLogClient; +} + +export interface IEventLogClient { + findEventsBySavedObject( + type: string, + id: string, + options?: Partial + ): Promise; +} + export interface IEventLogger { logEvent(properties: IEvent): void; startTiming(event: IEvent): void; diff --git a/x-pack/plugins/task_manager/server/README.md b/x-pack/plugins/task_manager/server/README.md index a4154f3ecf21..c3d45be5d8f2 100644 --- a/x-pack/plugins/task_manager/server/README.md +++ b/x-pack/plugins/task_manager/server/README.md @@ -456,6 +456,6 @@ The task manager's public API is create / delete / list. Updates aren't directly ``` - Integration tests: ``` - node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.js - node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.js + node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.ts + node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.ts ``` diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index b0ca33b00fde..7943da07716a 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -17,7 +17,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/alerting_api_integration/spaces_only/config.ts'), require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'), require.resolve('../test/detection_engine_api_integration/security_and_spaces/config.ts'), - require.resolve('../test/plugin_api_integration/config.js'), + require.resolve('../test/plugin_api_integration/config.ts'), require.resolve('../test/plugin_functional/config.ts'), require.resolve('../test/kerberos_api_integration/config.ts'), require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'), diff --git a/x-pack/test/plugin_api_integration/config.js b/x-pack/test/plugin_api_integration/config.ts similarity index 77% rename from x-pack/test/plugin_api_integration/config.js rename to x-pack/test/plugin_api_integration/config.ts index 83e8b1f84a9e..c581e0c246e1 100644 --- a/x-pack/test/plugin_api_integration/config.js +++ b/x-pack/test/plugin_api_integration/config.ts @@ -6,9 +6,10 @@ import path from 'path'; import fs from 'fs'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { services } from './services'; -export default async function({ readConfigFile }) { +export default async function({ readConfigFile }: FtrConfigProviderContext) { const integrationConfig = await readConfigFile(require.resolve('../api_integration/config')); // Find all folders in ./plugins since we treat all them as plugin folder @@ -18,7 +19,10 @@ export default async function({ readConfigFile }) { ); return { - testFiles: [require.resolve('./test_suites/task_manager')], + testFiles: [ + require.resolve('./test_suites/task_manager'), + require.resolve('./test_suites/event_log'), + ], services, servers: integrationConfig.get('servers'), esTestCluster: integrationConfig.get('esTestCluster'), @@ -34,6 +38,9 @@ export default async function({ readConfigFile }) { ...integrationConfig.get('kbnTestServer'), serverArgs: [ ...integrationConfig.get('kbnTestServer.serverArgs'), + '--xpack.eventLog.enabled=true', + '--xpack.eventLog.logEntries=true', + '--xpack.eventLog.indexEntries=true', ...plugins.map( pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/kibana.json b/x-pack/test/plugin_api_integration/plugins/event_log/kibana.json new file mode 100644 index 000000000000..4b467ce97501 --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "event_log_fixture", + "version": "1.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack"], + "requiredPlugins": ["eventLog"], + "server": true, + "ui": false +} diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/package.json b/x-pack/test/plugin_api_integration/plugins/event_log/package.json new file mode 100644 index 000000000000..222dfb2338e2 --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/package.json @@ -0,0 +1,15 @@ +{ + "name": "event_log_fixture", + "version": "0.0.0", + "kibana": { + "version": "kibana" + }, + "main": "target/test/plugin_api_integration/plugins/event_log", + "scripts": { + "kbn": "node ../../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts b/x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts new file mode 100644 index 000000000000..3d794a7c80fa --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'kibana/server'; +import { EventLogFixturePlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new EventLogFixturePlugin(initContext); diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts new file mode 100644 index 000000000000..eccbd4fb7f90 --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Plugin, + CoreSetup, + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IKibanaResponse, + IRouter, + Logger, + PluginInitializerContext, + RouteValidationResultFactory, +} from 'kibana/server'; +import { + IEventLogService, + IEventLogClientService, + IEventLogger, +} from '../../../../../plugins/event_log/server'; +import { IValidatedEvent } from '../../../../../plugins/event_log/server/types'; + +// this plugin's dependendencies +export interface EventLogFixtureSetupDeps { + eventLog: IEventLogService; +} +export interface EventLogFixtureStartDeps { + eventLog: IEventLogClientService; +} + +export class EventLogFixturePlugin + implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('plugins', 'eventLogFixture'); + } + + public setup(core: CoreSetup, { eventLog }: EventLogFixtureSetupDeps) { + const router = core.http.createRouter(); + + eventLog.registerProviderActions('event_log_fixture', ['test']); + const eventLogger = eventLog.getLogger({ + event: { provider: 'event_log_fixture' }, + }); + + core.savedObjects.registerType({ + name: 'event_log_test', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: {}, + }, + }); + + logEventRoute(router, eventLogger, this.logger); + } + + public start() {} + public stop() {} +} + +const logEventRoute = (router: IRouter, eventLogger: IEventLogger, logger: Logger) => { + router.post( + { + path: `/api/log_event_fixture/{id}/_log`, + validate: { + // removed validation as schema is currently broken in tests + // blocked by: https://github.com/elastic/kibana/issues/61652 + params: (value: any, { ok }: RouteValidationResultFactory) => ok(value), + body: (value: any, { ok }: RouteValidationResultFactory) => ok(value), + }, + }, + async function( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + const { id } = req.params as { id: string }; + const event: IValidatedEvent = req.body; + logger.info(`test fixture: log event: ${id} ${JSON.stringify(event)}`); + try { + await context.core.savedObjects.client.get('event_log_test', id); + logger.info(`found existing saved object`); + } catch (ex) { + logger.info(`log event error: ${ex}`); + await context.core.savedObjects.client.create('event_log_test', {}, { id }); + logger.info(`created saved object`); + } + eventLogger.logEvent(event); + logger.info(`logged`); + return res.ok({}); + } + ); +}; diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts new file mode 100644 index 000000000000..a68378decb1f --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('event_log', function taskManagerSuite() { + this.tags('ciGroup2'); + loadTestFile(require.resolve('./public_api_integration')); + loadTestFile(require.resolve('./service_api_integration')); + }); +} diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts new file mode 100644 index 000000000000..c440971225d7 --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts @@ -0,0 +1,236 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { merge, omit, times, chunk, isEmpty } from 'lodash'; +import uuid from 'uuid'; +import expect from '@kbn/expect/expect.js'; +import moment, { Moment } from 'moment'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { IEvent } from '../../../../plugins/event_log/server'; +import { IValidatedEvent } from '../../../../plugins/event_log/server/types'; + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const log = getService('log'); + const retry = getService('retry'); + + describe('Event Log public API', () => { + it('should allow querying for events by Saved Object', async () => { + const id = uuid.v4(); + + const expectedEvents = [fakeEvent(id), fakeEvent(id)]; + + await logTestEvent(id, expectedEvents[0]); + await logTestEvent(id, expectedEvents[1]); + + await retry.try(async () => { + const { + body: { data, total }, + } = await findEvents(id, {}); + + expect(data.length).to.be(2); + expect(total).to.be(2); + + assertEventsFromApiMatchCreatedEvents(data, expectedEvents); + }); + }); + + it('should support pagination for events', async () => { + const id = uuid.v4(); + + const timestamp = moment(); + const [firstExpectedEvent, ...expectedEvents] = times(6, () => + fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))) + ); + // run one first to create the SO and avoid clashes + await logTestEvent(id, firstExpectedEvent); + await Promise.all(expectedEvents.map(event => logTestEvent(id, event))); + + await retry.try(async () => { + const { + body: { data: foundEvents }, + } = await findEvents(id, {}); + + expect(foundEvents.length).to.be(6); + }); + + const [expectedFirstPage, expectedSecondPage] = chunk( + [firstExpectedEvent, ...expectedEvents], + 3 + ); + + const { + body: { data: firstPage }, + } = await findEvents(id, { per_page: 3 }); + + expect(firstPage.length).to.be(3); + assertEventsFromApiMatchCreatedEvents(firstPage, expectedFirstPage); + + const { + body: { data: secondPage }, + } = await findEvents(id, { per_page: 3, page: 2 }); + + expect(secondPage.length).to.be(3); + assertEventsFromApiMatchCreatedEvents(secondPage, expectedSecondPage); + }); + + it('should support sorting by event end', async () => { + const id = uuid.v4(); + + const timestamp = moment(); + const [firstExpectedEvent, ...expectedEvents] = times(6, () => + fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))) + ); + // run one first to create the SO and avoid clashes + await logTestEvent(id, firstExpectedEvent); + await Promise.all(expectedEvents.map(event => logTestEvent(id, event))); + + await retry.try(async () => { + const { + body: { data: foundEvents }, + } = await findEvents(id, { sort_field: 'event.end', sort_order: 'desc' }); + + expect(foundEvents.length).to.be(6); + assertEventsFromApiMatchCreatedEvents( + foundEvents, + [firstExpectedEvent, ...expectedEvents].reverse() + ); + }); + }); + + it('should support date ranges for events', async () => { + const id = uuid.v4(); + + const timestamp = moment(); + + const firstEvent = fakeEvent(id, fakeEventTiming(timestamp)); + await logTestEvent(id, firstEvent); + await delay(100); + + const start = timestamp.add(1, 's').toISOString(); + + const expectedEvents = times(6, () => fakeEvent(id, fakeEventTiming(timestamp.add(1, 's')))); + await Promise.all(expectedEvents.map(event => logTestEvent(id, event))); + + const end = timestamp.add(1, 's').toISOString(); + + await delay(100); + const lastEvent = fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))); + await logTestEvent(id, lastEvent); + + await retry.try(async () => { + const { + body: { data: foundEvents, total }, + } = await findEvents(id, {}); + + expect(foundEvents.length).to.be(8); + expect(total).to.be(8); + }); + + const { + body: { data: eventsWithinRange }, + } = await findEvents(id, { start, end }); + + expect(eventsWithinRange.length).to.be(expectedEvents.length); + assertEventsFromApiMatchCreatedEvents(eventsWithinRange, expectedEvents); + + const { + body: { data: eventsFrom }, + } = await findEvents(id, { start }); + + expect(eventsFrom.length).to.be(expectedEvents.length + 1); + assertEventsFromApiMatchCreatedEvents(eventsFrom, [...expectedEvents, lastEvent]); + + const { + body: { data: eventsUntil }, + } = await findEvents(id, { end }); + + expect(eventsUntil.length).to.be(expectedEvents.length + 1); + assertEventsFromApiMatchCreatedEvents(eventsUntil, [firstEvent, ...expectedEvents]); + }); + }); + + async function findEvents(id: string, query: Record = {}) { + const uri = `/api/event_log/event_log_test/${id}/_find${ + isEmpty(query) + ? '' + : `?${Object.entries(query) + .map(([key, val]) => `${key}=${val}`) + .join('&')}` + }`; + log.debug(`calling ${uri}`); + return await supertest + .get(uri) + .set('kbn-xsrf', 'foo') + .expect(200); + } + + function assertEventsFromApiMatchCreatedEvents( + foundEvents: IValidatedEvent[], + expectedEvents: IEvent[] + ) { + try { + foundEvents.forEach((foundEvent: IValidatedEvent, index: number) => { + expect(foundEvent!.event).to.eql(expectedEvents[index]!.event); + expect(omit(foundEvent!.kibana ?? {}, 'server_uuid')).to.eql(expectedEvents[index]!.kibana); + expect(foundEvent!.message).to.eql(expectedEvents[index]!.message); + }); + } catch (ex) { + log.debug(`failed to match ${JSON.stringify({ foundEvents, expectedEvents })}`); + throw ex; + } + } + + async function logTestEvent(id: string, event: IEvent) { + log.debug(`Logging Event for Saved Object ${id}`); + return await supertest + .post(`/api/log_event_fixture/${id}/_log`) + .set('kbn-xsrf', 'foo') + .send(event) + .expect(200); + } + + function fakeEventTiming(start: Moment): Partial { + return { + event: { + start: start.toISOString(), + end: start + .clone() + .add(500, 'milliseconds') + .toISOString(), + }, + }; + } + + function fakeEvent(id: string, overrides: Partial = {}): IEvent { + const start = moment().toISOString(); + const end = moment().toISOString(); + return merge( + { + event: { + provider: 'event_log_fixture', + action: 'test', + start, + end, + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'event_log_test', + id, + }, + ], + }, + message: `test ${moment().toISOString()}`, + }, + overrides + ); + } +} diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts new file mode 100644 index 000000000000..b055b22879bf --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function() { + describe('Event Log service API', () => { + it('should allow logging an event', async () => {}); + }); +} From d67f2220b34a887a0c7564d64bd1472de87dcc4e Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 6 Apr 2020 20:22:46 +0300 Subject: [PATCH 16/27] [SIEM][CASE] Configuration page tests (#61093) * Test ClosureOptionsRadio component * Test ClosureOptions component * Test ConnectorsDropdown component * Test Connectors * Test FieldMappingRow * Test FieldMapping * Create utils functions and refactor to be able to test * Test Mapping * Improve tests * Test ConfigureCases * Refactor tests * Fix flacky tests * Remove snapshots * Refactor tests * Test button * Test reducer * Move test * Better structure Co-authored-by: Elastic Machine --- .../configure_cases/__mock__/index.tsx | 122 +++ .../configure_cases/button.test.tsx | 114 +++ .../components/configure_cases/button.tsx | 10 +- .../configure_cases/closure_options.test.tsx | 67 ++ .../configure_cases/closure_options.tsx | 10 +- .../closure_options_radio.test.tsx | 79 ++ .../configure_cases/closure_options_radio.tsx | 3 +- .../configure_cases/connectors.test.tsx | 90 +++ .../components/configure_cases/connectors.tsx | 16 +- .../connectors_dropdown.test.tsx | 86 ++ .../configure_cases/connectors_dropdown.tsx | 7 +- .../configure_cases/field_mapping.test.tsx | 84 ++ .../configure_cases/field_mapping.tsx | 31 +- .../field_mapping_row.test.tsx | 106 +++ .../configure_cases/field_mapping_row.tsx | 4 +- .../components/configure_cases/index.test.tsx | 748 ++++++++++++++++++ .../case/components/configure_cases/index.tsx | 16 +- .../configure_cases/mapping.test.tsx | 65 ++ .../components/configure_cases/mapping.tsx | 13 +- .../configure_cases/reducer.test.ts | 68 ++ .../components/configure_cases/utils.test.tsx | 63 ++ .../case/components/configure_cases/utils.ts | 44 ++ 22 files changed, 1807 insertions(+), 39 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx new file mode 100644 index 000000000000..a3df3664398a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Connector, + CasesConfigurationMapping, +} from '../../../../../containers/case/configure/types'; +import { State } from '../reducer'; +import { ReturnConnectors } from '../../../../../containers/case/configure/use_connectors'; +import { ReturnUseCaseConfigure } from '../../../../../containers/case/configure/use_configure'; +import { createUseKibanaMock } from '../../../../../mock/kibana_react'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock'; + +export const connectors: Connector[] = [ + { + id: '123', + actionTypeId: '.servicenow', + name: 'My Connector', + config: { + apiUrl: 'https://instance1.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + }, + { + id: '456', + actionTypeId: '.servicenow', + name: 'My Connector 2', + config: { + apiUrl: 'https://instance2.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + }, +]; + +export const mapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, +]; + +export const searchURL = + '?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))'; + +export const initialState: State = { + connectorId: 'none', + closureType: 'close-by-user', + mapping: null, + currentConfiguration: { connectorId: 'none', closureType: 'close-by-user' }, +}; + +export const useCaseConfigureResponse: ReturnUseCaseConfigure = { + loading: false, + persistLoading: false, + refetchCaseConfigure: jest.fn(), + persistCaseConfigure: jest.fn(), +}; + +export const useConnectorsResponse: ReturnConnectors = { + loading: false, + connectors, + refetchConnectors: jest.fn(), +}; + +export const kibanaMockImplementationArgs = { + services: { + ...createUseKibanaMock()().services, + triggers_actions_ui: { actionTypeRegistry: actionTypeRegistryMock.create() }, + }, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx new file mode 100644 index 000000000000..cf52fef94ed1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; +import { EuiText } from '@elastic/eui'; + +import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button'; +import { TestProviders } from '../../../../mock'; +import { searchURL } from './__mock__'; + +describe('Configuration button', () => { + let wrapper: ReactWrapper; + const props: ConfigureCaseButtonProps = { + isDisabled: false, + label: 'My label', + msgTooltip: <>, + showToolTip: false, + titleTooltip: '', + urlSearch: searchURL, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders without the tooltip', () => { + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="configure-case-tooltip"]') + .first() + .exists() + ).toBe(false); + }); + + test('it pass the correct props to the button', () => { + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .props() + ).toMatchObject({ + href: `#/link-to/case/configure${searchURL}`, + iconType: 'controlsHorizontal', + isDisabled: false, + 'aria-label': 'My label', + children: 'My label', + }); + }); + + test('it renders the tooltip', () => { + const msgTooltip = {'My message tooltip'}; + + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + + expect( + newWrapper + .find('[data-test-subj="configure-case-tooltip"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the tooltip when hovering the button', () => { + const msgTooltip = 'My message tooltip'; + const titleTooltip = 'My title'; + + const newWrapper = mount( + {msgTooltip}} + />, + { + wrappingComponent: TestProviders, + } + ); + + newWrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .simulate('mouseOver'); + + expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx index b0bea83148bd..844ffea28415 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx @@ -8,7 +8,7 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; import { getConfigureCasesUrl } from '../../../../components/link_to'; -interface ConfigureCaseButtonProps { +export interface ConfigureCaseButtonProps { label: string; isDisabled: boolean; msgTooltip: JSX.Element; @@ -32,6 +32,7 @@ const ConfigureCaseButtonComponent: React.FC = ({ iconType="controlsHorizontal" isDisabled={isDisabled} aria-label={label} + data-test-subj="configure-case-button" > {label} @@ -39,7 +40,12 @@ const ConfigureCaseButtonComponent: React.FC = ({ [label, isDisabled, urlSearch] ); return showToolTip ? ( - {msgTooltip}

}> + {msgTooltip}

} + data-test-subj="configure-case-tooltip" + > {configureCaseButton}
) : ( diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx new file mode 100644 index 000000000000..209dce9aedff --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { ClosureOptions, ClosureOptionsProps } from './closure_options'; +import { TestProviders } from '../../../../mock'; +import { ClosureOptionsRadio } from './closure_options_radio'; + +describe('ClosureOptions', () => { + let wrapper: ReactWrapper; + const onChangeClosureType = jest.fn(); + const props: ClosureOptionsProps = { + disabled: false, + closureTypeSelected: 'close-by-user', + onChangeClosureType, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the closure options form group', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the closure options form row', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows closure options', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-radio"]') + .first() + .exists() + ).toBe(true); + }); + + test('it pass the correct props to child', () => { + const closureOptionsRadioComponent = wrapper.find(ClosureOptionsRadio); + expect(closureOptionsRadioComponent.props().disabled).toEqual(false); + expect(closureOptionsRadioComponent.props().closureTypeSelected).toEqual('close-by-user'); + expect(closureOptionsRadioComponent.props().onChangeClosureType).toEqual(onChangeClosureType); + }); + + test('the closure type is changed successfully', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + + expect(onChangeClosureType).toHaveBeenCalled(); + expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx index 9879b9149059..6fa97818dd0c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx @@ -11,7 +11,7 @@ import { ClosureType } from '../../../../containers/case/configure/types'; import { ClosureOptionsRadio } from './closure_options_radio'; import * as i18n from './translations'; -interface ClosureOptionsProps { +export interface ClosureOptionsProps { closureTypeSelected: ClosureType; disabled: boolean; onChangeClosureType: (newClosureType: ClosureType) => void; @@ -27,12 +27,18 @@ const ClosureOptionsComponent: React.FC = ({ fullWidth title={

{i18n.CASE_CLOSURE_OPTIONS_TITLE}

} description={i18n.CASE_CLOSURE_OPTIONS_DESC} + data-test-subj="case-closure-options-form-group" > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx new file mode 100644 index 000000000000..f2ef2c2d55c2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; + +import { ClosureOptionsRadio, ClosureOptionsRadioComponentProps } from './closure_options_radio'; +import { TestProviders } from '../../../../mock'; + +describe('ClosureOptionsRadio', () => { + let wrapper: ReactWrapper; + const onChangeClosureType = jest.fn(); + const props: ClosureOptionsRadioComponentProps = { + disabled: false, + closureTypeSelected: 'close-by-user', + onChangeClosureType, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="closure-options-radio-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the correct number of radio buttons', () => { + expect(wrapper.find('input[name="closure_options"]')).toHaveLength(2); + }); + + test('it renders close by user radio button', () => { + expect(wrapper.find('input[id="close-by-user"]').exists()).toBeTruthy(); + }); + + test('it renders close by pushing radio button', () => { + expect(wrapper.find('input[id="close-by-pushing"]').exists()).toBeTruthy(); + }); + + test('it disables the close by user radio button', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('input[id="close-by-user"]').prop('disabled')).toEqual(true); + }); + + test('it disables correctly the close by pushing radio button', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('input[id="close-by-pushing"]').prop('disabled')).toEqual(true); + }); + + test('it selects the correct radio button', () => { + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + expect(newWrapper.find('input[id="close-by-pushing"]').prop('checked')).toEqual(true); + }); + + test('it calls the onChangeClosureType function', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + expect(onChangeClosureType).toHaveBeenCalled(); + expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx index f32f867b2471..d2cdb7ecda7b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx @@ -26,7 +26,7 @@ const radios: ClosureRadios[] = [ }, ]; -interface ClosureOptionsRadioComponentProps { +export interface ClosureOptionsRadioComponentProps { closureTypeSelected: ClosureType; disabled: boolean; onChangeClosureType: (newClosureType: ClosureType) => void; @@ -51,6 +51,7 @@ const ClosureOptionsRadioComponent: React.FC idSelected={closureTypeSelected} onChange={onChangeLocal} name="closure_options" + data-test-subj="closure-options-radio-group" /> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx new file mode 100644 index 000000000000..5fb52c374b48 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { Connectors, Props } from './connectors'; +import { TestProviders } from '../../../../mock'; +import { ConnectorsDropdown } from './connectors_dropdown'; +import { connectors } from './__mock__'; + +describe('Connectors', () => { + let wrapper: ReactWrapper; + const onChangeConnector = jest.fn(); + const handleShowAddFlyout = jest.fn(); + const props: Props = { + disabled: false, + connectors, + selectedConnector: 'none', + isLoading: false, + onChangeConnector, + handleShowAddFlyout, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the connectors from group', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the connectors form row', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the connectors dropdown', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-dropdown"]') + .first() + .exists() + ).toBe(true); + }); + + test('it pass the correct props to child', () => { + const connectorsDropdownProps = wrapper.find(ConnectorsDropdown).props(); + expect(connectorsDropdownProps).toMatchObject({ + disabled: false, + isLoading: false, + connectors, + selectedConnector: 'none', + onChange: props.onChangeConnector, + }); + }); + + test('the connector is changed successfully', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + + expect(onChangeConnector).toHaveBeenCalled(); + expect(onChangeConnector).toHaveBeenCalledWith('456'); + }); + + test('the connector is changed successfully to none', () => { + onChangeConnector.mockClear(); + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + newWrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + newWrapper.find('button[data-test-subj="dropdown-connector-no-connector"]').simulate('click'); + + expect(onChangeConnector).toHaveBeenCalled(); + expect(onChangeConnector).toHaveBeenCalledWith('none'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx index 8fb1cfb1aa6c..de6d5f76cfad 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx @@ -28,7 +28,7 @@ const EuiFormRowExtended = styled(EuiFormRow)` } `; -interface Props { +export interface Props { connectors: Connector[]; disabled: boolean; isLoading: boolean; @@ -48,7 +48,11 @@ const ConnectorsComponent: React.FC = ({ {i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL} - + {i18n.ADD_NEW_CONNECTOR} @@ -61,14 +65,20 @@ const ConnectorsComponent: React.FC = ({ fullWidth title={

{i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}

} description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC} + data-test-subj="case-connectors-form-group" > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx new file mode 100644 index 000000000000..044108962efc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { EuiSuperSelect } from '@elastic/eui'; + +import { ConnectorsDropdown, Props } from './connectors_dropdown'; +import { TestProviders } from '../../../../mock'; +import { connectors } from './__mock__'; + +describe('ConnectorsDropdown', () => { + let wrapper: ReactWrapper; + const props: Props = { + disabled: false, + connectors, + isLoading: false, + onChange: jest.fn(), + selectedConnector: 'none', + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .exists() + ).toBe(true); + }); + + test('it formats the connectors correctly', () => { + const selectProps = wrapper.find(EuiSuperSelect).props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'none', + 'data-test-subj': 'dropdown-connector-no-connector', + }), + expect.objectContaining({ value: '123', 'data-test-subj': 'dropdown-connector-123' }), + expect.objectContaining({ value: '456', 'data-test-subj': 'dropdown-connector-456' }), + ]) + ); + }); + + test('it disables the dropdown', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect( + newWrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .prop('disabled') + ).toEqual(true); + }); + + test('it loading correctly', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect( + newWrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .prop('isLoading') + ).toEqual(true); + }); + + test('it selects the correct connector', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('button span').text()).toEqual('My Connector'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx index a0a0ad6cd3e7..15066e73eee8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx @@ -12,7 +12,7 @@ import { Connector } from '../../../../containers/case/configure/types'; import { connectors as connectorsDefinition } from '../../../../lib/connectors/config'; import * as i18n from './translations'; -interface Props { +export interface Props { connectors: Connector[]; disabled: boolean; isLoading: boolean; @@ -34,7 +34,7 @@ const noConnectorOption = { {i18n.NO_CONNECTOR} ), - 'data-test-subj': 'no-connector', + 'data-test-subj': 'dropdown-connector-no-connector', }; const ConnectorsDropdownComponent: React.FC = ({ @@ -60,7 +60,7 @@ const ConnectorsDropdownComponent: React.FC = ({ {connector.name} ), - 'data-test-subj': connector.id, + 'data-test-subj': `dropdown-connector-${connector.id}`, }, ], [noConnectorOption] @@ -76,6 +76,7 @@ const ConnectorsDropdownComponent: React.FC = ({ valueOfSelected={selectedConnector} fullWidth onChange={onChange} + data-test-subj="dropdown-connectors" /> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx new file mode 100644 index 000000000000..9ab752bb589c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { FieldMapping, FieldMappingProps } from './field_mapping'; +import { mapping } from './__mock__'; +import { FieldMappingRow } from './field_mapping_row'; +import { defaultMapping } from '../../../../lib/connectors/config'; +import { TestProviders } from '../../../../mock'; + +describe('FieldMappingRow', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const props: FieldMappingProps = { + disabled: false, + mapping, + onChangeMapping, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-field-mapping-cols"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="case-configure-field-mapping-row-wrapper"]') + .first() + .exists() + ).toBe(true); + + expect(wrapper.find(FieldMappingRow).length).toEqual(3); + }); + + test('it shows the correct number of FieldMappingRow with default mapping', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(FieldMappingRow).length).toEqual(3); + }); + + test('it pass the corrects props to mapping row', () => { + const rows = wrapper.find(FieldMappingRow); + rows.forEach((row, index) => { + expect(row.prop('siemField')).toEqual(mapping[index].source); + expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType); + expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target); + }); + }); + + test('it pass the default mapping when mapping is null', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + const rows = newWrapper.find(FieldMappingRow); + rows.forEach((row, index) => { + expect(row.prop('siemField')).toEqual(defaultMapping[index].source); + expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType); + expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target); + }); + }); + + test('it should show zero rows on empty array', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(FieldMappingRow).length).toEqual(0); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 0c0dc14f1c21..2934b1056e29 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -18,6 +18,7 @@ import { FieldMappingRow } from './field_mapping_row'; import * as i18n from './translations'; import { defaultMapping } from '../../../../lib/connectors/config'; +import { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; const FieldRowWrapper = styled.div` margin-top: 8px; @@ -28,22 +29,26 @@ const supportedThirdPartyFields: Array> = { value: 'not_mapped', inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, + 'data-test-subj': 'third-party-field-not-mapped', }, { value: 'short_description', inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, + 'data-test-subj': 'third-party-field-short-description', }, { value: 'comments', inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, + 'data-test-subj': 'third-party-field-comments', }, { value: 'description', inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, + 'data-test-subj': 'third-party-field-description', }, ]; -interface FieldMappingProps { +export interface FieldMappingProps { disabled: boolean; mapping: CasesConfigurationMapping[] | null; onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void; @@ -57,14 +62,7 @@ const FieldMappingComponent: React.FC = ({ const onChangeActionType = useCallback( (caseField: CaseField, newActionType: ActionType) => { const myMapping = mapping ?? defaultMapping; - const findItemIndex = myMapping.findIndex(item => item.source === caseField); - if (findItemIndex >= 0) { - onChangeMapping([ - ...myMapping.slice(0, findItemIndex), - { ...myMapping[findItemIndex], actionType: newActionType }, - ...myMapping.slice(findItemIndex + 1), - ]); - } + onChangeMapping(setActionTypeToMapping(caseField, newActionType, myMapping)); }, [mapping] ); @@ -72,22 +70,13 @@ const FieldMappingComponent: React.FC = ({ const onChangeThirdParty = useCallback( (caseField: CaseField, newThirdPartyField: ThirdPartyField) => { const myMapping = mapping ?? defaultMapping; - onChangeMapping( - myMapping.map(item => { - if (item.source !== caseField && item.target === newThirdPartyField) { - return { ...item, target: 'not_mapped' }; - } else if (item.source === caseField) { - return { ...item, target: newThirdPartyField }; - } - return item; - }) - ); + onChangeMapping(setThirdPartyToMapping(caseField, newThirdPartyField, myMapping)); }, [mapping] ); return ( <> - + {i18n.FIELD_MAPPING_FIRST_COL} @@ -100,7 +89,7 @@ const FieldMappingComponent: React.FC = ({ - + {(mapping ?? defaultMapping).map(item => ( > = [ + { + value: 'short_description', + inputDisplay: {'Short Description'}, + 'data-test-subj': 'third-party-short-desc', + }, + { + value: 'description', + inputDisplay: {'Description'}, + 'data-test-subj': 'third-party-desc', + }, +]; + +describe('FieldMappingRow', () => { + let wrapper: ReactWrapper; + const onChangeActionType = jest.fn(); + const onChangeThirdParty = jest.fn(); + + const props: RowProps = { + disabled: false, + siemField: 'title', + thirdPartyOptions, + onChangeActionType, + onChangeThirdParty, + selectedActionType: 'nothing', + selectedThirdParty: 'short_description', + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-third-party-select"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-type-select"]') + .first() + .exists() + ).toBe(true); + }); + + test('it passes thirdPartyOptions correctly', () => { + const selectProps = wrapper + .find(EuiSuperSelect) + .first() + .props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'short_description', + 'data-test-subj': 'third-party-short-desc', + }), + expect.objectContaining({ + value: 'description', + 'data-test-subj': 'third-party-desc', + }), + ]) + ); + }); + + test('it passes the correct actionTypeOptions', () => { + const selectProps = wrapper + .find(EuiSuperSelect) + .at(1) + .props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'nothing', + 'data-test-subj': 'edit-update-option-nothing', + }), + expect.objectContaining({ + value: 'overwrite', + 'data-test-subj': 'edit-update-option-overwrite', + }), + expect.objectContaining({ + value: 'append', + 'data-test-subj': 'edit-update-option-append', + }), + ]) + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx index 62e43c86af8d..732a11a58d35 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx @@ -21,7 +21,7 @@ import { ThirdPartyField, } from '../../../../containers/case/configure/types'; -interface RowProps { +export interface RowProps { disabled: boolean; siemField: CaseField; thirdPartyOptions: Array>; @@ -77,6 +77,7 @@ const FieldMappingRowComponent: React.FC = ({ options={thirdPartyOptions} valueOfSelected={selectedThirdParty} onChange={onChangeThirdParty.bind(null, siemField)} + data-test-subj={'case-configure-third-party-select'} /> @@ -85,6 +86,7 @@ const FieldMappingRowComponent: React.FC = ({ options={actionTypeOptions} valueOfSelected={selectedActionType} onChange={onChangeActionType.bind(null, siemField)} + data-test-subj={'case-configure-action-type-select'} />
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx new file mode 100644 index 000000000000..5ea3f500c034 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx @@ -0,0 +1,748 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { ReactWrapper, mount } from 'enzyme'; + +import { useKibana } from '../../../../lib/kibana'; +import { useConnectors } from '../../../../containers/case/configure/use_connectors'; +import { useCaseConfigure } from '../../../../containers/case/configure/use_configure'; +import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search'; + +import { + connectors, + searchURL, + useCaseConfigureResponse, + useConnectorsResponse, + kibanaMockImplementationArgs, +} from './__mock__'; + +jest.mock('../../../../lib/kibana'); +jest.mock('../../../../containers/case/configure/use_connectors'); +jest.mock('../../../../containers/case/configure/use_configure'); +jest.mock('../../../../components/navigation/use_get_url_search'); + +const useKibanaMock = useKibana as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; +const useCaseConfigureMock = useCaseConfigure as jest.Mock; +const useGetUrlSearchMock = useGetUrlSearch as jest.Mock; + +import { ConfigureCases } from './'; +import { TestProviders } from '../../../../mock'; +import { Connectors } from './connectors'; +import { ClosureOptions } from './closure_options'; +import { Mapping } from './mapping'; +import { + ActionsConnectorsContextProvider, + ConnectorAddFlyout, + ConnectorEditFlyout, +} from '../../../../../../../../plugins/triggers_actions_ui/public'; +import { EuiBottomBar } from '@elastic/eui'; + +describe('rendering', () => { + let wrapper: ReactWrapper; + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the Connectors', () => { + expect(wrapper.find('[data-test-subj="case-connectors-form-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ClosureType', () => { + expect( + wrapper.find('[data-test-subj="case-closure-options-form-group"]').exists() + ).toBeTruthy(); + }); + + test('it renders the Mapping', () => { + expect(wrapper.find('[data-test-subj="case-mapping-form-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ActionsConnectorsContextProvider', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ActionsConnectorsContextProvider).exists()).toBeTruthy(); + }); + + test('it renders the ConnectorAddFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorAddFlyout).exists()).toBeTruthy(); + }); + + test('it does NOT render the ConnectorEditFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiCallOut', () => { + expect(wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiBottomBar', () => { + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); +}); + +describe('ConfigureCases - Unhappy path', () => { + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + }); + + test('it shows the warning callout when configuration is invalid', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('not-id'), []); + return useCaseConfigureResponse; + } + ); + + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists() + ).toBeTruthy(); + }); +}); + +describe('ConfigureCases - Happy path', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('123'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the ConnectorEditFlyout', () => { + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeTruthy(); + }); + + test('it renders with correct props', () => { + // Connector + expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors); + expect(wrapper.find(Connectors).prop('disabled')).toBe(false); + expect(wrapper.find(Connectors).prop('isLoading')).toBe(false); + expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('123'); + + // ClosureOptions + expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(false); + expect(wrapper.find(ClosureOptions).prop('closureTypeSelected')).toBe('close-by-user'); + + // Mapping + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + expect(wrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(false); + expect(wrapper.find(Mapping).prop('mapping')).toEqual( + connectors[0].config.casesConfiguration.mapping + ); + + // Flyouts + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([ + { + id: '.servicenow', + name: 'ServiceNow', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'platinum', + }, + ]); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[0]); + }); + + test('it disables correctly when the user cannot crud', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + expect(newWrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(true); + }); + + test('it disables correctly Connector when loading connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it disables correctly Connector when saving configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it pass the correct value to isLoading attribute on Connector', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('isLoading')).toBe(true); + }); + + test('it set correctly the selected connector', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('selectedConnector')).toBe('456'); + }); + + test('it show the add flyout when pressing the add connector button', () => { + wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy(); + }); + + test('it disables correctly ClosureOptions when loading connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when saving configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when the connector is set to none', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('none'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables the mapping permanently', () => { + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when loading the connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when loading the configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when saving the configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when the connectorId is invalid', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('not-id'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when the connectorId is set to none', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('none'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it show the edit flyout when pressing the update connector button', () => { + wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy(); + }); + + test('it sets the mapping of a connector correctly', () => { + expect(wrapper.find(Mapping).prop('mapping')).toEqual( + connectors[0].config.casesConfiguration.mapping + ); + }); + + // TODO: When mapping is enabled the test.todo should be implemented. + test.todo('the mapping is changed successfully when changing the third party'); + test.todo('the mapping is changed successfully when changing the action type'); + + test('it does not shows the action bar when there is no change', () => { + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it shows the action bar when the connector is changed', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it shows the action bar when the closure type is changed', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it tracks the changes successfully', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('2 unsaved changes'); + }); + + test('it tracks and reverts the changes successfully ', () => { + // change settings + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // revert back to initial settings + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-123"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-user"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it close and restores the action bar when the add connector button is pressed', () => { + // Change closure type + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // Press add connector button + wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + + // Close the add flyout + wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it close and restores the action bar when the update connector button is pressed', () => { + // Change closure type + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // Press update connector button + wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + + // Close the edit flyout + wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it disables the buttons of action bar when loading connectors', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it disables the buttons of action bar when loading configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, loading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it disables the buttons of action bar when saving configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistLoading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it shows the loading spinner when saving configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistLoading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isLoading') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isLoading') + ).toBe(true); + }); + + test('it closes the action bar when pressing save', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .simulate('click'); + + newWrapper.update(); + + expect( + newWrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it submits the configuration correctly', () => { + const persistCaseConfigure = jest.fn(); + + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-pushing', + }), + [] + ); + return { ...useCaseConfigureResponse, persistCaseConfigure }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .simulate('click'); + + newWrapper.update(); + + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connectorId: '456', + connectorName: 'My Connector 2', + closureType: 'close-by-user', + }); + }); + + test('it has the correct url on cancel button', () => { + const persistCaseConfigure = jest.fn(); + + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistCaseConfigure }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('href') + ).toBe(`#/link-to/case${searchURL}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx index b8cf5a388080..241dcef14a14 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -140,6 +140,7 @@ const ConfigureCasesComponent: React.FC = ({ userC setClosureType, setCurrentConfiguration, }); + const { loading: isLoadingConnectors, connectors, refetchConnectors } = useConnectors(); // ActionsConnectorsContextProvider reloadConnectors prop expects a Promise. @@ -251,7 +252,12 @@ const ConfigureCasesComponent: React.FC = ({ userC {!connectorIsValid && ( - + {i18n.WARNING_NO_CONNECTOR_MESSAGE} @@ -283,11 +289,13 @@ const ConfigureCasesComponent: React.FC = ({ userC /> {actionBarVisible && ( - + - {i18n.UNSAVED_CHANGES(totalConfigurationChanges)} + + {i18n.UNSAVED_CHANGES(totalConfigurationChanges)} + @@ -300,6 +308,7 @@ const ConfigureCasesComponent: React.FC = ({ userC isLoading={persistLoading} aria-label={i18n.CANCEL} href={getCaseUrl(search)} + data-test-subj="case-configure-action-bottom-bar-cancel-button" > {i18n.CANCEL} @@ -313,6 +322,7 @@ const ConfigureCasesComponent: React.FC = ({ userC isDisabled={isLoadingAny} isLoading={persistLoading} onClick={handleSubmit} + data-test-subj="case-configure-action-bottom-bar-save-button" > {i18n.SAVE_CHANGES} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx new file mode 100644 index 000000000000..fefcb2ca8cf6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { Mapping, MappingProps } from './mapping'; +import { mapping } from './__mock__'; + +describe('Mapping', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const setEditFlyoutVisibility = jest.fn(); + const props: MappingProps = { + disabled: false, + mapping, + updateConnectorDisabled: false, + onChangeMapping, + setEditFlyoutVisibility, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows mapping form group', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows mapping form row', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the update button', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-update-connector-button"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the field mapping', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-field"]') + .first() + .exists() + ).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx index 8cba73d1249d..7340a49f6d0b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx @@ -20,7 +20,7 @@ import * as i18n from './translations'; import { FieldMapping } from './field_mapping'; import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; -interface MappingProps { +export interface MappingProps { disabled: boolean; updateConnectorDisabled: boolean; mapping: CasesConfigurationMapping[] | null; @@ -45,20 +45,27 @@ const MappingComponent: React.FC = ({ fullWidth title={

{i18n.FIELD_MAPPING_TITLE}

} description={i18n.FIELD_MAPPING_DESC} + data-test-subj="case-mapping-form-group" > - + {i18n.UPDATE_CONNECTOR} - + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts new file mode 100644 index 000000000000..df958b75dc6b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { configureCasesReducer, Action, State } from './reducer'; +import { initialState, mapping } from './__mock__'; + +describe('Reducer', () => { + let reducer: (state: State, action: Action) => State; + + beforeAll(() => { + reducer = configureCasesReducer(); + }); + + test('it should set the correct configuration', () => { + const action: Action = { + type: 'setCurrentConfiguration', + currentConfiguration: { connectorId: '123', closureType: 'close-by-user' }, + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + currentConfiguration: action.currentConfiguration, + }); + }); + + test('it should set the correct connector id', () => { + const action: Action = { + type: 'setConnectorId', + connectorId: '456', + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + connectorId: action.connectorId, + }); + }); + + test('it should set the closure type', () => { + const action: Action = { + type: 'setClosureType', + closureType: 'close-by-pushing', + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + closureType: action.closureType, + }); + }); + + test('it should set the mapping', () => { + const action: Action = { + type: 'setMapping', + mapping, + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + mapping: action.mapping, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx new file mode 100644 index 000000000000..1c6fc9b2d405 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mapping } from './__mock__'; +import { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; +import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; + +describe('FieldMappingRow', () => { + test('it should change the action type', () => { + const newMapping = setActionTypeToMapping('title', 'nothing', mapping); + expect(newMapping[0].actionType).toBe('nothing'); + }); + + test('it should not change other fields', () => { + const [newTitle, description, comments] = setActionTypeToMapping('title', 'nothing', mapping); + expect(newTitle).not.toEqual(mapping[0]); + expect(description).toEqual(mapping[1]); + expect(comments).toEqual(mapping[2]); + }); + + test('it should return a new array when changing action type', () => { + const newMapping = setActionTypeToMapping('title', 'nothing', mapping); + expect(newMapping).not.toBe(mapping); + }); + + test('it should change the third party', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping[0].target).toBe('description'); + }); + + test('it should not change other fields when there is not a conflict', () => { + const tempMapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ]; + + const [newTitle, comments] = setThirdPartyToMapping('title', 'description', tempMapping); + + expect(newTitle).not.toEqual(mapping[0]); + expect(comments).toEqual(tempMapping[1]); + }); + + test('it should return a new array when changing third party', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping).not.toBe(mapping); + }); + + test('it should change the target of the conflicting third party field to not_mapped', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping[1].target).toBe('not_mapped'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts new file mode 100644 index 000000000000..2ac6cc1a3858 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CaseField, + ActionType, + CasesConfigurationMapping, + ThirdPartyField, +} from '../../../../containers/case/configure/types'; + +export const setActionTypeToMapping = ( + caseField: CaseField, + newActionType: ActionType, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => { + const findItemIndex = mapping.findIndex(item => item.source === caseField); + + if (findItemIndex >= 0) { + return [ + ...mapping.slice(0, findItemIndex), + { ...mapping[findItemIndex], actionType: newActionType }, + ...mapping.slice(findItemIndex + 1), + ]; + } + + return [...mapping]; +}; + +export const setThirdPartyToMapping = ( + caseField: CaseField, + newThirdPartyField: ThirdPartyField, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => + mapping.map(item => { + if (item.source !== caseField && item.target === newThirdPartyField) { + return { ...item, target: 'not_mapped' }; + } else if (item.source === caseField) { + return { ...item, target: newThirdPartyField }; + } + return item; + }); From 0ebfe76b3fa0cb104c6accf8469fe390ba239b40 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Mon, 6 Apr 2020 19:26:40 +0200 Subject: [PATCH 17/27] [SIEM][Detection Engine] Fix signals count in Rule notifications (#62311) --- .../notifications/get_signals_count.ts | 53 +-- .../rules_notification_alert_type.ts | 22 +- .../schedule_notification_actions.ts | 4 +- .../detection_engine/notifications/utils.ts | 13 +- .../signals/search_after_bulk_create.test.ts | 64 ++- .../signals/search_after_bulk_create.ts | 13 +- .../signals/signal_rule_alert_type.test.ts | 399 ++++++++++++++++++ .../signals/signal_rule_alert_type.ts | 31 +- .../signals/single_bulk_create.test.ts | 40 +- .../signals/single_bulk_create.ts | 8 +- 10 files changed, 567 insertions(+), 80 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts index 33cee6d074b7..7ff6a4e5164b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts @@ -4,63 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; -import { getNotificationResultsLink } from './utils'; -import { NotificationExecutorOptions } from './types'; -import { parseScheduleDates } from '../signals/utils'; +import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { buildSignalsSearchQuery } from './build_signals_query'; -interface SignalsCountResults { - signalsCount: string; - resultsLink: string; -} - interface GetSignalsCount { - from: Date | string; - to: Date | string; - ruleAlertId: string; + from?: string; + to?: string; ruleId: string; index: string; - kibanaSiemAppUrl: string | undefined; - callCluster: NotificationExecutorOptions['services']['callCluster']; + callCluster: AlertServices['callCluster']; +} + +interface CountResult { + count: number; } export const getSignalsCount = async ({ from, to, - ruleAlertId, ruleId, index, callCluster, - kibanaSiemAppUrl = '', -}: GetSignalsCount): Promise => { - const fromMoment = moment.isDate(from) ? moment(from) : parseScheduleDates(from); - const toMoment = moment.isDate(to) ? moment(to) : parseScheduleDates(to); - - if (!fromMoment || !toMoment) { - throw new Error(`There was an issue with parsing ${from} or ${to} into Moment object`); +}: GetSignalsCount): Promise => { + if (from == null || to == null) { + throw Error('"from" or "to" was not provided to signals count query'); } - const fromInMs = fromMoment.format('x'); - const toInMs = toMoment.format('x'); - const query = buildSignalsSearchQuery({ index, ruleId, - to: toInMs, - from: fromInMs, + to, + from, }); - const result = await callCluster('count', query); - const resultsLink = getNotificationResultsLink({ - kibanaSiemAppUrl: `${kibanaSiemAppUrl}`, - id: ruleAlertId, - from: fromInMs, - to: toInMs, - }); + const result: CountResult = await callCluster('count', query); - return { - signalsCount: result.count, - resultsLink, - }; + return result.count; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index e74da583e919..546488caa5ee 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -13,6 +13,8 @@ import { getSignalsCount } from './get_signals_count'; import { RuleAlertAttributes } from '../signals/types'; import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; +import { getNotificationResultsLink } from './utils'; +import { parseScheduleDates } from '../signals/utils'; export const rulesNotificationAlertType = ({ logger, @@ -42,16 +44,26 @@ export const rulesNotificationAlertType = ({ const { params: ruleAlertParams, name: ruleName } = ruleAlertSavedObject.attributes; const ruleParams = { ...ruleAlertParams, name: ruleName, id: ruleAlertSavedObject.id }; - const { signalsCount, resultsLink } = await getSignalsCount({ - from: previousStartedAt ?? `now-${ruleParams.interval}`, - to: startedAt, + const fromInMs = parseScheduleDates( + previousStartedAt ? previousStartedAt.toISOString() : `now-${ruleParams.interval}` + )?.format('x'); + const toInMs = parseScheduleDates(startedAt.toISOString())?.format('x'); + + const signalsCount = await getSignalsCount({ + from: fromInMs, + to: toInMs, index: ruleParams.outputIndex, ruleId: ruleParams.ruleId!, - kibanaSiemAppUrl: ruleAlertParams.meta?.kibanaSiemAppUrl as string, - ruleAlertId: ruleAlertSavedObject.id, callCluster: services.callCluster, }); + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, + id: ruleAlertSavedObject.id, + kibanaSiemAppUrl: ruleAlertParams.meta?.kibanaSiemAppUrl as string, + }); + logger.info( `Found ${signalsCount} signals using signal rule name: "${ruleParams.name}", id: "${params.ruleAlertId}", rule_id: "${ruleParams.ruleId}" in "${ruleParams.outputIndex}" index` ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts index b858b25377ff..749b892ef506 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -15,7 +15,7 @@ type NotificationRuleTypeParams = RuleTypeParams & { interface ScheduleNotificationActions { alertInstance: AlertInstance; - signalsCount: string; + signalsCount: number; resultsLink: string; ruleParams: NotificationRuleTypeParams; } @@ -23,7 +23,7 @@ interface ScheduleNotificationActions { export const scheduleNotificationActions = ({ alertInstance, signalsCount, - resultsLink, + resultsLink = '', ruleParams, }: ScheduleNotificationActions): AlertInstance => alertInstance diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts index b8a3c4199c4f..5dc7e7fc30b7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts @@ -5,14 +5,17 @@ */ export const getNotificationResultsLink = ({ - kibanaSiemAppUrl, + kibanaSiemAppUrl = '/app/siem', id, from, to, }: { kibanaSiemAppUrl: string; id: string; - from: string; - to: string; -}) => - `${kibanaSiemAppUrl}#/detections/rules/id/${id}?timerange=(global:(linkTo:!(timeline),timerange:(from:${from},kind:absolute,to:${to})),timeline:(linkTo:!(global),timerange:(from:${from},kind:absolute,to:${to})))`; + from?: string; + to?: string; +}) => { + if (from == null || to == null) return ''; + + return `${kibanaSiemAppUrl}#/detections/rules/id/${id}?timerange=(global:(linkTo:!(timeline),timerange:(from:${from},kind:absolute,to:${to})),timeline:(linkTo:!(global),timerange:(from:${from},kind:absolute,to:${to})))`; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 06652028b374..414270ffcdd5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -34,7 +34,7 @@ describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -57,6 +57,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(0); }); test('if successful iteration of while loop with maxDocs', async () => { @@ -70,6 +71,11 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) @@ -80,6 +86,11 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) @@ -90,9 +101,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), ruleParams: sampleParams, services: mockService, @@ -115,13 +131,14 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(3); }); test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -144,6 +161,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { @@ -155,9 +173,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -180,6 +203,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { @@ -191,9 +215,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoHits(), ruleParams: sampleParams, services: mockService, @@ -215,6 +244,7 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { @@ -228,10 +258,15 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -253,6 +288,7 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { @@ -266,10 +302,15 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(sampleEmptyDocSearchResults()); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -291,6 +332,7 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if returns false when singleSearchAfter throws an exception', async () => { @@ -304,12 +346,17 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockImplementation(() => { throw Error('Fake Error'); }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -331,5 +378,6 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index a5d5dd0a7b71..ff81730bc4a7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -39,6 +39,7 @@ export interface SearchAfterAndBulkCreateReturnType { searchAfterTimes: string[]; bulkCreateTimes: string[]; lastLookBackDate: Date | null | undefined; + createdSignalsCount: number; } // search_after through documents and re-index using bulk endpoint. @@ -68,6 +69,7 @@ export const searchAfterAndBulkCreate = async ({ searchAfterTimes: [], bulkCreateTimes: [], lastLookBackDate: null, + createdSignalsCount: 0, }; if (someResult.hits.hits.length === 0) { toReturn.success = true; @@ -75,7 +77,7 @@ export const searchAfterAndBulkCreate = async ({ } logger.debug('[+] starting bulk insertion'); - const { bulkCreateDuration } = await singleBulkCreate({ + const { bulkCreateDuration, createdItemsCount } = await singleBulkCreate({ someResult, ruleParams, services, @@ -97,6 +99,9 @@ export const searchAfterAndBulkCreate = async ({ someResult.hits.hits.length > 0 ? new Date(someResult.hits.hits[someResult.hits.hits.length - 1]?._source['@timestamp']) : null; + if (createdItemsCount) { + toReturn.createdSignalsCount = createdItemsCount; + } if (bulkCreateDuration) { toReturn.bulkCreateTimes.push(bulkCreateDuration); } @@ -156,7 +161,10 @@ export const searchAfterAndBulkCreate = async ({ } sortId = sortIds[0]; logger.debug('next bulk index'); - const { bulkCreateDuration: bulkDuration } = await singleBulkCreate({ + const { + bulkCreateDuration: bulkDuration, + createdItemsCount: createdCount, + } = await singleBulkCreate({ someResult: searchResult, ruleParams, services, @@ -175,6 +183,7 @@ export const searchAfterAndBulkCreate = async ({ throttle, }); logger.debug('finished next bulk index'); + toReturn.createdSignalsCount += createdCount; if (bulkDuration) { toReturn.bulkCreateTimes.push(bulkDuration); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts new file mode 100644 index 000000000000..11d31f180544 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -0,0 +1,399 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { loggerMock } from 'src/core/server/logging/logger.mock'; +import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; +import { signalRulesAlertType } from './signal_rule_alert_type'; +import { AlertInstance } from '../../../../../../../plugins/alerting/server'; +import { ruleStatusServiceFactory } from './rule_status_service'; +import { getGapBetweenRuns } from './utils'; +import { RuleExecutorOptions } from './types'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; +import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; +import { RuleAlertType } from '../rules/types'; +import { findMlSignals } from './find_ml_signals'; +import { bulkCreateMlSignals } from './bulk_create_ml_signals'; + +jest.mock('./rule_status_saved_objects_client'); +jest.mock('./rule_status_service'); +jest.mock('./search_after_bulk_create'); +jest.mock('./get_filter'); +jest.mock('./utils'); +jest.mock('../notifications/schedule_notification_actions'); +jest.mock('./find_ml_signals'); +jest.mock('./bulk_create_ml_signals'); + +const getPayload = ( + ruleAlert: RuleAlertType, + alertInstanceFactoryMock: () => AlertInstance, + savedObjectsClient: ReturnType, + callClusterMock: jest.Mock +) => ({ + alertId: ruleAlert.id, + services: { + savedObjectsClient, + alertInstanceFactory: alertInstanceFactoryMock, + callCluster: callClusterMock, + }, + params: { + ...ruleAlert.params, + actions: [], + enabled: ruleAlert.enabled, + interval: ruleAlert.schedule.interval, + name: ruleAlert.name, + tags: ruleAlert.tags, + throttle: ruleAlert.throttle!, + scrollSize: 10, + scrollLock: '0', + }, + state: {}, + spaceId: '', + name: 'name', + tags: [], + startedAt: new Date('2019-12-13T16:50:33.400Z'), + previousStartedAt: new Date('2019-12-13T16:40:33.400Z'), + createdBy: 'elastic', + updatedBy: 'elastic', +}); + +describe('rules_notification_alert_type', () => { + const version = '8.0.0'; + const jobsSummaryMock = jest.fn(); + const mlMock = { + mlClient: { + callAsInternalUser: jest.fn(), + close: jest.fn(), + asScoped: jest.fn(), + }, + jobServiceProvider: jest.fn().mockReturnValue({ + jobsSummary: jobsSummaryMock, + }), + anomalyDetectorsProvider: jest.fn(), + mlSystemProvider: jest.fn(), + modulesProvider: jest.fn(), + resultsServiceProvider: jest.fn(), + }; + let payload: RuleExecutorOptions; + let alert: ReturnType; + let alertInstanceMock: Record; + let alertInstanceFactoryMock: () => AlertInstance; + let savedObjectsClient: ReturnType; + let logger: ReturnType; + let callClusterMock: jest.Mock; + let ruleStatusService: Record; + + beforeEach(() => { + alertInstanceMock = { + scheduleActions: jest.fn(), + replaceState: jest.fn(), + }; + alertInstanceMock.replaceState.mockReturnValue(alertInstanceMock); + alertInstanceFactoryMock = jest.fn().mockReturnValue(alertInstanceMock); + callClusterMock = jest.fn(); + savedObjectsClient = savedObjectsClientMock.create(); + logger = loggerMock.create(); + ruleStatusService = { + success: jest.fn(), + find: jest.fn(), + goingToRun: jest.fn(), + error: jest.fn(), + }; + (ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService); + (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0)); + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ + success: true, + searchAfterTimes: [], + createdSignalsCount: 10, + }); + callClusterMock.mockResolvedValue({ + hits: { + total: { value: 10 }, + }, + }); + const ruleAlert = getResult(); + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + + payload = getPayload(ruleAlert, alertInstanceFactoryMock, savedObjectsClient, callClusterMock); + + alert = signalRulesAlertType({ + logger, + version, + ml: mlMock, + }); + }); + + describe('executor', () => { + it('should warn about the gap between runs', async () => { + (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(1000)); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain( + 'a few seconds (1000ms) has passed since last rule execution, and signals may have been missed.' + ); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'a few seconds (1000ms) has passed since last rule execution, and signals may have been missed.' + ); + expect(ruleStatusService.error.mock.calls[0][1]).toEqual({ + gap: 'a few seconds', + }); + }); + + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { + const ruleAlert = getResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + signalsCount: 10, + }) + ); + }); + + describe('ML rule', () => { + it('should throw an error if ML plugin was not available', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + alert = signalRulesAlertType({ + logger, + version, + ml: undefined, + }); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'ML plugin unavailable during rule execution' + ); + }); + + it('should throw an error if machineLearningJobId or anomalyThreshold was not null', async () => { + const ruleAlert = getMlResult(); + ruleAlert.params.anomalyThreshold = undefined; + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'Machine learning rule is missing job id and/or anomaly threshold' + ); + }); + + it('should throw an error if Machine learning job summary was null', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([]); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job is not started'); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'Machine learning job is not started' + ); + }); + + it('should log an error if Machine learning job was not started', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'starting', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [], + }, + }); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job is not started'); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'Machine learning job is not started' + ); + }); + + it('should not call ruleStatusService.success if no anomalies were found', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 0, + createdItemsCount: 0, + }); + await alert.executor(payload); + expect(ruleStatusService.success).not.toHaveBeenCalled(); + }); + + it('should call ruleStatusService.success if signals were created', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'started', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + }); + await alert.executor(payload); + expect(ruleStatusService.success).toHaveBeenCalled(); + }); + + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { + const ruleAlert = getMlResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + jobsSummaryMock.mockResolvedValue([]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + }); + + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + signalsCount: 1, + }) + ); + }); + }); + }); + + describe('should catch error', () => { + it('when bulk indexing failed', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ + success: false, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: null, + createdSignalsCount: 0, + }); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'Bulk Indexing of signals failed. Check logs for further details.' + ); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + + it('when error was thrown', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({}); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution'); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + + it('and call ruleStatusService with the default message', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockRejectedValue({}); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution'); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 246701e94c99..417fcbbe42a5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -19,16 +19,16 @@ import { } from './search_after_bulk_create'; import { getFilter } from './get_filter'; import { SignalRuleAlertTypeDefinition, RuleAlertAttributes } from './types'; -import { getGapBetweenRuns, makeFloatString } from './utils'; +import { getGapBetweenRuns, makeFloatString, parseScheduleDates } from './utils'; import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; import { findMlSignals } from './find_ml_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; -import { getSignalsCount } from '../notifications/get_signals_count'; import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; import { ruleStatusServiceFactory } from './rule_status_service'; import { buildRuleMessageFactory } from './rule_messages'; import { ruleStatusSavedObjectsClientFactory } from './rule_status_saved_objects_client'; +import { getNotificationResultsLink } from '../notifications/utils'; export const signalRulesAlertType = ({ logger, @@ -71,6 +71,7 @@ export const signalRulesAlertType = ({ bulkCreateTimes: [], searchAfterTimes: [], lastLookBackDate: null, + createdSignalsCount: 0, }; const ruleStatusClient = ruleStatusSavedObjectsClientFactory(services.savedObjectsClient); const ruleStatusService = await ruleStatusServiceFactory({ @@ -161,7 +162,7 @@ export const signalRulesAlertType = ({ logger.info(buildRuleMessage(`Found ${anomalyCount} signals from ML anomalies.`)); } - const { success, bulkCreateDuration } = await bulkCreateMlSignals({ + const { success, bulkCreateDuration, createdItemsCount } = await bulkCreateMlSignals({ actions, throttle, someResult: anomalyResults, @@ -180,6 +181,7 @@ export const signalRulesAlertType = ({ tags, }); result.success = success; + result.createdSignalsCount = createdItemsCount; if (bulkCreateDuration) { result.bulkCreateTimes.push(bulkCreateDuration); } @@ -249,23 +251,26 @@ export const signalRulesAlertType = ({ name, id: savedObject.id, }; - const { signalsCount, resultsLink } = await getSignalsCount({ - from: `now-${interval}`, - to: 'now', - index: ruleParams.outputIndex, - ruleId: ruleParams.ruleId!, + + const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); + const toInMs = parseScheduleDates('now')?.format('x'); + + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, + id: savedObject.id, kibanaSiemAppUrl: meta?.kibanaSiemAppUrl as string, - ruleAlertId: savedObject.id, - callCluster: services.callCluster, }); - logger.info(buildRuleMessage(`Found ${signalsCount} signals for notification.`)); + logger.info( + buildRuleMessage(`Found ${result.createdSignalsCount} signals for notification.`) + ); - if (signalsCount) { + if (result.createdSignalsCount) { const alertInstance = services.alertInstanceFactory(alertId); scheduleNotificationActions({ alertInstance, - signalsCount, + signalsCount: result.createdSignalsCount, resultsLink, ruleParams: notificationRuleParams, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 45b5610e2d3c..56f061cdfa3c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -144,7 +144,7 @@ describe('singleBulkCreate', () => { }, ], }); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -163,6 +163,7 @@ describe('singleBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create successful bulk create with docs with no versioning', async () => { @@ -176,7 +177,7 @@ describe('singleBulkCreate', () => { }, ], }); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoVersion(), ruleParams: sampleParams, services: mockService, @@ -195,12 +196,13 @@ describe('singleBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValue(false); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -219,13 +221,14 @@ describe('singleBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create successful bulk create when bulk create has duplicate errors', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, @@ -246,13 +249,14 @@ describe('singleBulkCreate', () => { expect(mockLogger.error).not.toHaveBeenCalled(); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); }); test('create successful bulk create when bulk create has multiple error statuses', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateErrorResult); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, @@ -273,6 +277,7 @@ describe('singleBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); }); test('filter duplicate rules will return an empty array given an empty array', () => { @@ -341,4 +346,29 @@ describe('singleBulkCreate', () => { }, ]); }); + + test('create successful and returns proper createdItemsCount', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const { success, createdItemsCount } = await singleBulkCreate({ + someResult: sampleDocSearchResultsNoSortId(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + actions: [], + name: 'rule-name', + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index ffec40b839bf..6dd8823b57e4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -58,6 +58,7 @@ export const filterDuplicateRules = ( export interface SingleBulkCreateResponse { success: boolean; bulkCreateDuration?: string; + createdItemsCount: number; } // Bulk Index documents. @@ -81,7 +82,7 @@ export const singleBulkCreate = async ({ }: SingleBulkCreateParams): Promise => { someResult.hits.hits = filterDuplicateRules(id, someResult); if (someResult.hits.hits.length === 0) { - return { success: true }; + return { success: true, createdItemsCount: 0 }; } // index documents after creating an ID based on the // source documents' originating index, and the original @@ -145,5 +146,8 @@ export const singleBulkCreate = async ({ ); } } - return { success: true, bulkCreateDuration: makeFloatString(end - start) }; + + const createdItemsCount = countBy(response.items, 'create.status')['201'] ?? 0; + + return { success: true, bulkCreateDuration: makeFloatString(end - start), createdItemsCount }; }; From dfa083dc6041edbe584ca58618ecb9fe2f81d81e Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 6 Apr 2020 13:45:46 -0400 Subject: [PATCH 18/27] Prep for embed saved object refactor + helper (#62486) --- .../list_container/embeddable_list_item.tsx | 64 --------------- .../public/list_container/list_container.tsx | 19 ++--- .../list_container_component.tsx | 26 ++++-- .../list_container/list_container_factory.ts | 6 +- .../multi_task_todo_component.tsx | 17 ++-- .../multi_task_todo_embeddable.tsx | 27 +++---- examples/embeddable_examples/public/plugin.ts | 7 +- .../searchable_list_container.tsx | 16 ++-- .../searchable_list_container_component.tsx | 79 +++++++++++++------ .../searchable_list_container_factory.ts | 6 +- .../public/todo/todo_component.tsx | 10 ++- examples/embeddable_explorer/public/app.tsx | 13 +-- .../public/embeddable_panel_example.tsx | 49 ++---------- .../public/list_container_example.tsx | 10 ++- .../embeddable_child_panel.test.tsx | 2 +- .../lib/embeddables/with_subscription.tsx | 12 +-- .../lib/panel/embeddable_panel.test.tsx | 4 +- .../inspect_panel_action.test.tsx | 2 +- src/plugins/embeddable/public/mocks.ts | 10 ++- .../public/{plugin.ts => plugin.tsx} | 54 ++++++++++--- .../public/tests/apply_filter_action.test.ts | 2 +- .../embeddable/public/tests/test_plugin.ts | 11 ++- test/examples/embeddables/list_container.ts | 9 +-- .../public/np_ready/public/app/app.tsx | 34 ++------ .../app/dashboard_container_example.tsx | 33 ++------ .../public/np_ready/public/plugin.tsx | 15 +--- 26 files changed, 234 insertions(+), 303 deletions(-) delete mode 100644 examples/embeddable_examples/public/list_container/embeddable_list_item.tsx rename src/plugins/embeddable/public/{plugin.ts => plugin.tsx} (76%) diff --git a/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx b/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx deleted file mode 100644 index 2c80cef8a636..000000000000 --- a/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiPanel, EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui'; -import { IEmbeddable } from '../../../../src/plugins/embeddable/public'; - -interface Props { - embeddable: IEmbeddable; -} - -export class EmbeddableListItem extends React.Component { - private embeddableRoot: React.RefObject; - private rendered = false; - - constructor(props: Props) { - super(props); - this.embeddableRoot = React.createRef(); - } - - public componentDidMount() { - if (this.embeddableRoot.current && this.props.embeddable) { - this.props.embeddable.render(this.embeddableRoot.current); - this.rendered = true; - } - } - - public componentDidUpdate() { - if (this.embeddableRoot.current && this.props.embeddable && !this.rendered) { - this.props.embeddable.render(this.embeddableRoot.current); - this.rendered = true; - } - } - - public render() { - return ( - - - {this.props.embeddable ? ( -
- ) : ( - - )} - - - ); - } -} diff --git a/examples/embeddable_examples/public/list_container/list_container.tsx b/examples/embeddable_examples/public/list_container/list_container.tsx index bbbd0d6e3230..9e7bec7a1c95 100644 --- a/examples/embeddable_examples/public/list_container/list_container.tsx +++ b/examples/embeddable_examples/public/list_container/list_container.tsx @@ -31,16 +31,14 @@ export class ListContainer extends Container<{}, ContainerInput> { public readonly type = LIST_CONTAINER; private node?: HTMLElement; - constructor( - input: ContainerInput, - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'] - ) { - super(input, { embeddableLoaded: {} }, getEmbeddableFactory); + constructor(input: ContainerInput, private embeddableServices: EmbeddableStart) { + super(input, { embeddableLoaded: {} }, embeddableServices.getEmbeddableFactory); } - // This container has no input itself. - getInheritedInput(id: string) { - return {}; + getInheritedInput() { + return { + viewMode: this.input.viewMode, + }; } public render(node: HTMLElement) { @@ -48,7 +46,10 @@ export class ListContainer extends Container<{}, ContainerInput> { if (this.node) { ReactDOM.unmountComponentAtNode(this.node); } - ReactDOM.render(, node); + ReactDOM.render( + , + node + ); } public destroy() { diff --git a/examples/embeddable_examples/public/list_container/list_container_component.tsx b/examples/embeddable_examples/public/list_container/list_container_component.tsx index f6e04933ee89..da27889a2760 100644 --- a/examples/embeddable_examples/public/list_container/list_container_component.tsx +++ b/examples/embeddable_examples/public/list_container/list_container_component.tsx @@ -24,30 +24,35 @@ import { withEmbeddableSubscription, ContainerInput, ContainerOutput, + EmbeddableStart, } from '../../../../src/plugins/embeddable/public'; -import { EmbeddableListItem } from './embeddable_list_item'; interface Props { embeddable: IContainer; input: ContainerInput; output: ContainerOutput; + embeddableServices: EmbeddableStart; } -function renderList(embeddable: IContainer, panels: ContainerInput['panels']) { +function renderList( + embeddable: IContainer, + panels: ContainerInput['panels'], + embeddableServices: EmbeddableStart +) { let number = 0; const list = Object.values(panels).map(panel => { const child = embeddable.getChild(panel.explicitInput.id); number++; return ( - +

{number}

- +
@@ -56,12 +61,12 @@ function renderList(embeddable: IContainer, panels: ContainerInput['panels']) { return list; } -export function ListContainerComponentInner(props: Props) { +export function ListContainerComponentInner({ embeddable, input, embeddableServices }: Props) { return (
-

{props.embeddable.getTitle()}

+

{embeddable.getTitle()}

- {renderList(props.embeddable, props.input.panels)} + {renderList(embeddable, input.panels, embeddableServices)}
); } @@ -71,4 +76,9 @@ export function ListContainerComponentInner(props: Props) { // anything on input or output state changes. If you don't want that to happen (for example // if you expect something on input or output state to change frequently that your react // component does not care about, then you should probably hook this up manually). -export const ListContainerComponent = withEmbeddableSubscription(ListContainerComponentInner); +export const ListContainerComponent = withEmbeddableSubscription< + ContainerInput, + ContainerOutput, + IContainer, + { embeddableServices: EmbeddableStart } +>(ListContainerComponentInner); diff --git a/examples/embeddable_examples/public/list_container/list_container_factory.ts b/examples/embeddable_examples/public/list_container/list_container_factory.ts index 1fde254110c6..02a024b95349 100644 --- a/examples/embeddable_examples/public/list_container/list_container_factory.ts +++ b/examples/embeddable_examples/public/list_container/list_container_factory.ts @@ -26,7 +26,7 @@ import { import { LIST_CONTAINER, ListContainer } from './list_container'; interface StartServices { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + embeddableServices: EmbeddableStart; } export class ListContainerFactory implements EmbeddableFactoryDefinition { @@ -40,8 +40,8 @@ export class ListContainerFactory implements EmbeddableFactoryDefinition { } public create = async (initialInput: ContainerInput) => { - const { getEmbeddableFactory } = await this.getStartServices(); - return new ListContainer(initialInput, getEmbeddableFactory); + const { embeddableServices } = await this.getStartServices(); + return new ListContainer(initialInput, embeddableServices); }; public getDisplayName() { diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx index e33dfab0eaf4..b2882c97ef50 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx @@ -54,7 +54,7 @@ function wrapSearchTerms(task: string, search?: string) { ); } -function renderTasks(tasks: MultiTaskTodoOutput['tasks'], search?: string) { +function renderTasks(tasks: MultiTaskTodoInput['tasks'], search?: string) { return tasks.map(task => ( + {icon ? : } - +

{wrapSearchTerms(title, search)}

@@ -89,6 +88,8 @@ export function MultiTaskTodoEmbeddableComponentInner({ ); } -export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription( - MultiTaskTodoEmbeddableComponentInner -); +export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription< + MultiTaskTodoInput, + MultiTaskTodoOutput, + MultiTaskTodoEmbeddable +>(MultiTaskTodoEmbeddableComponentInner); diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx index a2197c9c06fe..a9e58c553810 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx @@ -36,30 +36,27 @@ export interface MultiTaskTodoInput extends EmbeddableInput { title: string; } -// This embeddable has output! It's the tasks list that is filtered. -// Output state is something only the embeddable itself can update. It -// can be something completely internal, or it can be state that is +// This embeddable has output! Output state is something only the embeddable itself +// can update. It can be something completely internal, or it can be state that is // derived from input state and updates when input does. export interface MultiTaskTodoOutput extends EmbeddableOutput { - tasks: string[]; + hasMatch: boolean; } -function getFilteredTasks(tasks: string[], search?: string) { - const filteredTasks: string[] = []; - if (search === undefined) return tasks; +function getHasMatch(tasks: string[], title?: string, search?: string) { + if (search === undefined || search === '') return false; - tasks.forEach(task => { - if (task.match(search)) { - filteredTasks.push(task); - } - }); + if (title && title.match(search)) return true; + + const match = tasks.find(task => task.match(search)); + if (match) return true; - return filteredTasks; + return false; } function getOutput(input: MultiTaskTodoInput) { - const tasks = getFilteredTasks(input.tasks, input.search); - return { tasks, hasMatch: tasks.length > 0 || (input.search && input.title.match(input.search)) }; + const hasMatch = getHasMatch(input.tasks, input.title, input.search); + return { hasMatch }; } export class MultiTaskTodoEmbeddable extends Embeddable { diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 5c202d96ceb1..31a3037332dd 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -53,20 +53,17 @@ export class EmbeddableExamplesPlugin new MultiTaskTodoEmbeddableFactory() ); - // These are registered in the start method because `getEmbeddableFactory ` - // is only available in start. We could reconsider this I think and make it - // available in both. deps.embeddable.registerEmbeddableFactory( SEARCHABLE_LIST_CONTAINER, new SearchableListContainerFactory(async () => ({ - getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + embeddableServices: (await core.getStartServices())[1].embeddable, })) ); deps.embeddable.registerEmbeddableFactory( LIST_CONTAINER, new ListContainerFactory(async () => ({ - getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + embeddableServices: (await core.getStartServices())[1].embeddable, })) ); diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx index 06462937c768..f6efb0b722c4 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx @@ -40,11 +40,8 @@ export class SearchableListContainer extends Container, node); + ReactDOM.render( + , + node + ); } public destroy() { diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx index b79f86e2a019..49dbce74788b 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx @@ -34,14 +34,15 @@ import { withEmbeddableSubscription, ContainerOutput, EmbeddableOutput, + EmbeddableStart, } from '../../../../src/plugins/embeddable/public'; -import { EmbeddableListItem } from '../list_container/embeddable_list_item'; import { SearchableListContainer, SearchableContainerInput } from './searchable_list_container'; interface Props { embeddable: SearchableListContainer; input: SearchableContainerInput; output: ContainerOutput; + embeddableServices: EmbeddableStart; } interface State { @@ -111,13 +112,27 @@ export class SearchableListContainerComponentInner extends Component { + const { input, embeddable } = this.props; + const checked: { [key: string]: boolean } = {}; + Object.values(input.panels).map(panel => { + const child = embeddable.getChild(panel.explicitInput.id); + const output = child.getOutput(); + if (hasHasMatchOutput(output) && output.hasMatch) { + checked[panel.explicitInput.id] = true; + } + }); + this.setState({ checked }); + }; + private toggleCheck = (isChecked: boolean, id: string) => { this.setState(prevState => ({ checked: { ...prevState.checked, [id]: isChecked } })); }; public renderControls() { + const { input } = this.props; return ( - + this.deleteChecked()}> @@ -125,6 +140,17 @@ export class SearchableListContainerComponentInner extends Component + + + this.checkMatching()} + > + Check matching + + + -

{embeddable.getTitle()}

- - {this.renderControls()} - - {this.renderList()} -
+ + +

{embeddable.getTitle()}

+ + {this.renderControls()} + + {this.renderList()} +
+
); } private renderList() { + const { embeddableServices, input, embeddable } = this.props; let id = 0; - const list = Object.values(this.props.input.panels).map(panel => { - const embeddable = this.props.embeddable.getChild(panel.explicitInput.id); - if (this.props.input.search && !this.state.hasMatch[panel.explicitInput.id]) return; + const list = Object.values(input.panels).map(panel => { + const childEmbeddable = embeddable.getChild(panel.explicitInput.id); id++; - return embeddable ? ( - - + return childEmbeddable ? ( + + this.toggleCheck(e.target.checked, embeddable.id)} + data-test-subj={`todoCheckBox-${childEmbeddable.id}`} + disabled={!childEmbeddable} + id={childEmbeddable ? childEmbeddable.id : ''} + checked={this.state.checked[childEmbeddable.id]} + onChange={e => this.toggleCheck(e.target.checked, childEmbeddable.id)} /> - + @@ -183,6 +211,9 @@ export class SearchableListContainerComponentInner extends Component(SearchableListContainerComponentInner); diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts index 382bb65e769e..34ea43c29462 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts @@ -29,7 +29,7 @@ import { } from './searchable_list_container'; interface StartServices { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + embeddableServices: EmbeddableStart; } export class SearchableListContainerFactory implements EmbeddableFactoryDefinition { @@ -43,8 +43,8 @@ export class SearchableListContainerFactory implements EmbeddableFactoryDefiniti } public create = async (initialInput: SearchableContainerInput) => { - const { getEmbeddableFactory } = await this.getStartServices(); - return new SearchableListContainer(initialInput, getEmbeddableFactory); + const { embeddableServices } = await this.getStartServices(); + return new SearchableListContainer(initialInput, embeddableServices); }; public getDisplayName() { diff --git a/examples/embeddable_examples/public/todo/todo_component.tsx b/examples/embeddable_examples/public/todo/todo_component.tsx index fbebfc98627b..a4593bea3cc5 100644 --- a/examples/embeddable_examples/public/todo/todo_component.tsx +++ b/examples/embeddable_examples/public/todo/todo_component.tsx @@ -51,12 +51,12 @@ function wrapSearchTerms(task: string, search?: string) { export function TodoEmbeddableComponentInner({ input: { icon, title, task, search } }: Props) { return ( - + {icon ? : } - +

{wrapSearchTerms(title || '', search)}

@@ -71,4 +71,8 @@ export function TodoEmbeddableComponentInner({ input: { icon, title, task, searc ); } -export const TodoEmbeddableComponent = withEmbeddableSubscription(TodoEmbeddableComponentInner); +export const TodoEmbeddableComponent = withEmbeddableSubscription< + TodoInput, + EmbeddableOutput, + TodoEmbeddable +>(TodoEmbeddableComponentInner); diff --git a/examples/embeddable_explorer/public/app.tsx b/examples/embeddable_explorer/public/app.tsx index 9c8568454855..e18012b4b3d8 100644 --- a/examples/embeddable_explorer/public/app.tsx +++ b/examples/embeddable_explorer/public/app.tsx @@ -117,18 +117,7 @@ const EmbeddableExplorerApp = ({ { title: 'Dynamically adding children to a container', id: 'embeddablePanelExamplae', - component: ( - - ), + component: , }, ]; diff --git a/examples/embeddable_explorer/public/embeddable_panel_example.tsx b/examples/embeddable_explorer/public/embeddable_panel_example.tsx index b26111bed7ff..54cd7c5b5b2c 100644 --- a/examples/embeddable_explorer/public/embeddable_panel_example.tsx +++ b/examples/embeddable_explorer/public/embeddable_panel_example.tsx @@ -29,43 +29,19 @@ import { EuiText, } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { OverlayStart, CoreStart, SavedObjectsStart, IUiSettingsClient } from 'kibana/public'; -import { - EmbeddablePanel, - EmbeddableStart, - IEmbeddable, -} from '../../../src/plugins/embeddable/public'; +import { EmbeddableStart, IEmbeddable } from '../../../src/plugins/embeddable/public'; import { HELLO_WORLD_EMBEDDABLE, TODO_EMBEDDABLE, MULTI_TASK_TODO_EMBEDDABLE, SEARCHABLE_LIST_CONTAINER, } from '../../embeddable_examples/public'; -import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; -import { Start as InspectorStartContract } from '../../../src/plugins/inspector/public'; -import { getSavedObjectFinder } from '../../../src/plugins/saved_objects/public'; interface Props { - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - uiActionsApi: UiActionsStart; - overlays: OverlayStart; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - savedObject: SavedObjectsStart; - uiSettingsClient: IUiSettingsClient; + embeddableServices: EmbeddableStart; } -export function EmbeddablePanelExample({ - inspector, - notifications, - overlays, - getAllEmbeddableFactories, - getEmbeddableFactory, - uiActionsApi, - savedObject, - uiSettingsClient, -}: Props) { +export function EmbeddablePanelExample({ embeddableServices }: Props) { const searchableInput = { id: '1', title: 'My searchable todo list', @@ -105,7 +81,7 @@ export function EmbeddablePanelExample({ useEffect(() => { ref.current = true; if (!embeddable) { - const factory = getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); + const factory = embeddableServices.getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); const promise = factory?.create(searchableInput); if (promise) { promise.then(e => { @@ -134,22 +110,13 @@ export function EmbeddablePanelExample({ You can render your embeddable inside the EmbeddablePanel component. This adds some extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel - to render your embeddable means you get access to the "e;Add panel flyout"e;. - Now you can see how to add embeddables to your container, and how - "e;getExplicitInput"e; is used to grab input not provided by the container. + to render your embeddable means you get access to the "Add panel flyout". Now + you can see how to add embeddables to your container, and how + "getExplicitInput" is used to grab input not provided by the container. {embeddable ? ( - + ) : ( Loading... )} diff --git a/examples/embeddable_explorer/public/list_container_example.tsx b/examples/embeddable_explorer/public/list_container_example.tsx index 969fdb0ca46d..98ad50418d3f 100644 --- a/examples/embeddable_explorer/public/list_container_example.tsx +++ b/examples/embeddable_explorer/public/list_container_example.tsx @@ -29,7 +29,11 @@ import { EuiText, } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { EmbeddableFactoryRenderer, EmbeddableStart } from '../../../src/plugins/embeddable/public'; +import { + EmbeddableFactoryRenderer, + EmbeddableStart, + ViewMode, +} from '../../../src/plugins/embeddable/public'; import { HELLO_WORLD_EMBEDDABLE, TODO_EMBEDDABLE, @@ -46,6 +50,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) { const listInput = { id: 'hello', title: 'My todo list', + viewMode: ViewMode.VIEW, panels: { '1': { type: HELLO_WORLD_EMBEDDABLE, @@ -76,6 +81,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) { const searchableInput = { id: '1', title: 'My searchable todo list', + viewMode: ViewMode.VIEW, panels: { '1': { type: HELLO_WORLD_EMBEDDABLE, @@ -150,7 +156,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {

- Check out the "e;Dynamically adding children"e; section, to see how to add + Check out the "Dynamically adding children" section, to see how to add children to this container, and see it rendered inside an `EmbeddablePanel` component.

diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx index 9e47da5cea03..2a0ffd723850 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx @@ -29,7 +29,7 @@ import { ContactCardEmbeddable, } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { mount } from 'enzyme'; import { embeddablePluginMock } from '../../mocks'; diff --git a/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx b/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx index 47b8001961cf..9bc5889715c7 100644 --- a/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx @@ -23,18 +23,19 @@ import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; export const withEmbeddableSubscription = < I extends EmbeddableInput, O extends EmbeddableOutput, - E extends IEmbeddable = IEmbeddable + E extends IEmbeddable = IEmbeddable, + ExtraProps = {} >( - WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E }> -): React.ComponentType<{ embeddable: E }> => + WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E } & ExtraProps> +): React.ComponentType<{ embeddable: E } & ExtraProps> => class WithEmbeddableSubscription extends React.Component< - { embeddable: E }, + { embeddable: E } & ExtraProps, { input: I; output: O } > { private subscription?: Rx.Subscription; private mounted: boolean = false; - constructor(props: { embeddable: E }) { + constructor(props: { embeddable: E } & ExtraProps) { super(props); this.state = { input: this.props.embeddable.getInput(), @@ -71,6 +72,7 @@ export const withEmbeddableSubscription = < input={this.state.input} output={this.state.output} embeddable={this.props.embeddable} + {...this.props} /> ); } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 649677dc67c7..1e7cbb2f3daf 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { I18nProvider } from '@kbn/i18n/react'; import { CONTEXT_MENU_TRIGGER } from '../triggers'; -import { Action, UiActionsStart, ActionType } from 'src/plugins/ui_actions/public'; +import { Action, UiActionsStart, ActionType } from '../../../../ui_actions/public'; import { Trigger, ViewMode } from '../types'; import { isErrorEmbeddable } from '../embeddables'; import { EmbeddablePanel } from './embeddable_panel'; @@ -41,7 +41,7 @@ import { ContactCardEmbeddableOutput, } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { EuiBadge } from '@elastic/eui'; import { embeddablePluginMock } from '../../mocks'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx index ee31127cb5a4..491eaad9faef 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx @@ -27,7 +27,7 @@ import { ContactCardEmbeddable, } from '../../../test_samples'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks'; import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables'; import { of } from '../../../../tests/helpers'; import { esFilters } from '../../../../../../../plugins/data/public'; diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts index 2ee05d8316ac..65b15f3a7614 100644 --- a/src/plugins/embeddable/public/mocks.ts +++ b/src/plugins/embeddable/public/mocks.ts @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ - import { EmbeddableStart, EmbeddableSetup } from '.'; import { EmbeddablePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../inspector/public/mocks'; // eslint-disable-next-line import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; @@ -39,6 +40,7 @@ const createStartContract = (): Start => { const startContract: Start = { getEmbeddableFactories: jest.fn(), getEmbeddableFactory: jest.fn(), + EmbeddablePanel: jest.fn(), }; return startContract; }; @@ -48,7 +50,11 @@ const createInstance = () => { const setup = plugin.setup(coreMock.createSetup(), { uiActions: uiActionsPluginMock.createSetupContract(), }); - const doStart = () => plugin.start(coreMock.createStart()); + const doStart = () => + plugin.start(coreMock.createStart(), { + uiActions: uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); return { plugin, setup, diff --git a/src/plugins/embeddable/public/plugin.ts b/src/plugins/embeddable/public/plugin.tsx similarity index 76% rename from src/plugins/embeddable/public/plugin.ts rename to src/plugins/embeddable/public/plugin.tsx index a483f90f76dd..01fbf52c8018 100644 --- a/src/plugins/embeddable/public/plugin.ts +++ b/src/plugins/embeddable/public/plugin.tsx @@ -16,7 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { UiActionsSetup } from 'src/plugins/ui_actions/public'; +import React from 'react'; +import { getSavedObjectFinder } from '../../saved_objects/public'; +import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; +import { Start as InspectorStart } from '../../inspector/public'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types'; import { bootstrap } from './bootstrap'; @@ -26,6 +29,7 @@ import { EmbeddableOutput, defaultEmbeddableFactoryProvider, IEmbeddable, + EmbeddablePanel, } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; @@ -33,6 +37,11 @@ export interface EmbeddableSetupDependencies { uiActions: UiActionsSetup; } +export interface EmbeddableStartDependencies { + uiActions: UiActionsStart; + inspector: InspectorStart; +} + export interface EmbeddableSetup { registerEmbeddableFactory: ( id: string, @@ -50,6 +59,7 @@ export interface EmbeddableStart { embeddableFactoryId: string ) => EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; + EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; } export class EmbeddablePublicPlugin implements Plugin { @@ -78,7 +88,10 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( def.type, @@ -89,15 +102,36 @@ export class EmbeddablePublicPlugin implements Plugin { - this.ensureFactoriesExist(); - return this.embeddableFactories.values(); - }, + getEmbeddableFactories: this.getEmbeddableFactories, + EmbeddablePanel: ({ + embeddable, + hideHeader, + }: { + embeddable: IEmbeddable; + hideHeader?: boolean; + }) => ( + + ), }; } public stop() {} + private getEmbeddableFactories = () => { + this.ensureFactoriesExist(); + return this.embeddableFactories.values(); + }; + private registerEmbeddableFactory = ( embeddableFactoryId: string, factory: EmbeddableFactoryDefinition @@ -130,11 +164,11 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type)); - } + }; - private ensureFactoryExists(type: string) { + private ensureFactoryExists = (type: string) => { if (!this.embeddableFactories.get(type)) { const def = this.embeddableFactoryDefinitions.get(type); if (!def) return; @@ -145,5 +179,5 @@ export class EmbeddablePublicPlugin implements Plugin { diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index e199ef193aa1..e13a906e3033 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -18,9 +18,11 @@ */ import { CoreSetup, CoreStart } from 'src/core/public'; +import { UiActionsStart } from '../../../ui_actions/public'; // eslint-disable-next-line -import { uiActionsPluginMock } from 'src/plugins/ui_actions/public/mocks'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../../inspector/public/mocks'; import { coreMock } from '../../../../core/public/mocks'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; @@ -48,7 +50,10 @@ export const testPlugin = ( coreStart, setup, doStart: (anotherCoreStart: CoreStart = coreStart) => { - const start = plugin.start(anotherCoreStart); + const start = plugin.start(anotherCoreStart, { + uiActions: uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); return start; }, uiActions: uiActions.doStart(coreStart), diff --git a/test/examples/embeddables/list_container.ts b/test/examples/embeddables/list_container.ts index b1b91ad2c37f..9e93d479471e 100644 --- a/test/examples/embeddables/list_container.ts +++ b/test/examples/embeddables/list_container.ts @@ -57,13 +57,12 @@ export default function({ getService }: PluginFunctionalProviderContext) { expect(text).to.eql(['HELLO WORLD!']); }); - it('searchable container filters multi-task children', async () => { + it('searchable container finds matches in multi-task children', async () => { await testSubjects.setValue('filterTodos', 'earth'); + await testSubjects.click('checkMatchingTodos'); + await testSubjects.click('deleteCheckedTodos'); - await retry.try(async () => { - const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask'); - expect(tasks).to.eql(['Watch planet earth']); - }); + await testSubjects.missingOrFail('multiTaskTodoTask'); }); }); } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx index 54d13efe4d79..2ecde823dc4d 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx @@ -18,21 +18,11 @@ */ import { EuiTab } from '@elastic/eui'; import React, { Component } from 'react'; -import { CoreStart } from 'src/core/public'; import { EmbeddableStart } from 'src/plugins/embeddable/public'; -import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; import { DashboardContainerExample } from './dashboard_container_example'; -import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; export interface AppProps { - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; - I18nContext: CoreStart['i18n']['Context']; + embeddableServices: EmbeddableStart; } export class App extends Component { @@ -72,29 +62,17 @@ export class App extends Component { public render() { return ( - -
-
{this.renderTabs()}
- {this.getContentsForTab()} -
-
+
+
{this.renderTabs()}
+ {this.getContentsForTab()} +
); } private getContentsForTab() { switch (this.state.selectedTabId) { case 'dashboardContainer': { - return ( - - ); + return ; } } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx index fd07416cadbc..16c2840d6a32 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx @@ -19,32 +19,17 @@ import React from 'react'; import { EuiButton, EuiLoadingChart } from '@elastic/eui'; import { ContainerOutput } from 'src/plugins/embeddable/public'; -import { - ErrorEmbeddable, - ViewMode, - isErrorEmbeddable, - EmbeddablePanel, - EmbeddableStart, -} from '../embeddable_api'; +import { ErrorEmbeddable, ViewMode, isErrorEmbeddable, EmbeddableStart } from '../embeddable_api'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer, DashboardContainerInput, } from '../../../../../../../../src/plugins/dashboard/public'; -import { CoreStart } from '../../../../../../../../src/core/public'; import { dashboardInput } from './dashboard_input'; -import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; -import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; interface Props { - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + embeddableServices: EmbeddableStart; } interface State { @@ -67,7 +52,7 @@ export class DashboardContainerExample extends React.Component { public async componentDidMount() { this.mounted = true; - const dashboardFactory = this.props.getEmbeddableFactory< + const dashboardFactory = this.props.embeddableServices.getEmbeddableFactory< DashboardContainerInput, ContainerOutput, DashboardContainer @@ -99,6 +84,7 @@ export class DashboardContainerExample extends React.Component { }; public render() { + const { embeddableServices } = this.props; return (

Dashboard Container

@@ -108,16 +94,7 @@ export class DashboardContainerExample extends React.Component { {!this.state.loaded || !this.container ? ( ) : ( - + )}
); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 18ceec652392..e5f5faa6ac36 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -33,7 +33,6 @@ const REACT_ROOT_ID = 'embeddableExplorerRoot'; import { SayHelloAction, createSendMessageAction } from './embeddable_api'; import { App } from './app'; -import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public'; import { EmbeddableStart, EmbeddableSetup, @@ -78,19 +77,7 @@ export class EmbeddableExplorerPublicPlugin plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); - ReactDOM.render( - , - root - ); + ReactDOM.render(, root); }); } From 29abe5b81bddd17dcdd671cf3456f99bfe7b08a0 Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Mon, 6 Apr 2020 13:54:21 -0400 Subject: [PATCH 19/27] [Ingest] EMT-146: agent status impl preparation (#62557) [Ingest] EMT-146: very light refactor a precursor for endpoint status change --- x-pack/plugins/endpoint/server/plugin.ts | 2 +- .../endpoint/server/routes/alerts/details/handlers.ts | 2 +- .../server/routes/{metadata.ts => metadata/index.ts} | 9 +++------ .../server/routes/{ => metadata}/metadata.test.ts | 10 +++++----- .../metadata/query_builders.test.ts} | 5 +---- .../metadata/query_builders.ts} | 4 ++-- 6 files changed, 13 insertions(+), 19 deletions(-) rename x-pack/plugins/endpoint/server/routes/{metadata.ts => metadata/index.ts} (93%) rename x-pack/plugins/endpoint/server/routes/{ => metadata}/metadata.test.ts (96%) rename x-pack/plugins/endpoint/server/{services/endpoint/metadata_query_builders.test.ts => routes/metadata/query_builders.test.ts} (97%) rename x-pack/plugins/endpoint/server/{services/endpoint/metadata_query_builders.ts => routes/metadata/query_builders.ts} (100%) diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index 6d2e9e510551..d3a399124124 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -9,9 +9,9 @@ import { PluginSetupContract as FeaturesPluginSetupContract } from '../../featur import { createConfig$, EndpointConfigType } from './config'; import { EndpointAppContext } from './types'; -import { registerEndpointRoutes } from './routes/metadata'; import { registerAlertRoutes } from './routes/alerts'; import { registerResolverRoutes } from './routes/resolver'; +import { registerEndpointRoutes } from './routes/metadata'; export type EndpointPluginStart = void; export type EndpointPluginSetup = void; diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts index b95c1aaf87c1..725e362f91ec 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts @@ -9,7 +9,7 @@ import { AlertEvent, EndpointAppConstants } from '../../../../common/types'; import { EndpointAppContext } from '../../../types'; import { AlertDetailsRequestParams } from '../types'; import { AlertDetailsPagination } from './lib'; -import { getHostData } from '../../../routes/metadata'; +import { getHostData } from '../../metadata'; export const alertDetailsHandlerWrapper = function( endpointAppContext: EndpointAppContext diff --git a/x-pack/plugins/endpoint/server/routes/metadata.ts b/x-pack/plugins/endpoint/server/routes/metadata/index.ts similarity index 93% rename from x-pack/plugins/endpoint/server/routes/metadata.ts rename to x-pack/plugins/endpoint/server/routes/metadata/index.ts index 787ffe58a537..ef01db9af98c 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/index.ts @@ -8,12 +8,9 @@ import { IRouter, RequestHandlerContext } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { schema } from '@kbn/config-schema'; -import { - kibanaRequestToMetadataListESQuery, - getESQueryHostMetadataByID, -} from '../services/endpoint/metadata_query_builders'; -import { HostMetadata, HostResultList } from '../../common/types'; -import { EndpointAppContext } from '../types'; +import { HostMetadata, HostResultList } from '../../../common/types'; +import { EndpointAppContext } from '../../types'; +import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders'; interface HitSource { _source: HostMetadata; diff --git a/x-pack/plugins/endpoint/server/routes/metadata.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts similarity index 96% rename from x-pack/plugins/endpoint/server/routes/metadata.test.ts rename to x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts index 65e07edbcde2..e0fd11e737e7 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts @@ -17,12 +17,12 @@ import { httpServerMock, httpServiceMock, loggingServiceMock, -} from '../../../../../src/core/server/mocks'; -import { HostMetadata, HostResultList } from '../../common/types'; +} from '../../../../../../src/core/server/mocks'; +import { HostMetadata, HostResultList } from '../../../common/types'; import { SearchResponse } from 'elasticsearch'; -import { registerEndpointRoutes } from './metadata'; -import { EndpointConfigSchema } from '../config'; -import * as data from '../test_data/all_metadata_data.json'; +import { EndpointConfigSchema } from '../../config'; +import * as data from '../../test_data/all_metadata_data.json'; +import { registerEndpointRoutes } from './index'; describe('test endpoint route', () => { let routerMock: jest.Mocked; diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts similarity index 97% rename from x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts rename to x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts index 0966b52c79f7..2514d5aa8581 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts @@ -5,10 +5,7 @@ */ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; -import { - kibanaRequestToMetadataListESQuery, - getESQueryHostMetadataByID, -} from './metadata_query_builders'; +import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; import { EndpointAppConstants } from '../../../common/types'; describe('query builder', () => { diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts similarity index 100% rename from x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts rename to x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts index 57b0a4ef1051..bd07604fe9ad 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { KibanaRequest } from 'kibana/server'; -import { EndpointAppConstants } from '../../../common/types'; -import { EndpointAppContext } from '../../types'; import { esKuery } from '../../../../../../src/plugins/data/server'; +import { EndpointAppContext } from '../../types'; +import { EndpointAppConstants } from '../../../common/types'; export const kibanaRequestToMetadataListESQuery = async ( request: KibanaRequest, From 42d7bb0c8154e7e7c01805254b9d726bcdbc5102 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 6 Apr 2020 11:11:56 -0700 Subject: [PATCH 20/27] [DOCS] Fixes nesting in APM and spaces API (#62659) --- .../resolve_copy_saved_objects_conflicts.asciidoc | 2 +- docs/apm/api.asciidoc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc index 7f35dc3834f0..565d12513815 100644 --- a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc +++ b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc @@ -103,7 +103,7 @@ Execute the <>, w .Properties of `error` [%collapsible%open] ======= - `type`::::: + `type`:::: (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. ======= ====== diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 76d898ba0cb1..a8f4f4bf0baa 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -44,7 +44,7 @@ The following Agent configuration APIs are available: `service`:: (required, object) Service identifying the configuration to create or update. - ++ .Properties of `service` [%collapsible%open] ====== @@ -100,7 +100,7 @@ PUT /api/apm/settings/agent-configuration ===== Request body `service`:: (required, object) Service identifying the configuration to delete - ++ .Properties of `service` [%collapsible%open] ====== @@ -217,7 +217,7 @@ GET /api/apm/settings/agent-configuration `service`:: (required, object) Service identifying the configuration. - ++ .Properties of `service` [%collapsible%open] ====== From 0da20fea6a1ec391113d30797be749a653b0f42f Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 6 Apr 2020 15:21:39 -0400 Subject: [PATCH 21/27] [Fleet] Move actions to their own saved objects (#62137) --- .../ingest_manager/common/constants/agent.ts | 2 +- .../common/types/models/agent.ts | 9 +- .../ingest_manager/server/constants/index.ts | 3 +- .../routes/agent/actions_handlers.test.ts | 2 +- .../server/routes/agent/actions_handlers.ts | 10 +- .../server/routes/agent/handlers.ts | 3 +- .../server/routes/agent/index.ts | 2 +- .../ingest_manager/server/saved_objects.ts | 20 +- .../server/services/agents/acks.test.ts | 96 +- .../server/services/agents/acks.ts | 107 +- .../server/services/agents/actions.test.ts | 68 +- .../server/services/agents/actions.ts | 60 +- .../server/services/agents/checkin.test.ts | 91 +- .../server/services/agents/checkin.ts | 26 +- .../server/services/agents/enroll.ts | 1 - .../server/services/agents/saved_objects.ts | 18 +- .../ingest_manager/server/types/index.tsx | 1 + .../server/types/models/agent.ts | 2 +- .../api_integration/apis/fleet/agents/acks.ts | 2 +- .../apis/fleet/agents/actions.ts | 21 +- .../es_archives/fleet/agents/data.json | 102 +- .../es_archives/fleet/agents/mappings.json | 1771 +++++++++++++++-- 22 files changed, 1962 insertions(+), 455 deletions(-) diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts index fe6f7f57e289..0b462fb4c031 100644 --- a/x-pack/plugins/ingest_manager/common/constants/agent.ts +++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts @@ -5,8 +5,8 @@ */ export const AGENT_SAVED_OBJECT_TYPE = 'agents'; - export const AGENT_EVENT_SAVED_OBJECT_TYPE = 'agent_events'; +export const AGENT_ACTION_SAVED_OBJECT_TYPE = 'agent_actions'; export const AGENT_TYPE_PERMANENT = 'PERMANENT'; export const AGENT_TYPE_EPHEMERAL = 'EPHEMERAL'; diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts index aa5729a101e1..4d03a30f9a59 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -16,15 +16,21 @@ export type AgentStatus = 'offline' | 'error' | 'online' | 'inactive' | 'warning export interface NewAgentAction { type: 'CONFIG_CHANGE' | 'DATA_DUMP' | 'RESUME' | 'PAUSE'; - data?: string; + data?: any; sent_at?: string; } export type AgentAction = NewAgentAction & { id: string; + agent_id: string; created_at: string; } & SavedObjectAttributes; +export interface AgentActionSOAttributes extends NewAgentAction, SavedObjectAttributes { + created_at: string; + agent_id: string; +} + export interface AgentEvent { type: 'STATE' | 'ERROR' | 'ACTION_RESULT' | 'ACTION'; subtype: // State @@ -62,7 +68,6 @@ interface AgentBase { config_revision?: number; config_newest_revision?: number; last_checkin?: string; - actions: AgentAction[]; } export interface Agent extends AgentBase { diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts index f6ee475614c5..6ac92ca5d2a9 100644 --- a/x-pack/plugins/ingest_manager/server/constants/index.ts +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -20,8 +20,9 @@ export { INSTALL_SCRIPT_API_ROUTES, SETUP_API_ROUTE, // Saved object types - AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_CONFIG_SAVED_OBJECT_TYPE, DATASOURCE_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts index a20ba4a88053..76247c338a24 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts @@ -78,7 +78,7 @@ describe('test actions handlers', () => { getAgent: jest.fn().mockReturnValueOnce({ id: 'agent', }), - updateAgentActions: jest.fn().mockReturnValueOnce(agentAction), + createAgentAction: jest.fn().mockReturnValueOnce(agentAction), } as jest.Mocked; const postNewAgentActionHandler = postNewAgentActionHandlerBuilder(actionsService); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts index 2b9c23080359..8eb427e5739b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts @@ -28,11 +28,11 @@ export const postNewAgentActionHandlerBuilder = function( const newAgentAction = request.body.action as NewAgentAction; - const savedAgentAction = await actionsService.updateAgentActions( - soClient, - agent, - newAgentAction - ); + const savedAgentAction = await actionsService.createAgentAction(soClient, { + created_at: new Date().toISOString(), + ...newAgentAction, + agent_id: agent.id, + }); const body: PostNewAgentActionResponse = { success: true, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index adff1fda1120..89c827abe30e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -187,8 +187,9 @@ export const postAgentCheckinHandler: RequestHandler< action: 'checkin', success: true, actions: actions.map(a => ({ + agent_id: agent.id, type: a.type, - data: a.data ? JSON.parse(a.data) : a.data, + data: a.data, id: a.id, created_at: a.created_at, })), diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index d46102701784..ac27e47db155 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -122,7 +122,7 @@ export const registerRoutes = (router: IRouter) => { }, postNewAgentActionHandlerBuilder({ getAgent: AgentService.getAgent, - updateAgentActions: AgentService.updateAgentActions, + createAgentAction: AgentService.createAgentAction, }) ); diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 9f3035e1aac1..13f84e4efa79 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -10,6 +10,7 @@ import { PACKAGES_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, } from './constants'; @@ -38,17 +39,16 @@ export const savedObjectMappings = { default_api_key: { type: 'keyword' }, updated_at: { type: 'date' }, current_error_events: { type: 'text' }, + }, + }, + [AGENT_ACTION_SAVED_OBJECT_TYPE]: { + properties: { + agent_id: { type: 'keyword' }, + type: { type: 'keyword' }, // FIXME_INGEST https://github.com/elastic/kibana/issues/56554 - actions: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - type: { type: 'keyword' }, - data: { type: 'text' }, - sent_at: { type: 'date' }, - created_at: { type: 'date' }, - }, - }, + data: { type: 'flattened' }, + sent_at: { type: 'date' }, + created_at: { type: 'date' }, }, }, [AGENT_EVENT_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index 3c07463e3af5..b4c1f09015a6 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -3,29 +3,46 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; +import { SavedObjectsBulkResponse } from 'kibana/server'; import { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; -import { Agent, AgentAction, AgentEvent } from '../../../common/types/models'; +import { + Agent, + AgentAction, + AgentActionSOAttributes, + AgentEvent, +} from '../../../common/types/models'; import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; import { acknowledgeAgentActions } from './acks'; -import { isBoom } from 'boom'; describe('test agent acks services', () => { it('should succeed on valid and matched actions', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: 'agent_actions', + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + const agentActions = await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], } as unknown) as Agent, [ { @@ -41,6 +58,7 @@ describe('test agent acks services', () => { ({ type: 'CONFIG_CHANGE', id: 'action1', + agent_id: 'id', sent_at: '2020-03-14T19:45:02.620Z', timestamp: '2019-01-04T14:32:03.36764-05:00', created_at: '2020-03-14T19:45:02.620Z', @@ -50,21 +68,26 @@ describe('test agent acks services', () => { it('should fail for actions that cannot be found on agent actions list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + error: { + message: 'Not found', + statusCode: 404, + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + try { await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], } as unknown) as Agent, [ ({ @@ -78,27 +101,38 @@ describe('test agent acks services', () => { ); expect(true).toBeFalsy(); } catch (e) { - expect(isBoom(e)).toBeTruthy(); + expect(Boom.isBoom(e)).toBeTruthy(); } }); it('should fail for events that have types not in the allowed acknowledgement type list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: 'agent_actions', + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + try { await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], } as unknown) as Agent, [ ({ @@ -112,7 +146,7 @@ describe('test agent acks services', () => { ); expect(true).toBeFalsy(); } catch (e) { - expect(isBoom(e)).toBeTruthy(); + expect(Boom.isBoom(e)).toBeTruthy(); } }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts index cf9a47979ae8..24c3b322aad7 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -17,8 +17,14 @@ import { AgentEvent, AgentEventSOAttributes, AgentSOAttributes, + AgentActionSOAttributes, } from '../../types'; -import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, +} from '../../constants'; +import { getAgentActionByIds } from './actions'; const ALLOWED_ACKNOWLEDGEMENT_TYPE: string[] = ['ACTION_RESULT']; @@ -27,50 +33,81 @@ export async function acknowledgeAgentActions( agent: Agent, agentEvents: AgentEvent[] ): Promise { - const now = new Date().toISOString(); - - const agentActionMap: Map = new Map( - agent.actions.map(agentAction => [agentAction.id, agentAction]) - ); - - const matchedUpdatedActions: AgentAction[] = []; - - agentEvents.forEach(agentEvent => { + for (const agentEvent of agentEvents) { if (!isAllowedType(agentEvent.type)) { throw Boom.badRequest(`${agentEvent.type} not allowed for acknowledgment only ACTION_RESULT`); } - if (agentActionMap.has(agentEvent.action_id!)) { - const action = agentActionMap.get(agentEvent.action_id!) as AgentAction; - if (!action.sent_at) { - action.sent_at = now; - } - matchedUpdatedActions.push(action); - } else { - throw Boom.badRequest('all actions should belong to current agent'); + } + + const actionIds = agentEvents + .map(event => event.action_id) + .filter(actionId => actionId !== undefined) as string[]; + + let actions; + try { + actions = await getAgentActionByIds(soClient, actionIds); + } catch (error) { + if (Boom.isBoom(error) && error.output.statusCode === 404) { + throw Boom.badRequest(`One or more actions cannot be found`); + } + throw error; + } + + for (const action of actions) { + if (action.agent_id !== agent.id) { + throw Boom.badRequest(`${action.id} not found`); } - }); + } + + if (actions.length === 0) { + return []; + } + const configRevision = getLatestConfigRevison(agent, actions); - if (matchedUpdatedActions.length > 0) { - const configRevision = matchedUpdatedActions.reduce((acc, action) => { - if (action.type !== 'CONFIG_CHANGE') { - return acc; - } - const data = action.data ? JSON.parse(action.data as string) : {}; + await soClient.bulkUpdate([ + buildUpdateAgentConfigRevision(agent.id, configRevision), + ...buildUpdateAgentActionSentAt(actionIds), + ]); - if (data?.config?.id !== agent.config_id) { - return acc; - } + return actions; +} - return data?.config?.revision > acc ? data?.config?.revision : acc; - }, agent.config_revision || 0); +function getLatestConfigRevison(agent: Agent, actions: AgentAction[]) { + return actions.reduce((acc, action) => { + if (action.type !== 'CONFIG_CHANGE') { + return acc; + } + const data = action.data || {}; - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, { - actions: matchedUpdatedActions, + if (data?.config?.id !== agent.config_id) { + return acc; + } + + return data?.config?.revision > acc ? data?.config?.revision : acc; + }, agent.config_revision || 0); +} + +function buildUpdateAgentConfigRevision(agentId: string, configRevision: number) { + return { + type: AGENT_SAVED_OBJECT_TYPE, + id: agentId, + attributes: { config_revision: configRevision, - }); - } + }, + }; +} - return matchedUpdatedActions; +function buildUpdateAgentActionSentAt( + actionsIds: string[], + sentAt: string = new Date().toISOString() +) { + return actionsIds.map(actionId => ({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + id: actionId, + attributes: { + sent_at: sentAt, + }, + })); } function isAllowedType(eventType: string): boolean { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts index b500aeb825fe..f2e671c6dbaa 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts @@ -4,64 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createAgentAction, updateAgentActions } from './actions'; -import { Agent, AgentAction, NewAgentAction } from '../../../common/types/models'; +import { createAgentAction } from './actions'; +import { SavedObject } from 'kibana/server'; +import { AgentAction, AgentActionSOAttributes } from '../../../common/types/models'; import { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; -import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; - -interface UpdatedActions { - actions: AgentAction[]; -} describe('test agent actions services', () => { - it('should update agent current actions with new action', async () => { + it('should create a new action', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); - const newAgentAction: NewAgentAction = { + const newAgentAction: AgentActionSOAttributes = { + agent_id: 'agentid', type: 'CONFIG_CHANGE', data: 'data', sent_at: '2020-03-14T19:45:02.620Z', + created_at: '2020-03-14T19:45:02.620Z', }; - - await updateAgentActions( - mockSavedObjectsClient, - ({ - id: 'id', - type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], - } as unknown) as Agent, - newAgentAction + mockSavedObjectsClient.create.mockReturnValue( + Promise.resolve({ + attributes: {}, + } as SavedObject) ); - - const updatedAgentActions = (mockSavedObjectsClient.update.mock - .calls[0][2] as unknown) as UpdatedActions; - - expect(updatedAgentActions.actions.length).toEqual(2); - const actualAgentAction = updatedAgentActions.actions.find(action => action?.data === 'data'); - expect(actualAgentAction?.type).toEqual(newAgentAction.type); - expect(actualAgentAction?.data).toEqual(newAgentAction.data); - expect(actualAgentAction?.sent_at).toEqual(newAgentAction.sent_at); - }); - - it('should create agent action from new agent action model', async () => { - const newAgentAction: NewAgentAction = { - type: 'CONFIG_CHANGE', - data: 'data', - sent_at: '2020-03-14T19:45:02.620Z', - }; - const now = new Date(); - const agentAction = createAgentAction(now, newAgentAction); - - expect(agentAction.type).toEqual(newAgentAction.type); - expect(agentAction.data).toEqual(newAgentAction.data); - expect(agentAction.sent_at).toEqual(newAgentAction.sent_at); + await createAgentAction(mockSavedObjectsClient, newAgentAction); + + const createdAction = (mockSavedObjectsClient.create.mock + .calls[0][1] as unknown) as AgentAction; + expect(createdAction).toBeDefined(); + expect(createdAction?.type).toEqual(newAgentAction.type); + expect(createdAction?.data).toEqual(newAgentAction.data); + expect(createdAction?.sent_at).toEqual(newAgentAction.sent_at); }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts index 2f8ed9f50445..a8ef0820f8d9 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts @@ -5,46 +5,52 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import uuid from 'uuid'; -import { - Agent, - AgentAction, - AgentSOAttributes, - NewAgentAction, -} from '../../../common/types/models'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../common/constants'; - -export async function updateAgentActions( +import { Agent, AgentAction, AgentActionSOAttributes } from '../../../common/types/models'; +import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { savedObjectToAgentAction } from './saved_objects'; + +export async function createAgentAction( soClient: SavedObjectsClientContract, - agent: Agent, - newAgentAction: NewAgentAction + newAgentAction: AgentActionSOAttributes ): Promise { - const agentAction = createAgentAction(new Date(), newAgentAction); + const so = await soClient.create(AGENT_ACTION_SAVED_OBJECT_TYPE, { + ...newAgentAction, + }); - agent.actions.push(agentAction); + return savedObjectToAgentAction(so); +} - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, { - actions: agent.actions, +export async function getAgentActionsForCheckin( + soClient: SavedObjectsClientContract, + agentId: string +): Promise { + const res = await soClient.find({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * and ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id:${agentId}`, }); - return agentAction; + return res.saved_objects.map(savedObjectToAgentAction); } -export function createAgentAction(createdAt: Date, newAgentAction: NewAgentAction): AgentAction { - const agentAction = { - id: uuid.v4(), - created_at: createdAt.toISOString(), - }; - - return Object.assign(agentAction, newAgentAction); +export async function getAgentActionByIds( + soClient: SavedObjectsClientContract, + actionIds: string[] +) { + const res = await soClient.bulkGet( + actionIds.map(actionId => ({ + id: actionId, + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + })) + ); + + return res.saved_objects.map(savedObjectToAgentAction); } export interface ActionsService { getAgent: (soClient: SavedObjectsClientContract, agentId: string) => Promise; - updateAgentActions: ( + createAgentAction: ( soClient: SavedObjectsClientContract, - agent: Agent, - newAgentAction: NewAgentAction + newAgentAction: AgentActionSOAttributes ) => Promise; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts index d3e10fcb6b63..d98052ea87e8 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts @@ -14,13 +14,13 @@ function getAgent(data: Partial) { describe('Agent checkin service', () => { describe('shouldCreateConfigAction', () => { it('should return false if the agent do not have an assigned config', () => { - const res = shouldCreateConfigAction(getAgent({})); + const res = shouldCreateConfigAction(getAgent({}), []); expect(res).toBeFalsy(); }); it('should return true if this is agent first checkin', () => { - const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' })); + const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' }), []); expect(res).toBeTruthy(); }); @@ -32,7 +32,8 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 1, - }) + }), + [] ); expect(res).toBeFalsy(); @@ -45,20 +46,21 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - actions: [ - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config1', - revision: 2, - }, - }), - }, - ], - }) + }), + [ + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config1', + revision: 2, + }, + }), + }, + ] ); expect(res).toBeFalsy(); @@ -71,31 +73,33 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - actions: [ - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config2', - revision: 2, - }, - }), - }, - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config1', - revision: 1, - }, - }), - }, - ], - }) + }), + [ + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config2', + revision: 2, + }, + }), + }, + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config1', + revision: 1, + }, + }), + }, + ] ); expect(res).toBeTruthy(); @@ -108,7 +112,8 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - }) + }), + [] ); expect(res).toBeTruthy(); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts index d80fff5d8ece..9a2b3f22b943 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts @@ -5,7 +5,6 @@ */ import { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server'; -import uuid from 'uuid'; import { Agent, AgentEvent, @@ -17,6 +16,7 @@ import { import { agentConfigService } from '../agent_config'; import * as APIKeysService from '../api_keys'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { getAgentActionsForCheckin, createAgentAction } from './actions'; export async function agentCheckin( soClient: SavedObjectsClientContract, @@ -34,10 +34,10 @@ export async function agentCheckin( last_checkin: new Date().toISOString(), }; - const actions = filterActionsForCheckin(agent); + const actions = await getAgentActionsForCheckin(soClient, agent.id); // Generate new agent config if config is updated - if (agent.config_id && shouldCreateConfigAction(agent)) { + if (agent.config_id && shouldCreateConfigAction(agent, actions)) { const config = await agentConfigService.getFullConfig(soClient, agent.config_id); if (config) { // Assign output API keys @@ -52,18 +52,14 @@ export async function agentCheckin( // Mutate the config to set the api token for this agent config.outputs.default.api_key = agent.default_api_key || updateData.default_api_key; - const configChangeAction: AgentAction = { - id: uuid.v4(), + const configChangeAction = await createAgentAction(soClient, { + agent_id: agent.id, type: 'CONFIG_CHANGE', + data: { config } as any, created_at: new Date().toISOString(), - data: JSON.stringify({ - config, - }), sent_at: undefined, - }; + }); actions.push(configChangeAction); - // persist new action - updateData.actions = actions; } } if (localMetadata) { @@ -149,7 +145,7 @@ function isActionEvent(event: AgentEvent) { ); } -export function shouldCreateConfigAction(agent: Agent): boolean { +export function shouldCreateConfigAction(agent: Agent, actions: AgentAction[]): boolean { if (!agent.config_id) { return false; } @@ -167,7 +163,7 @@ export function shouldCreateConfigAction(agent: Agent): boolean { return false; } - const isActionAlreadyGenerated = !!agent.actions.find(action => { + const isActionAlreadyGenerated = !!actions.find(action => { if (!action.data || action.type !== 'CONFIG_CHANGE') { return false; } @@ -181,7 +177,3 @@ export function shouldCreateConfigAction(agent: Agent): boolean { return !isActionAlreadyGenerated; } - -function filterActionsForCheckin(agent: Agent): AgentAction[] { - return agent.actions.filter((a: AgentAction) => !a.sent_at); -} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts index 52547e9bcb0f..a34d2e03e9b3 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts @@ -35,7 +35,6 @@ export async function enroll( user_provided_metadata: JSON.stringify(metadata?.userProvided ?? {}), local_metadata: JSON.stringify(metadata?.local ?? {}), current_error_events: undefined, - actions: [], access_api_key_id: undefined, last_checkin: undefined, default_api_key: undefined, diff --git a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts index dbe268818713..aa8852074068 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; import { SavedObject } from 'src/core/server'; -import { Agent, AgentSOAttributes } from '../../types'; +import { Agent, AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types'; export function savedObjectToAgent(so: SavedObject): Agent { if (so.error) { @@ -24,3 +25,18 @@ export function savedObjectToAgent(so: SavedObject): Agent { status: undefined, }; } + +export function savedObjectToAgentAction(so: SavedObject): AgentAction { + if (so.error) { + if (so.error.statusCode === 404) { + throw Boom.notFound(so.error.message); + } + + throw new Error(so.error.message); + } + + return { + id: so.id, + ...so.attributes, + }; +} diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 59c7f152e5cb..1cd5622c0c7b 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -14,6 +14,7 @@ export { AgentEvent, AgentEventSOAttributes, AgentAction, + AgentActionSOAttributes, Datasource, NewDatasource, FullAgentConfigDatasource, diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent.ts b/x-pack/plugins/ingest_manager/server/types/models/agent.ts index f70b3cf0ed09..f18846348432 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent.ts @@ -60,6 +60,6 @@ export const NewAgentActionSchema = schema.object({ schema.literal('RESUME'), schema.literal('PAUSE'), ]), - data: schema.maybe(schema.string()), + data: schema.maybe(schema.any()), sent_at: schema.maybe(schema.string()), }); diff --git a/x-pack/test/api_integration/apis/fleet/agents/acks.ts b/x-pack/test/api_integration/apis/fleet/agents/acks.ts index a2eba2c23c39..f08ce33d8b60 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/acks.ts @@ -178,7 +178,7 @@ export default function(providerContext: FtrProviderContext) { ], }) .expect(400); - expect(apiResponse.message).to.eql('all actions should belong to current agent'); + expect(apiResponse.message).to.eql('One or more actions cannot be found'); }); it('should return a 400 when request event list contains action types that are not allowed for acknowledgement', async () => { diff --git a/x-pack/test/api_integration/apis/fleet/agents/actions.ts b/x-pack/test/api_integration/apis/fleet/agents/actions.ts index f27b932cff5c..cf0641acf9e1 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/actions.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/actions.ts @@ -28,28 +28,15 @@ export default function(providerContext: FtrProviderContext) { .send({ action: { type: 'CONFIG_CHANGE', - data: 'action_data', + data: { data: 'action_data' }, sent_at: '2020-03-18T19:45:02.620Z', }, }) .expect(200); expect(apiResponse.success).to.be(true); - expect(apiResponse.item.data).to.be('action_data'); + expect(apiResponse.item.data).to.eql({ data: 'action_data' }); expect(apiResponse.item.sent_at).to.be('2020-03-18T19:45:02.620Z'); - - const { body: agentResponse } = await supertest - .get(`/api/ingest_manager/fleet/agents/agent1`) - .set('kbn-xsrf', 'xx') - .expect(200); - - const updatedAction = agentResponse.item.actions.find( - (itemAction: Record) => itemAction?.data === 'action_data' - ); - - expect(updatedAction.type).to.be('CONFIG_CHANGE'); - expect(updatedAction.data).to.be('action_data'); - expect(updatedAction.sent_at).to.be('2020-03-18T19:45:02.620Z'); }); it('should return a 400 when request does not have type information', async () => { @@ -58,7 +45,7 @@ export default function(providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xx') .send({ action: { - data: 'action_data', + data: { data: 'action_data' }, sent_at: '2020-03-18T19:45:02.620Z', }, }) @@ -75,7 +62,7 @@ export default function(providerContext: FtrProviderContext) { .send({ action: { type: 'CONFIG_CHANGE', - data: 'action_data', + data: { data: 'action_data' }, sent_at: '2020-03-18T19:45:02.620Z', }, }) diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index 9b29767d5162..1ffb119ca102 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -12,30 +12,7 @@ "config_id": "1", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [{ - "id": "37ed51ff-e80f-4f2a-a62d-f4fa975e7d85", - "created_at": "2019-09-04T15:04:07+0000", - "type": "RESUME" - }, - { - "id": "b400439c-bbbf-43d5-83cb-cf8b7e32506f", - "type": "PAUSE", - "created_at": "2019-09-04T15:01:07+0000", - "sent_at": "2019-09-04T15:03:07+0000" - }, - { - "created_at" : "2020-03-15T03:47:15.129Z", - "id" : "48cebde1-c906-4893-b89f-595d943b72a1", - "type" : "CONFIG_CHANGE", - "sent_at": "2020-03-04T15:03:07+0000" - }, - { - "created_at" : "2020-03-16T03:47:15.129Z", - "id" : "48cebde1-c906-4893-b89f-595d943b72a2", - "type" : "CONFIG_CHANGE", - "sent_at": "2020-03-04T15:03:07+0000" - }] + "user_provided_metadata": "{}" } } } @@ -54,8 +31,7 @@ "shared_id": "agent2_filebeat", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [] + "user_provided_metadata": "{}" } } } @@ -74,8 +50,7 @@ "shared_id": "agent3_metricbeat", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [] + "user_provided_metadata": "{}" } } } @@ -94,8 +69,7 @@ "shared_id": "agent4_metricbeat", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [] + "user_provided_metadata": "{}" } } } @@ -157,3 +131,71 @@ } } } + +{ + "type": "doc", + "value": { + "id": "agent_actions:37ed51ff-e80f-4f2a-a62d-f4fa975e7d85", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "created_at": "2019-09-04T15:04:07+0000", + "type": "RESUME", + "sent_at": "2019-09-04T15:03:07+0000" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "agent_actions:b400439c-bbbf-43d5-83cb-cf8b7e32506f", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "type": "PAUSE", + "created_at": "2019-09-04T15:01:07+0000", + "sent_at": "2019-09-04T15:03:07+0000" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "agent_actions:48cebde1-c906-4893-b89f-595d943b72a1", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "type": "CONFIG_CHANGE", + "created_at": "2020-03-15T03:47:15.129Z", + "sent_at": "2020-03-04T15:03:07+0000" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "agent_actions:48cebde1-c906-4893-b89f-595d943b72a2", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "type": "CONFIG_CHANGE", + "created_at": "2020-03-15T03:47:15.129Z", + "sent_at": "2020-03-04T15:03:07+0000" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/fleet/agents/mappings.json b/x-pack/test/functional/es_archives/fleet/agents/mappings.json index 0f632b7333ee..31ae16104930 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/agents/mappings.json @@ -9,58 +9,168 @@ "dynamic": "strict", "_meta": { "migrationMappingPropertyHashes": { + "outputs": "aee9782e0d500b867859650a36280165", "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "server": "ec97f1c5da1a19609a60874e5af1100c", "visualization": "52d7a13ad68a150c4525b292d23e12cc", "references": "7997cf5a56cc02bdc9c93361bde732b0", "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "policies": "1a096b98c98c2efebfdba77cefcfe54a", "type": "2f4316de49999235636386fe51dc06c1", - "lens": "21c3ea0763beb1ecb0162529706b88c5", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "action": "6e96ac5e648f57523879661ea72525b7", + "agent_configs": "38abaf89513877745c359e7700c0c66a", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", + "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "inventory-view": "9ecce5b58867403613d82fe496470b34", + "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "canvas-element": "7390014e1091044523666d97247392fc", + "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "lens": "21c3ea0763beb1ecb0162529706b88c5", "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "cases-configure": "42711cbb311976c0687853f4c1354572", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", "map": "23d7aa4a720d4938ccde3983f87bd58d", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", - "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", - "epm": "abf5b64aa599932bd181efc86dce14a7", - "siem-ui-timeline": "6485ab095be8d15246667b98a1a34295", - "agent_events": "8060c5567d33f6697164e1fd5c81b8ed", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", + "epm-package": "75d12cd13c867fd713d7dfb27366bc20", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "cases": "08b8b110dbca273d37e8aef131ecab61", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "url": "c7f66a0df8b1b52f17c28c4adb111105", - "apm-indices": "c69b68f3fe2bf27b4788d4191c1d6011", - "agents": "1c8e942384219bd899f381fd40e407d7", + "agents": "c3eeb7b9d97176f15f6d126370ab23c7", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "inventory-view": "84b320fd67209906333ffce261128462", - "enrollment_api_keys": "90e66b79e8e948e9c15434fdb3ae576e", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "canvas-element": "7390014e1091044523666d97247392fc", - "datasources": "2fed9e9883b9622cd59a73ee5550ef4f", - "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", "namespace": "2f4316de49999235636386fe51dc06c1", - "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "agent_actions": "ed270b46812f0fa1439366c428a2cf17", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "config": "87aca8fdb053154f11383fce3dbf3edf", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327" + "config": "ae24d22d5986d04124cc6568f771066f", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215" } }, "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "type": "object", + "enabled": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "type": "object", + "enabled": false + } + } + }, + "agent_actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "flattened" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agent_configs": { + "properties": { + "datasources": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "namespace": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "updated_on": { + "type": "keyword" + } + } + }, "agent_events": { "properties": { + "action_id": { + "type": "keyword" + }, "agent_id": { "type": "keyword" }, + "config_id": { + "type": "keyword" + }, "data": { "type": "text" }, @@ -70,6 +180,9 @@ "payload": { "type": "text" }, + "stream_id": { + "type": "keyword" + }, "subtype": { "type": "keyword" }, @@ -86,29 +199,24 @@ "access_api_key_id": { "type": "keyword" }, - "actions": { - "type": "nested", - "properties": { - "created_at": { - "type": "date" - }, - "data": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, "active": { "type": "boolean" }, + "config_id": { + "type": "keyword" + }, + "config_newest_revision": { + "type": "integer" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "type": "text" + }, + "default_api_key": { + "type": "keyword" + }, "enrolled_at": { "type": "date" }, @@ -121,9 +229,6 @@ "local_metadata": { "type": "text" }, - "config_id": { - "type": "keyword" - }, "shared_id": { "type": "keyword" }, @@ -136,21 +241,95 @@ "user_provided_metadata": { "type": "text" }, - "current_error_events": { - "type": "text" - }, "version": { "type": "keyword" } } }, - "apm-indices": { + "alert": { "properties": { - "apm_oss": { + "actions": { + "type": "nested", "properties": { - "apmAgentConfigurationIndex": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { "type": "keyword" }, + "params": { + "type": "object", + "enabled": false + } + } + }, + "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": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "params": { + "type": "object", + "enabled": false + }, + "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" }, @@ -173,33 +352,779 @@ } } }, - "apm-services-telemetry": { + "apm-telemetry": { "properties": { - "has_any_services": { - "type": "boolean" - }, - "services_per_agent": { + "agents": { "properties": { "dotnet": { - "type": "long", - "null_value": 0 + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } }, "go": { - "type": "long", - "null_value": 0 + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } }, "java": { - "type": "long", - "null_value": 0 + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } }, "js-base": { - "type": "long", - "null_value": 0 - }, - "nodejs": { - "type": "long", - "null_value": 0 - }, + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "nodejs": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "python": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "ruby": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "rum-js": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + } + } + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "type": "long", + "null_value": 0 + }, + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, "python": { "type": "long", "null_value": 0 @@ -213,6 +1138,155 @@ "null_value": 0 } } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" } } }, @@ -244,22 +1318,253 @@ } } }, - "canvas-workpad": { - "dynamic": "false", + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "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": { - "@created": { - "type": "date" + "action": { + "type": "keyword" }, - "@timestamp": { + "action_at": { "type": "date" }, - "name": { - "type": "text", - "fields": { - "keyword": { + "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" } } }, @@ -327,81 +1632,76 @@ }, "datasources": { "properties": { - "id": { - "type": "keyword" - }, - "name": { + "config_id": { "type": "keyword" }, - "package": { - "properties": { - "assets": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "description": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } + "description": { + "type": "text" }, - "read_alias": { - "type": "keyword" + "enabled": { + "type": "boolean" }, - "streams": { + "inputs": { + "type": "nested", "properties": { "config": { "type": "flattened" }, - "id": { + "enabled": { + "type": "boolean" + }, + "processors": { "type": "keyword" }, - "input": { + "streams": { + "type": "nested", "properties": { "config": { "type": "flattened" }, - "fields": { - "type": "flattened" - }, - "id": { - "type": "keyword" - }, - "ilm_policy": { + "dataset": { "type": "keyword" }, - "index_template": { - "type": "keyword" + "enabled": { + "type": "boolean" }, - "ingest_pipelines": { + "id": { "type": "keyword" }, - "type": { + "processors": { "type": "keyword" } } }, - "output_id": { + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { "type": "keyword" }, - "processors": { + "version": { "type": "keyword" } } + }, + "revision": { + "type": "integer" } } }, @@ -416,49 +1716,18 @@ "api_key_id": { "type": "keyword" }, + "config_id": { + "type": "keyword" + }, "created_at": { "type": "date" }, - "enrollment_rules": { - "type": "nested", - "properties": { - "created_at": { - "type": "date" - }, - "id": { - "type": "keyword" - }, - "ip_ranges": { - "type": "keyword" - }, - "types": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "window_duration": { - "type": "nested", - "properties": { - "from": { - "type": "date" - }, - "to": { - "type": "date" - } - } - } - } - }, "expire_at": { "type": "date" }, "name": { "type": "keyword" }, - "config_id": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -467,7 +1736,7 @@ } } }, - "epm": { + "epm-package": { "properties": { "installed": { "type": "nested", @@ -479,6 +1748,12 @@ "type": "keyword" } } + }, + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" } } }, @@ -631,6 +1906,26 @@ } } }, + "customMetrics": { + "type": "nested", + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, "customOptions": { "type": "nested", "properties": { @@ -665,6 +1960,18 @@ }, "metric": { "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, "type": { "type": "keyword" } @@ -792,9 +2099,19 @@ } } }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, "mapsTotalCount": { "type": "long" }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, "timeCaptured": { "type": "date" } @@ -894,30 +2211,33 @@ "namespace": { "type": "keyword" }, - "policies": { + "outputs": { "properties": { - "datasources": { + "api_key": { "type": "keyword" }, - "description": { - "type": "text" - }, - "id": { + "ca_sha256": { "type": "keyword" }, - "label": { - "type": "keyword" + "config": { + "type": "flattened" }, - "name": { - "type": "text" + "fleet_enroll_password": { + "type": "binary" }, - "status": { + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { "type": "keyword" }, - "updated_by": { + "is_default": { + "type": "boolean" + }, + "name": { "type": "keyword" }, - "updated_on": { + "type": { "type": "keyword" } } @@ -1011,6 +2331,73 @@ } } }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "type": "object", + "dynamic": "true" + } + } + }, + "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": { @@ -1145,6 +2532,9 @@ "description": { "type": "text" }, + "eventType": { + "type": "keyword" + }, "favorite": { "properties": { "favoriteDate": { @@ -1349,6 +2739,9 @@ }, "telemetry": { "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, "enabled": { "type": "boolean" }, @@ -1356,12 +2749,16 @@ "type": "date" }, "lastVersionChecked": { - "type": "keyword", - "ignore_above": 256 + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" }, "sendUsageFrom": { - "type": "keyword", - "ignore_above": 256 + "type": "keyword" }, "userHasSeenNotice": { "type": "boolean" @@ -1409,6 +2806,13 @@ } } }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, "type": { "type": "keyword" }, @@ -1485,6 +2889,13 @@ } } }, + "uptime-dynamic-settings": { + "properties": { + "heartbeatIndices": { + "type": "keyword" + } + } + }, "url": { "properties": { "accessCount": { From ab0cc8894a924dda18fc8664cf903fdf7a2d9920 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Mon, 6 Apr 2020 15:31:01 -0400 Subject: [PATCH 22/27] [Monitoring] Cluster state watch to Kibana alerting (#61685) * WIP * Add new alert with tests * Fix type issues, and disable new alerting for tests * Fix up the view all alerts view * Turn off for merging * Fix jest test Co-authored-by: Elastic Machine --- .../plugins/monitoring/common/constants.ts | 8 +- .../public/components/alerts/alerts.js | 44 +- .../public/components/alerts/status.test.tsx | 8 +- .../public/components/alerts/status.tsx | 2 +- .../cluster/overview/alerts_panel.js | 81 ++- .../monitoring/public/views/alerts/index.js | 30 +- x-pack/plugins/monitoring/common/constants.ts | 8 +- .../server/alerts/cluster_state.test.ts | 186 ++++++ .../monitoring/server/alerts/cluster_state.ts | 134 ++++ .../plugins/monitoring/server/alerts/enums.ts | 16 + .../server/alerts/license_expiration.test.ts | 572 +++++------------- .../server/alerts/license_expiration.ts | 127 ++-- .../monitoring/server/alerts/types.d.ts | 62 +- .../lib/alerts/cluster_state.lib.test.ts | 70 +++ .../server/lib/alerts/cluster_state.lib.ts | 88 +++ .../lib/alerts/fetch_cluster_state.test.ts | 39 ++ .../server/lib/alerts/fetch_cluster_state.ts | 53 ++ .../server/lib/alerts/fetch_clusters.test.ts | 46 +- .../server/lib/alerts/fetch_clusters.ts | 41 +- .../server/lib/alerts/fetch_licenses.test.ts | 67 +- .../server/lib/alerts/fetch_licenses.ts | 16 +- .../server/lib/alerts/fetch_status.test.ts | 122 ++++ .../server/lib/alerts/fetch_status.ts | 100 ++- .../lib/alerts/get_prepared_alert.test.ts | 163 +++++ .../server/lib/alerts/get_prepared_alert.ts | 87 +++ .../lib/alerts/license_expiration.lib.test.ts | 23 +- .../lib/alerts/license_expiration.lib.ts | 56 +- .../lib/cluster/get_clusters_from_request.js | 12 +- x-pack/plugins/monitoring/server/plugin.ts | 12 + .../server/routes/api/v1/alerts/alerts.js | 53 +- 30 files changed, 1570 insertions(+), 756 deletions(-) create mode 100644 x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts create mode 100644 x-pack/plugins/monitoring/server/alerts/cluster_state.ts create mode 100644 x-pack/plugins/monitoring/server/alerts/enums.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts index 9a4030f3eb21..3a4c7b71dcd0 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.ts +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; +/** + * This is the alert type id for the cluster state alert + */ +export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; /** * Matches the id for the built-in in email action type @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 1; +export const NUMBER_OF_MIGRATED_ALERTS = 2; /** * The advanced settings config name for the email address diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js index 11fcef73a4b9..95c1af554919 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js @@ -6,10 +6,15 @@ import React from 'react'; import chrome from '../../np_imports/ui/chrome'; -import { capitalize } from 'lodash'; +import { capitalize, get } from 'lodash'; import { formatDateTimeLocal } from '../../../common/formatting'; import { formatTimestampToDuration } from '../../../common'; -import { CALCULATE_DURATION_SINCE, EUI_SORT_DESCENDING } from '../../../common/constants'; +import { + CALCULATE_DURATION_SINCE, + EUI_SORT_DESCENDING, + ALERT_TYPE_LICENSE_EXPIRATION, + ALERT_TYPE_CLUSTER_STATE, +} from '../../../common/constants'; import { mapSeverity } from './map_severity'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; @@ -21,6 +26,8 @@ const linkToCategories = { 'elasticsearch/indices': 'Elasticsearch Indices', 'kibana/instances': 'Kibana Instances', 'logstash/instances': 'Logstash Nodes', + [ALERT_TYPE_LICENSE_EXPIRATION]: 'License expiration', + [ALERT_TYPE_CLUSTER_STATE]: 'Cluster state', }; const getColumns = (kbnUrl, scope, timezone) => [ { @@ -94,19 +101,22 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'message', sortable: true, - render: (message, alert) => ( - { - scope.$evalAsync(() => { - kbnUrl.changePath(target); - }); - }} - /> - ), + render: (_message, alert) => { + const message = get(alert, 'message.text', get(alert, 'message', '')); + return ( + { + scope.$evalAsync(() => { + kbnUrl.changePath(target); + }); + }} + /> + ); + }, }, { name: i18n.translate('xpack.monitoring.alerts.categoryColumnTitle', { @@ -148,8 +158,8 @@ const getColumns = (kbnUrl, scope, timezone) => [ export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange }) => { const alertsFlattened = alerts.map(alert => ({ ...alert, - status: alert.metadata.severity, - category: alert.metadata.link, + status: get(alert, 'metadata.severity', get(alert, 'severity', 0)), + category: get(alert, 'metadata.link', get(alert, 'type', null)), })); const injector = chrome.dangerouslyGetActiveInjector(); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx index 258a5b68db37..d3cf4b463a2c 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { kfetch } from 'ui/kfetch'; import { AlertsStatus, AlertsStatusProps } from './status'; -import { ALERT_TYPE_PREFIX } from '../../../common/constants'; +import { ALERT_TYPES } from '../../../common/constants'; import { getSetupModeState } from '../../lib/setup_mode'; import { mockUseEffects } from '../../jest.helpers'; @@ -63,11 +63,7 @@ describe('Status', () => { it('should render a success message if all alerts have been migrated and in setup mode', async () => { (kfetch as jest.Mock).mockReturnValue({ - data: [ - { - alertTypeId: ALERT_TYPE_PREFIX, - }, - ], + data: ALERT_TYPES.map(type => ({ alertTypeId: type })), }); (getSetupModeState as jest.Mock).mockReturnValue({ diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx index 072a98b12345..5f5329bf7fff 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx @@ -142,7 +142,7 @@ export const AlertsStatus: React.FC = (props: AlertsStatusPro ); } - const allMigrated = kibanaAlerts.length === NUMBER_OF_MIGRATED_ALERTS; + const allMigrated = kibanaAlerts.length >= NUMBER_OF_MIGRATED_ALERTS; if (allMigrated) { if (setupModeEnabled) { return ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js index 8455fb8cf308..d87ff98e79be 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js @@ -6,14 +6,12 @@ import React, { Fragment } from 'react'; import moment from 'moment-timezone'; -import chrome from '../../../np_imports/ui/chrome'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity'; import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration'; import { CALCULATE_DURATION_SINCE, KIBANA_ALERTING_ENABLED, - ALERT_TYPE_LICENSE_EXPIRATION, CALCULATE_DURATION_UNTIL, } from '../../../../common/constants'; import { formatDateTimeLocal } from '../../../../common/formatting'; @@ -31,6 +29,37 @@ import { EuiLink, } from '@elastic/eui'; +function replaceTokens(alert) { + if (!alert.message.tokens) { + return alert.message.text; + } + + let text = alert.message.text; + + for (const token of alert.message.tokens) { + if (token.type === 'time') { + text = text.replace( + token.startToken, + token.isRelative + ? formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) + : moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z') + ); + } else if (token.type === 'link') { + const linkPart = new RegExp(`${token.startToken}(.+?)${token.endToken}`).exec(text); + // TODO: we assume this is at the end, which works for now but will not always work + const nonLinkText = text.replace(linkPart[0], ''); + text = ( + + {nonLinkText} + {linkPart[1]} + + ); + } + } + + return text; +} + export function AlertsPanel({ alerts, changeUrl }) { const goToAlerts = () => changeUrl('/alerts'); @@ -58,9 +87,6 @@ export function AlertsPanel({ alerts, changeUrl }) { severityIcon.iconType = 'check'; } - const injector = chrome.dangerouslyGetActiveInjector(); - const timezone = injector.get('config').get('dateFormat:tz'); - return ( @@ -96,14 +122,7 @@ export function AlertsPanel({ alerts, changeUrl }) { const alertsList = KIBANA_ALERTING_ENABLED ? alerts.map((alert, idx) => { const callOutProps = mapSeverity(alert.severity); - let message = alert.message - // scan message prefix and replace relative times - // \w: Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_]. - .replace( - '#relative', - formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) - ) - .replace('#absolute', moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z')); + const message = replaceTokens(alert); if (!alert.isFiring) { callOutProps.title = i18n.translate( @@ -118,22 +137,30 @@ export function AlertsPanel({ alerts, changeUrl }) { ); callOutProps.color = 'success'; callOutProps.iconType = 'check'; - } else { - if (alert.type === ALERT_TYPE_LICENSE_EXPIRATION) { - message = ( - - {message} -   - Please update your license - - ); - } } return ( - -

{message}

-
+ + +

{message}

+ +

+ +

+
+
+ +
); }) : alerts.map((item, index) => ( diff --git a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js index 7c065a78a8af..62cc985887e9 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js @@ -18,25 +18,37 @@ import { Alerts } from '../../components/alerts'; import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui'; -import { CODE_PATH_ALERTS } from '../../../common/constants'; +import { CODE_PATH_ALERTS, KIBANA_ALERTING_ENABLED } from '../../../common/constants'; function getPageData($injector) { const globalState = $injector.get('globalState'); const $http = $injector.get('$http'); const Private = $injector.get('Private'); - const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; + const url = KIBANA_ALERTING_ENABLED + ? `../api/monitoring/v1/alert_status` + : `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; const timeBounds = timefilter.getBounds(); + const data = { + timeRange: { + min: timeBounds.min.toISOString(), + max: timeBounds.max.toISOString(), + }, + }; + + if (!KIBANA_ALERTING_ENABLED) { + data.ccs = globalState.ccs; + } return $http - .post(url, { - ccs: globalState.ccs, - timeRange: { - min: timeBounds.min.toISOString(), - max: timeBounds.max.toISOString(), - }, + .post(url, data) + .then(response => { + const result = get(response, 'data', []); + if (KIBANA_ALERTING_ENABLED) { + return result.alerts; + } + return result; }) - .then(response => get(response, 'data', [])) .catch(err => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 9a4030f3eb21..3a4c7b71dcd0 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; +/** + * This is the alert type id for the cluster state alert + */ +export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; /** * Matches the id for the built-in in email action type @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 1; +export const NUMBER_OF_MIGRATED_ALERTS = 2; /** * The advanced settings config name for the email address diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts new file mode 100644 index 000000000000..6a9ca8843734 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Logger } from 'src/core/server'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { getClusterState } from './cluster_state'; +import { AlertServices } from '../../../alerting/server'; +import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; +import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } from './types'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; +import { executeActions } from '../lib/alerts/cluster_state.lib'; +import { AlertClusterStateState } from './enums'; + +jest.mock('../lib/alerts/cluster_state.lib', () => ({ + executeActions: jest.fn(), + getUiMessage: jest.fn(), +})); + +jest.mock('../lib/alerts/get_prepared_alert', () => ({ + getPreparedAlert: jest.fn(() => { + return { + emailAddress: 'foo@foo.com', + }; + }), +})); + +interface MockServices { + callCluster: jest.Mock; + alertInstanceFactory: jest.Mock; + savedObjectsClient: jest.Mock; +} + +describe('getClusterState', () => { + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + + const params: AlertCommonParams = { + dateFormat: 'YYYY', + timezone: 'UTC', + }; + + const emailAddress = 'foo@foo.com'; + const clusterUuid = 'kdksdfj434'; + const clusterName = 'monitoring_test'; + const cluster = { clusterUuid, clusterName }; + + async function setupAlert( + previousState: AlertClusterStateState, + newState: AlertClusterStateState + ): Promise { + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), + }; + const getLogger = (): Logger => logger; + const ccrEnabled = false; + (getPreparedAlert as jest.Mock).mockImplementation(() => ({ + emailAddress, + data: [ + { + state: newState, + clusterUuid, + }, + ], + clusters: [cluster], + })); + + const alert = getClusterState(null as any, null as any, getLogger, ccrEnabled); + const state: AlertCommonState = { + [clusterUuid]: { + state: previousState, + ui: { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }, + } as AlertClusterStatePerClusterState, + }; + + return (await alert.executor({ services, params, state } as any)) as AlertCommonState; + } + + afterEach(() => { + (executeActions as jest.Mock).mockClear(); + }); + + it('should configure the alert properly', () => { + const alert = getClusterState(null as any, null as any, jest.fn(), false); + expect(alert.id).toBe(ALERT_TYPE_CLUSTER_STATE); + expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); + }); + + it('should alert if green -> yellow', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Yellow); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Yellow, + emailAddress + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); + expect(clusterResult.ui.isFiring).toBe(true); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should alert if yellow -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Green); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Green, + emailAddress, + true + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); + }); + + it('should alert if green -> red', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Red); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Red, + emailAddress + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Red); + expect(clusterResult.ui.isFiring).toBe(true); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should alert if red -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Green); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Green, + emailAddress, + true + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); + }); + + it('should not alert if red -> yellow', async () => { + const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Yellow); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Red); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should not alert if yellow -> red', async () => { + const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Red); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should not alert if green -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Green); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); +}); diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts new file mode 100644 index 000000000000..9a5805b8af7c --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; +import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; +import { AlertType } from '../../../alerting/server'; +import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; +import { + AlertCommonExecutorOptions, + AlertCommonState, + AlertClusterStatePerClusterState, + AlertCommonCluster, +} from './types'; +import { AlertClusterStateState } from './enums'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; +import { fetchClusterState } from '../lib/alerts/fetch_cluster_state'; + +export const getClusterState = ( + getUiSettingsService: () => Promise, + monitoringCluster: ICustomClusterClient, + getLogger: (...scopes: string[]) => Logger, + ccsEnabled: boolean +): AlertType => { + const logger = getLogger(ALERT_TYPE_CLUSTER_STATE); + return { + id: ALERT_TYPE_CLUSTER_STATE, + name: 'Monitoring Alert - Cluster Status', + actionGroups: [ + { + id: 'default', + name: i18n.translate('xpack.monitoring.alerts.clusterState.actionGroups.default', { + defaultMessage: 'Default', + }), + }, + ], + defaultActionGroupId: 'default', + async executor({ + services, + params, + state, + }: AlertCommonExecutorOptions): Promise { + logger.debug( + `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` + ); + + const preparedAlert = await getPreparedAlert( + ALERT_TYPE_CLUSTER_STATE, + getUiSettingsService, + monitoringCluster, + logger, + ccsEnabled, + services, + fetchClusterState + ); + + if (!preparedAlert) { + return state; + } + + const { emailAddress, data: states, clusters } = preparedAlert; + + const result: AlertCommonState = { ...state }; + const defaultAlertState: AlertClusterStatePerClusterState = { + state: AlertClusterStateState.Green, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + triggeredMS: 0, + lastCheckedMS: 0, + }, + }; + + for (const clusterState of states) { + const alertState: AlertClusterStatePerClusterState = + (state[clusterState.clusterUuid] as AlertClusterStatePerClusterState) || + defaultAlertState; + const cluster = clusters.find( + (c: AlertCommonCluster) => c.clusterUuid === clusterState.clusterUuid + ); + if (!cluster) { + logger.warn(`Unable to find cluster for clusterUuid='${clusterState.clusterUuid}'`); + continue; + } + const isNonGreen = clusterState.state !== AlertClusterStateState.Green; + const severity = clusterState.state === AlertClusterStateState.Red ? 2100 : 1100; + + const ui = alertState.ui; + let triggered = ui.triggeredMS; + let resolved = ui.resolvedMS; + let message = ui.message || {}; + let lastState = alertState.state; + const instance = services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE); + + if (isNonGreen) { + if (lastState === AlertClusterStateState.Green) { + logger.debug(`Cluster state changed from green to ${clusterState.state}`); + executeActions(instance, cluster, clusterState.state, emailAddress); + lastState = clusterState.state; + triggered = moment().valueOf(); + } + message = getUiMessage(clusterState.state); + resolved = 0; + } else if (!isNonGreen && lastState !== AlertClusterStateState.Green) { + logger.debug(`Cluster state changed from ${lastState} to green`); + executeActions(instance, cluster, clusterState.state, emailAddress, true); + lastState = clusterState.state; + message = getUiMessage(clusterState.state, true); + resolved = moment().valueOf(); + } + + result[clusterState.clusterUuid] = { + state: lastState, + ui: { + message, + isFiring: isNonGreen, + severity, + resolvedMS: resolved, + triggeredMS: triggered, + lastCheckedMS: moment().valueOf(), + }, + } as AlertClusterStatePerClusterState; + } + + return result; + }, + }; +}; diff --git a/x-pack/plugins/monitoring/server/alerts/enums.ts b/x-pack/plugins/monitoring/server/alerts/enums.ts new file mode 100644 index 000000000000..ccff588743af --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/enums.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum AlertClusterStateState { + Green = 'green', + Red = 'red', + Yellow = 'yellow', +} + +export enum AlertCommonPerClusterMessageTokenType { + Time = 'time', + Link = 'link', +} diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 0773af6e7f07..92047e300bc1 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -6,42 +6,31 @@ import moment from 'moment-timezone'; import { getLicenseExpiration } from './license_expiration'; -import { - ALERT_TYPE_LICENSE_EXPIRATION, - MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, -} from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { Logger } from 'src/core/server'; -import { AlertServices, AlertInstance } from '../../../alerting/server'; +import { AlertServices } from '../../../alerting/server'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { - AlertState, - AlertClusterState, - AlertParams, - LicenseExpirationAlertExecutorOptions, + AlertCommonParams, + AlertCommonState, + AlertLicensePerClusterState, + AlertLicense, } from './types'; -import { SavedObject, SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/server'; - -function fillLicense(license: any, clusterUuid?: string) { - return { - hits: { - hits: [ - { - _source: { - license, - cluster_uuid: clusterUuid, - }, - }, - ], - }, - }; -} - -const clusterUuid = 'a4545jhjb'; -const params: AlertParams = { - dateFormat: 'YYYY', - timezone: 'UTC', -}; +import { executeActions } from '../lib/alerts/license_expiration.lib'; +import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; + +jest.mock('../lib/alerts/license_expiration.lib', () => ({ + executeActions: jest.fn(), + getUiMessage: jest.fn(), +})); + +jest.mock('../lib/alerts/get_prepared_alert', () => ({ + getPreparedAlert: jest.fn(() => { + return { + emailAddress: 'foo@foo.com', + }; + }), +})); interface MockServices { callCluster: jest.Mock; @@ -49,428 +38,169 @@ interface MockServices { savedObjectsClient: jest.Mock; } -const alertExecutorOptions: LicenseExpirationAlertExecutorOptions = { - alertId: '', - startedAt: new Date(), - services: { - callCluster: (path: string, opts: any) => new Promise(resolve => resolve()), - alertInstanceFactory: (id: string) => new AlertInstance(), - savedObjectsClient: {} as jest.Mocked, - }, - params: {}, - state: {}, - spaceId: '', - name: '', - tags: [], - previousStartedAt: null, - createdBy: null, - updatedBy: null, -}; - describe('getLicenseExpiration', () => { - const emailAddress = 'foo@foo.com'; - const getUiSettingsService: any = () => ({ - asScopedToClient: (): any => ({ - get: () => new Promise(resolve => resolve(emailAddress)), - }), - }); - const monitoringCluster: any = null; - const logger: Logger = { - warn: jest.fn(), - log: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - get: jest.fn(), + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), }; - const getLogger = (): Logger => logger; - const ccrEnabled = false; - afterEach(() => { - (logger.warn as jest.Mock).mockClear(); - }); - - it('should have the right id and actionGroups', () => { - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); - expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); - }); + const params: AlertCommonParams = { + dateFormat: 'YYYY', + timezone: 'UTC', + }; - it('should return the state if no license is provided', async () => { - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); + const emailAddress = 'foo@foo.com'; + const clusterUuid = 'kdksdfj434'; + const clusterName = 'monitoring_test'; + const dateFormat = 'YYYY-MM-DD'; + const cluster = { clusterUuid, clusterName }; + const defaultUiState = { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), + async function setupAlert( + license: AlertLicense | null, + expiredCheckDateMS: number, + preparedAlertResponse: PreparedAlert | null | undefined = undefined + ): Promise { + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), }; - const state = { foo: 1 }; - - const result = await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - }); - - expect(result).toEqual(state); - }); + const getLogger = (): Logger => logger; + const ccrEnabled = false; + (getPreparedAlert as jest.Mock).mockImplementation(() => { + if (preparedAlertResponse !== undefined) { + return preparedAlertResponse; + } - it('should log a warning if no email is provided', async () => { - const customGetUiSettingsService: any = () => ({ - asScopedToClient: () => ({ - get: () => null, - }), + return { + emailAddress, + data: [license], + clusters: [cluster], + dateFormat, + }; }); - const alert = getLicenseExpiration( - customGetUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense({ - status: 'good', - type: 'basic', - expiry_date_in_millis: moment() - .add(7, 'days') - .valueOf(), - }) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), + const alert = getLicenseExpiration(null as any, null as any, getLogger, ccrEnabled); + const state: AlertCommonState = { + [clusterUuid]: { + expiredCheckDateMS, + ui: { ...defaultUiState }, + } as AlertLicensePerClusterState, }; - const state = {}; + return (await alert.executor({ services, params, state } as any)) as AlertCommonState; + } - await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - }); - - expect((logger.warn as jest.Mock).mock.calls.length).toBe(1); - expect(logger.warn).toHaveBeenCalledWith( - `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` - ); + afterEach(() => { + (executeActions as jest.Mock).mockClear(); + (getPreparedAlert as jest.Mock).mockClear(); }); - it('should fire actions if going to expire', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); + it('should have the right id and actionGroups', () => { + const alert = getLicenseExpiration(null as any, null as any, jest.fn(), false); + expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); + expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); + }); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); + it('should return the state if no license is provided', async () => { + const result = await setupAlert(null, 0, null); + expect(result[clusterUuid].ui).toEqual(defaultUiState); + }); - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'gold', - expiry_date_in_millis: moment() - .add(7, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + it('should fire actions if going to expire', async () => { + const expiryDateMS = moment() + .add(7, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'gold', + expiryDateMS, + clusterUuid, }; - - const state = {}; - - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; - + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(scheduleActions.mock.calls.length).toBe(1); - expect(scheduleActions.mock.calls[0][1].subject).toBe( - 'NEW X-Pack Monitoring: License Expiration' + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress ); - expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should fire actions if the user fixed their license', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'gold', - expiry_date_in_millis: moment() - .add(120, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, - }; - - const state: AlertState = { - [clusterUuid]: { - expiredCheckDateMS: moment() - .subtract(1, 'day') - .valueOf(), - ui: { isFiring: true, severity: 0, message: null, resolvedMS: 0, expirationTime: 0 }, - }, + const expiryDateMS = moment() + .add(365, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'gold', + expiryDateMS, + clusterUuid, }; - - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + const result = await setupAlert(license, 100); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS).toBe(0); - expect(scheduleActions.mock.calls.length).toBe(1); - expect(scheduleActions.mock.calls[0][1].subject).toBe( - 'RESOLVED X-Pack Monitoring: License Expiration' + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress, + true ); - expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should not fire actions for trial license that expire in more than 14 days', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'trial', - expiry_date_in_millis: moment() - .add(15, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + const expiryDateMS = moment() + .add(20, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'trial', + expiryDateMS, + clusterUuid, }; - - const state = {}; - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; - expect(newState.expiredCheckDateMS).toBe(undefined); - expect(scheduleActions).not.toHaveBeenCalled(); + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; + expect(newState.expiredCheckDateMS).toBe(0); + expect(executeActions).not.toHaveBeenCalled(); }); it('should fire actions for trial license that in 14 days or less', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'trial', - expiry_date_in_millis: moment() - .add(13, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + const expiryDateMS = moment() + .add(7, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'trial', + expiryDateMS, + clusterUuid, }; - - const state = {}; - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(scheduleActions.mock.calls.length).toBe(1); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress + ); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 93397ff3641a..2e5356150086 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -5,24 +5,20 @@ */ import moment from 'moment-timezone'; -import { get } from 'lodash'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; -import { ALERT_TYPE_LICENSE_EXPIRATION, INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { AlertType } from '../../../../plugins/alerting/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; -import { fetchDefaultEmailAddress } from '../lib/alerts/fetch_default_email_address'; -import { fetchClusters } from '../lib/alerts/fetch_clusters'; -import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; import { - AlertLicense, - AlertState, - AlertClusterState, - AlertClusterUiState, - LicenseExpirationAlertExecutorOptions, + AlertCommonState, + AlertLicensePerClusterState, + AlertCommonExecutorOptions, + AlertCommonCluster, + AlertLicensePerClusterUiState, } from './types'; -import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { executeActions, getUiMessage } from '../lib/alerts/license_expiration.lib'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; const EXPIRES_DAYS = [60, 30, 14, 7]; @@ -32,14 +28,6 @@ export const getLicenseExpiration = ( getLogger: (...scopes: string[]) => Logger, ccsEnabled: boolean ): AlertType => { - async function getCallCluster(services: any): Promise { - if (!monitoringCluster) { - return services.callCluster; - } - - return monitoringCluster.callAsInternalUser; - } - const logger = getLogger(ALERT_TYPE_LICENSE_EXPIRATION); return { id: ALERT_TYPE_LICENSE_EXPIRATION, @@ -53,54 +41,50 @@ export const getLicenseExpiration = ( }, ], defaultActionGroupId: 'default', - async executor({ - services, - params, - state, - }: LicenseExpirationAlertExecutorOptions): Promise { + async executor({ services, params, state }: AlertCommonExecutorOptions): Promise { logger.debug( `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` ); - const callCluster = await getCallCluster(services); - - // Support CCS use cases by querying to find available remote clusters - // and then adding those to the index pattern we are searching against - let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; - if (ccsEnabled) { - const availableCcs = await fetchAvailableCcs(callCluster); - if (availableCcs.length > 0) { - esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); - } - } - - const clusters = await fetchClusters(callCluster, esIndexPattern); + const preparedAlert = await getPreparedAlert( + ALERT_TYPE_LICENSE_EXPIRATION, + getUiSettingsService, + monitoringCluster, + logger, + ccsEnabled, + services, + fetchLicenses + ); - // Fetch licensing information from cluster_stats documents - const licenses: AlertLicense[] = await fetchLicenses(callCluster, clusters, esIndexPattern); - if (licenses.length === 0) { - logger.warn(`No license found for ${ALERT_TYPE_LICENSE_EXPIRATION}.`); + if (!preparedAlert) { return state; } - const uiSettings = (await getUiSettingsService()).asScopedToClient( - services.savedObjectsClient - ); - const dateFormat: string = await uiSettings.get('dateFormat'); - const timezone: string = await uiSettings.get('dateFormat:tz'); - const emailAddress = await fetchDefaultEmailAddress(uiSettings); - if (!emailAddress) { - // TODO: we can do more here - logger.warn( - `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` - ); - return; - } + const { emailAddress, data: licenses, clusters, dateFormat } = preparedAlert; - const result: AlertState = { ...state }; + const result: AlertCommonState = { ...state }; + const defaultAlertState: AlertLicensePerClusterState = { + expiredCheckDateMS: 0, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }, + }; for (const license of licenses) { - const licenseState: AlertClusterState = state[license.clusterUuid] || {}; + const alertState: AlertLicensePerClusterState = + (state[license.clusterUuid] as AlertLicensePerClusterState) || defaultAlertState; + const cluster = clusters.find( + (c: AlertCommonCluster) => c.clusterUuid === license.clusterUuid + ); + if (!cluster) { + logger.warn(`Unable to find cluster for clusterUuid='${license.clusterUuid}'`); + continue; + } const $expiry = moment.utc(license.expiryDateMS); let isExpired = false; let severity = 0; @@ -123,31 +107,26 @@ export const getLicenseExpiration = ( } } - const ui: AlertClusterUiState = get(licenseState, 'ui', { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - expirationTime: 0, - }); + const ui = alertState.ui; + let triggered = ui.triggeredMS; let resolved = ui.resolvedMS; let message = ui.message; - let expiredCheckDate = licenseState.expiredCheckDateMS; + let expiredCheckDate = alertState.expiredCheckDateMS; const instance = services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION); if (isExpired) { - if (!licenseState.expiredCheckDateMS) { + if (!alertState.expiredCheckDateMS) { logger.debug(`License will expire soon, sending email`); - executeActions(instance, license, $expiry, dateFormat, emailAddress); - expiredCheckDate = moment().valueOf(); + executeActions(instance, cluster, $expiry, dateFormat, emailAddress); + expiredCheckDate = triggered = moment().valueOf(); } - message = getUiMessage(license, timezone); + message = getUiMessage(); resolved = 0; - } else if (!isExpired && licenseState.expiredCheckDateMS) { + } else if (!isExpired && alertState.expiredCheckDateMS) { logger.debug(`License expiration has been resolved, sending email`); - executeActions(instance, license, $expiry, dateFormat, emailAddress, true); + executeActions(instance, cluster, $expiry, dateFormat, emailAddress, true); expiredCheckDate = 0; - message = getUiMessage(license, timezone, true); + message = getUiMessage(true); resolved = moment().valueOf(); } @@ -159,8 +138,10 @@ export const getLicenseExpiration = ( isFiring: expiredCheckDate > 0, severity, resolvedMS: resolved, - }, - }; + triggeredMS: triggered, + lastCheckedMS: moment().valueOf(), + } as AlertLicensePerClusterUiState, + } as AlertLicensePerClusterState; } return result; diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index ff47d6f2ad4d..b689d008b51a 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -5,41 +5,79 @@ */ import { Moment } from 'moment'; import { AlertExecutorOptions } from '../../../alerting/server'; +import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { status: string; type: string; expiryDateMS: number; clusterUuid: string; - clusterName: string; } -export interface AlertState { - [clusterUuid: string]: AlertClusterState; +export interface AlertClusterState { + state: AlertClusterStateState; + clusterUuid: string; +} + +export interface AlertCommonState { + [clusterUuid: string]: AlertCommonPerClusterState; } -export interface AlertClusterState { - expiredCheckDateMS: number | Moment; - ui: AlertClusterUiState; +export interface AlertCommonPerClusterState { + ui: AlertCommonPerClusterUiState; } -export interface AlertClusterUiState { +export interface AlertClusterStatePerClusterState extends AlertCommonPerClusterState { + state: AlertClusterStateState; +} + +export interface AlertLicensePerClusterState extends AlertCommonPerClusterState { + expiredCheckDateMS: number; +} + +export interface AlertCommonPerClusterUiState { isFiring: boolean; severity: number; - message: string | null; + message: AlertCommonPerClusterMessage | null; resolvedMS: number; + lastCheckedMS: number; + triggeredMS: number; +} + +export interface AlertCommonPerClusterMessage { + text: string; // Do this. #link this is a link #link + tokens?: AlertCommonPerClusterMessageToken[]; +} + +export interface AlertCommonPerClusterMessageToken { + startToken: string; + endToken?: string; + type: AlertCommonPerClusterMessageTokenType; +} + +export interface AlertCommonPerClusterMessageLinkToken extends AlertCommonPerClusterMessageToken { + url?: string; +} + +export interface AlertCommonPerClusterMessageTimeToken extends AlertCommonPerClusterMessageToken { + isRelative: boolean; + isAbsolute: boolean; +} + +export interface AlertLicensePerClusterUiState extends AlertCommonPerClusterUiState { expirationTime: number; } -export interface AlertCluster { +export interface AlertCommonCluster { clusterUuid: string; + clusterName: string; } -export interface LicenseExpirationAlertExecutorOptions extends AlertExecutorOptions { - state: AlertState; +export interface AlertCommonExecutorOptions extends AlertExecutorOptions { + state: AlertCommonState; } -export interface AlertParams { +export interface AlertCommonParams { dateFormat: string; timezone: string; } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts new file mode 100644 index 000000000000..81e375734cc5 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { executeActions, getUiMessage } from './cluster_state.lib'; +import { AlertClusterStateState } from '../../alerts/enums'; +import { AlertCommonPerClusterMessageLinkToken } from '../../alerts/types'; + +describe('clusterState lib', () => { + describe('executeActions', () => { + const clusterName = 'clusterA'; + const instance: any = { scheduleActions: jest.fn() }; + const license: any = { clusterName }; + const status = AlertClusterStateState.Green; + const emailAddress = 'test@test.com'; + + beforeEach(() => { + instance.scheduleActions.mockClear(); + }); + + it('should schedule actions when firing', () => { + executeActions(instance, license, status, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: Cluster Status', + message: `Allocate missing replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + + it('should have a different message for red state', () => { + executeActions(instance, license, AlertClusterStateState.Red, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: Cluster Status', + message: `Allocate missing primary and replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + + it('should schedule actions when resolved', () => { + executeActions(instance, license, status, emailAddress, true); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'RESOLVED X-Pack Monitoring: Cluster Status', + message: `This cluster alert has been resolved: Allocate missing replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + }); + + describe('getUiMessage', () => { + it('should return a message when firing', () => { + const message = getUiMessage(AlertClusterStateState.Red, false); + expect(message.text).toBe( + `Elasticsearch cluster status is red. #start_linkAllocate missing primary and replica shards#end_link` + ); + expect(message.tokens && message.tokens.length).toBe(1); + expect(message.tokens && message.tokens[0].startToken).toBe('#start_link'); + expect(message.tokens && message.tokens[0].endToken).toBe('#end_link'); + expect( + message.tokens && (message.tokens[0] as AlertCommonPerClusterMessageLinkToken).url + ).toBe('elasticsearch/indices'); + }); + + it('should return a message when resolved', () => { + const message = getUiMessage(AlertClusterStateState.Green, true); + expect(message.text).toBe(`Elasticsearch cluster status is green.`); + expect(message.tokens).not.toBeDefined(); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts new file mode 100644 index 000000000000..ae66d603507c --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { AlertInstance } from '../../../../alerting/server'; +import { + AlertCommonCluster, + AlertCommonPerClusterMessage, + AlertCommonPerClusterMessageLinkToken, +} from '../../alerts/types'; +import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; + +const RESOLVED_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.resolvedSubject', { + defaultMessage: 'RESOLVED X-Pack Monitoring: Cluster Status', +}); + +const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.newSubject', { + defaultMessage: 'NEW X-Pack Monitoring: Cluster Status', +}); + +const RED_STATUS_MESSAGE = i18n.translate('xpack.monitoring.alerts.clusterStatus.redMessage', { + defaultMessage: 'Allocate missing primary and replica shards', +}); + +const YELLOW_STATUS_MESSAGE = i18n.translate( + 'xpack.monitoring.alerts.clusterStatus.yellowMessage', + { + defaultMessage: 'Allocate missing replica shards', + } +); + +export function executeActions( + instance: AlertInstance, + cluster: AlertCommonCluster, + status: AlertClusterStateState, + emailAddress: string, + resolved: boolean = false +) { + const message = + status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; + if (resolved) { + instance.scheduleActions('default', { + subject: RESOLVED_SUBJECT, + message: `This cluster alert has been resolved: ${message} for cluster '${cluster.clusterName}'`, + to: emailAddress, + }); + } else { + instance.scheduleActions('default', { + subject: NEW_SUBJECT, + message: `${message} for cluster '${cluster.clusterName}'`, + to: emailAddress, + }); + } +} + +export function getUiMessage( + status: AlertClusterStateState, + resolved: boolean = false +): AlertCommonPerClusterMessage { + if (resolved) { + return { + text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.resolvedMessage', { + defaultMessage: `Elasticsearch cluster status is green.`, + }), + }; + } + const message = + status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; + return { + text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.firingMessage', { + defaultMessage: `Elasticsearch cluster status is {status}. #start_link{message}#end_link`, + values: { + status, + message, + }, + }), + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertCommonPerClusterMessageTokenType.Link, + url: 'elasticsearch/indices', + } as AlertCommonPerClusterMessageLinkToken, + ], + }; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts new file mode 100644 index 000000000000..642ae3c39a02 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchClusterState } from './fetch_cluster_state'; + +describe('fetchClusterState', () => { + it('should return the cluster state', async () => { + const status = 'green'; + const clusterUuid = 'sdfdsaj34434'; + const callCluster = jest.fn(() => ({ + hits: { + hits: [ + { + _source: { + cluster_state: { + status, + }, + cluster_uuid: clusterUuid, + }, + }, + ], + }, + })); + + const clusters = [{ clusterUuid, clusterName: 'foo' }]; + const index = '.monitoring-es-*'; + + const state = await fetchClusterState(callCluster, clusters, index); + expect(state).toEqual([ + { + state: status, + clusterUuid, + }, + ]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts new file mode 100644 index 000000000000..66ea30d5f2e9 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { get } from 'lodash'; +import { AlertCommonCluster, AlertClusterState } from '../../alerts/types'; + +export async function fetchClusterState( + callCluster: any, + clusters: AlertCommonCluster[], + index: string +): Promise { + const params = { + index, + filterPath: ['hits.hits._source.cluster_state.status', 'hits.hits._source.cluster_uuid'], + body: { + size: 1, + sort: [{ timestamp: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + terms: { + cluster_uuid: clusters.map(cluster => cluster.clusterUuid), + }, + }, + { + term: { + type: 'cluster_stats', + }, + }, + { + range: { + timestamp: { + gte: 'now-2m', + }, + }, + }, + ], + }, + }, + }, + }; + + const response = await callCluster('search', params); + return get(response, 'hits.hits', []).map((hit: any) => { + return { + state: get(hit, '_source.cluster_state.status'), + clusterUuid: get(hit, '_source.cluster_uuid'), + }; + }); +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts index 78eb9773df15..7a9b61f37707 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -6,21 +6,51 @@ import { fetchClusters } from './fetch_clusters'; describe('fetchClusters', () => { + const clusterUuid = '1sdfds734'; + const clusterName = 'monitoring'; + it('return a list of clusters', async () => { const callCluster = jest.fn().mockImplementation(() => ({ - aggregations: { - clusters: { - buckets: [ - { - key: 'clusterA', + hits: { + hits: [ + { + _source: { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + }, + }, + ], + }, + })); + const index = '.monitoring-es-*'; + const result = await fetchClusters(callCluster, index); + expect(result).toEqual([{ clusterUuid, clusterName }]); + }); + + it('return the metadata name if available', async () => { + const metadataName = 'custom-monitoring'; + const callCluster = jest.fn().mockImplementation(() => ({ + hits: { + hits: [ + { + _source: { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + cluster_settings: { + cluster: { + metadata: { + display_name: metadataName, + }, + }, + }, }, - ], - }, + }, + ], }, })); const index = '.monitoring-es-*'; const result = await fetchClusters(callCluster, index); - expect(result).toEqual([{ clusterUuid: 'clusterA' }]); + expect(result).toEqual([{ clusterUuid, clusterName: metadataName }]); }); it('should limit the time period in the query', async () => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index 8ef7339618a2..d1513ac16fb1 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -4,18 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertCluster } from '../../alerts/types'; +import { AlertCommonCluster } from '../../alerts/types'; -interface AggregationResult { - key: string; -} - -export async function fetchClusters(callCluster: any, index: string): Promise { +export async function fetchClusters( + callCluster: any, + index: string +): Promise { const params = { index, - filterPath: 'aggregations.clusters.buckets', + filterPath: [ + 'hits.hits._source.cluster_settings.cluster.metadata.display_name', + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.cluster_name', + ], body: { - size: 0, + size: 1000, query: { bool: { filter: [ @@ -34,19 +37,21 @@ export async function fetchClusters(callCluster: any, index: string): Promise ({ - clusterUuid: bucket.key, - })); + return get(response, 'hits.hits', []).map((hit: any) => { + const clusterName: string = + get(hit, '_source.cluster_settings.cluster.metadata.display_name') || + get(hit, '_source.cluster_name') || + get(hit, '_source.cluster_uuid'); + return { + clusterUuid: get(hit, '_source.cluster_uuid'), + clusterName, + }; + }); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts index dd6c074e68b1..9dcb4ffb82a5 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -6,28 +6,28 @@ import { fetchLicenses } from './fetch_licenses'; describe('fetchLicenses', () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; + it('return a list of licenses', async () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; const callCluster = jest.fn().mockImplementation(() => ({ hits: { hits: [ { _source: { license, - cluster_name: clusterName, cluster_uuid: clusterUuid, }, }, ], }, })); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; const result = await fetchLicenses(callCluster, clusters, index); expect(result).toEqual([ @@ -36,15 +36,13 @@ describe('fetchLicenses', () => { type: license.type, expiryDateMS: license.expiry_date_in_millis, clusterUuid, - clusterName, }, ]); }); it('should only search for the clusters provided', async () => { - const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; @@ -52,54 +50,11 @@ describe('fetchLicenses', () => { }); it('should limit the time period in the query', async () => { - const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; expect(params.body.query.bool.filter[2].range.timestamp.gte).toBe('now-2m'); }); - - it('should give priority to the metadata name', async () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; - const callCluster = jest.fn().mockImplementation(() => ({ - hits: { - hits: [ - { - _source: { - license, - cluster_name: 'fakeName', - cluster_uuid: clusterUuid, - cluster_settings: { - cluster: { - metadata: { - display_name: clusterName, - }, - }, - }, - }, - }, - ], - }, - })); - const clusters = [{ clusterUuid }]; - const index = '.monitoring-es-*'; - const result = await fetchLicenses(callCluster, clusters, index); - expect(result).toEqual([ - { - status: license.status, - type: license.type, - expiryDateMS: license.expiry_date_in_millis, - clusterUuid, - clusterName, - }, - ]); - }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 31a68e8aa9c3..5b05c907e796 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -4,21 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertLicense, AlertCluster } from '../../alerts/types'; +import { AlertLicense, AlertCommonCluster } from '../../alerts/types'; export async function fetchLicenses( callCluster: any, - clusters: AlertCluster[], + clusters: AlertCommonCluster[], index: string ): Promise { const params = { index, - filterPath: [ - 'hits.hits._source.license.*', - 'hits.hits._source.cluster_settings.cluster.metadata.display_name', - 'hits.hits._source.cluster_uuid', - 'hits.hits._source.cluster_name', - ], + filterPath: ['hits.hits._source.license.*', 'hits.hits._source.cluster_uuid'], body: { size: 1, sort: [{ timestamp: { order: 'desc' } }], @@ -50,17 +45,12 @@ export async function fetchLicenses( const response = await callCluster('search', params); return get(response, 'hits.hits', []).map((hit: any) => { - const clusterName: string = - get(hit, '_source.cluster_settings.cluster.metadata.display_name') || - get(hit, '_source.cluster_name') || - get(hit, '_source.cluster_uuid'); const rawLicense: any = get(hit, '_source.license', {}); const license: AlertLicense = { status: rawLicense.status, type: rawLicense.type, expiryDateMS: rawLicense.expiry_date_in_millis, clusterUuid: get(hit, '_source.cluster_uuid'), - clusterName, }; return license; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts new file mode 100644 index 000000000000..a3bcb61afacd --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchStatus } from './fetch_status'; +import { AlertCommonPerClusterState } from '../../alerts/types'; + +describe('fetchStatus', () => { + const alertType = 'monitoringTest'; + const log = { warn: jest.fn() }; + const start = 0; + const end = 0; + const id = 1; + const defaultUiState = { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; + const alertsClient = { + find: jest.fn(() => ({ + total: 1, + data: [ + { + id, + }, + ], + })), + getAlertState: jest.fn(() => ({ + alertTypeState: { + state: { + ui: defaultUiState, + } as AlertCommonPerClusterState, + }, + })), + }; + + afterEach(() => { + (alertsClient.find as jest.Mock).mockClear(); + (alertsClient.getAlertState as jest.Mock).mockClear(); + }); + + it('should fetch from the alerts client', async () => { + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); + + it('should return alerts that are firing', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: { + state: { + ui: { + ...defaultUiState, + isFiring: true, + }, + } as AlertCommonPerClusterState, + }, + })); + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status.length).toBe(1); + expect(status[0].type).toBe(alertType); + expect(status[0].isFiring).toBe(true); + }); + + it('should return alerts that have been resolved in the time period', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: { + state: { + ui: { + ...defaultUiState, + resolvedMS: 1500, + }, + } as AlertCommonPerClusterState, + }, + })); + + const customStart = 1000; + const customEnd = 2000; + + const status = await fetchStatus( + alertsClient as any, + [alertType], + customStart, + customEnd, + log as any + ); + expect(status.length).toBe(1); + expect(status[0].type).toBe(alertType); + expect(status[0].isFiring).toBe(false); + }); + + it('should pass in the right filter to the alerts client', async () => { + await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect((alertsClient.find as jest.Mock).mock.calls[0][0].options.filter).toBe( + `alert.attributes.alertTypeId:${alertType}` + ); + }); + + it('should return nothing if no alert state is found', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: null, + })) as any; + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); + + it('should return nothing if no alerts are found', async () => { + alertsClient.find = jest.fn(() => ({ + total: 0, + data: [], + })) as any; + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 9f7c1d5a994d..bf6ee965d3b2 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -4,81 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment'; -import { get } from 'lodash'; -import { AlertClusterState } from '../../alerts/types'; -import { ALERT_TYPES, LOGGING_TAG } from '../../../common/constants'; +import { Logger } from '../../../../../../src/core/server'; +import { AlertCommonPerClusterState } from '../../alerts/types'; +import { AlertsClient } from '../../../../alerting/server'; export async function fetchStatus( - callCluster: any, + alertsClient: AlertsClient, + alertTypes: string[], start: number, end: number, - clusterUuid: string, - server: any + log: Logger ): Promise { - // TODO: this shouldn't query task manager directly but rather - // use an api exposed by the alerting/actions plugin - // See https://github.com/elastic/kibana/issues/48442 const statuses = await Promise.all( - ALERT_TYPES.map( + alertTypes.map( type => new Promise(async (resolve, reject) => { - try { - const params = { - index: '.kibana_task_manager', - filterPath: ['hits.hits._source.task.state'], - body: { - size: 1, - sort: [{ updated_at: { order: 'desc' } }], - query: { - bool: { - filter: [ - { - term: { - 'task.taskType': `alerting:${type}`, - }, - }, - ], - }, - }, - }, - }; - - const response = await callCluster('search', params); - const state = get(response, 'hits.hits[0]._source.task.state', '{}'); - const clusterState: AlertClusterState = get( - JSON.parse(state), - `alertTypeState.${clusterUuid}`, - { - expiredCheckDateMS: 0, - ui: { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - expirationTime: 0, - }, - } - ); - const isInBetween = moment(clusterState.ui.resolvedMS).isBetween(start, end); - if (clusterState.ui.isFiring || isInBetween) { - return resolve({ - type, - ...clusterState.ui, - }); - } + // We need to get the id from the alertTypeId + const alerts = await alertsClient.find({ + options: { + filter: `alert.attributes.alertTypeId:${type}`, + }, + }); + if (alerts.total === 0) { return resolve(false); - } catch (err) { - const reason = get(err, 'body.error.type'); - if (reason === 'index_not_found_exception') { - server.log( - ['error', LOGGING_TAG], - `Unable to fetch alerts. Alerts depends on task manager, which has not been started yet.` - ); - } else { - server.log(['error', LOGGING_TAG], err.message); - } + } + + if (alerts.total !== 1) { + log.warn(`Found more than one alert for type ${type} which is unexpected.`); + } + + const id = alerts.data[0].id; + + // Now that we have the id, we can get the state + const states = await alertsClient.getAlertState({ id }); + if (!states || !states.alertTypeState) { + log.warn(`No alert states found for type ${type} which is unexpected.`); return resolve(false); } + + const state = Object.values(states.alertTypeState)[0] as AlertCommonPerClusterState; + const isInBetween = moment(state.ui.resolvedMS).isBetween(start, end); + if (state.ui.isFiring || isInBetween) { + return resolve({ + type, + ...state.ui, + }); + } + return resolve(false); }) ) ); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts new file mode 100644 index 000000000000..1840a2026a75 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getPreparedAlert } from './get_prepared_alert'; +import { fetchClusters } from './fetch_clusters'; +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; + +jest.mock('./fetch_clusters', () => ({ + fetchClusters: jest.fn(), +})); + +jest.mock('./fetch_default_email_address', () => ({ + fetchDefaultEmailAddress: jest.fn(), +})); + +describe('getPreparedAlert', () => { + const uiSettings = { get: jest.fn() }; + const alertType = 'test'; + const getUiSettingsService = async () => ({ + asScopedToClient: () => uiSettings, + }); + const monitoringCluster = null; + const logger = { warn: jest.fn() }; + const ccsEnabled = false; + const services = { + callCluster: jest.fn(), + savedObjectsClient: null, + }; + const emailAddress = 'foo@foo.com'; + const data = [{ foo: 1 }]; + const dataFetcher = () => data; + const clusterName = 'MonitoringCluster'; + const clusterUuid = 'sdf34sdf'; + const clusters = [{ clusterName, clusterUuid }]; + + afterEach(() => { + (uiSettings.get as jest.Mock).mockClear(); + (services.callCluster as jest.Mock).mockClear(); + (fetchClusters as jest.Mock).mockClear(); + (fetchDefaultEmailAddress as jest.Mock).mockClear(); + }); + + beforeEach(() => { + (fetchClusters as jest.Mock).mockImplementation(() => clusters); + (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => emailAddress); + }); + + it('should return fields as expected', async () => { + (uiSettings.get as jest.Mock).mockImplementation(() => { + return emailAddress; + }); + + const alert = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + ccsEnabled, + services as any, + dataFetcher as any + ); + + expect(alert && alert.emailAddress).toBe(emailAddress); + expect(alert && alert.data).toBe(data); + }); + + it('should add ccs if specified', async () => { + const ccsClusterName = 'remoteCluster'; + (services.callCluster as jest.Mock).mockImplementation(() => { + return { + [ccsClusterName]: { + connected: true, + }, + }; + }); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(true); + }); + + it('should ignore ccs if no remote clusters are available', async () => { + const ccsClusterName = 'remoteCluster'; + (services.callCluster as jest.Mock).mockImplementation(() => { + return { + [ccsClusterName]: { + connected: false, + }, + }; + }); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(false); + }); + + it('should pass in the clusters into the data fetcher', async () => { + const customDataFetcher = jest.fn(() => data); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + customDataFetcher as any + ); + + expect((customDataFetcher as jest.Mock).mock.calls[0][1]).toBe(clusters); + }); + + it('should return nothing if the data fetcher returns nothing', async () => { + const customDataFetcher = jest.fn(() => []); + + const result = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + customDataFetcher as any + ); + + expect(result).toBe(null); + }); + + it('should return nothing if there is no email address', async () => { + (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => null); + + const result = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect(result).toBe(null); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts new file mode 100644 index 000000000000..83a9e26e4c58 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { AlertServices } from '../../../../alerting/server'; +import { AlertCommonCluster } from '../../alerts/types'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; +import { fetchAvailableCcs } from './fetch_available_ccs'; +import { getCcsIndexPattern } from './get_ccs_index_pattern'; +import { fetchClusters } from './fetch_clusters'; +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; + +export interface PreparedAlert { + emailAddress: string; + clusters: AlertCommonCluster[]; + data: any[]; + timezone: string; + dateFormat: string; +} + +async function getCallCluster( + monitoringCluster: ICustomClusterClient, + services: Pick +): Promise { + if (!monitoringCluster) { + return services.callCluster; + } + + return monitoringCluster.callAsInternalUser; +} + +export async function getPreparedAlert( + alertType: string, + getUiSettingsService: () => Promise, + monitoringCluster: ICustomClusterClient, + logger: Logger, + ccsEnabled: boolean, + services: Pick, + dataFetcher: ( + callCluster: CallCluster, + clusters: AlertCommonCluster[], + esIndexPattern: string + ) => Promise +): Promise { + const callCluster = await getCallCluster(monitoringCluster, services); + + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; + if (ccsEnabled) { + const availableCcs = await fetchAvailableCcs(callCluster); + if (availableCcs.length > 0) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + } + + const clusters = await fetchClusters(callCluster, esIndexPattern); + + // Fetch the specific data + const data = await dataFetcher(callCluster, clusters, esIndexPattern); + if (data.length === 0) { + logger.warn(`No data found for ${alertType}.`); + return null; + } + + const uiSettings = (await getUiSettingsService()).asScopedToClient(services.savedObjectsClient); + const dateFormat: string = await uiSettings.get('dateFormat'); + const timezone: string = await uiSettings.get('dateFormat:tz'); + const emailAddress = await fetchDefaultEmailAddress(uiSettings); + if (!emailAddress) { + // TODO: we can do more here + logger.warn(`Unable to send email for ${alertType} because there is no email configured.`); + return null; + } + + return { + emailAddress, + data, + clusters, + dateFormat, + timezone, + }; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts index 1a2eb1e44be8..6c0301b6cc34 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts @@ -39,17 +39,26 @@ describe('licenseExpiration lib', () => { }); describe('getUiMessage', () => { - const timezone = 'Europe/London'; - const license: any = { expiryDateMS: moment.tz('2020-01-20 08:00:00', timezone).utc() }; - it('should return a message when firing', () => { - const message = getUiMessage(license, timezone, false); - expect(message).toBe(`This cluster's license is going to expire in #relative at #absolute.`); + const message = getUiMessage(false); + expect(message.text).toBe( + `This cluster's license is going to expire in #relative at #absolute. #start_linkPlease update your license#end_link` + ); + // LOL How do I avoid this in TS???? + if (!message.tokens) { + return expect(false).toBe(true); + } + expect(message.tokens.length).toBe(3); + expect(message.tokens[0].startToken).toBe('#relative'); + expect(message.tokens[1].startToken).toBe('#absolute'); + expect(message.tokens[2].startToken).toBe('#start_link'); + expect(message.tokens[2].endToken).toBe('#end_link'); }); it('should return a message when resolved', () => { - const message = getUiMessage(license, timezone, true); - expect(message).toBe(`This cluster's license is active.`); + const message = getUiMessage(true); + expect(message.text).toBe(`This cluster's license is active.`); + expect(message.tokens).not.toBeDefined(); }); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index 41b68d69bbd2..a590021a2f29 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -6,7 +6,13 @@ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { AlertInstance } from '../../../../alerting/server'; -import { AlertLicense } from '../../alerts/types'; +import { + AlertCommonPerClusterMessageLinkToken, + AlertCommonPerClusterMessageTimeToken, + AlertCommonCluster, + AlertCommonPerClusterMessage, +} from '../../alerts/types'; +import { AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; const RESOLVED_SUBJECT = i18n.translate( 'xpack.monitoring.alerts.licenseExpiration.resolvedSubject', @@ -21,7 +27,7 @@ const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.licenseExpiration.ne export function executeActions( instance: AlertInstance, - license: AlertLicense, + cluster: AlertCommonCluster, $expiry: Moment, dateFormat: string, emailAddress: string, @@ -31,14 +37,14 @@ export function executeActions( instance.scheduleActions('default', { subject: RESOLVED_SUBJECT, message: `This cluster alert has been resolved: Cluster '${ - license.clusterName + cluster.clusterName }' license was going to expire on ${$expiry.format(dateFormat)}.`, to: emailAddress, }); } else { instance.scheduleActions('default', { subject: NEW_SUBJECT, - message: `Cluster '${license.clusterName}' license is going to expire on ${$expiry.format( + message: `Cluster '${cluster.clusterName}' license is going to expire on ${$expiry.format( dateFormat )}. Please update your license.`, to: emailAddress, @@ -46,13 +52,43 @@ export function executeActions( } } -export function getUiMessage(license: AlertLicense, timezone: string, resolved: boolean = false) { +export function getUiMessage(resolved: boolean = false): AlertCommonPerClusterMessage { if (resolved) { - return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { - defaultMessage: `This cluster's license is active.`, - }); + return { + text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { + defaultMessage: `This cluster's license is active.`, + }), + }; } - return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { - defaultMessage: `This cluster's license is going to expire in #relative at #absolute.`, + const linkText = i18n.translate('xpack.monitoring.alerts.licenseExpiration.linkText', { + defaultMessage: 'Please update your license', }); + return { + text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { + defaultMessage: `This cluster's license is going to expire in #relative at #absolute. #start_link{linkText}#end_link`, + values: { + linkText, + }, + }), + tokens: [ + { + startToken: '#relative', + type: AlertCommonPerClusterMessageTokenType.Time, + isRelative: true, + isAbsolute: false, + } as AlertCommonPerClusterMessageTimeToken, + { + startToken: '#absolute', + type: AlertCommonPerClusterMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + } as AlertCommonPerClusterMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertCommonPerClusterMessageTokenType.Link, + url: 'license', + } as AlertCommonPerClusterMessageLinkToken, + ], + }; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index c5091c36c3bb..1bddede52207 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -29,6 +29,7 @@ import { CODE_PATH_BEATS, CODE_PATH_APM, KIBANA_ALERTING_ENABLED, + ALERT_TYPES, } from '../../../common/constants'; import { getApmsForClusters } from '../apm/get_apms_for_clusters'; import { i18n } from '@kbn/i18n'; @@ -102,15 +103,8 @@ export async function getClustersFromRequest( if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { if (KIBANA_ALERTING_ENABLED) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - const callCluster = (...args) => callWithRequest(req, ...args); - cluster.alerts = await fetchStatus( - callCluster, - start, - end, - cluster.cluster_uuid, - req.server - ); + const alertsClient = req.getAlertsClient ? req.getAlertsClient() : null; + cluster.alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); } else { cluster.alerts = await alertsClusterSearch( req, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 24d8bcaa4397..784226dca66f 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,6 +47,7 @@ import { PluginSetupContract as AlertingPluginSetupContract, } from '../../alerting/server'; import { getLicenseExpiration } from './alerts/license_expiration'; +import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; export interface LegacyAPI { @@ -154,6 +155,17 @@ export class Plugin { config.ui.ccs.enabled ) ); + plugins.alerting.registerType( + getClusterState( + async () => { + const coreStart = (await core.getStartServices())[0]; + return coreStart.uiSettings; + }, + cluster, + this.getLogger, + config.ui.ccs.enabled + ) + ); } // Initialize telemetry diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js index 56922bd8e87e..d5a43d32f600 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js @@ -8,8 +8,12 @@ import { schema } from '@kbn/config-schema'; import { isFunction } from 'lodash'; import { ALERT_TYPE_LICENSE_EXPIRATION, + ALERT_TYPE_CLUSTER_STATE, MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, + ALERT_TYPES, } from '../../../../../common/constants'; +import { handleError } from '../../../../lib/errors'; +import { fetchStatus } from '../../../../lib/alerts/fetch_status'; async function createAlerts(req, alertsClient, { selectedEmailActionId }) { const createdAlerts = []; @@ -17,7 +21,21 @@ async function createAlerts(req, alertsClient, { selectedEmailActionId }) { // Create alerts const ALERT_TYPES = { [ALERT_TYPE_LICENSE_EXPIRATION]: { - schedule: { interval: '10s' }, + schedule: { interval: '1m' }, + actions: [ + { + group: 'default', + id: selectedEmailActionId, + params: { + subject: '{{context.subject}}', + message: `{{context.message}}`, + to: ['{{context.to}}'], + }, + }, + ], + }, + [ALERT_TYPE_CLUSTER_STATE]: { + schedule: { interval: '1m' }, actions: [ { group: 'default', @@ -86,4 +104,37 @@ export function createKibanaAlertsRoute(server) { return { alerts, emailResponse }; }, }); + + server.route({ + method: 'POST', + path: '/api/monitoring/v1/alert_status', + config: { + validate: { + payload: schema.object({ + timeRange: schema.object({ + min: schema.string(), + max: schema.string(), + }), + }), + }, + }, + async handler(req, headers) { + const alertsClient = isFunction(req.getAlertsClient) ? req.getAlertsClient() : null; + if (!alertsClient) { + return headers.response().code(404); + } + + const start = req.payload.timeRange.min; + const end = req.payload.timeRange.max; + let alerts; + + try { + alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); + } catch (err) { + throw handleError(err, req); + } + + return { alerts }; + }, + }); } From 813d6cb796b42738fc24db43b0df2f4d337a06ed Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 6 Apr 2020 21:42:43 +0200 Subject: [PATCH 23/27] [SIEM] View signal in default timeline (#62616) * adds test data * adds 'View a signal in timeline' test * implements test * fixes implementation * changes view signal for investigate signal --- .../integration/detections_timeline.spec.ts | 43 + .../plugins/siem/cypress/objects/timeline.ts | 10 + .../siem/cypress/screens/detections.ts | 6 + .../plugins/siem/cypress/screens/timeline.ts | 2 + .../plugins/siem/cypress/tasks/detections.ts | 14 + .../es_archives/timeline_signals/data.json.gz | Bin 0 -> 225608 bytes .../timeline_signals/mappings.json | 9063 +++++++++++++++++ 7 files changed, 9138 insertions(+) create mode 100644 x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts create mode 100644 x-pack/legacy/plugins/siem/cypress/objects/timeline.ts create mode 100644 x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz create mode 100644 x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json diff --git a/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts new file mode 100644 index 000000000000..2cac6e0f603b --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SIGNAL_ID } from '../screens/detections'; +import { PROVIDER_BADGE } from '../screens/timeline'; + +import { + expandFirstSignal, + investigateFirstSignalInTimeline, + waitForSignalsPanelToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPage } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detections timeline', () => { + beforeEach(() => { + esArchiverLoad('timeline_signals'); + loginAndWaitForPage(DETECTIONS); + }); + + afterEach(() => { + esArchiverUnload('timeline_signals'); + }); + + it('Investigate signal in default timeline', () => { + waitForSignalsPanelToBeLoaded(); + expandFirstSignal(); + cy.get(SIGNAL_ID) + .first() + .invoke('text') + .then(eventId => { + investigateFirstSignalInTimeline(); + cy.get(PROVIDER_BADGE) + .invoke('text') + .should('eql', `_id: "${eventId}"`); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts b/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts new file mode 100644 index 000000000000..bca99bfa9266 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface Timeline { + title: string; + query: string; +} diff --git a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts index cb776be8d7b6..d9ffa5b5a4ab 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts @@ -6,6 +6,8 @@ export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; +export const EXPAND_SIGNAL_BTN = '[data-test-subj="expand-event"]'; + export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; @@ -20,8 +22,12 @@ export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; +export const SEND_SIGNAL_TO_TIMELINE_BTN = '[data-test-subj="send-signal-to-timeline-button"]'; + export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; export const SIGNALS = '[data-test-subj="event"]'; +export const SIGNAL_ID = '[data-test-subj="draggable-content-_id"]'; + export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts index fbce585a70f8..53d8273d9ce6 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts @@ -14,6 +14,8 @@ export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; +export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; + export const SEARCH_OR_FILTER_CONTAINER = '[data-test-subj="timeline-search-or-filter-search-container"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts index abea4a887b8b..c30a178eab48 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts @@ -6,11 +6,13 @@ import { CLOSED_SIGNALS_BTN, + EXPAND_SIGNAL_BTN, LOADING_SIGNALS_PANEL, MANAGE_SIGNAL_DETECTION_RULES_BTN, OPEN_CLOSE_SIGNAL_BTN, OPEN_CLOSE_SIGNALS_BTN, OPENED_SIGNALS_BTN, + SEND_SIGNAL_TO_TIMELINE_BTN, SIGNALS, SIGNAL_CHECKBOX, } from '../screens/detections'; @@ -26,6 +28,12 @@ export const closeSignals = () => { cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); }; +export const expandFirstSignal = () => { + cy.get(EXPAND_SIGNAL_BTN) + .first() + .click({ force: true }); +}; + export const goToClosedSignals = () => { cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); }; @@ -58,6 +66,12 @@ export const selectNumberOfSignals = (numberOfSignals: number) => { } }; +export const investigateFirstSignalInTimeline = () => { + cy.get(SEND_SIGNAL_TO_TIMELINE_BTN) + .first() + .click({ force: true }); +}; + export const waitForSignals = () => { cy.get(REFRESH_BUTTON) .invoke('text') diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..485d9868efd21af89ca7145386c7b95142f17205 GIT binary patch literal 225608 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�v;L$hnqumq#%Q&~bCD~dtqmHn?q_jJa+^^T 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%0AdcUA0@M3 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

b7 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

6HtE@>Q|5mvQH37=0N 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 RO?c=MA;jMto;NH2{{i5^BW3^q literal 0 HcmV?d00001 diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json new file mode 100644 index 000000000000..a1a9e7bfeae7 --- /dev/null +++ b/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json @@ -0,0 +1,9063 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "c0c235fba02ebd2a2412bcda79009b58", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "e588043a01d3d43477e7cad7efa0f5d8", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "config": "87aca8fdb053154f11383fce3dbf3edf", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "84b320fd67209906333ffce261128462", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-detection-engine-rule-status": "0367e4d775814b56a4bee29384f9aafe", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "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": { + "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-services-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "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" + } + } + }, + "config": { + "dynamic": "true", + "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" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "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" + } + } + }, + "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" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "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" + }, + "metric": { + "properties": { + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "time": { + "type": "integer" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "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": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "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" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "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": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "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" + } + } + } + } + }, + "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" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "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" + } + } + }, + "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": { + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "ignore_above": 256, + "type": "keyword" + }, + "sendUsageFrom": { + "ignore_above": 256, + "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": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "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" + } + } + } + } + }, + "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" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".siem-signals-default": { + "is_write_index": true + } + }, + "index": ".siem-signals-default-000001", + "mappings": { + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "doc_values": false, + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "signal": { + "properties": { + "ancestors": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_event": { + "properties": { + "action": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "code": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + }, + "module": { + "type": "keyword" + }, + "original": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "outcome": { + "type": "keyword" + }, + "provider": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_time": { + "type": "date" + }, + "parent": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "false_positives": { + "type": "keyword" + }, + "filters": { + "type": "object" + }, + "from": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "immutable": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "language": { + "type": "keyword" + }, + "max_signals": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "output_index": { + "type": "keyword" + }, + "query": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "keyword" + }, + "rule_id": { + "type": "keyword" + }, + "saved_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "size": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + } + } + }, + "timeline_id": { + "type": "keyword" + }, + "timeline_title": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".siem-signals-default", + "rollover_alias": ".siem-signals-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "auditbeat-7.6.2": { + "is_write_index": true + } + }, + "index": "auditbeat-7.6.2-2020.03.20-000001", + "mappings": { + "_meta": { + "beat": "auditbeat", + "version": "7.6.2" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "dns.answers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "dns.answers.*" + } + }, + { + "log.syslog": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "log.syslog.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "auditd": { + "properties": { + "data": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "a1": { + "ignore_above": 1024, + "type": "keyword" + }, + "a2": { + "ignore_above": 1024, + "type": "keyword" + }, + "a3": { + "ignore_above": 1024, + "type": "keyword" + }, + "a[0-3]": { + "ignore_above": 1024, + "type": "keyword" + }, + "acct": { + "ignore_above": 1024, + "type": "keyword" + }, + "acl": { + "ignore_above": 1024, + "type": "keyword" + }, + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "added": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "apparmor": { + "ignore_above": 1024, + "type": "keyword" + }, + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "argc": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_wait_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_failure": { + "ignore_above": 1024, + "type": "keyword" + }, + "banners": { + "ignore_above": 1024, + "type": "keyword" + }, + "bool": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "capability": { + "ignore_above": 1024, + "type": "keyword" + }, + "cgroup": { + "ignore_above": 1024, + "type": "keyword" + }, + "changed": { + "ignore_above": 1024, + "type": "keyword" + }, + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "compat": { + "ignore_above": 1024, + "type": "keyword" + }, + "daddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "default-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "dmac": { + "ignore_above": 1024, + "type": "keyword" + }, + "dport": { + "ignore_above": 1024, + "type": "keyword" + }, + "enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "entries": { + "ignore_above": 1024, + "type": "keyword" + }, + "exit": { + "ignore_above": 1024, + "type": "keyword" + }, + "fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "fd": { + "ignore_above": 1024, + "type": "keyword" + }, + "fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "feature": { + "ignore_above": 1024, + "type": "keyword" + }, + "fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "file": { + "ignore_above": 1024, + "type": "keyword" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "format": { + "ignore_above": 1024, + "type": "keyword" + }, + "fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "grantors": { + "ignore_above": 1024, + "type": "keyword" + }, + "grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "hook": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "icmp_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "igid": { + "ignore_above": 1024, + "type": "keyword" + }, + "img-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "inif": { + "ignore_above": 1024, + "type": "keyword" + }, + "ino": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "invalid_context": { + "ignore_above": 1024, + "type": "keyword" + }, + "ioctlcmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipx-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "iuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "ksize": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "len": { + "ignore_above": 1024, + "type": "keyword" + }, + "list": { + "ignore_above": 1024, + "type": "keyword" + }, + "lport": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "macproto": { + "ignore_above": 1024, + "type": "keyword" + }, + "maj": { + "ignore_above": 1024, + "type": "keyword" + }, + "major": { + "ignore_above": 1024, + "type": "keyword" + }, + "minor": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "nargs": { + "ignore_above": 1024, + "type": "keyword" + }, + "net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ocomm": { + "ignore_above": 1024, + "type": "keyword" + }, + "oflag": { + "ignore_above": 1024, + "type": "keyword" + }, + "old": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_val": { + "ignore_above": 1024, + "type": "keyword" + }, + "op": { + "ignore_above": 1024, + "type": "keyword" + }, + "opid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oses": { + "ignore_above": 1024, + "type": "keyword" + }, + "outif": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "ignore_above": 1024, + "type": "keyword" + }, + "per": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm_mask": { + "ignore_above": 1024, + "type": "keyword" + }, + "permissive": { + "ignore_above": 1024, + "type": "keyword" + }, + "pfs": { + "ignore_above": 1024, + "type": "keyword" + }, + "printer": { + "ignore_above": 1024, + "type": "keyword" + }, + "prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "proto": { + "ignore_above": 1024, + "type": "keyword" + }, + "qbytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "range": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "removed": { + "ignore_above": 1024, + "type": "keyword" + }, + "res": { + "ignore_above": 1024, + "type": "keyword" + }, + "resrc": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "ignore_above": 1024, + "type": "keyword" + }, + "sauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "scontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "selected-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperm": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperms": { + "ignore_above": 1024, + "type": "keyword" + }, + "seqno": { + "ignore_above": 1024, + "type": "keyword" + }, + "seresult": { + "ignore_above": 1024, + "type": "keyword" + }, + "ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "sig": { + "ignore_above": 1024, + "type": "keyword" + }, + "sigev_signo": { + "ignore_above": 1024, + "type": "keyword" + }, + "smac": { + "ignore_above": 1024, + "type": "keyword" + }, + "socket": { + "properties": { + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "ignore_above": 1024, + "type": "keyword" + }, + "saddr": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "spid": { + "ignore_above": 1024, + "type": "keyword" + }, + "sport": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "subj": { + "ignore_above": 1024, + "type": "keyword" + }, + "success": { + "ignore_above": 1024, + "type": "keyword" + }, + "syscall": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "tclass": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "uri": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "val": { + "ignore_above": 1024, + "type": "keyword" + }, + "ver": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "watch": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "paths": { + "properties": { + "dev": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "nametype": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_role": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "objtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "ogid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ouid": { + "ignore_above": 1024, + "type": "keyword" + }, + "rdev": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "session": { + "ignore_above": 1024, + "type": "keyword" + }, + "summary": { + "properties": { + "actor": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "how": { + "ignore_above": 1024, + "type": "keyword" + }, + "object": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "fields": { + "raw": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "selinux": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "setgid": { + "type": "boolean" + }, + "setuid": { + "type": "boolean" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geoip": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jolokia": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "secured": { + "type": "boolean" + }, + "server": { + "properties": { + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "socket": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "system": { + "properties": { + "audit": { + "properties": { + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "boottime": { + "type": "date" + }, + "containerized": { + "type": "boolean" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timezone": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "properties": { + "sec": { + "type": "long" + } + } + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "package": { + "properties": { + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "installtime": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "release": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "summary": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "properties": { + "last_changed": { + "type": "date" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shell": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "user_information": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tracing": { + "properties": { + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name_map": { + "type": "object" + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selinux": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": "auditbeat", + "rollover_alias": "auditbeat-7.6.2" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1", + "query": { + "default_field": [ + "message", + "tags", + "agent.ephemeral_id", + "agent.id", + "agent.name", + "agent.type", + "agent.version", + "as.organization.name", + "client.address", + "client.as.organization.name", + "client.domain", + "client.geo.city_name", + "client.geo.continent_name", + "client.geo.country_iso_code", + "client.geo.country_name", + "client.geo.name", + "client.geo.region_iso_code", + "client.geo.region_name", + "client.mac", + "client.registered_domain", + "client.top_level_domain", + "client.user.domain", + "client.user.email", + "client.user.full_name", + "client.user.group.domain", + "client.user.group.id", + "client.user.group.name", + "client.user.hash", + "client.user.id", + "client.user.name", + "cloud.account.id", + "cloud.availability_zone", + "cloud.instance.id", + "cloud.instance.name", + "cloud.machine.type", + "cloud.provider", + "cloud.region", + "container.id", + "container.image.name", + "container.image.tag", + "container.name", + "container.runtime", + "destination.address", + "destination.as.organization.name", + "destination.domain", + "destination.geo.city_name", + "destination.geo.continent_name", + "destination.geo.country_iso_code", + "destination.geo.country_name", + "destination.geo.name", + "destination.geo.region_iso_code", + "destination.geo.region_name", + "destination.mac", + "destination.registered_domain", + "destination.top_level_domain", + "destination.user.domain", + "destination.user.email", + "destination.user.full_name", + "destination.user.group.domain", + "destination.user.group.id", + "destination.user.group.name", + "destination.user.hash", + "destination.user.id", + "destination.user.name", + "dns.answers.class", + "dns.answers.data", + "dns.answers.name", + "dns.answers.type", + "dns.header_flags", + "dns.id", + "dns.op_code", + "dns.question.class", + "dns.question.name", + "dns.question.registered_domain", + "dns.question.subdomain", + "dns.question.top_level_domain", + "dns.question.type", + "dns.response_code", + "dns.type", + "ecs.version", + "error.code", + "error.id", + "error.message", + "error.stack_trace", + "error.type", + "event.action", + "event.category", + "event.code", + "event.dataset", + "event.hash", + "event.id", + "event.kind", + "event.module", + "event.original", + "event.outcome", + "event.provider", + "event.timezone", + "event.type", + "file.device", + "file.directory", + "file.extension", + "file.gid", + "file.group", + "file.hash.md5", + "file.hash.sha1", + "file.hash.sha256", + "file.hash.sha512", + "file.inode", + "file.mode", + "file.name", + "file.owner", + "file.path", + "file.target_path", + "file.type", + "file.uid", + "geo.city_name", + "geo.continent_name", + "geo.country_iso_code", + "geo.country_name", + "geo.name", + "geo.region_iso_code", + "geo.region_name", + "group.domain", + "group.id", + "group.name", + "hash.md5", + "hash.sha1", + "hash.sha256", + "hash.sha512", + "host.architecture", + "host.geo.city_name", + "host.geo.continent_name", + "host.geo.country_iso_code", + "host.geo.country_name", + "host.geo.name", + "host.geo.region_iso_code", + "host.geo.region_name", + "host.hostname", + "host.id", + "host.mac", + "host.name", + "host.os.family", + "host.os.full", + "host.os.kernel", + "host.os.name", + "host.os.platform", + "host.os.version", + "host.type", + "host.user.domain", + "host.user.email", + "host.user.full_name", + "host.user.group.domain", + "host.user.group.id", + "host.user.group.name", + "host.user.hash", + "host.user.id", + "host.user.name", + "http.request.body.content", + "http.request.method", + "http.request.referrer", + "http.response.body.content", + "http.version", + "log.level", + "log.logger", + "log.origin.file.name", + "log.origin.function", + "log.original", + "log.syslog.facility.name", + "log.syslog.severity.name", + "network.application", + "network.community_id", + "network.direction", + "network.iana_number", + "network.name", + "network.protocol", + "network.transport", + "network.type", + "observer.geo.city_name", + "observer.geo.continent_name", + "observer.geo.country_iso_code", + "observer.geo.country_name", + "observer.geo.name", + "observer.geo.region_iso_code", + "observer.geo.region_name", + "observer.hostname", + "observer.mac", + "observer.name", + "observer.os.family", + "observer.os.full", + "observer.os.kernel", + "observer.os.name", + "observer.os.platform", + "observer.os.version", + "observer.product", + "observer.serial_number", + "observer.type", + "observer.vendor", + "observer.version", + "organization.id", + "organization.name", + "os.family", + "os.full", + "os.kernel", + "os.name", + "os.platform", + "os.version", + "package.architecture", + "package.checksum", + "package.description", + "package.install_scope", + "package.license", + "package.name", + "package.path", + "package.version", + "process.args", + "text", + "process.executable", + "process.hash.md5", + "process.hash.sha1", + "process.hash.sha256", + "process.hash.sha512", + "process.name", + "text", + "text", + "text", + "text", + "text", + "process.thread.name", + "process.title", + "process.working_directory", + "server.address", + "server.as.organization.name", + "server.domain", + "server.geo.city_name", + "server.geo.continent_name", + "server.geo.country_iso_code", + "server.geo.country_name", + "server.geo.name", + "server.geo.region_iso_code", + "server.geo.region_name", + "server.mac", + "server.registered_domain", + "server.top_level_domain", + "server.user.domain", + "server.user.email", + "server.user.full_name", + "server.user.group.domain", + "server.user.group.id", + "server.user.group.name", + "server.user.hash", + "server.user.id", + "server.user.name", + "service.ephemeral_id", + "service.id", + "service.name", + "service.node.name", + "service.state", + "service.type", + "service.version", + "source.address", + "source.as.organization.name", + "source.domain", + "source.geo.city_name", + "source.geo.continent_name", + "source.geo.country_iso_code", + "source.geo.country_name", + "source.geo.name", + "source.geo.region_iso_code", + "source.geo.region_name", + "source.mac", + "source.registered_domain", + "source.top_level_domain", + "source.user.domain", + "source.user.email", + "source.user.full_name", + "source.user.group.domain", + "source.user.group.id", + "source.user.group.name", + "source.user.hash", + "source.user.id", + "source.user.name", + "threat.framework", + "threat.tactic.id", + "threat.tactic.name", + "threat.tactic.reference", + "threat.technique.id", + "threat.technique.name", + "threat.technique.reference", + "tracing.trace.id", + "tracing.transaction.id", + "url.domain", + "url.extension", + "url.fragment", + "url.full", + "url.original", + "url.password", + "url.path", + "url.query", + "url.registered_domain", + "url.scheme", + "url.top_level_domain", + "url.username", + "user.domain", + "user.email", + "user.full_name", + "user.group.domain", + "user.group.id", + "user.group.name", + "user.hash", + "user.id", + "user.name", + "user_agent.device.name", + "user_agent.name", + "text", + "user_agent.original", + "user_agent.os.family", + "user_agent.os.full", + "user_agent.os.kernel", + "user_agent.os.name", + "user_agent.os.platform", + "user_agent.os.version", + "user_agent.version", + "text", + "agent.hostname", + "timeseries.instance", + "cloud.project.id", + "cloud.image.id", + "host.os.build", + "host.os.codename", + "kubernetes.pod.name", + "kubernetes.pod.uid", + "kubernetes.namespace", + "kubernetes.node.name", + "kubernetes.replicaset.name", + "kubernetes.deployment.name", + "kubernetes.statefulset.name", + "kubernetes.container.name", + "kubernetes.container.image", + "jolokia.agent.version", + "jolokia.agent.id", + "jolokia.server.product", + "jolokia.server.version", + "jolokia.server.vendor", + "jolokia.url", + "raw", + "file.origin", + "file.selinux.user", + "file.selinux.role", + "file.selinux.domain", + "file.selinux.level", + "user.audit.id", + "user.audit.name", + "user.effective.id", + "user.effective.name", + "user.effective.group.id", + "user.effective.group.name", + "user.filesystem.id", + "user.filesystem.name", + "user.filesystem.group.id", + "user.filesystem.group.name", + "user.saved.id", + "user.saved.name", + "user.saved.group.id", + "user.saved.group.name", + "user.selinux.user", + "user.selinux.role", + "user.selinux.domain", + "user.selinux.level", + "user.selinux.category", + "source.path", + "destination.path", + "auditd.message_type", + "auditd.session", + "auditd.result", + "auditd.summary.actor.primary", + "auditd.summary.actor.secondary", + "auditd.summary.object.type", + "auditd.summary.object.primary", + "auditd.summary.object.secondary", + "auditd.summary.how", + "auditd.paths.inode", + "auditd.paths.dev", + "auditd.paths.obj_user", + "auditd.paths.obj_role", + "auditd.paths.obj_domain", + "auditd.paths.obj_level", + "auditd.paths.objtype", + "auditd.paths.ouid", + "auditd.paths.rdev", + "auditd.paths.nametype", + "auditd.paths.ogid", + "auditd.paths.item", + "auditd.paths.mode", + "auditd.paths.name", + "auditd.data.action", + "auditd.data.minor", + "auditd.data.acct", + "auditd.data.addr", + "auditd.data.cipher", + "auditd.data.id", + "auditd.data.entries", + "auditd.data.kind", + "auditd.data.ksize", + "auditd.data.spid", + "auditd.data.arch", + "auditd.data.argc", + "auditd.data.major", + "auditd.data.unit", + "auditd.data.table", + "auditd.data.terminal", + "auditd.data.grantors", + "auditd.data.direction", + "auditd.data.op", + "auditd.data.tty", + "auditd.data.syscall", + "auditd.data.data", + "auditd.data.family", + "auditd.data.mac", + "auditd.data.pfs", + "auditd.data.items", + "auditd.data.a0", + "auditd.data.a1", + "auditd.data.a2", + "auditd.data.a3", + "auditd.data.hostname", + "auditd.data.lport", + "auditd.data.rport", + "auditd.data.exit", + "auditd.data.fp", + "auditd.data.laddr", + "auditd.data.sport", + "auditd.data.capability", + "auditd.data.nargs", + "auditd.data.new-enabled", + "auditd.data.audit_backlog_limit", + "auditd.data.dir", + "auditd.data.cap_pe", + "auditd.data.model", + "auditd.data.new_pp", + "auditd.data.old-enabled", + "auditd.data.oauid", + "auditd.data.old", + "auditd.data.banners", + "auditd.data.feature", + "auditd.data.vm-ctx", + "auditd.data.opid", + "auditd.data.seperms", + "auditd.data.seresult", + "auditd.data.new-rng", + "auditd.data.old-net", + "auditd.data.sigev_signo", + "auditd.data.ino", + "auditd.data.old_enforcing", + "auditd.data.old-vcpu", + "auditd.data.range", + "auditd.data.res", + "auditd.data.added", + "auditd.data.fam", + "auditd.data.nlnk-pid", + "auditd.data.subj", + "auditd.data.a[0-3]", + "auditd.data.cgroup", + "auditd.data.kernel", + "auditd.data.ocomm", + "auditd.data.new-net", + "auditd.data.permissive", + "auditd.data.class", + "auditd.data.compat", + "auditd.data.fi", + "auditd.data.changed", + "auditd.data.msg", + "auditd.data.dport", + "auditd.data.new-seuser", + "auditd.data.invalid_context", + "auditd.data.dmac", + "auditd.data.ipx-net", + "auditd.data.iuid", + "auditd.data.macproto", + "auditd.data.obj", + "auditd.data.ipid", + "auditd.data.new-fs", + "auditd.data.vm-pid", + "auditd.data.cap_pi", + "auditd.data.old-auid", + "auditd.data.oses", + "auditd.data.fd", + "auditd.data.igid", + "auditd.data.new-disk", + "auditd.data.parent", + "auditd.data.len", + "auditd.data.oflag", + "auditd.data.uuid", + "auditd.data.code", + "auditd.data.nlnk-grp", + "auditd.data.cap_fp", + "auditd.data.new-mem", + "auditd.data.seperm", + "auditd.data.enforcing", + "auditd.data.new-chardev", + "auditd.data.old-rng", + "auditd.data.outif", + "auditd.data.cmd", + "auditd.data.hook", + "auditd.data.new-level", + "auditd.data.sauid", + "auditd.data.sig", + "auditd.data.audit_backlog_wait_time", + "auditd.data.printer", + "auditd.data.old-mem", + "auditd.data.perm", + "auditd.data.old_pi", + "auditd.data.state", + "auditd.data.format", + "auditd.data.new_gid", + "auditd.data.tcontext", + "auditd.data.maj", + "auditd.data.watch", + "auditd.data.device", + "auditd.data.grp", + "auditd.data.bool", + "auditd.data.icmp_type", + "auditd.data.new_lock", + "auditd.data.old_prom", + "auditd.data.acl", + "auditd.data.ip", + "auditd.data.new_pi", + "auditd.data.default-context", + "auditd.data.inode_gid", + "auditd.data.new-log_passwd", + "auditd.data.new_pe", + "auditd.data.selected-context", + "auditd.data.cap_fver", + "auditd.data.file", + "auditd.data.net", + "auditd.data.virt", + "auditd.data.cap_pp", + "auditd.data.old-range", + "auditd.data.resrc", + "auditd.data.new-range", + "auditd.data.obj_gid", + "auditd.data.proto", + "auditd.data.old-disk", + "auditd.data.audit_failure", + "auditd.data.inif", + "auditd.data.vm", + "auditd.data.flags", + "auditd.data.nlnk-fam", + "auditd.data.old-fs", + "auditd.data.old-ses", + "auditd.data.seqno", + "auditd.data.fver", + "auditd.data.qbytes", + "auditd.data.seuser", + "auditd.data.cap_fe", + "auditd.data.new-vcpu", + "auditd.data.old-level", + "auditd.data.old_pp", + "auditd.data.daddr", + "auditd.data.old-role", + "auditd.data.ioctlcmd", + "auditd.data.smac", + "auditd.data.apparmor", + "auditd.data.fe", + "auditd.data.perm_mask", + "auditd.data.ses", + "auditd.data.cap_fi", + "auditd.data.obj_uid", + "auditd.data.reason", + "auditd.data.list", + "auditd.data.old_lock", + "auditd.data.bus", + "auditd.data.old_pe", + "auditd.data.new-role", + "auditd.data.prom", + "auditd.data.uri", + "auditd.data.audit_enabled", + "auditd.data.old-log_passwd", + "auditd.data.old-seuser", + "auditd.data.per", + "auditd.data.scontext", + "auditd.data.tclass", + "auditd.data.ver", + "auditd.data.new", + "auditd.data.val", + "auditd.data.img-ctx", + "auditd.data.old-chardev", + "auditd.data.old_val", + "auditd.data.success", + "auditd.data.inode_uid", + "auditd.data.removed", + "auditd.data.socket.port", + "auditd.data.socket.saddr", + "auditd.data.socket.addr", + "auditd.data.socket.family", + "auditd.data.socket.path", + "geoip.continent_name", + "geoip.city_name", + "geoip.region_name", + "geoip.country_iso_code", + "hash.blake2b_256", + "hash.blake2b_384", + "hash.blake2b_512", + "hash.md5", + "hash.sha1", + "hash.sha224", + "hash.sha256", + "hash.sha384", + "hash.sha3_224", + "hash.sha3_256", + "hash.sha3_384", + "hash.sha3_512", + "hash.sha512", + "hash.sha512_224", + "hash.sha512_256", + "hash.xxh64", + "event.origin", + "user.entity_id", + "user.terminal", + "process.entity_id", + "process.hash.blake2b_256", + "process.hash.blake2b_384", + "process.hash.blake2b_512", + "process.hash.sha224", + "process.hash.sha384", + "process.hash.sha3_224", + "process.hash.sha3_256", + "process.hash.sha3_384", + "process.hash.sha3_512", + "process.hash.sha512_224", + "process.hash.sha512_256", + "process.hash.xxh64", + "socket.entity_id", + "system.audit.host.timezone.name", + "system.audit.host.hostname", + "system.audit.host.id", + "system.audit.host.architecture", + "system.audit.host.mac", + "system.audit.host.os.codename", + "system.audit.host.os.platform", + "system.audit.host.os.name", + "system.audit.host.os.family", + "system.audit.host.os.version", + "system.audit.host.os.kernel", + "system.audit.package.entity_id", + "system.audit.package.name", + "system.audit.package.version", + "system.audit.package.release", + "system.audit.package.arch", + "system.audit.package.license", + "system.audit.package.summary", + "system.audit.package.url", + "system.audit.user.name", + "system.audit.user.uid", + "system.audit.user.gid", + "system.audit.user.dir", + "system.audit.user.shell", + "system.audit.user.user_information", + "system.audit.user.password.type", + "fields.*" + ] + }, + "refresh_interval": "5s" + } + } + } +} \ No newline at end of file From 0bdcda8f20c1ac4e69a6829860d607d90d0a3d3b Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Mon, 6 Apr 2020 13:44:46 -0600 Subject: [PATCH 24/27] [SIEM] Fixes UX issues around prebuilt ML Rules (#62396) ## Summary This PR fixes a number of UX issues around the new prebuilt `machine_learning` rules when the user does not have the necessary permissions to manage the backing ML Job. Along with https://github.com/elastic/kibana/pull/62383, this ensures there is adequate information for the user determine if a rule is not working because the backing job is not running (and helping to prevent this from occurring). This also includes some requested copy changes, including: * Renames `Anomaly Detection` dropdown to `ML job settings`

* Updates copy in `ML job settings` dropdown

* Only shows `ML job settings` UI when on `/detections/` routes

### All Rules Changes * Disables the `activate switch` if user does not have permission to enable/disable jobs

* Adds warning toast when attempting to activate via bulk actions (if user does not have permission to enable/disable jobs)

### Rule Details Changes * `Machine Learning job` link now links to ML App with table filtered to the relevant job * Disables the `activate switch` if user does not have permission to enable/disable jobs

### Create/Edit Rule Changes * If the job selected _is not running_, a warning will be displayed to remind the user to enable the job before running the rule. cc @benskelker @MikePaquette -- this okay copy here?

Resolves https://github.com/elastic/siem-team/issues/575 Resolves https://github.com/elastic/siem-team/issues/519 ### Checklist Delete any items that are not applicable to this PR. - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Scheduled time with @benskelker to update docs - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- .../components/header_global/index.test.tsx | 14 +++ .../public/components/header_global/index.tsx | 116 +++++++++--------- .../popover_description.test.tsx.snap | 2 +- .../components/ml_popover/ml_popover.tsx | 6 +- .../ml_popover/popover_description.tsx | 2 +- .../components/ml_popover/translations.ts | 13 +- .../siem/public/components/toasters/utils.ts | 24 ++++ .../rules/all/batch_actions.tsx | 22 +++- .../detection_engine/rules/all/columns.tsx | 32 +++-- .../detection_engine/rules/all/index.tsx | 29 ++++- .../description_step/ml_job_description.tsx | 4 +- .../rules/components/ml_job_select/index.tsx | 112 +++++++++++------ .../step_define_rule/translations.tsx | 8 ++ .../detection_engine/rules/details/index.tsx | 34 +++-- .../pages/detection_engine/translations.ts | 14 +++ .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 17 files changed, 305 insertions(+), 131 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index 098de39bbfef..56fa0d56f3c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -10,6 +10,16 @@ import React from 'react'; import '../../mock/match_media'; import { HeaderGlobal } from './index'; +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ + pathname: '/app/siem#/hosts/allHosts', + hash: '', + search: '', + state: '', + }), + withRouter: () => jest.fn(), +})); + jest.mock('ui/new_platform'); // Test will fail because we will to need to mock some core services to make the test work @@ -19,6 +29,10 @@ jest.mock('../search_bar', () => ({ })); describe('HeaderGlobal', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + test('it renders', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx index a12fab8a4f5d..adc2be4f9c36 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -9,6 +9,7 @@ import { pickBy } from 'lodash/fp'; import React from 'react'; import styled, { css } from 'styled-components'; +import { useLocation } from 'react-router-dom'; import { gutterTimeline } from '../../lib/helpers'; import { navTabs } from '../../pages/home/home_navigations'; import { SiemPageName } from '../../pages/home/types'; @@ -36,63 +37,68 @@ FlexItem.displayName = 'FlexItem'; interface HeaderGlobalProps { hideDetectionEngine?: boolean; } -export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => ( - - - - {({ indicesExist }) => ( - <> - - - - - - - +export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => { + const currentLocation = useLocation(); - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - key !== SiemPageName.detections, navTabs) - : navTabs - } - /> - ) : ( - key === SiemPageName.overview, navTabs)} - /> - )} - - - - - - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) && ( + return ( + + + + {({ indicesExist }) => ( + <> + + - + + + + + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + key !== SiemPageName.detections, navTabs) + : navTabs + } + /> + ) : ( + key === SiemPageName.overview, navTabs)} + /> + )} - )} + + - - - {i18n.BUTTON_ADD_DATA} - - - - - - )} - - - -)); + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) && + currentLocation.pathname.includes(`/${SiemPageName.detections}/`) && ( + + + + )} + + + + {i18n.BUTTON_ADD_DATA} + + + + + + )} + +
+ + ); +}); HeaderGlobal.displayName = 'HeaderGlobal'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap index 09e95c5ff59e..46e61f9e939e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap @@ -5,7 +5,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` size="s" > { iconSide="right" onClick={() => setIsPopoverOpen(!isPopoverOpen)} > - {i18n.ANOMALY_DETECTION} + {i18n.ML_JOB_SETTINGS} } isOpen={isPopoverOpen} @@ -142,14 +142,14 @@ export const MlPopover = React.memo(() => { dispatch({ type: 'refresh' }); }} > - {i18n.ANOMALY_DETECTION} + {i18n.ML_JOB_SETTINGS} } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} > - {i18n.ANOMALY_DETECTION_TITLE} + {i18n.ML_JOB_SETTINGS} diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx index 20e8dd2492fe..a491d4b6b769 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx @@ -14,7 +14,7 @@ export const PopoverDescriptionComponent = () => ( diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts index 442068dd0e19..613691e55dcf 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts @@ -6,17 +6,10 @@ import { i18n } from '@kbn/i18n'; -export const ANOMALY_DETECTION = i18n.translate( - 'xpack.siem.components.mlPopup.anomalyDetectionButtonLabel', +export const ML_JOB_SETTINGS = i18n.translate( + 'xpack.siem.components.mlPopup.mlJobSettingsButtonLabel', { - defaultMessage: 'Anomaly detection', - } -); - -export const ANOMALY_DETECTION_TITLE = i18n.translate( - 'xpack.siem.components.mlPopup.anomalyDetectionTitle', - { - defaultMessage: 'Anomaly detection settings', + defaultMessage: 'ML job settings', } ); diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts b/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts index e624144c9826..e7cc389d4c06 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts @@ -37,6 +37,30 @@ export const displayErrorToast = ( }); }; +/** + * Displays a warning toast for the provided title and message + * + * @param title warning message to display in toaster and modal + * @param dispatchToaster provided by useStateToaster() + * @param id unique ID if necessary + */ +export const displayWarningToast = ( + title: string, + dispatchToaster: React.Dispatch, + id: string = uuid.v4() +): void => { + const toast: AppToast = { + id, + title, + color: 'warning', + iconType: 'help', + }; + dispatchToaster({ + type: 'addToaster', + toast, + }); +}; + /** * Displays a success toast for the provided title and message * diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index 60ad68b8c914..454ef18e0ae1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -14,13 +14,15 @@ import { enableRulesAction, exportRulesAction, } from './actions'; -import { ActionToaster } from '../../../../components/toasters'; +import { ActionToaster, displayWarningToast } from '../../../../components/toasters'; import { Rule } from '../../../../containers/detection_engine/rules'; +import * as detectionI18n from '../../translations'; interface GetBatchItems { closePopover: () => void; dispatch: Dispatch; dispatchToaster: Dispatch; + hasMlPermissions: boolean; loadingRuleIds: string[]; reFetchRules: (refreshPrePackagedRule?: boolean) => void; rules: Rule[]; @@ -31,6 +33,7 @@ export const getBatchItems = ({ closePopover, dispatch, dispatchToaster, + hasMlPermissions, loadingRuleIds, reFetchRules, rules, @@ -57,7 +60,22 @@ export const getBatchItems = ({ const deactivatedIds = selectedRuleIds.filter( id => !rules.find(r => r.id === id)?.enabled ?? false ); - await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster); + + const deactivatedIdsNoML = deactivatedIds.filter( + id => rules.find(r => r.id === id)?.type !== 'machine_learning' ?? false + ); + + const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length; + if (!hasMlPermissions && mlRuleCount > 0) { + displayWarningToast(detectionI18n.ML_RULES_UNAVAILABLE(mlRuleCount), dispatchToaster); + } + + await enableRulesAction( + hasMlPermissions ? deactivatedIds : deactivatedIdsNoML, + true, + dispatch, + dispatchToaster + ); }} > {i18n.BATCH_ACTION_ACTIVATE_SELECTED} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 9a84d33ab5fd..80e644f80033 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -13,6 +13,7 @@ import { EuiTableActionsColumnType, EuiText, EuiHealth, + EuiToolTip, } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import * as H from 'history'; @@ -36,6 +37,8 @@ import { } from './actions'; import { Action } from './reducer'; import { LocalizedDateTooltip } from '../../../../components/localized_date_tooltip'; +import * as detectionI18n from '../../translations'; +import { isMlRule } from '../../../../../common/detection_engine/ml_helpers'; export const getActions = ( dispatch: React.Dispatch, @@ -88,6 +91,7 @@ interface GetColumns { dispatch: React.Dispatch; dispatchToaster: Dispatch; history: H.History; + hasMlPermissions: boolean; hasNoPermissions: boolean; loadingRuleIds: string[]; reFetchRules: (refreshPrePackagedRule?: boolean) => void; @@ -98,6 +102,7 @@ export const getColumns = ({ dispatch, dispatchToaster, history, + hasMlPermissions, hasNoPermissions, loadingRuleIds, reFetchRules, @@ -182,14 +187,25 @@ export const getColumns = ({ field: 'enabled', name: i18n.COLUMN_ACTIVATE, render: (value: Rule['enabled'], item: Rule) => ( - + + + ), sortable: true, width: '95px', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index ccdfd1ed1be3..e96ed856208b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -40,6 +40,8 @@ import { getColumns, getMonitoringColumns } from './columns'; import { showRulesTable } from './helpers'; import { allRulesReducer, State } from './reducer'; import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; +import { useMlCapabilities } from '../../../../components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../components/ml/permissions/has_ml_admin_permissions'; const SORT_FIELD = 'enabled'; const initialState: State = { @@ -111,6 +113,11 @@ export const AllRules = React.memo( const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const history = useHistory(); const [, dispatchToaster] = useStateToaster(); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); const setRules = useCallback((newRules: Rule[], newPagination: Partial) => { dispatch({ @@ -145,6 +152,7 @@ export const AllRules = React.memo( closePopover, dispatch, dispatchToaster, + hasMlPermissions, loadingRuleIds, selectedRuleIds, reFetchRules: reFetchRulesData, @@ -152,7 +160,15 @@ export const AllRules = React.memo( })} /> ), - [dispatch, dispatchToaster, loadingRuleIds, reFetchRulesData, rules, selectedRuleIds] + [ + dispatch, + dispatchToaster, + hasMlPermissions, + loadingRuleIds, + reFetchRulesData, + rules, + selectedRuleIds, + ] ); const paginationMemo = useMemo( @@ -184,6 +200,7 @@ export const AllRules = React.memo( dispatch, dispatchToaster, history, + hasMlPermissions, hasNoPermissions, loadingRuleIds: loadingRulesAction != null && @@ -192,7 +209,15 @@ export const AllRules = React.memo( : [], reFetchRules: reFetchRulesData, }); - }, [dispatch, dispatchToaster, history, loadingRuleIds, loadingRulesAction, reFetchRulesData]); + }, [ + dispatch, + dispatchToaster, + hasMlPermissions, + history, + loadingRuleIds, + loadingRulesAction, + reFetchRulesData, + ]); const monitoringColumns = useMemo(() => getMonitoringColumns(), []); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx index 8276aa357856..5a9593f1a6de 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx @@ -66,7 +66,9 @@ const Wrapper = styled.div` `; export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { - const jobUrl = useKibana().services.application.getUrlForApp('ml#/jobs'); + const jobUrl = useKibana().services.application.getUrlForApp( + `ml#/jobs?mlManagement=(jobId:${encodeURI(job.id)})` + ); return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx index 3d253b71b53d..794edf0ab5de 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx @@ -4,37 +4,64 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiIcon, EuiLink, EuiSuperSelect, EuiText, } from '@elastic/eui'; +import styled from 'styled-components'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports'; import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; import { useKibana } from '../../../../../lib/kibana'; -import { ML_JOB_SELECT_PLACEHOLDER_TEXT } from '../step_define_rule/translations'; +import { + ML_JOB_SELECT_PLACEHOLDER_TEXT, + ENABLE_ML_JOB_WARNING, +} from '../step_define_rule/translations'; +import { isJobStarted } from '../../../../../../common/detection_engine/ml_helpers'; + +const HelpTextWarningContainer = styled.div` + margin-top: 10px; +`; + +const MlJobSelectEuiFlexGroup = styled(EuiFlexGroup)` + margin-bottom: 5px; +`; -const HelpText: React.FC<{ href: string }> = ({ href }) => ( - - - - ), - }} - /> +const HelpText: React.FC<{ href: string; showEnableWarning: boolean }> = ({ + href, + showEnableWarning = false, +}) => ( + <> + + + + ), + }} + /> + {showEnableWarning && ( + + + + {ENABLE_ML_JOB_WARNING} + + + )} + ); const JobDisplay: React.FC<{ title: string; description: string }> = ({ title, description }) => ( @@ -77,26 +104,37 @@ export const MlJobSelect: React.FC = ({ describedByIds = [], f const options = [placeholderOption, ...jobOptions]; + const isJobRunning = useMemo(() => { + // If the selected job is not found in the list, it means the placeholder is selected + // and so we don't want to show the warning, thus isJobRunning will be true when 'job == null' + const job = siemJobs.find(j => j.id === jobId); + return job == null || isJobStarted(job.jobState, job.datafeedState); + }, [siemJobs, jobId]); + return ( - } - isInvalid={isInvalid} - error={errorMessage} - data-test-subj="mlJobSelect" - describedByIds={describedByIds} - > - - - - - - + + + } + isInvalid={isInvalid} + error={errorMessage} + data-test-subj="mlJobSelect" + describedByIds={describedByIds} + > + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx index 1d8821aceb24..bbdb2130ce29 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx @@ -62,3 +62,11 @@ export const ML_JOB_SELECT_PLACEHOLDER_TEXT = i18n.translate( defaultMessage: 'Select a job', } ); + +export const ENABLE_ML_JOB_WARNING = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.mlEnableJobWarningTitle', + { + defaultMessage: + 'This ML job is not currently running. Please set this job to run via "ML job settings" before activating this rule.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index cb4d88a8bb53..2b648a3b3f82 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -14,6 +14,7 @@ import { EuiSpacer, EuiTab, EuiTabs, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC, memo, useCallback, useMemo, useState } from 'react'; @@ -66,6 +67,8 @@ import { RuleActionsOverflow } from '../components/rule_actions_overflow'; import { RuleStatusFailedCallOut } from './status_failed_callout'; import { FailureHistory } from './failure_history'; import { RuleStatus } from '../components/rule_status'; +import { useMlCapabilities } from '../../../../components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../components/ml/permissions/has_ml_admin_permissions'; enum RuleDetailTabs { signals = 'signals', @@ -114,6 +117,11 @@ const RuleDetailsPageComponent: FC = ({ scheduleRuleData: null, }; const [lastSignals] = useSignalInfo({ ruleId }); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); const title = isLoading === true || rule === null ? : rule.name; const subTitle = useMemo( @@ -259,13 +267,25 @@ const RuleDetailsPageComponent: FC = ({ > - + + + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index 39277b3d3c77..008d660be9d8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -86,3 +86,17 @@ export const USER_UNAUTHENTICATED_MSG_BODY = i18n.translate( 'You do not have the required permissions for viewing the detection engine. For more help, contact your administrator.', } ); + +export const ML_RULES_DISABLED_MESSAGE = i18n.translate( + 'xpack.siem.detectionEngine.mlRulesDisabledMessageTitle', + { + defaultMessage: 'ML rules require Platinum License and ML Admin Permissions', + } +); + +export const ML_RULES_UNAVAILABLE = (totalRules: number) => + i18n.translate('xpack.siem.detectionEngine.mlUnavailableTitle', { + values: { totalRules }, + defaultMessage: + '{totalRules} {totalRules, plural, =1 {rule requires} other {rules require}} Machine Learning to enable.', + }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 023a97274b95..79c1bbc49810 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13548,9 +13548,7 @@ "xpack.siem.components.mlPopover.jobsTable.filters.searchFilterPlaceholder": "例: rare_process_linux", "xpack.siem.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Elastic ジョブ", "xpack.siem.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "カスタムジョブ", - "xpack.siem.components.mlPopup.anomalyDetectionButtonLabel": "異常検知", "xpack.siem.components.mlPopup.anomalyDetectionDescription": "下のいずれかの機械学習ジョブを実行して、SIEM アプリケーション全体の異常イベントを表示することができます。始めに使えるように、いくつかの一般的な検出ジョブが提供されています。独自のカスタムジョブを追加する場合は、{machineLearning} アプリケーションでジョブを作成して「SIEM」でタグ付けすると、ここに追加されます。", - "xpack.siem.components.mlPopup.anomalyDetectionTitle": "異常検知設定", "xpack.siem.components.mlPopup.cloudLink": "クラウド展開", "xpack.siem.components.mlPopup.errors.createJobFailureTitle": "ジョブ作成エラー", "xpack.siem.components.mlPopup.errors.startJobFailureTitle": "ジョブ開始エラー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b359014e95e7..77bf8f146778 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13552,9 +13552,7 @@ "xpack.siem.components.mlPopover.jobsTable.filters.searchFilterPlaceholder": "例如 rare_process_linux", "xpack.siem.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Elastic 作业", "xpack.siem.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "定制作业", - "xpack.siem.components.mlPopup.anomalyDetectionButtonLabel": "异常检测", "xpack.siem.components.mlPopup.anomalyDetectionDescription": "运行以下任何 Machine Learning 作业以查看该 SIEM 应用程序的所有异常事件。我们提供若干可让您入门的常规检测作业。如果您希望添加自己的定制作业,只需从用于纳入定制作业的 {machineLearning} 应用程序中创建定制作业并使用“SIEM”标记它们。", - "xpack.siem.components.mlPopup.anomalyDetectionTitle": "异常检测设置", "xpack.siem.components.mlPopup.cloudLink": "云部署", "xpack.siem.components.mlPopup.errors.createJobFailureTitle": "创建作业失败", "xpack.siem.components.mlPopup.errors.startJobFailureTitle": "启动作业失败", From ee02df445ba64dc99a274d7a9bad9b773552e1e3 Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Mon, 6 Apr 2020 15:45:11 -0400 Subject: [PATCH 25/27] [EPM] fix /packages response to return older packages (#62623) * compare package list by name * use the internal property, adding as saved object attribute * remove HiddenPackages type --- .../plugins/ingest_manager/server/saved_objects.ts | 1 + .../server/services/epm/packages/get.ts | 12 ++++++------ .../server/services/epm/packages/install.ts | 8 +++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 13f84e4efa79..6800cb405670 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -148,6 +148,7 @@ export const savedObjectMappings = { properties: { name: { type: 'keyword' }, version: { type: 'keyword' }, + internal: { type: 'boolean' }, installed: { type: 'nested', properties: { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index d655b81f8cde..e963ea138dfd 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -31,17 +31,17 @@ export async function getPackages( Object.assign({}, item, { title: item.title || nameAsTitle(item.name) }) ); }); - const searchObjects = registryItems.map(({ name, version }) => ({ + // get the installed packages + const results = await savedObjectsClient.find({ type: PACKAGES_SAVED_OBJECT_TYPE, - id: `${name}-${version}`, - })); - const results = await savedObjectsClient.bulkGet(searchObjects); - const savedObjects = results.saved_objects.filter(o => !o.error); // ignore errors for now + }); + // filter out any internal packages + const savedObjectsVisible = results.saved_objects.filter(o => !o.attributes.internal); const packageList = registryItems .map(item => createInstallableFrom( item, - savedObjects.find(({ id }) => id === `${item.name}-${item.version}`) + savedObjectsVisible.find(({ attributes }) => attributes.name === item.name) ) ) .sort(sortByName); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 3cce238f582f..82523e37509d 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -87,7 +87,7 @@ export async function installPackage(options: { }): Promise { const { savedObjectsClient, pkgkey, callCluster } = options; const registryPackageInfo = await Registry.fetchInfo(pkgkey); - const { name: pkgName, version: pkgVersion } = registryPackageInfo; + const { name: pkgName, version: pkgVersion, internal = false } = registryPackageInfo; const installKibanaAssetsPromise = installKibanaAssets({ savedObjectsClient, @@ -116,6 +116,7 @@ export async function installPackage(options: { pkgkey, pkgName, pkgVersion, + internal, toSave, }); return toSave; @@ -145,9 +146,10 @@ export async function saveInstallationReferences(options: { pkgkey: string; pkgName: string; pkgVersion: string; + internal: boolean; toSave: AssetReference[]; }) { - const { savedObjectsClient, pkgkey, pkgName, pkgVersion, toSave } = options; + const { savedObjectsClient, pkgkey, pkgName, pkgVersion, internal, toSave } = options; const installation = await getInstallation({ savedObjectsClient, pkgkey }); const savedRefs = installation?.installed || []; const mergeRefsReducer = (current: AssetReference[], pending: AssetReference) => { @@ -159,7 +161,7 @@ export async function saveInstallationReferences(options: { const toInstall = toSave.reduce(mergeRefsReducer, savedRefs); await savedObjectsClient.create( PACKAGES_SAVED_OBJECT_TYPE, - { installed: toInstall, name: pkgName, version: pkgVersion }, + { installed: toInstall, name: pkgName, version: pkgVersion, internal }, { id: pkgkey, overwrite: true } ); From a7b3e5539efa7cc26458770f585b52e219cf4052 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 6 Apr 2020 21:47:15 +0200 Subject: [PATCH 26/27] [ML] Fix boolean cell values in analytics table result views and transforms wizard. (#62618) Fixes the rendering of boolean values in table results views and transforms wizards: - Fixed: Boolean cells in the transform wizard ended up being empty. - Fixed: Boolean cells in regression/classification result table would render as Yes/No instead of true/false. --- .../classification_exploration/results_table.tsx | 1 + .../components/regression_exploration/results_table.tsx | 1 + .../public/app/components/pivot_preview/pivot_preview.tsx | 4 ++++ .../source_index_preview/source_index_preview.tsx | 7 +++++++ 4 files changed, 13 insertions(+) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx index fbdb47c87c7e..9758dd969b44 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx @@ -211,6 +211,7 @@ export const ResultsTable: FC = React.memo( switch (type) { case ES_FIELD_TYPES.BOOLEAN: column.dataType = ES_FIELD_TYPES.BOOLEAN; + column.render = d => (d ? 'true' : 'false'); break; case ES_FIELD_TYPES.DATE: column.align = 'right'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx index 8d53a9278a1a..a35be5400f46 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx @@ -213,6 +213,7 @@ export const ResultsTable: FC = React.memo( switch (type) { case ES_FIELD_TYPES.BOOLEAN: column.dataType = ES_FIELD_TYPES.BOOLEAN; + column.render = d => (d ? 'true' : 'false'); break; case ES_FIELD_TYPES.DATE: column.align = 'right'; diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx index c0c85f74418f..c50df0366d69 100644 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx +++ b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx @@ -245,6 +245,10 @@ export const PivotPreview: FC = React.memo( return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); } + if (previewMappings.properties[columnId].type === ES_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + return cellValue; }; }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappings.properties]); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx index c56263b72103..bcdeb7ddb0d3 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx @@ -105,6 +105,9 @@ export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, q let schema; switch (field?.type) { + case KBN_FIELD_TYPES.BOOLEAN: + schema = 'boolean'; + break; case KBN_FIELD_TYPES.DATE: schema = 'datetime'; break; @@ -190,6 +193,10 @@ export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, q return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); } + if (field?.type === KBN_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + return cellValue; }; }, [data, indexPattern.fields, pagination.pageIndex, pagination.pageSize]); From f80925af9768cd5e9b4ccd3d4d4d5ebd739fe49c Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 6 Apr 2020 14:01:38 -0600 Subject: [PATCH 27/27] [Maps] Move layers to np maps (#61877) * Move layers to new location * Update layer path refs * Update np kibana services to cover all required services * Init np kibana services in legacy plugin. Port init functions to np * Path updates, supporting file moves, general clean up * More moves of related files and clean-up of legacy refs * Path updates. Typescript warning fixes * Update test paths * Clean up unused kibana services usage in legacy * Remove unused http ref * Test fixes and clean up * Remove unused snapshots * Add np service init to embeddables too * Move validate color picker to NP --- .../maps/public/actions/map_actions.d.ts | 57 +------------ .../maps/public/angular/get_initial_layers.js | 9 +- .../public/angular/get_initial_layers.test.js | 8 +- .../public/angular/services/saved_gis_map.js | 3 +- .../connected_components/gis_map/index.d.ts | 3 +- .../connected_components/gis_map/view.js | 3 +- .../layer_addpanel/import_editor/view.js | 3 +- .../source_select/source_select.js | 3 +- .../join_editor/resources/join_expression.js | 6 +- .../resources/metrics_expression.js | 3 +- .../resources/metrics_expression.test.js | 2 +- .../layer_settings/layer_settings.js | 3 +- .../layer_panel/view.d.ts | 8 +- .../feature_geometry_filter_form.js | 3 +- .../map/mb/draw_control/draw_control.js | 3 +- .../connected_components/map/mb/utils.js | 63 ++------------ .../connected_components/map/mb/view.js | 9 +- .../maps/public/embeddable/map_embeddable.tsx | 3 +- .../embeddable/map_embeddable_factory.ts | 7 ++ x-pack/legacy/plugins/maps/public/index.scss | 2 +- x-pack/legacy/plugins/maps/public/index.ts | 6 +- .../plugins/maps/public/kibana_services.js | 62 -------------- .../plugins/maps/public/layers/_index.scss | 1 - .../maps/public/layers/styles/_index.scss | 4 - x-pack/legacy/plugins/maps/public/plugin.ts | 36 ++++---- .../maps/public/selectors/map_selectors.js | 21 +++-- .../public/selectors/map_selectors.test.js | 10 +-- .../maps/public/actions/map_actions.d.ts | 64 +++++++++++++++ .../add_tooltip_field_popover.test.js.snap | 0 .../tooltip_selector.test.js.snap | 0 .../validated_range.test.js.snap | 0 .../components/add_tooltip_field_popover.js | 2 +- .../add_tooltip_field_popover.test.js | 0 .../maps/public/components/metric_editor.js | 0 .../maps/public/components/metric_select.js | 0 .../maps/public/components/metrics_editor.js | 0 .../components/no_index_pattern_callout.js | 8 +- .../public/components/single_field_select.js | 2 +- .../public/components/tooltip_selector.js | 0 .../components/tooltip_selector.test.js | 0 .../maps/public/components/validated_range.js | 0 .../public/components/validated_range.test.js | 0 .../layer_panel/view.d.ts | 14 ++++ .../map/mb/image_utils.js | 0 .../connected_components/map/mb/utils.js | 62 ++++++++++++++ .../maps/public/elasticsearch_geo_utils.js | 0 .../public/elasticsearch_geo_utils.test.js | 2 +- .../plugins/maps/public/index_pattern_util.js | 2 +- .../maps/public/index_pattern_util.test.js | 0 x-pack/plugins/maps/public/kibana_services.js | 82 +++++++++++++++++++ x-pack/plugins/maps/public/layers/_index.scss | 1 + .../public/layers/blended_vector_layer.ts | 0 .../public/layers/fields/ems_file_field.ts | 0 .../public/layers/fields/es_agg_field.test.ts | 0 .../maps/public/layers/fields/es_agg_field.ts | 0 .../maps/public/layers/fields/es_doc_field.ts | 4 +- .../maps/public/layers/fields/field.ts | 0 .../layers/fields/kibana_region_field.ts | 0 .../fields/top_term_percentage_field.ts | 0 .../maps/public/layers/heatmap_layer.js | 0 .../maps/public/layers/joins/inner_join.js | 0 .../public/layers/joins/inner_join.test.js | 0 .../plugins/maps/public/layers/joins/join.ts | 0 .../plugins/maps/public/layers/layer.d.ts | 0 .../plugins/maps/public/layers/layer.js | 2 +- .../public/layers/layer_wizard_registry.ts | 0 .../maps/public/layers/load_layer_wizards.js | 0 .../create_client_file_source_editor.js | 0 .../client_file_source/geojson_file_source.js | 0 .../sources/client_file_source/index.js | 0 .../ems_file_source/create_source_editor.js | 0 .../ems_file_source/ems_file_source.d.ts | 0 .../ems_file_source/ems_file_source.js | 0 .../ems_file_source/ems_file_source.test.js | 0 .../layers/sources/ems_file_source/index.js | 0 .../ems_file_source/update_source_editor.js | 0 .../sources/ems_tms_source/ems_tms_source.js | 0 .../ems_tms_source/ems_tms_source.test.js | 0 .../layers/sources/ems_tms_source/index.js | 0 .../ems_tms_source/tile_service_select.js | 0 .../ems_tms_source/update_source_editor.js | 0 .../layers/sources/ems_unavailable_message.js | 0 .../public/layers/sources/es_agg_source.d.ts | 0 .../public/layers/sources/es_agg_source.js | 0 .../layers/sources/es_agg_source.test.ts | 0 .../es_geo_grid_source/convert_to_geojson.js | 0 .../convert_to_geojson.test.ts | 0 .../create_source_editor.js | 0 .../es_geo_grid_source.d.ts | 0 .../es_geo_grid_source/es_geo_grid_source.js | 0 .../es_geo_grid_source.test.ts | 0 .../es_geo_grid_source/geo_tile_utils.js | 0 .../es_geo_grid_source/geo_tile_utils.test.js | 0 .../sources/es_geo_grid_source/index.js | 0 .../es_geo_grid_source/render_as_select.tsx | 0 .../es_geo_grid_source/resolution_editor.js | 0 .../update_source_editor.js | 2 +- .../es_pew_pew_source/convert_to_lines.js | 0 .../convert_to_lines.test.ts | 0 .../es_pew_pew_source/create_source_editor.js | 0 .../es_pew_pew_source/es_pew_pew_source.js | 2 +- .../es_pew_pew_source/update_source_editor.js | 2 +- .../__snapshots__/scaling_form.test.tsx.snap | 0 .../update_source_editor.test.js.snap | 0 .../sources/es_search_source/constants.js | 0 .../es_search_source/create_source_editor.js | 2 +- .../es_search_source/es_search_source.d.ts | 0 .../es_search_source/es_search_source.js | 0 .../es_search_source/es_search_source.test.ts | 0 .../layers/sources/es_search_source/index.js | 0 .../es_search_source/load_index_settings.js | 0 .../es_search_source/scaling_form.test.tsx | 0 .../sources/es_search_source/scaling_form.tsx | 4 +- .../es_search_source/update_source_editor.js | 2 +- .../update_source_editor.test.js | 0 .../maps/public/layers/sources/es_source.d.ts | 2 +- .../maps/public/layers/sources/es_source.js | 2 +- .../public/layers/sources/es_term_source.d.ts | 0 .../public/layers/sources/es_term_source.js | 0 .../layers/sources/es_term_source.test.js | 0 .../create_source_editor.js | 0 .../sources/kibana_regionmap_source/index.js | 0 .../kibana_regionmap_source.d.ts | 0 .../kibana_regionmap_source.js | 0 .../create_source_editor.js | 0 .../sources/kibana_tilemap_source/index.js | 0 .../kibana_tilemap_source.js | 0 .../maps/public/layers/sources/source.d.ts | 0 .../maps/public/layers/sources/source.js | 2 +- .../public/layers/sources/source_registry.ts | 0 .../public/layers/sources/tms_source.d.ts | 0 .../maps/public/layers/sources/tms_source.js | 0 .../layers/sources/vector_feature_types.js | 0 .../public/layers/sources/vector_source.d.ts | 0 .../public/layers/sources/vector_source.js | 0 .../public/layers/sources/wms_source/index.js | 0 .../layers/sources/wms_source/wms_client.js | 0 .../sources/wms_source/wms_client.test.js | 0 .../wms_source/wms_create_source_editor.js | 0 .../layers/sources/wms_source/wms_source.js | 0 .../public/layers/sources/xyz_tms_source.d.ts | 0 .../public/layers/sources/xyz_tms_source.js | 0 .../layers/sources/xyz_tms_source.test.ts | 0 .../maps/public/layers/styles/_index.scss | 4 + .../public/layers/styles/abstract_style.js | 0 .../maps/public/layers/styles/color_utils.js | 2 +- .../public/layers/styles/color_utils.test.js | 0 .../styles/components/_color_gradient.scss | 0 .../styles/components/color_gradient.js | 0 .../components/ranged_style_legend_row.js | 0 .../heatmap_style_editor.test.js.snap | 0 .../heatmap/components/heatmap_constants.js | 0 .../components/heatmap_style_editor.js | 0 .../components/heatmap_style_editor.test.js | 0 .../components/legend/heatmap_legend.js | 0 .../layers/styles/heatmap/heatmap_style.js | 0 .../vector/components/_style_prop_editor.scss | 0 .../vector/components/color/_color_stops.scss | 0 .../components/color/color_map_select.js | 0 .../vector/components/color/color_stops.js | 0 .../color/color_stops_categorical.js | 0 .../components/color/color_stops_ordinal.js | 0 .../components/color/color_stops_utils.js | 0 .../components/color/dynamic_color_form.js | 0 .../color/mb_validated_color_picker.tsx | 0 .../components/color/static_color_form.js | 0 .../color/vector_style_color_editor.js | 0 .../categorical_field_meta_popover.tsx | 0 .../field_meta/field_meta_popover.tsx | 0 .../field_meta/ordinal_field_meta_popover.tsx | 0 .../styles/vector/components/field_select.js | 2 +- .../components/get_vector_style_label.js | 0 .../components/label/dynamic_label_form.js | 0 .../components/label/static_label_form.js | 0 .../vector_style_label_border_size_editor.js | 0 .../label/vector_style_label_editor.js | 0 .../__snapshots__/vector_icon.test.js.snap | 0 .../vector/components/legend/category.js | 0 .../vector/components/legend/circle_icon.js | 0 .../extract_color_from_style_property.js | 0 .../vector/components/legend/line_icon.js | 0 .../vector/components/legend/polygon_icon.js | 0 .../vector/components/legend/symbol_icon.js | 0 .../vector/components/legend/vector_icon.js | 0 .../components/legend/vector_icon.test.js | 0 .../components/legend/vector_style_legend.js | 0 .../orientation/dynamic_orientation_form.js | 0 .../orientation/orientation_editor.js | 0 .../orientation/static_orientation_form.js | 0 .../components/size/dynamic_size_form.js | 0 .../components/size/size_range_selector.js | 2 +- .../components/size/static_size_form.js | 0 .../size/vector_style_size_editor.js | 0 .../styles/vector/components/stop_input.js | 0 .../vector/components/style_map_select.js | 0 .../vector/components/style_option_shapes.js | 0 .../vector/components/style_prop_editor.js | 0 .../__snapshots__/icon_select.test.js.snap | 0 .../components/symbol/_icon_select.scss | 0 .../components/symbol/dynamic_icon_form.js | 0 .../components/symbol/icon_map_select.js | 0 .../vector/components/symbol/icon_select.js | 0 .../components/symbol/icon_select.test.js | 0 .../vector/components/symbol/icon_stops.js | 0 .../components/symbol/icon_stops.test.js | 0 .../components/symbol/static_icon_form.js | 0 .../symbol/vector_style_icon_editor.js | 0 .../vector_style_symbolize_as_editor.js | 0 .../vector/components/vector_style_editor.js | 0 .../dynamic_color_property.test.js.snap | 0 .../components/categorical_legend.js | 0 .../properties/components/ordinal_legend.js | 0 .../properties/dynamic_color_property.js | 0 .../properties/dynamic_color_property.test.js | 0 .../properties/dynamic_icon_property.js | 0 .../dynamic_orientation_property.js | 0 .../properties/dynamic_size_property.js | 0 .../properties/dynamic_style_property.d.ts | 0 .../properties/dynamic_style_property.js | 0 .../properties/dynamic_text_property.js | 0 .../properties/label_border_size_property.js | 0 .../properties/static_color_property.js | 0 .../vector/properties/static_icon_property.js | 0 .../properties/static_orientation_property.js | 0 .../vector/properties/static_size_property.js | 0 .../properties/static_style_property.js | 0 .../vector/properties/static_text_property.js | 0 .../vector/properties/style_property.ts | 0 .../properties/symbolize_as_property.js | 0 .../public/layers/styles/vector/style_meta.ts | 0 .../public/layers/styles/vector/style_util.js | 0 .../layers/styles/vector/style_util.test.js | 0 .../layers/styles/vector/symbol_utils.js | 0 .../layers/styles/vector/symbol_utils.test.js | 0 .../layers/styles/vector/vector_style.d.ts | 0 .../layers/styles/vector/vector_style.js | 0 .../layers/styles/vector/vector_style.test.js | 0 .../styles/vector/vector_style_defaults.ts | 0 .../maps/public/layers/tile_layer.d.ts | 0 .../plugins/maps/public/layers/tile_layer.js | 0 .../maps/public/layers/tile_layer.test.ts | 0 .../tooltips/es_agg_tooltip_property.ts | 0 .../layers/tooltips/es_tooltip_property.ts | 4 +- .../layers/tooltips/join_tooltip_property.ts | 2 +- .../layers/tooltips/tooltip_property.ts | 4 +- .../layers/util/assign_feature_ids.test.ts | 0 .../public/layers/util/assign_feature_ids.ts | 0 .../public/layers/util/can_skip_fetch.test.js | 0 .../maps/public/layers/util/can_skip_fetch.ts | 0 .../maps/public/layers/util/data_request.ts | 0 .../public/layers/util/es_agg_utils.test.ts | 0 .../maps/public/layers/util/es_agg_utils.ts | 2 +- .../public/layers/util/is_metric_countable.ts | 0 .../layers/util/is_refresh_only_query.ts | 0 .../layers/util/mb_filter_expressions.ts | 0 .../maps/public/layers/vector_layer.d.ts | 0 .../maps/public/layers/vector_layer.js | 0 .../maps/public/layers/vector_tile_layer.js | 0 .../{legacy => }/plugins/maps/public/meta.js | 25 +++--- .../plugins/maps/public/meta.test.js | 45 ++++------ x-pack/plugins/maps/public/plugin.ts | 37 +++++++++ 261 files changed, 417 insertions(+), 318 deletions(-) delete mode 100644 x-pack/legacy/plugins/maps/public/layers/_index.scss delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/_index.scss create mode 100644 x-pack/plugins/maps/public/actions/map_actions.d.ts rename x-pack/{legacy => }/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/components/add_tooltip_field_popover.js (98%) rename x-pack/{legacy => }/plugins/maps/public/components/add_tooltip_field_popover.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/metric_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/metric_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/metrics_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/no_index_pattern_callout.js (85%) rename x-pack/{legacy => }/plugins/maps/public/components/single_field_select.js (95%) rename x-pack/{legacy => }/plugins/maps/public/components/tooltip_selector.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/tooltip_selector.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/validated_range.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/validated_range.test.js (100%) create mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/view.d.ts rename x-pack/{legacy => }/plugins/maps/public/connected_components/map/mb/image_utils.js (100%) create mode 100644 x-pack/plugins/maps/public/connected_components/map/mb/utils.js rename x-pack/{legacy => }/plugins/maps/public/elasticsearch_geo_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/elasticsearch_geo_utils.test.js (99%) rename x-pack/{legacy => }/plugins/maps/public/index_pattern_util.js (95%) rename x-pack/{legacy => }/plugins/maps/public/index_pattern_util.test.js (100%) create mode 100644 x-pack/plugins/maps/public/layers/_index.scss rename x-pack/{legacy => }/plugins/maps/public/layers/blended_vector_layer.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/ems_file_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/es_agg_field.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/es_agg_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/es_doc_field.ts (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/kibana_region_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/top_term_percentage_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/heatmap_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/joins/inner_join.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/joins/inner_join.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/joins/join.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/layer.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/layer.js (99%) rename x-pack/{legacy => }/plugins/maps/public/layers/layer_wizard_registry.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/load_layer_wizards.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/client_file_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_unavailable_message.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_agg_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_agg_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_agg_source.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js (97%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/constants.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/es_search_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx (97%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_source.d.ts (92%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_source.js (99%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_term_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_term_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_term_source.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/source.js (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/source_registry.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/tms_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/tms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/vector_feature_types.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/vector_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/vector_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_client.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_client.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/xyz_tms_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/xyz_tms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/xyz_tms_source.test.ts (100%) create mode 100644 x-pack/plugins/maps/public/layers/styles/_index.scss rename x-pack/{legacy => }/plugins/maps/public/layers/styles/abstract_style.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/color_utils.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/color_utils.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/components/_color_gradient.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/components/color_gradient.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/heatmap_style.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_select.js (97%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/category.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js (92%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/stop_input.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/style_map_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_color_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_size_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_style_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_text_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/style_property.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/style_meta.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/style_util.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/style_util.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/symbol_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/symbol_utils.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tile_layer.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tile_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tile_layer.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/es_tooltip_property.ts (95%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/join_tooltip_property.ts (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/tooltip_property.ts (92%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/assign_feature_ids.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/assign_feature_ids.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/can_skip_fetch.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/can_skip_fetch.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/data_request.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/es_agg_utils.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/es_agg_utils.ts (95%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/is_metric_countable.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/is_refresh_only_query.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/mb_filter_expressions.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/vector_layer.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/vector_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/vector_tile_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/meta.js (76%) rename x-pack/{legacy => }/plugins/maps/public/meta.test.js (57%) diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts index b4a8ff90c351..34f8c30b5187 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts @@ -5,59 +5,4 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { Filter, Query, TimeRange } from 'src/plugins/data/public'; -import { AnyAction } from 'redux'; -import { LAYER_TYPE } from '../../common/constants'; -import { DataMeta, MapFilters } from '../../common/descriptor_types'; -import { - MapCenterAndZoom, - MapRefreshConfig, -} from '../../../../../plugins/maps/common/descriptor_types'; - -export type SyncContext = { - startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void; - stopLoading(dataId: string, requestToken: symbol, data: unknown, meta: DataMeta): void; - onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; - updateSourceData(newData: unknown): void; - isRequestStillActive(dataId: string, requestToken: symbol): boolean; - registerCancelCallback(requestToken: symbol, callback: () => void): void; - dataFilters: MapFilters; -}; - -export function updateSourceProp( - layerId: string, - propName: string, - value: unknown, - newLayerType?: LAYER_TYPE -): void; - -export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction; - -export function replaceLayerList(layerList: unknown[]): AnyAction; - -export type QueryGroup = { - filters: Filter[]; - query?: Query; - timeFilters?: TimeRange; - refresh?: boolean; -}; - -export function setQuery(query: QueryGroup): AnyAction; - -export function setRefreshConfig(config: MapRefreshConfig): AnyAction; - -export function disableScrollZoom(): AnyAction; - -export function disableInteractive(): AnyAction; - -export function disableTooltipControl(): AnyAction; - -export function hideToolbarOverlay(): AnyAction; - -export function hideLayerControl(): AnyAction; - -export function hideViewControl(): AnyAction; - -export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; - -export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; +export * from '../../../../../plugins/maps/public/actions/map_actions'; diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js index 8fc32aef5477..5e497ff0736b 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js @@ -4,10 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import _ from 'lodash'; -import { KibanaTilemapSource } from '../layers/sources/kibana_tilemap_source'; -import { EMSTMSSource } from '../layers/sources/ems_tms_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaTilemapSource } from '../../../../../plugins/maps/public/layers/sources/kibana_tilemap_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { EMSTMSSource } from '../../../../../plugins/maps/public/layers/sources/ems_tms_source'; import { getInjectedVarFunc } from '../kibana_services'; -import { getKibanaTileMap } from '../meta'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getKibanaTileMap } from '../../../../../plugins/maps/public/meta'; export function getInitialLayers(layerListJSON, initialLayers = []) { if (layerListJSON) { diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js index f41ed26b2a05..5334beaaf714 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../meta', () => { +jest.mock('../../../../../plugins/maps/public/meta', () => { return {}; }); jest.mock('../kibana_services'); @@ -32,7 +32,7 @@ describe('Saved object has layer list', () => { describe('kibana.yml configured with map.tilemap.url', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return { url: 'myTileUrl', }; @@ -62,7 +62,7 @@ describe('kibana.yml configured with map.tilemap.url', () => { describe('EMS is enabled', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; require('../kibana_services').getInjectedVarFunc = () => key => { @@ -106,7 +106,7 @@ describe('EMS is enabled', () => { describe('EMS is not enabled', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; diff --git a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js index f846d3d4a617..990a0613da68 100644 --- a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js +++ b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js @@ -17,7 +17,8 @@ import { getFilters, } from '../../selectors/map_selectors'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors'; -import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { convertMapExtentToPolygon } from '../../../../../../plugins/maps/public/elasticsearch_geo_utils'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; import { extractReferences, injectReferences } from '../../../common/migrations/references'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts index 00a9400109dc..8689d8829717 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts @@ -6,7 +6,8 @@ import React from 'react'; import { Filter } from 'src/plugins/data/public'; -import { RenderToolTipContent } from '../../layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { RenderToolTipContent } from '../../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; export const GisMap: React.ComponentType<{ addFilters: ((filters: Filter[]) => void) | null; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js index 97139103ab7c..358313b8f5b6 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js @@ -13,7 +13,8 @@ import { LayerPanel } from '../layer_panel/index'; import { AddLayerPanel } from '../layer_addpanel/index'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; -import { getIndexPatternsFromIds } from '../../index_pattern_util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIndexPatternsFromIds } from '../../../../../../plugins/maps/public/index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { indexPatterns as indexPatternsUtils } from '../../../../../../../src/plugins/data/public'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js index 762409b25628..cb20d80733c3 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js @@ -7,7 +7,8 @@ import React, { Fragment } from 'react'; import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { uploadLayerWizardConfig } from '../../../layers/sources/client_file_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { uploadLayerWizardConfig } from '../../../../../../../plugins/maps/public/layers/sources/client_file_source'; export const ImportEditor = ({ clearSource, isIndexingTriggered, ...props }) => { const editorProperties = getEditorProperties({ isIndexingTriggered, ...props }); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js index b34a432bec88..67cc17ebaa22 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js @@ -5,7 +5,8 @@ */ import React, { Fragment } from 'react'; -import { getLayerWizards } from '../../../layers/layer_wizard_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getLayerWizards } from '../../../../../../../plugins/maps/public/layers/layer_wizard_registry'; import { EuiTitle, EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js index f7edcf6e85e2..6c080ace4442 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js @@ -16,9 +16,11 @@ import { EuiFormHelpText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SingleFieldSelect } from '../../../../components/single_field_select'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { SingleFieldSelect } from '../../../../../../../../plugins/maps/public/components/single_field_select'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getTermsFields } from '../../../../index_pattern_util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getTermsFields } from '../../../../../../../../plugins/maps/public/index_pattern_util'; import { getIndexPatternService, getIndexPatternSelectComponent, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js index 0944d0e602c2..c6a79a398f9a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js @@ -14,7 +14,8 @@ import { EuiFormErrorText, EuiFormHelpText, } from '@elastic/eui'; -import { MetricsEditor } from '../../../../components/metrics_editor'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { MetricsEditor } from '../../../../../../../../plugins/maps/public/components/metrics_editor'; import { FormattedMessage } from '@kbn/i18n/react'; import { AGG_TYPE } from '../../../../../common/constants'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js index e4e3776c8e92..d8bf86224944 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../../components/metrics_editor', () => ({ +jest.mock('../../../../../../../../plugins/maps/public/components/metric_editor', () => ({ MetricsEditor: () => { return
mockMetricsEditor
; }, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js index eb23607aa215..bd2745094363 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js @@ -8,7 +8,8 @@ import React, { Fragment } from 'react'; import { EuiTitle, EuiPanel, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; -import { ValidatedRange } from '../../../components/validated_range'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidatedRange } from '../../../../../../../plugins/maps/public/components/validated_range'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ValidatedDualRange } from '../../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts index 6d1d076c723a..cf4fdc7be70c 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts @@ -5,10 +5,4 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { LAYER_TYPE } from '../../../common/constants'; - -export type OnSourceChangeArgs = { - propName: string; - value: unknown; - newLayerType?: LAYER_TYPE; -}; +export * from '../../../../../../plugins/maps/public/connected_components/layer_panel/view'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index 416af9558105..7063c50edad6 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -8,7 +8,8 @@ import React, { Component, Fragment } from 'react'; import { EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { createSpatialFilterWithGeometry } from '../../../elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createSpatialFilterWithGeometry } from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; import { UrlOverflowService } from 'ui/error_url_overflow'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index 99abe5d108b5..df2988d399c5 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -16,7 +16,8 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../elasticsearch_geo_utils'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { DrawTooltip } from './draw_tooltip'; const mbDrawModes = MapboxDraw.modes; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js index a2850d2bb6c2..a1d1341b7c4f 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js @@ -5,7 +5,13 @@ */ import _ from 'lodash'; -import { RGBAImage } from './image_utils'; +import { + loadSpriteSheetImageData, + addSpriteSheetToMapFromImageData, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/connected_components/map/mb/utils'; + +export { loadSpriteSheetImageData, addSpriteSheetToMapFromImageData }; export function removeOrphanedSourcesAndLayers(mbMap, layerList) { const mbStyle = mbMap.getStyle(); @@ -95,62 +101,7 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { }); } -function getImageData(img) { - const canvas = window.document.createElement('canvas'); - const context = canvas.getContext('2d'); - if (!context) { - throw new Error('failed to create canvas 2d context'); - } - canvas.width = img.width; - canvas.height = img.height; - context.drawImage(img, 0, 0, img.width, img.height); - return context.getImageData(0, 0, img.width, img.height); -} - -export async function loadSpriteSheetImageData(imgUrl) { - return new Promise((resolve, reject) => { - const image = new Image(); - if (isCrossOriginUrl(imgUrl)) { - image.crossOrigin = 'Anonymous'; - } - image.onload = el => { - const imgData = getImageData(el.currentTarget); - resolve(imgData); - }; - image.onerror = e => { - reject(e); - }; - image.src = imgUrl; - }); -} - -export function addSpriteSheetToMapFromImageData(json, imgData, mbMap) { - for (const imageId in json) { - if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { - continue; - } - const { width, height, x, y, sdf, pixelRatio } = json[imageId]; - if (typeof width !== 'number' || typeof height !== 'number') { - continue; - } - - const data = new RGBAImage({ width, height }); - RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); - mbMap.addImage(imageId, data, { pixelRatio, sdf }); - } -} - export async function addSpritesheetToMap(json, imgUrl, mbMap) { const imgData = await loadSpriteSheetImageData(imgUrl); addSpriteSheetToMapFromImageData(json, imgData, mbMap); } - -function isCrossOriginUrl(url) { - const a = window.document.createElement('a'); - a.href = url; - return ( - a.protocol !== window.document.location.protocol || - a.host !== window.document.location.host || - a.port !== window.document.location.port - ); -} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 2995ea039e7a..fedc1902d80a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -12,7 +12,8 @@ import { removeOrphanedSourcesAndLayers, addSpritesheetToMap, } from './utils'; -import { getGlyphUrl, isRetina } from '../../../meta'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getGlyphUrl, isRetina } from '../../../../../../../plugins/maps/public/meta'; import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants'; import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; @@ -23,7 +24,11 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../elasticsearch_geo_utils'; +import { + clampToLatBounds, + clampToLonBounds, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx index 9544e8714f26..bdd2d863e692 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx @@ -57,7 +57,8 @@ import { } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; -import { RenderToolTipContent } from '../layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { RenderToolTipContent } from '../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; interface MapEmbeddableConfig { editUrl?: string; diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts index 5a036ed47fb6..5deb3057a449 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -27,6 +27,11 @@ import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; import '../angular/services/gis_map_saved_object_loader'; import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin'; +// @ts-ignore +import { + bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, + bindStartCoreAndPlugins as bindNpStartCoreAndPlugins, +} from '../../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { type = MAP_SAVED_OBJECT_TYPE; @@ -40,7 +45,9 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { constructor() { // Init required services. Necessary while in legacy bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins); + bindNpSetupCoreAndPlugins(npSetup.core, npSetup.plugins); bindStartCoreAndPlugins(npStart.core, npStart.plugins); + bindNpStartCoreAndPlugins(npStart.core, npStart.plugins); } async isEditable() { diff --git a/x-pack/legacy/plugins/maps/public/index.scss b/x-pack/legacy/plugins/maps/public/index.scss index 328b2e576e0e..b2ac514299d8 100644 --- a/x-pack/legacy/plugins/maps/public/index.scss +++ b/x-pack/legacy/plugins/maps/public/index.scss @@ -14,4 +14,4 @@ @import './mapbox_hacks'; @import './connected_components/index'; @import './components/index'; -@import './layers/index'; +@import '../../../../plugins/maps/public/layers/index'; diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts index 2d13f005f1a7..b69485e251be 100644 --- a/x-pack/legacy/plugins/maps/public/index.ts +++ b/x-pack/legacy/plugins/maps/public/index.ts @@ -26,5 +26,9 @@ export const plugin = (initializerContext: PluginInitializerContext) => { return new MapsPlugin(); }; -export { RenderTooltipContentParams, ITooltipProperty } from './layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export { + RenderTooltipContentParams, + ITooltipProperty, +} from '../../../../plugins/maps/public/layers/tooltips/tooltip_property'; export { MapEmbeddable, MapEmbeddableInput } from './embeddable'; diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index 3b0f501dc0f6..a6491fe1aa6d 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -4,88 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { esFilters, search } from '../../../../../src/plugins/data/public'; -const { getRequestInspectorStats, getResponseInspectorStats } = search; - -export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; -export { SearchSource } from '../../../../../src/plugins/data/public'; - let indexPatternService; export const setIndexPatternService = dataIndexPatterns => (indexPatternService = dataIndexPatterns); export const getIndexPatternService = () => indexPatternService; -let autocompleteService; -export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); -export const getAutocompleteService = () => autocompleteService; - -let licenseId; -export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); -export const getLicenseId = () => { - return licenseId; -}; - let inspector; export const setInspector = newInspector => (inspector = newInspector); export const getInspector = () => { return inspector; }; -let fileUploadPlugin; -export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); -export const getFileUploadComponent = () => { - return fileUploadPlugin.JsonUploadAndParse; -}; - let getInjectedVar; export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); export const getInjectedVarFunc = () => getInjectedVar; -let uiSettings; -export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); -export const getUiSettings = () => uiSettings; - let indexPatternSelectComponent; export const setIndexPatternSelect = indexPatternSelect => (indexPatternSelectComponent = indexPatternSelect); export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; -let coreHttp; -export const setHttp = http => (coreHttp = http); -export const getHttp = () => coreHttp; - let dataTimeFilter; export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); export const getTimeFilter = () => dataTimeFilter; - -let toast; -export const setToasts = notificationToast => (toast = notificationToast); -export const getToasts = () => toast; - -export async function fetchSearchSourceAndRecordWithInspector({ - searchSource, - requestId, - requestName, - requestDesc, - inspectorAdapters, - abortSignal, -}) { - const inspectorRequest = inspectorAdapters.requests.start(requestName, { - id: requestId, - description: requestDesc, - }); - let resp; - try { - inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then(body => { - inspectorRequest.json(body); - }); - resp = await searchSource.fetch({ abortSignal }); - inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); - } catch (error) { - inspectorRequest.error({ error }); - throw error; - } - - return resp; -} diff --git a/x-pack/legacy/plugins/maps/public/layers/_index.scss b/x-pack/legacy/plugins/maps/public/layers/_index.scss deleted file mode 100644 index a2ce58e0381a..000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './styles/index'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss deleted file mode 100644 index b5d9113619c7..000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './components/color_gradient'; -@import './vector/components/style_prop_editor'; -@import './vector/components/color/color_stops'; -@import './vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index c08ed6fc6da6..0fa7e1106a6d 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import './layers/layer_wizard_registry'; -import './layers/sources/source_registry'; -import './layers/load_layer_wizards'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/layer_wizard_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/sources/source_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/load_layer_wizards'; import { Plugin, CoreStart, CoreSetup } from 'src/core/public'; // @ts-ignore @@ -17,20 +20,17 @@ import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { MapListing } from './components/map_listing'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { - setLicenseId, setInspector, - setFileUpload, setIndexPatternSelect, - setHttp, setTimeFilter, - setUiSettings, setInjectedVarFunc, - setToasts, setIndexPatternService, - setAutocompleteService, } from './kibana_services'; // @ts-ignore -import { setInjectedVarFunc as npSetInjectedVarFunc } from '../../../../plugins/maps/public/kibana_services'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { + bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, + bindStartCoreAndPlugins as bindNpStartCoreAndPlugins, +} from '../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { featureCatalogueEntry } from './feature_catalogue_entry'; @@ -63,27 +63,17 @@ interface MapsPluginStartDependencies { } export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { - const { licensing } = plugins; - const { injectedMetadata, http } = core; - if (licensing) { - licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); - } + const { injectedMetadata } = core; setInjectedVarFunc(injectedMetadata.getInjectedVar); - setHttp(http); - setUiSettings(core.uiSettings); setInjectedVarFunc(core.injectedMetadata.getInjectedVar); - npSetInjectedVarFunc(core.injectedMetadata.getInjectedVar); - setToasts(core.notifications.toasts); }; export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { - const { file_upload, data, inspector } = plugins; + const { data, inspector } = plugins; setInspector(inspector); - setFileUpload(file_upload); setIndexPatternSelect(data.ui.IndexPatternSelect); setTimeFilter(data.query.timefilter.timefilter); setIndexPatternService(data.indexPatterns); - setAutocompleteService(data.autocomplete); }; /** @internal */ @@ -96,11 +86,13 @@ export class MapsPlugin implements Plugin { }); bindSetupCoreAndPlugins(core, np); + bindNpSetupCoreAndPlugins(core, np); np.home.featureCatalogue.register(featureCatalogueEntry); } public start(core: CoreStart, plugins: MapsPluginStartDependencies) { bindStartCoreAndPlugins(core, plugins); + bindNpStartCoreAndPlugins(core, plugins); } } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index 397478cfd1d1..59346e4c6fb9 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -6,11 +6,16 @@ import { createSelector } from 'reselect'; import _ from 'lodash'; -import { TileLayer } from '../layers/tile_layer'; -import { VectorTileLayer } from '../layers/vector_tile_layer'; -import { VectorLayer } from '../layers/vector_layer'; -import { HeatmapLayer } from '../layers/heatmap_layer'; -import { BlendedVectorLayer } from '../layers/blended_vector_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TileLayer } from '../../../../../plugins/maps/public/layers/tile_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VectorTileLayer } from '../../../../../plugins/maps/public/layers/vector_tile_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VectorLayer } from '../../../../../plugins/maps/public/layers/vector_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { HeatmapLayer } from '../../../../../plugins/maps/public/layers/heatmap_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BlendedVectorLayer } from '../../../../../plugins/maps/public/layers/blended_vector_layer'; import { getTimeFilter } from '../kibana_services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; @@ -19,8 +24,10 @@ import { TRACKED_LAYER_DESCRIPTOR, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../plugins/maps/public/reducers/util'; -import { InnerJoin } from '../layers/joins/inner_join'; -import { getSourceByType } from '../layers/sources/source_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { InnerJoin } from '../../../../../plugins/maps/public/layers/joins/inner_join'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getSourceByType } from '../../../../../plugins/maps/public/layers/sources/source_registry'; function createLayerInstance(layerDescriptor, inspectorAdapters) { const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters); diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js index 1a5ab633a569..77bd29259647 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../layers/vector_layer', () => {}); -jest.mock('../layers/blended_vector_layer', () => {}); -jest.mock('../layers/heatmap_layer', () => {}); -jest.mock('../layers/vector_tile_layer', () => {}); -jest.mock('../layers/joins/inner_join', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/vector_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/blended_vector_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/heatmap_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/vector_tile_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/joins/inner_join', () => {}); jest.mock('../../../../../plugins/maps/public/reducers/non_serializable_instances', () => ({ getInspectorAdapters: () => { return {}; diff --git a/x-pack/plugins/maps/public/actions/map_actions.d.ts b/x-pack/plugins/maps/public/actions/map_actions.d.ts new file mode 100644 index 000000000000..debead3ad5c4 --- /dev/null +++ b/x-pack/plugins/maps/public/actions/map_actions.d.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { Filter, Query, TimeRange } from 'src/plugins/data/public'; +import { AnyAction } from 'redux'; +import { LAYER_TYPE } from '../../common/constants'; +import { + DataMeta, + MapFilters, + MapCenterAndZoom, + MapRefreshConfig, +} from '../../common/descriptor_types'; + +export type SyncContext = { + startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void; + stopLoading(dataId: string, requestToken: symbol, data: unknown, meta: DataMeta): void; + onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; + updateSourceData(newData: unknown): void; + isRequestStillActive(dataId: string, requestToken: symbol): boolean; + registerCancelCallback(requestToken: symbol, callback: () => void): void; + dataFilters: MapFilters; +}; + +export function updateSourceProp( + layerId: string, + propName: string, + value: unknown, + newLayerType?: LAYER_TYPE +): void; + +export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction; + +export function replaceLayerList(layerList: unknown[]): AnyAction; + +export type QueryGroup = { + filters: Filter[]; + query?: Query; + timeFilters?: TimeRange; + refresh?: boolean; +}; + +export function setQuery(query: QueryGroup): AnyAction; + +export function setRefreshConfig(config: MapRefreshConfig): AnyAction; + +export function disableScrollZoom(): AnyAction; + +export function disableInteractive(): AnyAction; + +export function disableTooltipControl(): AnyAction; + +export function hideToolbarOverlay(): AnyAction; + +export function hideLayerControl(): AnyAction; + +export function hideViewControl(): AnyAction; + +export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; + +export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js rename to x-pack/plugins/maps/public/components/add_tooltip_field_popover.js index 07bc54663c1d..984ace4fd870 100644 --- a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js +++ b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../src/plugins/kibana_react/public'; const sortByLabel = (a, b) => { return a.label.localeCompare(b.label); diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.test.js b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.test.js rename to x-pack/plugins/maps/public/components/add_tooltip_field_popover.test.js diff --git a/x-pack/legacy/plugins/maps/public/components/metric_editor.js b/x-pack/plugins/maps/public/components/metric_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metric_editor.js rename to x-pack/plugins/maps/public/components/metric_editor.js diff --git a/x-pack/legacy/plugins/maps/public/components/metric_select.js b/x-pack/plugins/maps/public/components/metric_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metric_select.js rename to x-pack/plugins/maps/public/components/metric_select.js diff --git a/x-pack/legacy/plugins/maps/public/components/metrics_editor.js b/x-pack/plugins/maps/public/components/metrics_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metrics_editor.js rename to x-pack/plugins/maps/public/components/metrics_editor.js diff --git a/x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js similarity index 85% rename from x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js rename to x-pack/plugins/maps/public/components/no_index_pattern_callout.js index 3266f13155ca..131960754680 100644 --- a/x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js +++ b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - +import { getHttp } from '../kibana_services'; import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; export function NoIndexPatternCallout() { + const http = getHttp(); return ( - + - + { + const image = new Image(); + if (isCrossOriginUrl(imgUrl)) { + image.crossOrigin = 'Anonymous'; + } + image.onload = el => { + const imgData = getImageData(el.currentTarget); + resolve(imgData); + }; + image.onerror = e => { + reject(e); + }; + image.src = imgUrl; + }); +} + +export function addSpriteSheetToMapFromImageData(json, imgData, mbMap) { + for (const imageId in json) { + if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { + continue; + } + const { width, height, x, y, sdf, pixelRatio } = json[imageId]; + if (typeof width !== 'number' || typeof height !== 'number') { + continue; + } + + const data = new RGBAImage({ width, height }); + RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); + mbMap.addImage(imageId, data, { pixelRatio, sdf }); + } +} diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/public/elasticsearch_geo_utils.js diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js rename to x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js index fb4b0a6e29e6..5db7556be463 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js @@ -20,7 +20,7 @@ import { convertMapExtentToPolygon, roundCoordinates, } from './elasticsearch_geo_utils'; -import { indexPatterns } from '../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../src/plugins/data/public'; const geoFieldName = 'location'; const mapExtent = { diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.js b/x-pack/plugins/maps/public/index_pattern_util.js similarity index 95% rename from x-pack/legacy/plugins/maps/public/index_pattern_util.js rename to x-pack/plugins/maps/public/index_pattern_util.js index 30a0a6826db8..6cb02c7605e2 100644 --- a/x-pack/legacy/plugins/maps/public/index_pattern_util.js +++ b/x-pack/plugins/maps/public/index_pattern_util.js @@ -5,7 +5,7 @@ */ import { getIndexPatternService } from './kibana_services'; -import { indexPatterns } from '../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../src/plugins/data/public'; import { ES_GEO_FIELD_TYPE } from '../common/constants'; export async function getIndexPatternsFromIds(indexPatternIds = []) { diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.test.js b/x-pack/plugins/maps/public/index_pattern_util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/index_pattern_util.test.js rename to x-pack/plugins/maps/public/index_pattern_util.test.js diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index 1073e44fa711..d2ddecfdf915 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -3,7 +3,89 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { esFilters, search } from '../../../../src/plugins/data/public'; + +export { SearchSource } from '../../../../src/plugins/data/public'; + +export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; +const { getRequestInspectorStats, getResponseInspectorStats } = search; + +let indexPatternService; +export const setIndexPatternService = dataIndexPatterns => + (indexPatternService = dataIndexPatterns); +export const getIndexPatternService = () => indexPatternService; + +let autocompleteService; +export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); +export const getAutocompleteService = () => autocompleteService; + +let licenseId; +export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); +export const getLicenseId = () => { + return licenseId; +}; + +let inspector; +export const setInspector = newInspector => (inspector = newInspector); +export const getInspector = () => { + return inspector; +}; + +let fileUploadPlugin; +export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); +export const getFileUploadComponent = () => { + return fileUploadPlugin.JsonUploadAndParse; +}; let getInjectedVar; export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); export const getInjectedVarFunc = () => getInjectedVar; + +let uiSettings; +export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); +export const getUiSettings = () => uiSettings; + +let indexPatternSelectComponent; +export const setIndexPatternSelect = indexPatternSelect => + (indexPatternSelectComponent = indexPatternSelect); +export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; + +let coreHttp; +export const setHttp = http => (coreHttp = http); +export const getHttp = () => coreHttp; + +let dataTimeFilter; +export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); +export const getTimeFilter = () => dataTimeFilter; + +let toast; +export const setToasts = notificationToast => (toast = notificationToast); +export const getToasts = () => toast; + +export async function fetchSearchSourceAndRecordWithInspector({ + searchSource, + requestId, + requestName, + requestDesc, + inspectorAdapters, + abortSignal, +}) { + const inspectorRequest = inspectorAdapters.requests.start(requestName, { + id: requestId, + description: requestDesc, + }); + let resp; + try { + inspectorRequest.stats(getRequestInspectorStats(searchSource)); + searchSource.getSearchRequestBody().then(body => { + inspectorRequest.json(body); + }); + resp = await searchSource.fetch({ abortSignal }); + inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); + } catch (error) { + inspectorRequest.error({ error }); + throw error; + } + + return resp; +} diff --git a/x-pack/plugins/maps/public/layers/_index.scss b/x-pack/plugins/maps/public/layers/_index.scss new file mode 100644 index 000000000000..29a576125527 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/_index.scss @@ -0,0 +1 @@ +@import 'styles/index'; diff --git a/x-pack/legacy/plugins/maps/public/layers/blended_vector_layer.ts b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/blended_vector_layer.ts rename to x-pack/plugins/maps/public/layers/blended_vector_layer.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/ems_file_field.ts b/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/ems_file_field.ts rename to x-pack/plugins/maps/public/layers/fields/ems_file_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.ts b/x-pack/plugins/maps/public/layers/fields/es_agg_field.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.ts rename to x-pack/plugins/maps/public/layers/fields/es_agg_field.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts b/x-pack/plugins/maps/public/layers/fields/es_agg_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts rename to x-pack/plugins/maps/public/layers/fields/es_agg_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts b/x-pack/plugins/maps/public/layers/fields/es_doc_field.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts rename to x-pack/plugins/maps/public/layers/fields/es_doc_field.ts index 4401452841a4..b7647d881fcf 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts +++ b/x-pack/plugins/maps/public/layers/fields/es_doc_field.ts @@ -8,8 +8,8 @@ import { FIELD_ORIGIN } from '../../../common/constants'; import { ESTooltipProperty } from '../tooltips/es_tooltip_property'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants'; -import { indexPatterns } from '../../../../../../../src/plugins/data/public'; -import { IFieldType } from '../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../src/plugins/data/public'; +import { IFieldType } from '../../../../../../src/plugins/data/public'; import { IField, AbstractField } from './field'; import { IESSource } from '../sources/es_source'; import { IVectorSource } from '../sources/vector_source'; diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/field.ts b/x-pack/plugins/maps/public/layers/fields/field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/field.ts rename to x-pack/plugins/maps/public/layers/fields/field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/kibana_region_field.ts b/x-pack/plugins/maps/public/layers/fields/kibana_region_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/kibana_region_field.ts rename to x-pack/plugins/maps/public/layers/fields/kibana_region_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/top_term_percentage_field.ts b/x-pack/plugins/maps/public/layers/fields/top_term_percentage_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/top_term_percentage_field.ts rename to x-pack/plugins/maps/public/layers/fields/top_term_percentage_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js b/x-pack/plugins/maps/public/layers/heatmap_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js rename to x-pack/plugins/maps/public/layers/heatmap_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js b/x-pack/plugins/maps/public/layers/joins/inner_join.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js rename to x-pack/plugins/maps/public/layers/joins/inner_join.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/plugins/maps/public/layers/joins/inner_join.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js rename to x-pack/plugins/maps/public/layers/joins/inner_join.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/join.ts b/x-pack/plugins/maps/public/layers/joins/join.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/join.ts rename to x-pack/plugins/maps/public/layers/joins/join.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts b/x-pack/plugins/maps/public/layers/layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/layer.d.ts rename to x-pack/plugins/maps/public/layers/layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/plugins/maps/public/layers/layer.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/layer.js rename to x-pack/plugins/maps/public/layers/layer.js index e9616be89b60..26bce872b3c2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/plugins/maps/public/layers/layer.js @@ -15,7 +15,7 @@ import { } from '../../common/constants'; import uuid from 'uuid/v4'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util.js'; +import { copyPersistentState } from '../reducers/util.js'; import { i18n } from '@kbn/i18n'; export class AbstractLayer { diff --git a/x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts b/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts rename to x-pack/plugins/maps/public/layers/layer_wizard_registry.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js b/x-pack/plugins/maps/public/layers/load_layer_wizards.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js rename to x-pack/plugins/maps/public/layers/load_layer_wizards.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js b/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js rename to x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_agg_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/plugins/maps/public/layers/sources/es_agg_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_agg_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index 269c2a8b8633..cd494db3897f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isMetricCountable } from '../../util/is_metric_countable'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; export class UpdateSourceEditor extends Component { state = { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index da2b663746b9..ea3a2d2fe634 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -25,7 +25,7 @@ import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { COLOR_GRADIENTS } from '../../styles/color_utils'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { registerSource } from '../source_registry'; const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js index ce1f53c33ba5..dea59a1c82f8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js @@ -11,7 +11,7 @@ import { getIndexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; export class UpdateSourceEditor extends Component { state = { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap rename to x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap b/x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap rename to x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/constants.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/constants.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/constants.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/constants.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index 73bea574ace2..aeb3835354f0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -15,7 +15,7 @@ import { NoIndexPatternCallout } from '../../../components/no_index_pattern_call import { i18n } from '@kbn/i18n'; import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; import { getTermsFields } from '../../../index_pattern_util'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx rename to x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx rename to x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx index c5950f113297..d86fc6d4026e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx @@ -22,8 +22,6 @@ import { SingleFieldSelect } from '../../../components/single_field_select'; // @ts-ignore import { indexPatternService } from '../../../kibana_services'; // @ts-ignore -import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; -// @ts-ignore import { ValidatedRange } from '../../../components/validated_range'; import { DEFAULT_MAX_INNER_RESULT_WINDOW, @@ -33,7 +31,7 @@ import { } from '../../../../common/constants'; // @ts-ignore import { loadIndexSettings } from './load_index_settings'; -import { IFieldType } from '../../../../../../../../src/plugins/data/public'; +import { IFieldType } from '../../../../../../../src/plugins/data/public'; import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; interface Props { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js index 9c92ec5801e4..cb6255afd0a4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js @@ -16,7 +16,7 @@ import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; export class UpdateSourceEditor extends Component { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_source.d.ts similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_source.d.ts index ffd1d343b59e..65851d0e7bd3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_source.d.ts @@ -6,7 +6,7 @@ import { AbstractVectorSource } from './vector_source'; import { IVectorSource } from './vector_source'; -import { IndexPattern, SearchSource } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, SearchSource } from '../../../../../../src/plugins/data/public'; import { VectorSourceRequestMeta } from '../../../common/descriptor_types'; export interface IESSource extends IVectorSource { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/plugins/maps/public/layers/sources/es_source.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_source.js rename to x-pack/plugins/maps/public/layers/sources/es_source.js index 441d52d23398..d90a802a3834 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_source.js @@ -17,7 +17,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; +import { copyPersistentState } from '../../reducers/util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_term_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_term_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/plugins/maps/public/layers/sources/es_term_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js rename to x-pack/plugins/maps/public/layers/sources/es_term_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/plugins/maps/public/layers/sources/es_term_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js rename to x-pack/plugins/maps/public/layers/sources/es_term_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts b/x-pack/plugins/maps/public/layers/sources/source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts rename to x-pack/plugins/maps/public/layers/sources/source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/plugins/maps/public/layers/sources/source.js similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/sources/source.js rename to x-pack/plugins/maps/public/layers/sources/source.js index b6b6c10831bb..368de421e23c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/plugins/maps/public/layers/sources/source.js @@ -5,7 +5,7 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; +import { copyPersistentState } from '../../reducers/util'; export class AbstractSource { static isIndexingSource = false; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts b/x-pack/plugins/maps/public/layers/sources/source_registry.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts rename to x-pack/plugins/maps/public/layers/sources/source_registry.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts b/x-pack/plugins/maps/public/layers/sources/tms_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/tms_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.js b/x-pack/plugins/maps/public/layers/sources/tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/tms_source.js rename to x-pack/plugins/maps/public/layers/sources/tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_feature_types.js b/x-pack/plugins/maps/public/layers/sources/vector_feature_types.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_feature_types.js rename to x-pack/plugins/maps/public/layers/sources/vector_feature_types.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts b/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/vector_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/plugins/maps/public/layers/sources/vector_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js rename to x-pack/plugins/maps/public/layers/sources/vector_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js b/x-pack/plugins/maps/public/layers/sources/wms_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.test.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.test.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.test.ts diff --git a/x-pack/plugins/maps/public/layers/styles/_index.scss b/x-pack/plugins/maps/public/layers/styles/_index.scss new file mode 100644 index 000000000000..a1c4c297a3ac --- /dev/null +++ b/x-pack/plugins/maps/public/layers/styles/_index.scss @@ -0,0 +1,4 @@ +@import 'components/color_gradient'; +@import 'vector/components/style_prop_editor'; +@import 'vector/components/color/color_stops'; +@import 'vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/abstract_style.js b/x-pack/plugins/maps/public/layers/styles/abstract_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/abstract_style.js rename to x-pack/plugins/maps/public/layers/styles/abstract_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js b/x-pack/plugins/maps/public/layers/styles/color_utils.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js rename to x-pack/plugins/maps/public/layers/styles/color_utils.js index 09c7d76db169..23b61b07bf87 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js +++ b/x-pack/plugins/maps/public/layers/styles/color_utils.js @@ -10,7 +10,7 @@ import chroma from 'chroma-js'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { ColorGradient } from './components/color_gradient'; import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants'; -import { vislibColorMaps } from '../../../../../../../src/plugins/charts/public'; +import { vislibColorMaps } from '../../../../../../src/plugins/charts/public'; const GRADIENT_INTERVALS = 8; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js b/x-pack/plugins/maps/public/layers/styles/color_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js rename to x-pack/plugins/maps/public/layers/styles/color_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/_color_gradient.scss b/x-pack/plugins/maps/public/layers/styles/components/_color_gradient.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/_color_gradient.scss rename to x-pack/plugins/maps/public/layers/styles/components/_color_gradient.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/color_gradient.js b/x-pack/plugins/maps/public/layers/styles/components/color_gradient.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/color_gradient.js rename to x-pack/plugins/maps/public/layers/styles/components/color_gradient.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js b/x-pack/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js rename to x-pack/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap b/x-pack/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js index 2f5de507657a..ed2e7a4eab7e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -10,7 +10,7 @@ import React from 'react'; import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FIELD_ORIGIN } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; function renderOption(option, searchValue, contentClassName) { return ( diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/category.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/category.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js index 5de7b462136e..ec847e2a5384 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { ValidatedDualRange } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { ValidatedDualRange } from '../../../../../../../../../src/plugins/kibana_react/public'; import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js b/x-pack/plugins/maps/public/layers/styles/vector/components/stop_input.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/stop_input.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_color_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_color_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_text_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_text_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.ts b/x-pack/plugins/maps/public/layers/styles/vector/properties/style_property.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.ts rename to x-pack/plugins/maps/public/layers/styles/vector/properties/style_property.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_meta.ts b/x-pack/plugins/maps/public/layers/styles/vector/style_meta.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_meta.ts rename to x-pack/plugins/maps/public/layers/styles/vector/style_meta.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/plugins/maps/public/layers/styles/vector/style_util.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js rename to x-pack/plugins/maps/public/layers/styles/vector/style_util.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/plugins/maps/public/layers/styles/vector/style_util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/style_util.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js b/x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js rename to x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js b/x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.d.ts rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts b/x-pack/plugins/maps/public/layers/tile_layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts rename to x-pack/plugins/maps/public/layers/tile_layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.js b/x-pack/plugins/maps/public/layers/tile_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.js rename to x-pack/plugins/maps/public/layers/tile_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/plugins/maps/public/layers/tile_layer.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts rename to x-pack/plugins/maps/public/layers/tile_layer.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts index 8fd7e173435c..5c3500988192 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts @@ -7,8 +7,8 @@ import _ from 'lodash'; import { ITooltipProperty } from './tooltip_property'; import { IField } from '../fields/field'; -import { esFilters, IFieldType, IndexPattern } from '../../../../../../../src/plugins/data/public'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; +import { esFilters, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/public'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; export class ESTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts index 02f0920ce3c6..4af236f6e9e3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts @@ -6,7 +6,7 @@ import { ITooltipProperty } from './tooltip_property'; import { IJoin } from '../joins/join'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; export class JoinTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts index 46e27bbd770a..7d680dfe9cae 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts @@ -5,8 +5,8 @@ */ import _ from 'lodash'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; -import { TooltipFeature } from '../../../../../../plugins/maps/common/descriptor_types'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; +import { TooltipFeature } from '../../../../../plugins/maps/common/descriptor_types'; export interface ITooltipProperty { getPropertyKey(): string; diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.ts b/x-pack/plugins/maps/public/layers/util/assign_feature_ids.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.ts rename to x-pack/plugins/maps/public/layers/util/assign_feature_ids.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.ts b/x-pack/plugins/maps/public/layers/util/assign_feature_ids.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.ts rename to x-pack/plugins/maps/public/layers/util/assign_feature_ids.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js b/x-pack/plugins/maps/public/layers/util/can_skip_fetch.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js rename to x-pack/plugins/maps/public/layers/util/can_skip_fetch.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/layers/util/can_skip_fetch.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.ts rename to x-pack/plugins/maps/public/layers/util/can_skip_fetch.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/data_request.ts b/x-pack/plugins/maps/public/layers/util/data_request.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/data_request.ts rename to x-pack/plugins/maps/public/layers/util/data_request.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts b/x-pack/plugins/maps/public/layers/util/es_agg_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts rename to x-pack/plugins/maps/public/layers/util/es_agg_utils.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts b/x-pack/plugins/maps/public/layers/util/es_agg_utils.ts similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts rename to x-pack/plugins/maps/public/layers/util/es_agg_utils.ts index 9d4f24f80d6c..329a2a6fc64f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts +++ b/x-pack/plugins/maps/public/layers/util/es_agg_utils.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IndexPattern, IFieldType } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, IFieldType } from '../../../../../../src/plugins/data/public'; import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; export function getField(indexPattern: IndexPattern, fieldName: string) { diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.ts b/x-pack/plugins/maps/public/layers/util/is_metric_countable.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.ts rename to x-pack/plugins/maps/public/layers/util/is_metric_countable.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts b/x-pack/plugins/maps/public/layers/util/is_refresh_only_query.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts rename to x-pack/plugins/maps/public/layers/util/is_refresh_only_query.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/layers/util/mb_filter_expressions.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.ts rename to x-pack/plugins/maps/public/layers/util/mb_filter_expressions.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.d.ts b/x-pack/plugins/maps/public/layers/vector_layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_layer.d.ts rename to x-pack/plugins/maps/public/layers/vector_layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/plugins/maps/public/layers/vector_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_layer.js rename to x-pack/plugins/maps/public/layers/vector_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/plugins/maps/public/layers/vector_tile_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js rename to x-pack/plugins/maps/public/layers/vector_tile_layer.js diff --git a/x-pack/legacy/plugins/maps/public/meta.js b/x-pack/plugins/maps/public/meta.js similarity index 76% rename from x-pack/legacy/plugins/maps/public/meta.js rename to x-pack/plugins/maps/public/meta.js index 4d81785ff7a0..d4612554cf00 100644 --- a/x-pack/legacy/plugins/maps/public/meta.js +++ b/x-pack/plugins/maps/public/meta.js @@ -11,20 +11,19 @@ import { EMS_GLYPHS_PATH, EMS_APP_NAME, } from '../common/constants'; -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { EMSClient } from '@elastic/ems-client'; -import { getLicenseId } from './kibana_services'; +import { getInjectedVarFunc, getLicenseId } from './kibana_services'; import fetch from 'node-fetch'; const GIS_API_RELATIVE = `../${GIS_API_PATH}`; export function getKibanaRegionList() { - return chrome.getInjected('regionmapLayers'); + return getInjectedVarFunc()('regionmapLayers'); } export function getKibanaTileMap() { - return chrome.getInjected('tilemap'); + return getInjectedVarFunc()('tilemap'); } function relativeToAbsolute(url) { @@ -41,27 +40,27 @@ let emsClient = null; let latestLicenseId = null; export function getEMSClient() { if (!emsClient) { - const isEmsEnabled = chrome.getInjected('isEmsEnabled', true); + const isEmsEnabled = getInjectedVarFunc()('isEmsEnabled', true); if (isEmsEnabled) { - const proxyElasticMapsServiceInMaps = chrome.getInjected( + const proxyElasticMapsServiceInMaps = getInjectedVarFunc()( 'proxyElasticMapsServiceInMaps', false ); const proxyPath = ''; const tileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_TILES_CATALOGUE_PATH}`) - : chrome.getInjected('emsTileApiUrl'); + : getInjectedVarFunc()('emsTileApiUrl'); const fileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_FILES_CATALOGUE_PATH}`) - : chrome.getInjected('emsFileApiUrl'); + : getInjectedVarFunc()('emsFileApiUrl'); emsClient = new EMSClient({ language: i18n.getLocale(), - appVersion: chrome.getInjected('kbnPkgVersion'), + appVersion: getInjectedVarFunc()('kbnPkgVersion'), appName: EMS_APP_NAME, tileApiUrl, fileApiUrl, - landingPageUrl: chrome.getInjected('emsLandingPageUrl'), + landingPageUrl: getInjectedVarFunc()('emsLandingPageUrl'), fetchFunction: fetchFunction, //import this from client-side, so the right instance is returned (bootstrapped from common/* would not work proxyPath, }); @@ -87,13 +86,13 @@ export function getEMSClient() { } export function getGlyphUrl() { - if (!chrome.getInjected('isEmsEnabled', true)) { + if (!getInjectedVarFunc()('isEmsEnabled', true)) { return ''; } - return chrome.getInjected('proxyElasticMapsServiceInMaps', false) + return getInjectedVarFunc()('proxyElasticMapsServiceInMaps', false) ? relativeToAbsolute(`../${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}`) + `/{fontstack}/{range}` - : chrome.getInjected('emsFontLibraryUrl', true); + : getInjectedVarFunc()('emsFontLibraryUrl', true); } export function isRetina() { diff --git a/x-pack/legacy/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js similarity index 57% rename from x-pack/legacy/plugins/maps/public/meta.test.js rename to x-pack/plugins/maps/public/meta.test.js index 64dd73fe109f..d83f2adb35ef 100644 --- a/x-pack/legacy/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -9,39 +9,24 @@ import { getEMSClient } from './meta'; jest.mock('@elastic/ems-client'); -jest.mock('ui/chrome', () => ({ - getBasePath: () => { - return ''; - }, - getInjected(key) { - if (key === 'proxyElasticMapsServiceInMaps') { - return false; - } else if (key === 'isEmsEnabled') { - return true; - } else if (key === 'emsFileApiUrl') { - return 'https://file-api'; - } else if (key === 'emsTileApiUrl') { - return 'https://tile-api'; - } - }, - getUiSettingsClient: () => { - return { - get: () => { - return ''; - }, +describe('default use without proxy', () => { + beforeEach(() => { + require('./kibana_services').getInjectedVarFunc = () => key => { + if (key === 'proxyElasticMapsServiceInMaps') { + return false; + } else if (key === 'isEmsEnabled') { + return true; + } else if (key === 'emsFileApiUrl') { + return 'https://file-api'; + } else if (key === 'emsTileApiUrl') { + return 'https://tile-api'; + } }; - }, -})); - -jest.mock('./kibana_services', () => { - return { - getLicenseId() { + require('./kibana_services').getLicenseId = () => { return 'foobarlicenseid'; - }, - }; -}); + }; + }); -describe('default use without proxy', () => { it('should construct EMSClient with absolute file and tile API urls', async () => { getEMSClient(); const mockEmsClientCall = EMSClient.mock.calls[0]; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 506b0c426f0f..9437c2512ded 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -8,6 +8,20 @@ import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public'; // @ts-ignore import { MapView } from './inspector/views/map_view'; +import { + setAutocompleteService, + setFileUpload, + setHttp, + setIndexPatternSelect, + setIndexPatternService, + setInjectedVarFunc, + setInspector, + setLicenseId, + setTimeFilter, + setToasts, + setUiSettings, + // @ts-ignore +} from './kibana_services'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -15,6 +29,29 @@ export interface MapsPluginSetupDependencies { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MapsPluginStartDependencies {} +export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { + const { licensing } = plugins; + const { injectedMetadata, http } = core; + if (licensing) { + licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); + } + setInjectedVarFunc(injectedMetadata.getInjectedVar); + setHttp(http); + setUiSettings(core.uiSettings); + setInjectedVarFunc(core.injectedMetadata.getInjectedVar); + setToasts(core.notifications.toasts); +}; + +export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { + const { file_upload, data, inspector } = plugins; + setInspector(inspector); + setFileUpload(file_upload); + setIndexPatternSelect(data.ui.IndexPatternSelect); + setTimeFilter(data.query.timefilter.timefilter); + setIndexPatternService(data.indexPatterns); + setAutocompleteService(data.autocomplete); +}; + /** * These are the interfaces with your public contracts. You should export these * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces.