From 920e4fa280aaed62be7ebb6e3dc47b1a0720240c Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 1 Dec 2020 15:55:54 +0100 Subject: [PATCH 01/37] Update code-comments describing babel plugins (#84622) --- packages/kbn-babel-preset/common_preset.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index 8e2f1d207f3f4..b14dcd8971c31 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -28,18 +28,19 @@ const plugins = [ // See https://github.com/babel/proposals/issues/12 for progress require.resolve('@babel/plugin-proposal-class-properties'), - // Optional Chaining proposal is stage 3 (https://github.com/tc39/proposal-optional-chaining) + // Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining) // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-optional-chaining'), - // Nullish coalescing proposal is stage 3 (https://github.com/tc39/proposal-nullish-coalescing) + + // Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing) // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), - // Proposal is on stage 4 (https://github.com/tc39/proposal-export-ns-from) + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) // Need this since we are using TypeScript 3.8+ require.resolve('@babel/plugin-proposal-export-namespace-from'), - // Proposal is on stage 4 (https://github.com/tc39/proposal-export-ns-from) + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) // Need this since we are using TypeScript 3.9+ require.resolve('@babel/plugin-proposal-private-methods'), ]; From 0b5c55c59717f977ec39a7657aa6b570e19bf876 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 1 Dec 2020 07:10:09 -0800 Subject: [PATCH 02/37] Revert "[Alerting] renames Resolved action group to Recovered (#84123)" This reverts commit 7dcaff5dddb0389bd2e5b35b41d58006710afc91. --- .../alerts/common/builtin_action_groups.ts | 10 +-- .../alerts/server/alert_type_registry.test.ts | 14 ++--- .../tests/get_alert_instance_summary.test.ts | 2 +- ...rt_instance_summary_from_event_log.test.ts | 62 +++---------------- .../alert_instance_summary_from_event_log.ts | 5 +- x-pack/plugins/alerts/server/plugin.ts | 5 +- .../server/task_runner/task_runner.test.ts | 12 ++-- .../alerts/server/task_runner/task_runner.ts | 32 +++++----- .../inventory_metric_threshold_executor.ts | 4 +- .../metric_threshold_executor.test.ts | 6 +- .../metric_threshold_executor.ts | 4 +- .../public/application/constants/index.ts | 6 +- .../action_form.test.tsx | 27 ++++---- .../action_type_form.tsx | 10 +-- .../tests/alerting/list_alert_types.ts | 4 +- .../spaces_only/tests/alerting/alerts_base.ts | 16 ++--- .../spaces_only/tests/alerting/event_log.ts | 14 ++--- .../alerting/get_alert_instance_summary.ts | 2 +- .../tests/alerting/list_alert_types.ts | 2 +- 19 files changed, 90 insertions(+), 147 deletions(-) diff --git a/x-pack/plugins/alerts/common/builtin_action_groups.ts b/x-pack/plugins/alerts/common/builtin_action_groups.ts index d9c5ae613f787..d31f75357d370 100644 --- a/x-pack/plugins/alerts/common/builtin_action_groups.ts +++ b/x-pack/plugins/alerts/common/builtin_action_groups.ts @@ -6,13 +6,13 @@ import { i18n } from '@kbn/i18n'; import { ActionGroup } from './alert_type'; -export const RecoveredActionGroup: ActionGroup = { - id: 'recovered', - name: i18n.translate('xpack.alerts.builtinActionGroups.recovered', { - defaultMessage: 'Recovered', +export const ResolvedActionGroup: ActionGroup = { + id: 'resolved', + name: i18n.translate('xpack.alerts.builtinActionGroups.resolved', { + defaultMessage: 'Resolved', }), }; export function getBuiltinActionGroups(): ActionGroup[] { - return [RecoveredActionGroup]; + return [ResolvedActionGroup]; } diff --git a/x-pack/plugins/alerts/server/alert_type_registry.test.ts b/x-pack/plugins/alerts/server/alert_type_registry.test.ts index b04871a047e4b..8dc387f96addb 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.test.ts @@ -105,8 +105,8 @@ describe('register()', () => { name: 'Default', }, { - id: 'recovered', - name: 'Recovered', + id: 'resolved', + name: 'Resolved', }, ], defaultActionGroupId: 'default', @@ -117,7 +117,7 @@ describe('register()', () => { expect(() => registry.register(alertType)).toThrowError( new Error( - `Alert type [id="${alertType.id}"] cannot be registered. Action groups [recovered] are reserved by the framework.` + `Alert type [id="${alertType.id}"] cannot be registered. Action groups [resolved] are reserved by the framework.` ) ); }); @@ -229,8 +229,8 @@ describe('get()', () => { "name": "Default", }, Object { - "id": "recovered", - "name": "Recovered", + "id": "resolved", + "name": "Resolved", }, ], "actionVariables": Object { @@ -287,8 +287,8 @@ describe('list()', () => { "name": "Test Action Group", }, Object { - "id": "recovered", - "name": "Recovered", + "id": "resolved", + "name": "Resolved", }, ], "actionVariables": Object { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 0a764ea768591..9bd61c0fe66d2 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -122,7 +122,7 @@ describe('getAlertInstanceSummary()', () => { .addActiveInstance('instance-previously-active', 'action group B') .advanceTime(10000) .addExecute() - .addRecoveredInstance('instance-previously-active') + .addResolvedInstance('instance-previously-active') .addActiveInstance('instance-currently-active', 'action group A') .getEvents(); const eventsResult = { diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index 1d5ebe2b5911e..f9e4a2908d6ce 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -6,7 +6,7 @@ import { SanitizedAlert, AlertInstanceSummary } from '../types'; import { IValidatedEvent } from '../../../event_log/server'; -import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; +import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; import { alertInstanceSummaryFromEventLog } from './alert_instance_summary_from_event_log'; const ONE_HOUR_IN_MILLIS = 60 * 60 * 1000; @@ -189,43 +189,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addRecoveredInstance('instance-1') - .getEvents(); - - const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ - alert, - events, - dateStart, - dateEnd, - }); - - const { lastRun, status, instances } = summary; - expect({ lastRun, status, instances }).toMatchInlineSnapshot(` - Object { - "instances": Object { - "instance-1": Object { - "actionGroupId": undefined, - "activeStartDate": undefined, - "muted": false, - "status": "OK", - }, - }, - "lastRun": "2020-06-18T00:00:10.000Z", - "status": "OK", - } - `); - }); - - test('legacy alert with currently inactive instance', async () => { - const alert = createAlert({}); - const eventsFactory = new EventsFactory(); - const events = eventsFactory - .addExecute() - .addNewInstance('instance-1') - .addActiveInstance('instance-1', 'action group A') - .advanceTime(10000) - .addExecute() - .addLegacyResolvedInstance('instance-1') + .addResolvedInstance('instance-1') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -260,7 +224,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addRecoveredInstance('instance-1') + .addResolvedInstance('instance-1') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -442,7 +406,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group A') - .addRecoveredInstance('instance-2') + .addResolvedInstance('instance-2') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -487,7 +451,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group A') - .addRecoveredInstance('instance-2') + .addResolvedInstance('instance-2') .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group B') @@ -597,24 +561,12 @@ export class EventsFactory { return this; } - addRecoveredInstance(instanceId: string): EventsFactory { - this.events.push({ - '@timestamp': this.date, - event: { - provider: EVENT_LOG_PROVIDER, - action: EVENT_LOG_ACTIONS.recoveredInstance, - }, - kibana: { alerting: { instance_id: instanceId } }, - }); - return this; - } - - addLegacyResolvedInstance(instanceId: string): EventsFactory { + addResolvedInstance(instanceId: string): EventsFactory { this.events.push({ '@timestamp': this.date, event: { provider: EVENT_LOG_PROVIDER, - action: LEGACY_EVENT_LOG_ACTIONS.resolvedInstance, + action: EVENT_LOG_ACTIONS.resolvedInstance, }, kibana: { alerting: { instance_id: instanceId } }, }); diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index 6fed8b4aa4ee6..8fed97a74435d 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -6,7 +6,7 @@ import { SanitizedAlert, AlertInstanceSummary, AlertInstanceStatus } from '../types'; import { IEvent } from '../../../event_log/server'; -import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; +import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; export interface AlertInstanceSummaryFromEventLogParams { alert: SanitizedAlert; @@ -80,8 +80,7 @@ export function alertInstanceSummaryFromEventLog( status.status = 'Active'; status.actionGroupId = event?.kibana?.alerting?.action_group_id; break; - case LEGACY_EVENT_LOG_ACTIONS.resolvedInstance: - case EVENT_LOG_ACTIONS.recoveredInstance: + case EVENT_LOG_ACTIONS.resolvedInstance: status.status = 'OK'; status.activeStartDate = undefined; status.actionGroupId = undefined; diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index bafb89c64076b..4bfb44425544a 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -82,11 +82,8 @@ export const EVENT_LOG_ACTIONS = { execute: 'execute', executeAction: 'execute-action', newInstance: 'new-instance', - recoveredInstance: 'recovered-instance', - activeInstance: 'active-instance', -}; -export const LEGACY_EVENT_LOG_ACTIONS = { resolvedInstance: 'resolved-instance', + activeInstance: 'active-instance', }; export interface PluginSetupContract { diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index d4c4f746392c3..07d08f5837d54 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -26,12 +26,12 @@ import { alertsMock, alertsClientMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { IEventLogger } from '../../../event_log/server'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import { Alert, RecoveredActionGroup } from '../../common'; +import { Alert, ResolvedActionGroup } from '../../common'; import { omit } from 'lodash'; const alertType = { id: 'test', name: 'My test alert', - actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], + actionGroups: [{ id: 'default', name: 'Default' }, ResolvedActionGroup], defaultActionGroupId: 'default', executor: jest.fn(), producer: 'alerts', @@ -114,7 +114,7 @@ describe('Task Runner', () => { }, }, { - group: RecoveredActionGroup.id, + group: ResolvedActionGroup.id, id: '2', actionTypeId: 'action', params: { @@ -517,7 +517,7 @@ describe('Task Runner', () => { `); }); - test('fire recovered actions for execution for the alertInstances which is in the recovered state', async () => { + test('fire resolved actions for execution for the alertInstances which is in the resolved state', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); @@ -650,7 +650,7 @@ describe('Task Runner', () => { Array [ Object { "event": Object { - "action": "recovered-instance", + "action": "resolved-instance", }, "kibana": Object { "alerting": Object { @@ -666,7 +666,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' instance '2' has recovered", + "message": "test:1: 'alert-name' resolved instance: '2'", }, ], Array [ diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 5a7247ac50ea0..24d96788c3395 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -39,7 +39,7 @@ import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_l import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error'; import { AlertsClient } from '../alerts_client'; import { partiallyUpdateAlert } from '../saved_objects'; -import { RecoveredActionGroup } from '../../common'; +import { ResolvedActionGroup } from '../../common'; const FALLBACK_RETRY_INTERVAL = '5m'; @@ -219,7 +219,7 @@ export class TaskRunner { alertInstance.hasScheduledActions() ); - generateNewAndRecoveredInstanceEvents({ + generateNewAndResolvedInstanceEvents({ eventLogger, originalAlertInstances, currentAlertInstances: instancesWithScheduledActions, @@ -229,7 +229,7 @@ export class TaskRunner { }); if (!muteAll) { - scheduleActionsForRecoveredInstances( + scheduleActionsForResolvedInstances( alertInstances, executionHandler, originalAlertInstances, @@ -436,7 +436,7 @@ export class TaskRunner { } } -interface GenerateNewAndRecoveredInstanceEventsParams { +interface GenerateNewAndResolvedInstanceEventsParams { eventLogger: IEventLogger; originalAlertInstances: Dictionary; currentAlertInstances: Dictionary; @@ -445,20 +445,18 @@ interface GenerateNewAndRecoveredInstanceEventsParams { namespace: string | undefined; } -function generateNewAndRecoveredInstanceEvents( - params: GenerateNewAndRecoveredInstanceEventsParams -) { +function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInstanceEventsParams) { const { eventLogger, alertId, namespace, currentAlertInstances, originalAlertInstances } = params; const originalAlertInstanceIds = Object.keys(originalAlertInstances); const currentAlertInstanceIds = Object.keys(currentAlertInstances); const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); - const recoveredIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); + const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); - for (const id of recoveredIds) { + for (const id of resolvedIds) { const actionGroup = originalAlertInstances[id].getLastScheduledActions()?.group; - const message = `${params.alertLabel} instance '${id}' has recovered`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.recoveredInstance, message, actionGroup); + const message = `${params.alertLabel} resolved instance: '${id}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message, actionGroup); } for (const id of newIds) { @@ -498,7 +496,7 @@ function generateNewAndRecoveredInstanceEvents( } } -function scheduleActionsForRecoveredInstances( +function scheduleActionsForResolvedInstances( alertInstancesMap: Record, executionHandler: ReturnType, originalAlertInstances: Record, @@ -507,22 +505,22 @@ function scheduleActionsForRecoveredInstances( ) { const currentAlertInstanceIds = Object.keys(currentAlertInstances); const originalAlertInstanceIds = Object.keys(originalAlertInstances); - const recoveredIds = without( + const resolvedIds = without( originalAlertInstanceIds, ...currentAlertInstanceIds, ...mutedInstanceIds ); - for (const id of recoveredIds) { + for (const id of resolvedIds) { const instance = alertInstancesMap[id]; - instance.updateLastScheduledActions(RecoveredActionGroup.id); + instance.updateLastScheduledActions(ResolvedActionGroup.id); instance.unscheduleActions(); executionHandler({ - actionGroup: RecoveredActionGroup.id, + actionGroup: ResolvedActionGroup.id, context: {}, state: {}, alertInstanceId: id, }); - instance.scheduleActions(RecoveredActionGroup.id); + instance.scheduleActions(ResolvedActionGroup.id); } } diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 1941ec6326ddb..14785f64cffac 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; -import { RecoveredActionGroup } from '../../../../../alerts/common'; +import { ResolvedActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; @@ -103,7 +103,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } if (reason) { const actionGroupId = - nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK ? ResolvedActionGroup.id : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group: item, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index a1d6428f3b52b..b31afba8ac9cc 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -6,7 +6,7 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; -import { RecoveredActionGroup } from '../../../../../alerts/common'; +import { ResolvedActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, @@ -367,7 +367,7 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute([2]); - expect(mostRecentAction(instanceID).id).toBe(RecoveredActionGroup.id); + expect(mostRecentAction(instanceID).id).toBe(ResolvedActionGroup.id); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('does not continue to send a recovery alert if the metric is still OK', async () => { @@ -383,7 +383,7 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute([2]); - expect(mostRecentAction(instanceID).id).toBe(RecoveredActionGroup.id); + expect(mostRecentAction(instanceID).id).toBe(ResolvedActionGroup.id); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); }); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 60790648d9a9b..7c3918c37ebbf 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -6,7 +6,7 @@ import { first, last } from 'lodash'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import { RecoveredActionGroup } from '../../../../../alerts/common'; +import { ResolvedActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraBackendLibs } from '../../infra_types'; import { @@ -89,7 +89,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const firstResult = first(alertResults); const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString(); const actionGroupId = - nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK ? ResolvedActionGroup.id : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 156f65f094342..7af8e5ba88300 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -16,10 +16,10 @@ export const routeToConnectors = `/connectors`; export const routeToAlerts = `/alerts`; export const routeToAlertDetails = `/alert/:alertId`; -export const recoveredActionGroupMessage = i18n.translate( - 'xpack.triggersActionsUI.sections.actionForm.RecoveredMessage', +export const resolvedActionGroupMessage = i18n.translate( + 'xpack.triggersActionsUI.sections.actionForm.ResolvedMessage', { - defaultMessage: 'Recovered', + defaultMessage: 'Resolved', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index ddbf933078043..5b56720737b7e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -10,9 +10,8 @@ import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, Alert, AlertAction } from '../../../types'; import ActionForm from './action_form'; -import { RecoveredActionGroup } from '../../../../../alerts/common'; +import { ResolvedActionGroup } from '../../../../../alerts/common'; import { useKibana } from '../../../common/lib/kibana'; -import { EuiScreenReaderOnly } from '@elastic/eui'; jest.mock('../../../common/lib/kibana'); jest.mock('../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -229,7 +228,7 @@ describe('action_form', () => { }} actionGroups={[ { id: 'default', name: 'Default' }, - { id: 'recovered', name: 'Recovered' }, + { id: 'resolved', name: 'Resolved' }, ]} setActionGroupIdByIndex={(group: string, index: number) => { initialAlert.actions[index].group = group; @@ -348,18 +347,18 @@ describe('action_form', () => { "value": "default", }, Object { - "data-test-subj": "addNewActionConnectorActionGroup-0-option-recovered", - "inputDisplay": "Recovered", - "value": "recovered", + "data-test-subj": "addNewActionConnectorActionGroup-0-option-resolved", + "inputDisplay": "Resolved", + "value": "resolved", }, ] `); }); - it('renders selected Recovered action group', async () => { + it('renders selected Resolved action group', async () => { const wrapper = await setup([ { - group: RecoveredActionGroup.id, + group: ResolvedActionGroup.id, id: 'test', actionTypeId: actionType.id, params: { @@ -382,17 +381,15 @@ describe('action_form', () => { "value": "default", }, Object { - "data-test-subj": "addNewActionConnectorActionGroup-0-option-recovered", - "inputDisplay": "Recovered", - "value": "recovered", + "data-test-subj": "addNewActionConnectorActionGroup-0-option-resolved", + "inputDisplay": "Resolved", + "value": "resolved", }, ] `); - - expect(actionGroupsSelect.first().find(EuiScreenReaderOnly).text()).toEqual( - 'Select an option: Recovered, is selected' + expect(actionGroupsSelect.first().text()).toEqual( + 'Select an option: Resolved, is selectedResolved' ); - expect(actionGroupsSelect.first().find('button').first().text()).toEqual('Recovered'); }); it('renders available connectors for the selected action type', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index fffc3bd32125e..7e38805957931 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -26,7 +26,7 @@ import { EuiBadge, EuiErrorBoundary, } from '@elastic/eui'; -import { AlertActionParam, RecoveredActionGroup } from '../../../../../alerts/common'; +import { AlertActionParam, ResolvedActionGroup } from '../../../../../alerts/common'; import { IErrorObject, AlertAction, @@ -40,7 +40,7 @@ import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_en import { hasSaveActionsCapability } from '../../lib/capabilities'; import { ActionAccordionFormProps } from './action_form'; import { transformActionVariables } from '../../lib/action_variables'; -import { recoveredActionGroupMessage } from '../../constants'; +import { resolvedActionGroupMessage } from '../../constants'; import { useKibana } from '../../../common/lib/kibana'; import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params'; @@ -105,8 +105,8 @@ export const ActionTypeForm = ({ useEffect(() => { setAvailableActionVariables(getAvailableActionVariables(messageVariables, actionItem.group)); const res = - actionItem.group === RecoveredActionGroup.id - ? recoveredActionGroupMessage + actionItem.group === ResolvedActionGroup.id + ? resolvedActionGroupMessage : defaultActionMessage; setAvailableDefaultActionMessage(res); const paramsDefaults = getDefaultsForActionParams(actionItem.actionTypeId, actionItem.group); @@ -374,7 +374,7 @@ function getAvailableActionVariables( return []; } const filteredActionVariables = - actionGroup === RecoveredActionGroup.id + actionGroup === ResolvedActionGroup.id ? { params: actionVariables.params, state: actionVariables.state } : actionVariables; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts index bfaf8a2a4788e..b3635b9f40e27 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts @@ -17,7 +17,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { const expectedNoOpType = { actionGroups: [ { id: 'default', name: 'Default' }, - { id: 'recovered', name: 'Recovered' }, + { id: 'resolved', name: 'Resolved' }, ], defaultActionGroupId: 'default', id: 'test.noop', @@ -33,7 +33,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { const expectedRestrictedNoOpType = { actionGroups: [ { id: 'default', name: 'Default' }, - { id: 'recovered', name: 'Recovered' }, + { id: 'resolved', name: 'Resolved' }, ], defaultActionGroupId: 'default', id: 'test.restricted-noop', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts index 8dab199271da8..64e99190e183a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { Response as SupertestResponse } from 'supertest'; -import { RecoveredActionGroup } from '../../../../../plugins/alerts/common'; +import { ResolvedActionGroup } from '../../../../../plugins/alerts/common'; import { Space } from '../../../common/types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { @@ -137,7 +137,7 @@ instanceStateValue: true await taskManagerUtils.waitForActionTaskParamsToBeCleanedUp(testStart); }); - it('should fire actions when an alert instance is recovered', async () => { + it('should fire actions when an alert instance is resolved', async () => { const reference = alertUtils.generateReference(); const { body: createdAction } = await supertestWithoutAuth @@ -174,12 +174,12 @@ instanceStateValue: true params: {}, }, { - group: RecoveredActionGroup.id, + group: ResolvedActionGroup.id, id: indexRecordActionId, params: { index: ES_TEST_INDEX_NAME, reference, - message: 'Recovered message', + message: 'Resolved message', }, }, ], @@ -194,10 +194,10 @@ instanceStateValue: true await esTestIndexTool.waitForDocs('action:test.index-record', reference) )[0]; - expect(actionTestRecord._source.params.message).to.eql('Recovered message'); + expect(actionTestRecord._source.params.message).to.eql('Resolved message'); }); - it('should not fire actions when an alert instance is recovered, but alert is muted', async () => { + it('should not fire actions when an alert instance is resolved, but alert is muted', async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); @@ -237,12 +237,12 @@ instanceStateValue: true params: {}, }, { - group: RecoveredActionGroup.id, + group: ResolvedActionGroup.id, id: indexRecordActionId, params: { index: ES_TEST_INDEX_NAME, reference, - message: 'Recovered message', + message: 'Resolved message', }, }, ], diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 3766785680925..d3cd3db124ecd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -78,7 +78,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { 'execute-action', 'new-instance', 'active-instance', - 'recovered-instance', + 'resolved-instance', ], }); }); @@ -87,25 +87,25 @@ export default function eventLogTests({ getService }: FtrProviderContext) { const executeEvents = getEventsByAction(events, 'execute'); const executeActionEvents = getEventsByAction(events, 'execute-action'); const newInstanceEvents = getEventsByAction(events, 'new-instance'); - const recoveredInstanceEvents = getEventsByAction(events, 'recovered-instance'); + const resolvedInstanceEvents = getEventsByAction(events, 'resolved-instance'); expect(executeEvents.length >= 4).to.be(true); expect(executeActionEvents.length).to.be(2); expect(newInstanceEvents.length).to.be(1); - expect(recoveredInstanceEvents.length).to.be(1); + expect(resolvedInstanceEvents.length).to.be(1); // make sure the events are in the right temporal order const executeTimes = getTimestamps(executeEvents); const executeActionTimes = getTimestamps(executeActionEvents); const newInstanceTimes = getTimestamps(newInstanceEvents); - const recoveredInstanceTimes = getTimestamps(recoveredInstanceEvents); + const resolvedInstanceTimes = getTimestamps(resolvedInstanceEvents); expect(executeTimes[0] < newInstanceTimes[0]).to.be(true); expect(executeTimes[1] <= newInstanceTimes[0]).to.be(true); expect(executeTimes[2] > newInstanceTimes[0]).to.be(true); expect(executeTimes[1] <= executeActionTimes[0]).to.be(true); expect(executeTimes[2] > executeActionTimes[0]).to.be(true); - expect(recoveredInstanceTimes[0] > newInstanceTimes[0]).to.be(true); + expect(resolvedInstanceTimes[0] > newInstanceTimes[0]).to.be(true); // validate each event let executeCount = 0; @@ -136,8 +136,8 @@ export default function eventLogTests({ getService }: FtrProviderContext) { case 'new-instance': validateInstanceEvent(event, `created new instance: 'instance'`); break; - case 'recovered-instance': - validateInstanceEvent(event, `recovered instance: 'instance'`); + case 'resolved-instance': + validateInstanceEvent(event, `resolved instance: 'instance'`); break; case 'active-instance': validateInstanceEvent(event, `active instance: 'instance' in actionGroup: 'default'`); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts index 404c6020fa237..22034328e5275 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts @@ -216,7 +216,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr await alertUtils.muteInstance(createdAlert.id, 'instanceC'); await alertUtils.muteInstance(createdAlert.id, 'instanceD'); - await waitForEvents(createdAlert.id, ['new-instance', 'recovered-instance']); + await waitForEvents(createdAlert.id, ['new-instance', 'resolved-instance']); const response = await supertest.get( `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts index 9d38f4abb7f3f..3fb2cc40437d8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts @@ -25,7 +25,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { expect(fixtureAlertType).to.eql({ actionGroups: [ { id: 'default', name: 'Default' }, - { id: 'recovered', name: 'Recovered' }, + { id: 'resolved', name: 'Resolved' }, ], defaultActionGroupId: 'default', id: 'test.noop', From 6e80d9fe09e82c59853a18146dcc96b684bbd22c Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Tue, 1 Dec 2020 10:15:51 -0500 Subject: [PATCH 03/37] [Security Solution] [Detections] Create a 'partial failure' status for rules (#84293) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../schemas/common/schemas.ts | 7 ++++- .../components/rules/rule_status/helpers.ts | 2 +- .../detection_engine/rules/types.ts | 11 +++++++- .../detection_engine/rules/details/index.tsx | 27 ++++++++++++++----- .../details/status_failed_callout.test.tsx | 7 +++++ .../rules/details/status_failed_callout.tsx | 8 ++++-- .../rules/details/translations.ts | 7 +++++ .../signals/rule_status_service.mock.ts | 2 ++ .../signals/rule_status_service.test.ts | 15 +++++++++++ .../signals/rule_status_service.ts | 20 ++++++++++++++ 10 files changed, 95 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 82b803c62a940..ff76a0fcb5593 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -320,7 +320,12 @@ export type SeverityMappingOrUndefined = t.TypeOf; -export const job_status = t.keyof({ succeeded: null, failed: null, 'going to run': null }); +export const job_status = t.keyof({ + succeeded: null, + failed: null, + 'going to run': null, + 'partial failure': null, +}); export type JobStatus = t.TypeOf; export const conflicts = t.keyof({ abort: null, proceed: null }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts index e99894afeb63c..e6482577f89ed 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts @@ -13,6 +13,6 @@ export const getStatusColor = (status: RuleStatusType | string | null) => ? 'success' : status === 'failed' ? 'danger' - : status === 'executing' || status === 'going to run' + : status === 'executing' || status === 'going to run' || status === 'partial failure' ? 'warning' : 'subdued'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index e9c89130736c0..d7908a6780ebb 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -70,6 +70,13 @@ const MetaRule = t.intersection([ }), ]); +const StatusTypes = t.union([ + t.literal('succeeded'), + t.literal('failed'), + t.literal('going to run'), + t.literal('partial failure'), +]); + export const RuleSchema = t.intersection([ t.type({ author, @@ -108,13 +115,15 @@ export const RuleSchema = t.intersection([ license, last_failure_at: t.string, last_failure_message: t.string, + last_success_message: t.string, + last_success_at: t.string, meta: MetaRule, machine_learning_job_id: t.string, output_index: t.string, query: t.string, rule_name_override, saved_id: t.string, - status: t.string, + status: StatusTypes, status_date: t.string, threshold, threat_query, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index ba676835d60f1..4866824f882cf 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -275,18 +275,33 @@ export const RuleDetailsPageComponent: FC = ({ ), [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] ); - const ruleError = useMemo( - () => + const ruleError = useMemo(() => { + if ( rule?.status === 'failed' && ruleDetailTab === RuleDetailTabs.alerts && - rule?.last_failure_at != null ? ( + rule?.last_failure_at != null + ) { + return ( - ) : null, - [rule, ruleDetailTab] - ); + ); + } else if ( + rule?.status === 'partial failure' && + ruleDetailTab === RuleDetailTabs.alerts && + rule?.last_success_at != null + ) { + return ( + + ); + } + return null; + }, [rule, ruleDetailTab]); const updateDateRangeCallback = useCallback( ({ x }) => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx index 3394b0fc8c5c0..c743623402063 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx @@ -13,6 +13,13 @@ describe('RuleStatusFailedCallOut', () => { it('renders correctly', () => { const wrapper = shallow(); + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); + it('renders correctly with optional params', () => { + const wrapper = shallow( + + ); + expect(wrapper.find('EuiCallOut')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx index 5b5b96ace8670..121ff6b8686c3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx @@ -13,22 +13,26 @@ import * as i18n from './translations'; interface RuleStatusFailedCallOutComponentProps { date: string; message: string; + color?: 'danger' | 'primary' | 'success' | 'warning'; } const RuleStatusFailedCallOutComponent: React.FC = ({ date, message, + color, }) => ( - {i18n.ERROR_CALLOUT_TITLE} + + {color === 'warning' ? i18n.PARTIAL_FAILURE_CALLOUT_TITLE : i18n.ERROR_CALLOUT_TITLE} + } - color="danger" + color={color ? color : 'danger'} iconType="alert" >

{message}

diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts index 94dfdc3e9daa0..5fbe0a5b78671 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts @@ -48,6 +48,13 @@ export const ERROR_CALLOUT_TITLE = i18n.translate( } ); +export const PARTIAL_FAILURE_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.partialErrorCalloutTitle', + { + defaultMessage: 'Partial rule failure at', + } +); + export const FAILURE_HISTORY_TAB = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.failureHistoryTab', { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts index 1d6a8227ebc13..f7b1790e127d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts @@ -22,6 +22,8 @@ export const ruleStatusServiceFactoryMock = async ({ success: jest.fn(), + partialFailure: jest.fn(), + error: jest.fn(), }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts index cde6a506c657d..449ecd11257d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts @@ -53,6 +53,21 @@ describe('buildRuleStatusAttributes', () => { expect(result.statusDate).toEqual(result.lastSuccessAt); }); + it('returns partial failure fields if "partial failure"', () => { + const result = buildRuleStatusAttributes( + 'partial failure', + 'some indices missing timestamp override field' + ); + expect(result).toEqual({ + status: 'partial failure', + statusDate: expectIsoDateString, + lastSuccessAt: expectIsoDateString, + lastSuccessMessage: 'some indices missing timestamp override field', + }); + + expect(result.statusDate).toEqual(result.lastSuccessAt); + }); + it('returns failure fields if "failed"', () => { const result = buildRuleStatusAttributes('failed', 'failure message'); expect(result).toEqual({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts index 433ad4e2affea..debc329bf40d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts @@ -23,6 +23,7 @@ interface Attributes { export interface RuleStatusService { goingToRun: () => Promise; success: (message: string, attributes?: Attributes) => Promise; + partialFailure: (message: string, attributes?: Attributes) => Promise; error: (message: string, attributes?: Attributes) => Promise; } @@ -46,6 +47,13 @@ export const buildRuleStatusAttributes: ( lastSuccessMessage: message, }; } + case 'partial failure': { + return { + ...baseAttributes, + lastSuccessAt: now, + lastSuccessMessage: message, + }; + } case 'failed': { return { ...baseAttributes, @@ -93,6 +101,18 @@ export const ruleStatusServiceFactory = async ({ }); }, + partialFailure: async (message, attributes) => { + const [currentStatus] = await getOrCreateRuleStatuses({ + alertId, + ruleStatusClient, + }); + + await ruleStatusClient.update(currentStatus.id, { + ...currentStatus.attributes, + ...buildRuleStatusAttributes('partial failure', message, attributes), + }); + }, + error: async (message, attributes) => { const ruleStatuses = await getOrCreateRuleStatuses({ alertId, From a65c12ffa2604ea625c0cc9944314b80ca63d48c Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Tue, 1 Dec 2020 11:01:06 -0500 Subject: [PATCH 04/37] Fix flaky test suite (#84602) --- test/api_integration/apis/saved_objects/migrations.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/api_integration/apis/saved_objects/migrations.ts b/test/api_integration/apis/saved_objects/migrations.ts index b5fa16558514a..fa9c2fd1a2d7f 100644 --- a/test/api_integration/apis/saved_objects/migrations.ts +++ b/test/api_integration/apis/saved_objects/migrations.ts @@ -54,8 +54,7 @@ function getLogMock() { export default ({ getService }: FtrProviderContext) => { const esClient = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/84445 - describe.skip('Kibana index migration', () => { + describe('Kibana index migration', () => { before(() => esClient.indices.delete({ index: '.migrate-*' })); it('Migrates an existing index that has never been migrated before', async () => { @@ -313,7 +312,10 @@ export default ({ getService }: FtrProviderContext) => { result // @ts-expect-error destIndex exists only on MigrationResult status: 'migrated'; .map(({ status, destIndex }) => ({ status, destIndex })) - .sort((a) => (a.destIndex ? 0 : 1)) + .sort(({ destIndex: a }, { destIndex: b }) => + // sort by destIndex in ascending order, keeping falsy values at the end + (a && !b) || a < b ? -1 : (!a && b) || a > b ? 1 : 0 + ) ).to.eql([ { status: 'migrated', destIndex: '.migration-c_2' }, { status: 'skipped', destIndex: undefined }, From 636f91c29a5585ddf17bdb232eb561a554f37c03 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 1 Dec 2020 18:01:37 +0200 Subject: [PATCH 05/37] [Security Solution][Detections] Fix labels and issue with mandatory fields (#84525) * Fix labeling and bugs * Improve naming --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - .../components/builtin_action_types/jira/jira.test.tsx | 2 +- .../components/builtin_action_types/jira/jira.tsx | 4 ++-- .../components/builtin_action_types/jira/translations.ts | 6 +++--- .../builtin_action_types/resilient/resilient.test.tsx | 2 +- .../builtin_action_types/resilient/resilient.tsx | 5 +++-- .../builtin_action_types/resilient/translations.ts | 8 ++++---- .../builtin_action_types/servicenow/servicenow.test.tsx | 2 +- .../builtin_action_types/servicenow/servicenow.tsx | 2 +- .../builtin_action_types/servicenow/translations.ts | 2 +- 11 files changed, 17 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 490bf56d76062..4991c4d16099c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19600,7 +19600,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredDescriptionTextField": "説明が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredEmailTextField": "電子メールアドレスまたはユーザー名が必要です", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredProjectKeyTextField": "プロジェクトキーが必要です", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredTitleTextField": "タイトルが必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldHelp": "JIRAは、このアクションを、Kibanaの保存されたオブジェクトのIDに関連付けます。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldLabel": "オブジェクトID(任意)", "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "親問題を選択", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 15699c3f47dd5..59ab890cfd9db 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19619,7 +19619,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredDescriptionTextField": "“描述”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredEmailTextField": "“电子邮件”或“用户名”必填", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredProjectKeyTextField": "“项目键”必填", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredTitleTextField": "“标题”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldHelp": "JIRA 将此操作与 Kibana 已保存对象的 ID 关联。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldLabel": "对象 ID(可选)", "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "选择父问题", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx index f476522c2bf5a..b10341fa00f1b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx @@ -93,7 +93,7 @@ describe('jira action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - title: ['Title is required.'], + title: ['Summary is required.'], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx index 81f0bbfe8a02f..20374cfbe3a3b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx @@ -64,8 +64,8 @@ export function getActionType(): ActionTypeModel(), }; validationResult.errors = errors; - if (actionParams.subActionParams && !actionParams.subActionParams.title?.length) { - errors.title.push(i18n.TITLE_REQUIRED); + if (!actionParams.subActionParams?.title?.length) { + errors.title.push(i18n.SUMMARY_REQUIRED); } return validationResult; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts index 6f45316ff4433..c9642da9ba440 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts @@ -127,10 +127,10 @@ export const DESCRIPTION_REQUIRED = i18n.translate( } ); -export const TITLE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredTitleTextField', +export const SUMMARY_REQUIRED = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredSummaryTextField', { - defaultMessage: 'Title is required.', + defaultMessage: 'Summary is required.', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx index 937fe61e887ea..17e9b42e7878e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx @@ -93,7 +93,7 @@ describe('resilient action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - title: ['Title is required.'], + title: ['Name is required.'], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx index 6d57fc98fe20f..251274a08ba6c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx @@ -72,8 +72,9 @@ export function getActionType(): ActionTypeModel< title: new Array(), }; validationResult.errors = errors; - if (actionParams.subActionParams && !actionParams.subActionParams.title?.length) { - errors.title.push(i18n.TITLE_REQUIRED); + + if (!actionParams.subActionParams?.title?.length) { + errors.title.push(i18n.NAME_REQUIRED); } return validationResult; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts index 65d08c9f7de68..7483ba2f461df 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts @@ -134,16 +134,16 @@ export const MAPPING_FIELD_COMMENTS = i18n.translate( ); export const DESCRIPTION_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredDescriptionTextField', + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredDescriptionTextField', { defaultMessage: 'Description is required.', } ); -export const TITLE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredTitleTextField', +export const NAME_REQUIRED = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredNameTextField', { - defaultMessage: 'Title is required.', + defaultMessage: 'Name is required.', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx index 5e70bc20f5c51..c29ddbf385de6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx @@ -90,7 +90,7 @@ describe('servicenow action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - title: ['Title is required.'], + title: ['Short description is required.'], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx index 9cc689d8f48b1..8eca7f3ef3120 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx @@ -67,7 +67,7 @@ export function getActionType(): ActionTypeModel< title: new Array(), }; validationResult.errors = errors; - if (actionParams.subActionParams && !actionParams.subActionParams.title?.length) { + if (!actionParams.subActionParams?.title?.length) { errors.title.push(i18n.TITLE_REQUIRED); } return validationResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts index 312cb9844bd75..91a5c0a54397b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts @@ -157,6 +157,6 @@ export const DESCRIPTION_REQUIRED = i18n.translate( export const TITLE_REQUIRED = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredTitleTextField', { - defaultMessage: 'Title is required.', + defaultMessage: 'Short description is required.', } ); From 5420177485796793d57b6439853caa1c05513433 Mon Sep 17 00:00:00 2001 From: Olivier V Date: Tue, 1 Dec 2020 17:06:19 +0100 Subject: [PATCH 06/37] Update create.asciidoc (#84046) The url templates provided for call of the API with space information was missing the /api/ section in it. (cherry picked from commit 35f1cc16eaa29666d7212402f57ab17858ebc96d) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/api/saved-objects/create.asciidoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index 50809a1bd5d4e..d7a368034ef07 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -9,11 +9,13 @@ experimental[] Create {kib} saved objects. [[saved-objects-api-create-request]] ==== Request -`POST :/api/saved_objects/` + +`POST :/api/saved_objects/` `POST :/api/saved_objects//` -`POST :/s//saved_objects/` +`POST :/s//api/saved_objects/` + +`POST :/s//api/saved_objects//` [[saved-objects-api-create-path-params]] ==== Path parameters From 5889e366da02a910e3bb3c5c3878e16ca661947b Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 1 Dec 2020 18:05:57 +0100 Subject: [PATCH 07/37] [ML] Fix unnecessary trigger of wildcard field type search for ML plugin routes. (#84605) Passing in an empty string '' to useResolver() would trigger a wild card search across all indices and fields, potentially causing a timeout and the page would fail to load. The following pages were affected: Single Metric Viewer, Data frame analytics models list, Data frame analytics jobs list, Data frame analytics exploration page, File Data Visualizer (Data visualizer - Import data from a log file). This PR fixes it by passing undefined instead of '' to useResolver to avoid calling _fields_for_wildcard with an empty pattern. Jest tests were added to cover the two parameter scenarios empty string/undefined. --- .../jobs/new_job/utils/new_job_utils.ts | 2 +- .../analytics_job_exploration.tsx | 2 +- .../analytics_jobs_list.tsx | 2 +- .../data_frame_analytics/analytics_map.tsx | 2 +- .../data_frame_analytics/models_list.tsx | 2 +- .../routes/datavisualizer/file_based.tsx | 2 +- .../routing/routes/timeseriesexplorer.tsx | 2 +- .../application/routing/use_resolver.test.ts | 89 +++++++++++++++++++ .../application/routing/use_resolver.ts | 76 ++++++++++------ .../ml/public/application/util/index_utils.ts | 7 +- 10 files changed, 150 insertions(+), 36 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/routing/use_resolver.test.ts diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts index b7b6aa15ffe44..543e6898193a4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts @@ -30,7 +30,7 @@ export function getDefaultDatafeedQuery() { export function createSearchItems( kibanaConfig: IUiSettingsClient, - indexPattern: IIndexPattern, + indexPattern: IIndexPattern | undefined, savedSearch: SavedSearchSavedObject | null ) { // query is only used by the data visualizer as it needs diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx index e436ff468ccf0..771123532dcbf 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx @@ -38,7 +38,7 @@ export const analyticsJobExplorationRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); const [globalState] = useUrlState('_g'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx index 80706a82121d5..3b68c5078e99e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx @@ -34,7 +34,7 @@ export const analyticsJobsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx index 18002648cfaa6..3acd12402932f 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx @@ -34,7 +34,7 @@ export const analyticsMapRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx index b1fd6e93a744c..2f58ef756e51c 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx @@ -34,7 +34,7 @@ export const modelsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx index 837616a8a76d2..f651d17e02de4 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx @@ -45,7 +45,7 @@ export const fileBasedRouteFactory = ( const PageWrapper: FC = ({ location, deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver('', undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, { checkBasicLicense, loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), checkFindFileStructurePrivilege: () => diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index df92c77252565..7de59cba495af 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -63,7 +63,7 @@ export const timeSeriesExplorerRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context, results } = useResolver('', undefined, deps.config, { + const { context, results } = useResolver(undefined, undefined, deps.config, { ...basicResolvers(deps), jobs: mlJobService.loadJobsWrapper, jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts new file mode 100644 index 0000000000000..07cc038538745 --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; + +import { IUiSettingsClient } from 'kibana/public'; + +import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; +import { useNotifications } from '../contexts/kibana'; + +import { useResolver } from './use_resolver'; + +jest.mock('../contexts/kibana/use_create_url', () => { + return { + useCreateAndNavigateToMlLink: jest.fn(), + }; +}); + +jest.mock('../contexts/kibana', () => { + return { + useMlUrlGenerator: () => ({ + createUrl: jest.fn(), + }), + useNavigateToPath: () => jest.fn(), + useNotifications: jest.fn(), + }; +}); + +const addError = jest.fn(); +(useNotifications as jest.Mock).mockImplementation(() => ({ + toasts: { addSuccess: jest.fn(), addDanger: jest.fn(), addError }, +})); + +const redirectToJobsManagementPage = jest.fn(() => Promise.resolve()); +(useCreateAndNavigateToMlLink as jest.Mock).mockImplementation(() => redirectToJobsManagementPage); + +describe('useResolver', () => { + afterEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.advanceTimersByTime(0); + jest.useRealTimers(); + }); + + it('should accept undefined as indexPatternId and savedSearchId.', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useResolver(undefined, undefined, {} as IUiSettingsClient, {}) + ); + + await act(async () => { + await waitForNextUpdate(); + }); + + expect(result.current).toStrictEqual({ + context: { + combinedQuery: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + currentIndexPattern: null, + currentSavedSearch: null, + indexPatterns: null, + kibanaConfig: {}, + }, + results: {}, + }); + expect(addError).toHaveBeenCalledTimes(0); + expect(redirectToJobsManagementPage).toHaveBeenCalledTimes(0); + }); + + it('should add an error toast and redirect if indexPatternId is an empty string.', async () => { + const { result } = renderHook(() => useResolver('', undefined, {} as IUiSettingsClient, {})); + + await act(async () => {}); + + expect(result.current).toStrictEqual({ context: null, results: {} }); + expect(addError).toHaveBeenCalledTimes(1); + expect(redirectToJobsManagementPage).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.ts index e4cd90145bee4..3ce23f8b8a19c 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.ts @@ -11,6 +11,7 @@ import { getIndexPatternById, getIndexPatternsContract, getIndexPatternAndSavedSearch, + IndexPatternAndSavedSearch, } from '../util/index_utils'; import { createSearchItems } from '../jobs/new_job/utils/new_job_utils'; import { ResolverResults, Resolvers } from './resolvers'; @@ -19,6 +20,14 @@ import { useNotifications } from '../contexts/kibana'; import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; import { ML_PAGES } from '../../../common/constants/ml_url_generator'; +/** + * Hook to resolve route specific requirements + * @param indexPatternId optional Kibana index pattern id, used for wizards + * @param savedSearchId optional Kibana saved search id, used for wizards + * @param config Kibana UI Settings + * @param resolvers an array of resolvers to be executed for the route + * @return { context, results } returns the ML context and resolver results + */ export const useResolver = ( indexPatternId: string | undefined, savedSearchId: string | undefined, @@ -52,36 +61,49 @@ export const useResolver = ( return; } - if (indexPatternId !== undefined || savedSearchId !== undefined) { - try { - // note, currently we're using our own kibana context that requires a current index pattern to be set - // this means, if the page uses this context, useResolver must be passed a string for the index pattern id - // and loadIndexPatterns must be part of the resolvers. - const { indexPattern, savedSearch } = - savedSearchId !== undefined - ? await getIndexPatternAndSavedSearch(savedSearchId) - : { savedSearch: null, indexPattern: await getIndexPatternById(indexPatternId!) }; + try { + if (indexPatternId === '') { + throw new Error( + i18n.translate('xpack.ml.useResolver.errorIndexPatternIdEmptyString', { + defaultMessage: 'indexPatternId must not be empty string.', + }) + ); + } - const { combinedQuery } = createSearchItems(config, indexPattern!, savedSearch); + let indexPatternAndSavedSearch: IndexPatternAndSavedSearch = { + savedSearch: null, + indexPattern: null, + }; - setContext({ - combinedQuery, - currentIndexPattern: indexPattern, - currentSavedSearch: savedSearch, - indexPatterns: getIndexPatternsContract()!, - kibanaConfig: config, - }); - } catch (error) { - // an unexpected error has occurred. This could be caused by an incorrect index pattern or saved search ID - notifications.toasts.addError(new Error(error), { - title: i18n.translate('xpack.ml.useResolver.errorTitle', { - defaultMessage: 'An error has occurred', - }), - }); - await redirectToJobsManagementPage(); + if (savedSearchId !== undefined) { + indexPatternAndSavedSearch = await getIndexPatternAndSavedSearch(savedSearchId); + } else if (indexPatternId !== undefined) { + indexPatternAndSavedSearch.indexPattern = await getIndexPatternById(indexPatternId); } - } else { - setContext({}); + + const { savedSearch, indexPattern } = indexPatternAndSavedSearch; + + const { combinedQuery } = createSearchItems( + config, + indexPattern !== null ? indexPattern : undefined, + savedSearch + ); + + setContext({ + combinedQuery, + currentIndexPattern: indexPattern, + currentSavedSearch: savedSearch, + indexPatterns: getIndexPatternsContract(), + kibanaConfig: config, + }); + } catch (error) { + // an unexpected error has occurred. This could be caused by an incorrect index pattern or saved search ID + notifications.toasts.addError(new Error(error), { + title: i18n.translate('xpack.ml.useResolver.errorTitle', { + defaultMessage: 'An error has occurred', + }), + }); + await redirectToJobsManagementPage(); } })(); }, []); diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index 42be3dd8252f9..de08553af9906 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -73,9 +73,12 @@ export function getIndexPatternIdFromName(name: string) { } return null; } - +export interface IndexPatternAndSavedSearch { + savedSearch: SavedSearchSavedObject | null; + indexPattern: IIndexPattern | null; +} export async function getIndexPatternAndSavedSearch(savedSearchId: string) { - const resp: { savedSearch: SavedSearchSavedObject | null; indexPattern: IIndexPattern | null } = { + const resp: IndexPatternAndSavedSearch = { savedSearch: null, indexPattern: null, }; From 3958cddca3b76f351da552639a9448bb9874a5cc Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 1 Dec 2020 10:09:20 -0700 Subject: [PATCH 08/37] Remove unscripted fields from sample data index-pattern saved objects (#84659) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../services/sample_data/data_sets/ecommerce/saved_objects.ts | 3 +-- .../services/sample_data/data_sets/flights/saved_objects.ts | 2 +- .../services/sample_data/data_sets/logs/saved_objects.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 37657912deb95..60d05890028d1 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -294,8 +294,7 @@ export const getSavedObjects = (): SavedObject[] => [ attributes: { title: 'kibana_sample_data_ecommerce', timeFieldName: 'order_date', - fields: - '[{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"category"}}},{"name":"currency","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_birth_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_first_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_first_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_first_name"}}},{"name":"customer_full_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_full_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_full_name"}}},{"name":"customer_gender","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_last_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_last_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_last_name"}}},{"name":"customer_phone","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week_i","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"email","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.city_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.continent_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.country_iso_code","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.location","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.region_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"manufacturer"}}},{"name":"order_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"order_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products._id","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products._id.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products._id"}}},{"name":"products.base_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.base_unit_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.category"}}},{"name":"products.created_on","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_percentage","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.manufacturer"}}},{"name":"products.min_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_id","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.product_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.product_name"}}},{"name":"products.quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.tax_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxful_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxless_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.unit_discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxful_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxless_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_unique_products","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"type","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"user","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + fields: '[]', fieldFormatMap: '{"taxful_total_price":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, references: [], diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 6f701d75e7d52..e65b6ad40651b 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -439,7 +439,7 @@ export const getSavedObjects = (): SavedObject[] => [ title: 'kibana_sample_data_flights', timeFieldName: 'timestamp', fields: - '[{"name":"AvgTicketPrice","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Cancelled","type":"boolean","esTypes":["boolean"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Carrier","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestAirportID","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestCityName","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestCountry","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestLocation","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestRegion","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestWeather","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DistanceKilometers","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DistanceMiles","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelay","type":"boolean","esTypes":["boolean"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelayMin","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelayType","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightNum","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightTimeHour","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightTimeMin","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Origin","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginAirportID","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginCityName","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginCountry","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginLocation","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginRegion","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginWeather","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"dayOfWeek","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', + '[{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{"id":"number","params":{"pattern":"00"}},"AvgTicketPrice":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index f8d39e6689fa8..068ba66c4b0de 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -270,7 +270,7 @@ export const getSavedObjects = (): SavedObject[] => [ title: 'kibana_sample_data_logs', timeFieldName: 'timestamp', fields: - '[{"name":"@timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"agent","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"agent.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "agent"}}},{"name":"bytes","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"clientip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"extension","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"extension.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "extension"}}},{"name":"geo.coordinates","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.src","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.srcdest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"host","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"host.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "host"}}},{"name":"index","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"index.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "index"}}},{"name":"ip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"machine.os","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"machine.os.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "machine.os"}}},{"name":"machine.ram","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"memory","type":"number","esTypes":["double"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"message","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"message.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "message"}}},{"name":"phpmemory","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"referer","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"request","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"request.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "request"}}},{"name":"response","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"response.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "response"}}},{"name":"tags","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"tags.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "tags"}}},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"url","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"url.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "url"}}},{"name":"utc_time","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', + '[{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{}}', }, references: [], From 68decb8352ce7fd2223dba140d7470d88867600b Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 1 Dec 2020 20:24:54 +0300 Subject: [PATCH 09/37] declare kbn/monaco dependency on kbn/i18n explicitly (#84660) --- packages/kbn-monaco/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json index e2406a73f5342..eef68d3a35e0c 100644 --- a/packages/kbn-monaco/package.json +++ b/packages/kbn-monaco/package.json @@ -11,5 +11,8 @@ "devDependencies": { "@kbn/babel-preset": "link:../kbn-babel-preset", "@kbn/dev-utils": "link:../kbn-dev-utils" + }, + "dependencies": { + "@kbn/i18n": "link:../kbn-i18n" } } \ No newline at end of file From 6da6db28ac9738aab65581ca1faadc08642e511b Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Tue, 1 Dec 2020 17:30:05 +0000 Subject: [PATCH 10/37] Revert the Revert of "[Alerting] renames Resolved action group to Recovered (#84123)" (#84662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reapplies the #84123 PR: This PR changes the default term from “Resolved” to “Recovered”, as it fits most use cases and we feel users are most likely to understand its meaning across domains. --- .../alerts/common/builtin_action_groups.ts | 10 +-- .../alerts/server/alert_type_registry.test.ts | 14 ++--- .../tests/get_alert_instance_summary.test.ts | 2 +- ...rt_instance_summary_from_event_log.test.ts | 62 ++++++++++++++++--- .../alert_instance_summary_from_event_log.ts | 5 +- x-pack/plugins/alerts/server/plugin.ts | 5 +- .../server/task_runner/task_runner.test.ts | 12 ++-- .../alerts/server/task_runner/task_runner.ts | 32 +++++----- .../inventory_metric_threshold_executor.ts | 4 +- .../metric_threshold_executor.test.ts | 6 +- .../metric_threshold_executor.ts | 4 +- .../public/application/constants/index.ts | 6 +- .../get_defaults_for_action_params.test.ts | 6 +- .../lib/get_defaults_for_action_params.ts | 4 +- .../action_form.test.tsx | 27 ++++---- .../action_type_form.tsx | 10 +-- .../tests/alerting/list_alert_types.ts | 4 +- .../spaces_only/tests/alerting/alerts_base.ts | 16 ++--- .../spaces_only/tests/alerting/event_log.ts | 14 ++--- .../alerting/get_alert_instance_summary.ts | 2 +- .../tests/alerting/list_alert_types.ts | 2 +- 21 files changed, 152 insertions(+), 95 deletions(-) diff --git a/x-pack/plugins/alerts/common/builtin_action_groups.ts b/x-pack/plugins/alerts/common/builtin_action_groups.ts index d31f75357d370..d9c5ae613f787 100644 --- a/x-pack/plugins/alerts/common/builtin_action_groups.ts +++ b/x-pack/plugins/alerts/common/builtin_action_groups.ts @@ -6,13 +6,13 @@ import { i18n } from '@kbn/i18n'; import { ActionGroup } from './alert_type'; -export const ResolvedActionGroup: ActionGroup = { - id: 'resolved', - name: i18n.translate('xpack.alerts.builtinActionGroups.resolved', { - defaultMessage: 'Resolved', +export const RecoveredActionGroup: ActionGroup = { + id: 'recovered', + name: i18n.translate('xpack.alerts.builtinActionGroups.recovered', { + defaultMessage: 'Recovered', }), }; export function getBuiltinActionGroups(): ActionGroup[] { - return [ResolvedActionGroup]; + return [RecoveredActionGroup]; } diff --git a/x-pack/plugins/alerts/server/alert_type_registry.test.ts b/x-pack/plugins/alerts/server/alert_type_registry.test.ts index 8dc387f96addb..b04871a047e4b 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.test.ts @@ -105,8 +105,8 @@ describe('register()', () => { name: 'Default', }, { - id: 'resolved', - name: 'Resolved', + id: 'recovered', + name: 'Recovered', }, ], defaultActionGroupId: 'default', @@ -117,7 +117,7 @@ describe('register()', () => { expect(() => registry.register(alertType)).toThrowError( new Error( - `Alert type [id="${alertType.id}"] cannot be registered. Action groups [resolved] are reserved by the framework.` + `Alert type [id="${alertType.id}"] cannot be registered. Action groups [recovered] are reserved by the framework.` ) ); }); @@ -229,8 +229,8 @@ describe('get()', () => { "name": "Default", }, Object { - "id": "resolved", - "name": "Resolved", + "id": "recovered", + "name": "Recovered", }, ], "actionVariables": Object { @@ -287,8 +287,8 @@ describe('list()', () => { "name": "Test Action Group", }, Object { - "id": "resolved", - "name": "Resolved", + "id": "recovered", + "name": "Recovered", }, ], "actionVariables": Object { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 9bd61c0fe66d2..0a764ea768591 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -122,7 +122,7 @@ describe('getAlertInstanceSummary()', () => { .addActiveInstance('instance-previously-active', 'action group B') .advanceTime(10000) .addExecute() - .addResolvedInstance('instance-previously-active') + .addRecoveredInstance('instance-previously-active') .addActiveInstance('instance-currently-active', 'action group A') .getEvents(); const eventsResult = { diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index f9e4a2908d6ce..1d5ebe2b5911e 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -6,7 +6,7 @@ import { SanitizedAlert, AlertInstanceSummary } from '../types'; import { IValidatedEvent } from '../../../event_log/server'; -import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; +import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; import { alertInstanceSummaryFromEventLog } from './alert_instance_summary_from_event_log'; const ONE_HOUR_IN_MILLIS = 60 * 60 * 1000; @@ -189,7 +189,43 @@ describe('alertInstanceSummaryFromEventLog', () => { .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addResolvedInstance('instance-1') + .addRecoveredInstance('instance-1') + .getEvents(); + + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); + + const { lastRun, status, instances } = summary; + expect({ lastRun, status, instances }).toMatchInlineSnapshot(` + Object { + "instances": Object { + "instance-1": Object { + "actionGroupId": undefined, + "activeStartDate": undefined, + "muted": false, + "status": "OK", + }, + }, + "lastRun": "2020-06-18T00:00:10.000Z", + "status": "OK", + } + `); + }); + + test('legacy alert with currently inactive instance', async () => { + const alert = createAlert({}); + const eventsFactory = new EventsFactory(); + const events = eventsFactory + .addExecute() + .addNewInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') + .advanceTime(10000) + .addExecute() + .addLegacyResolvedInstance('instance-1') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -224,7 +260,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addResolvedInstance('instance-1') + .addRecoveredInstance('instance-1') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -406,7 +442,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group A') - .addResolvedInstance('instance-2') + .addRecoveredInstance('instance-2') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -451,7 +487,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group A') - .addResolvedInstance('instance-2') + .addRecoveredInstance('instance-2') .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group B') @@ -561,12 +597,24 @@ export class EventsFactory { return this; } - addResolvedInstance(instanceId: string): EventsFactory { + addRecoveredInstance(instanceId: string): EventsFactory { + this.events.push({ + '@timestamp': this.date, + event: { + provider: EVENT_LOG_PROVIDER, + action: EVENT_LOG_ACTIONS.recoveredInstance, + }, + kibana: { alerting: { instance_id: instanceId } }, + }); + return this; + } + + addLegacyResolvedInstance(instanceId: string): EventsFactory { this.events.push({ '@timestamp': this.date, event: { provider: EVENT_LOG_PROVIDER, - action: EVENT_LOG_ACTIONS.resolvedInstance, + action: LEGACY_EVENT_LOG_ACTIONS.resolvedInstance, }, kibana: { alerting: { instance_id: instanceId } }, }); diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index 8fed97a74435d..6fed8b4aa4ee6 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -6,7 +6,7 @@ import { SanitizedAlert, AlertInstanceSummary, AlertInstanceStatus } from '../types'; import { IEvent } from '../../../event_log/server'; -import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; +import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; export interface AlertInstanceSummaryFromEventLogParams { alert: SanitizedAlert; @@ -80,7 +80,8 @@ export function alertInstanceSummaryFromEventLog( status.status = 'Active'; status.actionGroupId = event?.kibana?.alerting?.action_group_id; break; - case EVENT_LOG_ACTIONS.resolvedInstance: + case LEGACY_EVENT_LOG_ACTIONS.resolvedInstance: + case EVENT_LOG_ACTIONS.recoveredInstance: status.status = 'OK'; status.activeStartDate = undefined; status.actionGroupId = undefined; diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 4bfb44425544a..bafb89c64076b 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -82,9 +82,12 @@ export const EVENT_LOG_ACTIONS = { execute: 'execute', executeAction: 'execute-action', newInstance: 'new-instance', - resolvedInstance: 'resolved-instance', + recoveredInstance: 'recovered-instance', activeInstance: 'active-instance', }; +export const LEGACY_EVENT_LOG_ACTIONS = { + resolvedInstance: 'resolved-instance', +}; export interface PluginSetupContract { registerType: AlertTypeRegistry['register']; diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 07d08f5837d54..d4c4f746392c3 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -26,12 +26,12 @@ import { alertsMock, alertsClientMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { IEventLogger } from '../../../event_log/server'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import { Alert, ResolvedActionGroup } from '../../common'; +import { Alert, RecoveredActionGroup } from '../../common'; import { omit } from 'lodash'; const alertType = { id: 'test', name: 'My test alert', - actionGroups: [{ id: 'default', name: 'Default' }, ResolvedActionGroup], + actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], defaultActionGroupId: 'default', executor: jest.fn(), producer: 'alerts', @@ -114,7 +114,7 @@ describe('Task Runner', () => { }, }, { - group: ResolvedActionGroup.id, + group: RecoveredActionGroup.id, id: '2', actionTypeId: 'action', params: { @@ -517,7 +517,7 @@ describe('Task Runner', () => { `); }); - test('fire resolved actions for execution for the alertInstances which is in the resolved state', async () => { + test('fire recovered actions for execution for the alertInstances which is in the recovered state', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); @@ -650,7 +650,7 @@ describe('Task Runner', () => { Array [ Object { "event": Object { - "action": "resolved-instance", + "action": "recovered-instance", }, "kibana": Object { "alerting": Object { @@ -666,7 +666,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' resolved instance: '2'", + "message": "test:1: 'alert-name' instance '2' has recovered", }, ], Array [ diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 24d96788c3395..5a7247ac50ea0 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -39,7 +39,7 @@ import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_l import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error'; import { AlertsClient } from '../alerts_client'; import { partiallyUpdateAlert } from '../saved_objects'; -import { ResolvedActionGroup } from '../../common'; +import { RecoveredActionGroup } from '../../common'; const FALLBACK_RETRY_INTERVAL = '5m'; @@ -219,7 +219,7 @@ export class TaskRunner { alertInstance.hasScheduledActions() ); - generateNewAndResolvedInstanceEvents({ + generateNewAndRecoveredInstanceEvents({ eventLogger, originalAlertInstances, currentAlertInstances: instancesWithScheduledActions, @@ -229,7 +229,7 @@ export class TaskRunner { }); if (!muteAll) { - scheduleActionsForResolvedInstances( + scheduleActionsForRecoveredInstances( alertInstances, executionHandler, originalAlertInstances, @@ -436,7 +436,7 @@ export class TaskRunner { } } -interface GenerateNewAndResolvedInstanceEventsParams { +interface GenerateNewAndRecoveredInstanceEventsParams { eventLogger: IEventLogger; originalAlertInstances: Dictionary; currentAlertInstances: Dictionary; @@ -445,18 +445,20 @@ interface GenerateNewAndResolvedInstanceEventsParams { namespace: string | undefined; } -function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInstanceEventsParams) { +function generateNewAndRecoveredInstanceEvents( + params: GenerateNewAndRecoveredInstanceEventsParams +) { const { eventLogger, alertId, namespace, currentAlertInstances, originalAlertInstances } = params; const originalAlertInstanceIds = Object.keys(originalAlertInstances); const currentAlertInstanceIds = Object.keys(currentAlertInstances); const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); - const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); + const recoveredIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); - for (const id of resolvedIds) { + for (const id of recoveredIds) { const actionGroup = originalAlertInstances[id].getLastScheduledActions()?.group; - const message = `${params.alertLabel} resolved instance: '${id}'`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message, actionGroup); + const message = `${params.alertLabel} instance '${id}' has recovered`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.recoveredInstance, message, actionGroup); } for (const id of newIds) { @@ -496,7 +498,7 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst } } -function scheduleActionsForResolvedInstances( +function scheduleActionsForRecoveredInstances( alertInstancesMap: Record, executionHandler: ReturnType, originalAlertInstances: Record, @@ -505,22 +507,22 @@ function scheduleActionsForResolvedInstances( ) { const currentAlertInstanceIds = Object.keys(currentAlertInstances); const originalAlertInstanceIds = Object.keys(originalAlertInstances); - const resolvedIds = without( + const recoveredIds = without( originalAlertInstanceIds, ...currentAlertInstanceIds, ...mutedInstanceIds ); - for (const id of resolvedIds) { + for (const id of recoveredIds) { const instance = alertInstancesMap[id]; - instance.updateLastScheduledActions(ResolvedActionGroup.id); + instance.updateLastScheduledActions(RecoveredActionGroup.id); instance.unscheduleActions(); executionHandler({ - actionGroup: ResolvedActionGroup.id, + actionGroup: RecoveredActionGroup.id, context: {}, state: {}, alertInstanceId: id, }); - instance.scheduleActions(ResolvedActionGroup.id); + instance.scheduleActions(RecoveredActionGroup.id); } } diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 14785f64cffac..1941ec6326ddb 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; @@ -103,7 +103,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } if (reason) { const actionGroupId = - nextState === AlertStates.OK ? ResolvedActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group: item, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index b31afba8ac9cc..a1d6428f3b52b 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -6,7 +6,7 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, @@ -367,7 +367,7 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute([2]); - expect(mostRecentAction(instanceID).id).toBe(ResolvedActionGroup.id); + expect(mostRecentAction(instanceID).id).toBe(RecoveredActionGroup.id); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('does not continue to send a recovery alert if the metric is still OK', async () => { @@ -383,7 +383,7 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute([2]); - expect(mostRecentAction(instanceID).id).toBe(ResolvedActionGroup.id); + expect(mostRecentAction(instanceID).id).toBe(RecoveredActionGroup.id); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); }); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 7c3918c37ebbf..60790648d9a9b 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -6,7 +6,7 @@ import { first, last } from 'lodash'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraBackendLibs } from '../../infra_types'; import { @@ -89,7 +89,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const firstResult = first(alertResults); const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString(); const actionGroupId = - nextState === AlertStates.OK ? ResolvedActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 7af8e5ba88300..156f65f094342 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -16,10 +16,10 @@ export const routeToConnectors = `/connectors`; export const routeToAlerts = `/alerts`; export const routeToAlertDetails = `/alert/:alertId`; -export const resolvedActionGroupMessage = i18n.translate( - 'xpack.triggersActionsUI.sections.actionForm.ResolvedMessage', +export const recoveredActionGroupMessage = i18n.translate( + 'xpack.triggersActionsUI.sections.actionForm.RecoveredMessage', { - defaultMessage: 'Resolved', + defaultMessage: 'Recovered', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts index c1f7cbb9fafed..57cc45786b2da 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ResolvedActionGroup } from '../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../alerts/common'; import { AlertProvidedActionVariables } from './action_variables'; import { getDefaultsForActionParams } from './get_defaults_for_action_params'; @@ -16,8 +16,8 @@ describe('getDefaultsForActionParams', () => { }); }); - test('pagerduty defaults for resolved action group', async () => { - expect(getDefaultsForActionParams('.pagerduty', ResolvedActionGroup.id)).toEqual({ + test('pagerduty defaults for recovered action group', async () => { + expect(getDefaultsForActionParams('.pagerduty', RecoveredActionGroup.id)).toEqual({ dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`, eventAction: 'resolve', }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts index c2143553e63c6..36c054977ac30 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertActionParam, ResolvedActionGroup } from '../../../../alerts/common'; +import { AlertActionParam, RecoveredActionGroup } from '../../../../alerts/common'; import { AlertProvidedActionVariables } from './action_variables'; export const getDefaultsForActionParams = ( @@ -17,7 +17,7 @@ export const getDefaultsForActionParams = ( dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`, eventAction: 'trigger', }; - if (actionGroupId === ResolvedActionGroup.id) { + if (actionGroupId === RecoveredActionGroup.id) { pagerDutyDefaults.eventAction = 'resolve'; } return pagerDutyDefaults; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 5b56720737b7e..ddbf933078043 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -10,8 +10,9 @@ import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, Alert, AlertAction } from '../../../types'; import ActionForm from './action_form'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { useKibana } from '../../../common/lib/kibana'; +import { EuiScreenReaderOnly } from '@elastic/eui'; jest.mock('../../../common/lib/kibana'); jest.mock('../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -228,7 +229,7 @@ describe('action_form', () => { }} actionGroups={[ { id: 'default', name: 'Default' }, - { id: 'resolved', name: 'Resolved' }, + { id: 'recovered', name: 'Recovered' }, ]} setActionGroupIdByIndex={(group: string, index: number) => { initialAlert.actions[index].group = group; @@ -347,18 +348,18 @@ describe('action_form', () => { "value": "default", }, Object { - "data-test-subj": "addNewActionConnectorActionGroup-0-option-resolved", - "inputDisplay": "Resolved", - "value": "resolved", + "data-test-subj": "addNewActionConnectorActionGroup-0-option-recovered", + "inputDisplay": "Recovered", + "value": "recovered", }, ] `); }); - it('renders selected Resolved action group', async () => { + it('renders selected Recovered action group', async () => { const wrapper = await setup([ { - group: ResolvedActionGroup.id, + group: RecoveredActionGroup.id, id: 'test', actionTypeId: actionType.id, params: { @@ -381,15 +382,17 @@ describe('action_form', () => { "value": "default", }, Object { - "data-test-subj": "addNewActionConnectorActionGroup-0-option-resolved", - "inputDisplay": "Resolved", - "value": "resolved", + "data-test-subj": "addNewActionConnectorActionGroup-0-option-recovered", + "inputDisplay": "Recovered", + "value": "recovered", }, ] `); - expect(actionGroupsSelect.first().text()).toEqual( - 'Select an option: Resolved, is selectedResolved' + + expect(actionGroupsSelect.first().find(EuiScreenReaderOnly).text()).toEqual( + 'Select an option: Recovered, is selected' ); + expect(actionGroupsSelect.first().find('button').first().text()).toEqual('Recovered'); }); it('renders available connectors for the selected action type', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index 7e38805957931..fffc3bd32125e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -26,7 +26,7 @@ import { EuiBadge, EuiErrorBoundary, } from '@elastic/eui'; -import { AlertActionParam, ResolvedActionGroup } from '../../../../../alerts/common'; +import { AlertActionParam, RecoveredActionGroup } from '../../../../../alerts/common'; import { IErrorObject, AlertAction, @@ -40,7 +40,7 @@ import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_en import { hasSaveActionsCapability } from '../../lib/capabilities'; import { ActionAccordionFormProps } from './action_form'; import { transformActionVariables } from '../../lib/action_variables'; -import { resolvedActionGroupMessage } from '../../constants'; +import { recoveredActionGroupMessage } from '../../constants'; import { useKibana } from '../../../common/lib/kibana'; import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params'; @@ -105,8 +105,8 @@ export const ActionTypeForm = ({ useEffect(() => { setAvailableActionVariables(getAvailableActionVariables(messageVariables, actionItem.group)); const res = - actionItem.group === ResolvedActionGroup.id - ? resolvedActionGroupMessage + actionItem.group === RecoveredActionGroup.id + ? recoveredActionGroupMessage : defaultActionMessage; setAvailableDefaultActionMessage(res); const paramsDefaults = getDefaultsForActionParams(actionItem.actionTypeId, actionItem.group); @@ -374,7 +374,7 @@ function getAvailableActionVariables( return []; } const filteredActionVariables = - actionGroup === ResolvedActionGroup.id + actionGroup === RecoveredActionGroup.id ? { params: actionVariables.params, state: actionVariables.state } : actionVariables; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts index b3635b9f40e27..bfaf8a2a4788e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts @@ -17,7 +17,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { const expectedNoOpType = { actionGroups: [ { id: 'default', name: 'Default' }, - { id: 'resolved', name: 'Resolved' }, + { id: 'recovered', name: 'Recovered' }, ], defaultActionGroupId: 'default', id: 'test.noop', @@ -33,7 +33,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { const expectedRestrictedNoOpType = { actionGroups: [ { id: 'default', name: 'Default' }, - { id: 'resolved', name: 'Resolved' }, + { id: 'recovered', name: 'Recovered' }, ], defaultActionGroupId: 'default', id: 'test.restricted-noop', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts index 64e99190e183a..8dab199271da8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { Response as SupertestResponse } from 'supertest'; -import { ResolvedActionGroup } from '../../../../../plugins/alerts/common'; +import { RecoveredActionGroup } from '../../../../../plugins/alerts/common'; import { Space } from '../../../common/types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { @@ -137,7 +137,7 @@ instanceStateValue: true await taskManagerUtils.waitForActionTaskParamsToBeCleanedUp(testStart); }); - it('should fire actions when an alert instance is resolved', async () => { + it('should fire actions when an alert instance is recovered', async () => { const reference = alertUtils.generateReference(); const { body: createdAction } = await supertestWithoutAuth @@ -174,12 +174,12 @@ instanceStateValue: true params: {}, }, { - group: ResolvedActionGroup.id, + group: RecoveredActionGroup.id, id: indexRecordActionId, params: { index: ES_TEST_INDEX_NAME, reference, - message: 'Resolved message', + message: 'Recovered message', }, }, ], @@ -194,10 +194,10 @@ instanceStateValue: true await esTestIndexTool.waitForDocs('action:test.index-record', reference) )[0]; - expect(actionTestRecord._source.params.message).to.eql('Resolved message'); + expect(actionTestRecord._source.params.message).to.eql('Recovered message'); }); - it('should not fire actions when an alert instance is resolved, but alert is muted', async () => { + it('should not fire actions when an alert instance is recovered, but alert is muted', async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); @@ -237,12 +237,12 @@ instanceStateValue: true params: {}, }, { - group: ResolvedActionGroup.id, + group: RecoveredActionGroup.id, id: indexRecordActionId, params: { index: ES_TEST_INDEX_NAME, reference, - message: 'Resolved message', + message: 'Recovered message', }, }, ], diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index d3cd3db124ecd..3766785680925 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -78,7 +78,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { 'execute-action', 'new-instance', 'active-instance', - 'resolved-instance', + 'recovered-instance', ], }); }); @@ -87,25 +87,25 @@ export default function eventLogTests({ getService }: FtrProviderContext) { const executeEvents = getEventsByAction(events, 'execute'); const executeActionEvents = getEventsByAction(events, 'execute-action'); const newInstanceEvents = getEventsByAction(events, 'new-instance'); - const resolvedInstanceEvents = getEventsByAction(events, 'resolved-instance'); + const recoveredInstanceEvents = getEventsByAction(events, 'recovered-instance'); expect(executeEvents.length >= 4).to.be(true); expect(executeActionEvents.length).to.be(2); expect(newInstanceEvents.length).to.be(1); - expect(resolvedInstanceEvents.length).to.be(1); + expect(recoveredInstanceEvents.length).to.be(1); // make sure the events are in the right temporal order const executeTimes = getTimestamps(executeEvents); const executeActionTimes = getTimestamps(executeActionEvents); const newInstanceTimes = getTimestamps(newInstanceEvents); - const resolvedInstanceTimes = getTimestamps(resolvedInstanceEvents); + const recoveredInstanceTimes = getTimestamps(recoveredInstanceEvents); expect(executeTimes[0] < newInstanceTimes[0]).to.be(true); expect(executeTimes[1] <= newInstanceTimes[0]).to.be(true); expect(executeTimes[2] > newInstanceTimes[0]).to.be(true); expect(executeTimes[1] <= executeActionTimes[0]).to.be(true); expect(executeTimes[2] > executeActionTimes[0]).to.be(true); - expect(resolvedInstanceTimes[0] > newInstanceTimes[0]).to.be(true); + expect(recoveredInstanceTimes[0] > newInstanceTimes[0]).to.be(true); // validate each event let executeCount = 0; @@ -136,8 +136,8 @@ export default function eventLogTests({ getService }: FtrProviderContext) { case 'new-instance': validateInstanceEvent(event, `created new instance: 'instance'`); break; - case 'resolved-instance': - validateInstanceEvent(event, `resolved instance: 'instance'`); + case 'recovered-instance': + validateInstanceEvent(event, `recovered instance: 'instance'`); break; case 'active-instance': validateInstanceEvent(event, `active instance: 'instance' in actionGroup: 'default'`); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts index 22034328e5275..404c6020fa237 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts @@ -216,7 +216,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr await alertUtils.muteInstance(createdAlert.id, 'instanceC'); await alertUtils.muteInstance(createdAlert.id, 'instanceD'); - await waitForEvents(createdAlert.id, ['new-instance', 'resolved-instance']); + await waitForEvents(createdAlert.id, ['new-instance', 'recovered-instance']); const response = await supertest.get( `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts index 3fb2cc40437d8..9d38f4abb7f3f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts @@ -25,7 +25,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { expect(fixtureAlertType).to.eql({ actionGroups: [ { id: 'default', name: 'Default' }, - { id: 'resolved', name: 'Resolved' }, + { id: 'recovered', name: 'Recovered' }, ], defaultActionGroupId: 'default', id: 'test.noop', From 9b74fe6d39b2d7126c650120a1c9b7523985f641 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 1 Dec 2020 12:55:15 -0500 Subject: [PATCH 11/37] [Fleet] Handler api key creation errors when Fleet Admin is invalid (#84576) --- .../components/new_enrollment_key_flyout.tsx | 3 + x-pack/plugins/fleet/server/errors/index.ts | 7 ++- .../server/services/api_keys/security.ts | 58 +++++++++++++++---- .../apis/enrollment_api_keys/crud.ts | 31 +++++++++- 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx index ed607e361bd6e..d9aeba2372672 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx @@ -46,6 +46,9 @@ function useCreateApiKeyForm( policy_id: policyIdInput.value, }), }); + if (res.error) { + throw res.error; + } policyIdInput.clear(); apiKeyNameInput.clear(); setIsLoading(false); diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index e3ca6a9b48dcf..d6fa79a2baeba 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -5,7 +5,11 @@ */ /* eslint-disable max-classes-per-file */ -export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; +export { + defaultIngestErrorHandler, + ingestErrorToResponseOptions, + isLegacyESClientError, +} from './handlers'; export class IngestManagerError extends Error { constructor(message?: string) { @@ -24,3 +28,4 @@ export class PackageUnsupportedMediaTypeError extends IngestManagerError {} export class PackageInvalidArchiveError extends IngestManagerError {} export class PackageCacheError extends IngestManagerError {} export class PackageOperationNotSupportedError extends IngestManagerError {} +export class FleetAdminUserInvalidError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/services/api_keys/security.ts b/x-pack/plugins/fleet/server/services/api_keys/security.ts index dfd53d55fbbf5..5fdf8626a9fb2 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/security.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/security.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, FakeRequest, SavedObjectsClientContract } from 'src/core/server'; +import type { Request } from '@hapi/hapi'; +import { KibanaRequest, SavedObjectsClientContract } from '../../../../../../src/core/server'; +import { FleetAdminUserInvalidError, isLegacyESClientError } from '../../errors'; import { CallESAsCurrentUser } from '../../types'; import { appContextService } from '../app_context'; import { outputService } from '../output'; @@ -18,22 +20,38 @@ export async function createAPIKey( if (!adminUser) { throw new Error('No admin user configured'); } - const request: FakeRequest = { + const request = KibanaRequest.from(({ + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, headers: { authorization: `Basic ${Buffer.from(`${adminUser.username}:${adminUser.password}`).toString( 'base64' )}`, }, - }; + } as unknown) as Request); const security = appContextService.getSecurity(); if (!security) { throw new Error('Missing security plugin'); } - return security.authc.createAPIKey(request as KibanaRequest, { - name, - role_descriptors: roleDescriptors, - }); + try { + const key = await security.authc.createAPIKey(request, { + name, + role_descriptors: roleDescriptors, + }); + + return key; + } catch (err) { + if (isLegacyESClientError(err) && err.statusCode === 401) { + // Clear Fleet admin user cache as the user is probably not valid anymore + outputService.invalidateCache(); + throw new FleetAdminUserInvalidError(`Fleet Admin user is invalid: ${err.message}`); + } + + throw err; + } } export async function authenticate(callCluster: CallESAsCurrentUser) { try { @@ -51,20 +69,36 @@ export async function invalidateAPIKey(soClient: SavedObjectsClientContract, id: if (!adminUser) { throw new Error('No admin user configured'); } - const request: FakeRequest = { + const request = KibanaRequest.from(({ + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, headers: { authorization: `Basic ${Buffer.from(`${adminUser.username}:${adminUser.password}`).toString( 'base64' )}`, }, - }; + } as unknown) as Request); const security = appContextService.getSecurity(); if (!security) { throw new Error('Missing security plugin'); } - return security.authc.invalidateAPIKey(request as KibanaRequest, { - id, - }); + try { + const res = await security.authc.invalidateAPIKey(request, { + id, + }); + + return res; + } catch (err) { + if (isLegacyESClientError(err) && err.statusCode === 401) { + // Clear Fleet admin user cache as the user is probably not valid anymore + outputService.invalidateCache(); + throw new FleetAdminUserInvalidError(`Fleet Admin user is invalid: ${err.message}`); + } + + throw err; + } } diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts index 3a526fac2f08a..dd72016476526 100644 --- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts @@ -92,12 +92,12 @@ export default function (providerContext: FtrProviderContext) { .expect(400); }); - it('should not allow to create an enrollment api key for a non existing agent policy', async () => { + it('should return a 400 if the fleet admin user is modifed outside of Fleet', async () => { await supertest .post(`/api/fleet/enrollment-api-keys`) .set('kbn-xsrf', 'xxx') .send({ - policy_id: 'idonotexistspolicy', + raoul: 'raoul', }) .expect(400); }); @@ -161,6 +161,33 @@ export default function (providerContext: FtrProviderContext) { }, }); }); + + describe('It should handle error when the Fleet user is invalid', () => { + before(async () => {}); + after(async () => { + await getService('supertest') + .post(`/api/fleet/agents/setup`) + .set('kbn-xsrf', 'xxx') + .send({ forceRecreate: true }); + }); + + it('should not allow to create an enrollment api key if the Fleet admin user is invalid', async () => { + await es.security.changePassword({ + username: 'fleet_enroll', + body: { + password: Buffer.from((Math.random() * 10000000).toString()).toString('base64'), + }, + }); + const res = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + }) + .expect(400); + expect(res.body.message).match(/Fleet Admin user is invalid/); + }); + }); }); }); } From d8643f719ecb449cc75b17f592ae76ae42e4f0d5 Mon Sep 17 00:00:00 2001 From: Jeffrey Chu <56368296+achuguy@users.noreply.github.com> Date: Tue, 1 Dec 2020 13:03:56 -0500 Subject: [PATCH 12/37] endpoint telemetry cloned endpoint tests (#81498) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../data.json | 530 ++++ .../mappings.json | 2592 +++++++++++++++++ .../cloned_endpoint_installed/data.json | 533 ++++ .../cloned_endpoint_installed/mappings.json | 2592 +++++++++++++++++ .../cloned_endpoint_uninstalled/data.json | 527 ++++ .../cloned_endpoint_uninstalled/mappings.json | 2592 +++++++++++++++++ .../apps/endpoint/endpoint_telemetry.ts | 118 + 7 files changed, 9484 insertions(+) create mode 100644 x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json create mode 100644 x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json create mode 100644 x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json create mode 100644 x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json create mode 100644 x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json create mode 100644 x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json new file mode 100644 index 0000000000000..9b6804beabfe5 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json @@ -0,0 +1,530 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:37.093Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "MngOEjmt4OWFSCvya8AWgDF9p0nPqiCZLpNrqntWdjcGl+vPcbVs+un3ilKC3GQKtKP6KLtMziLR/60teHpAJ0Ls1f+mbCP1PjjAfFL1ZBnGHsvkR099iRJ9q4rCxzmZtifGZQ/s2+t99DRUe8GkJhIj3VR1uN/EKPXmXDWZo0f+bTUDT7vGZVY=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:58.866Z", + "default_api_key_id": "ieriwHQBXUUrssdI83FW", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "E-riwHQBXUUrssdIvHEw", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:1cfe:7be7:f700:8810/64", + "fdbb:cb5c:fb4:68:257d:7303:389d:f335/64", + "fdbb:cb5c:fb4:68:7470:3bec:14b5:2caf/64", + "fdbb:cb5c:fb4:68:9c5f:eab7:8345:f711/64", + "fdbb:cb5c:fb4:68:dc96:8bac:67e0:99dd/64", + "fdbb:cb5c:fb4:68:60c6:73b6:1540:602/64", + "fdbb:cb5c:fb4:68:144:6a1b:1aae:a57d/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:296c368b-35d3-4241-905f-75a24f52ec13", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "PEF8+bfiv21Yn5yj8I2/vIaQWMrUQK4PeBBwXsrvmVTsbuFejXM0IQtYVKXShBJAoY9CUEKPCRR4rIIdXWZc51i1ZneLoFw+yBw8BsSwhHfbQXvAVQowH7UqKHp0CiA5J9uGSgmw3Q55a4dv4IHih+sBKji7Qf2durs5gCWUJExrRCpMiU3OHSg=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:56.620Z", + "default_api_key_id": "xOrjwHQBXUUrssdIDnHH", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "S67iwHQBEiA0_Dvks-Cm", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "296c368b-35d3-4241-905f-75a24f52ec13" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "sdv6996k+S1BiZ/12K3Wi6rb8Lsoh/+shwzKNqujwcmhdbeQ92ygLoO+tudJaJOnL129WT+hhanEf6OgH5PpQBezc03hl9v2AI+BlU+hssfce5OfgFRGLYg8S+ryNHwFhK6EJeN1aivoie+YholNpcpt2l/t+lQpevMI4QYGaMfUzofuivs5JM4=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:54.037Z", + "default_api_key_id": "lq7iwHQBEiA0_Dvk8-Fb", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "geriwHQBXUUrssdIqXB2", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::107d:2365:5a7c:8da/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.195/22", + "fdbb:cb5c:fb4:68:d4ef:63a5:8ffc:f933/64", + "fdbb:cb5c:fb4:68:b082:8681:cf85:27d0/64", + "fdbb:cb5c:fb4:68:7c3d:13f3:5339:be7b/64", + "fdbb:cb5c:fb4:68:19a4:2a63:cc88:6e59/64", + "fdbb:cb5c:fb4:68:494a:3867:57b8:4027/64", + "fdbb:cb5c:fb4:68:1c88:41e:6ce1:4be7/64", + "fdbb:cb5c:fb4:68:114:b84:8faf:b12b/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "mac": [ + "00:50:56:b1:e4:06" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:ac0ab6c1-2317-478c-93d9-c514d845302d", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "UnSz8pAKTP/0DENATzn13Yo0jcdbWq70IiBJcDY+DF5M063+El91o+448KVaMHj3rCSrULfJboBf1Ao80UKU5WKz4CYJ3ZVjHm39/f8rXMZSah5lQAkl9Ak2v5wUCFd4KTEwUUEmnUKKSQGC53cBhnvoyPdzfNjt1ml96lZFZbxXt/VyU3u8vhQ=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:30.880Z", + "default_api_key_id": "Va7iwHQBEiA0_DvkcN-4", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sq7iwHQBEiA0_DvkT98X", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-1", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:6c9d:def9:bb8a:6695/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-1", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "ac0ab6c1-2317-478c-93d9-c514d845302d" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:2d187287-658a-4cb6-84d8-d66d1b9a6299", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "fpQcy/QWSbafzl6avELe9goTtyojPwQX3id1pe+BBqDarSCB3k5QwWLQP2SXEl2rwJdywUrBz3gMySKi80RYWJFUoWHiipfaE/jXJRqJxZZvhBe8fdSP7YPkdIdLQl/3ktIWqAzjjS1CErqMb5K4HTZIp5FswDQB40SbDkQKPECl9o8pBhLjH/A=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:28.949Z", + "default_api_key_id": "aeriwHQBXUUrssdIdXAX", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sa7iwHQBEiA0_DvkR99k", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-2", + "ip": [ + "fdbb:cb5c:fb4:68:dda8:b7a:3e20:9ca0/64", + "fdbb:cb5c:fb4:68:e922:9626:5193:ef68/128", + "fe80::dda8:b7a:3e20:9ca0/64", + "10.0.6.96/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-2", + "mac": [ + "00:50:56:b1:26:07" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "2d187287-658a-4cb6-84d8-d66d1b9a6299" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:c216aea0-58ba-40a3-b6fe-afa2f5457835", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "tSCsI7HPfRVIcw3Yx3xUAl20Hfe9AdEIs/4IBBH9ZO1gxnMMjRkVb/hxhfcdg6dkW+RIc6Pc9Jz7rUvybq8fY0r/pTKGXTFr46dC2+E9jfb7rs/PmYhG2V0/Ei2p+ZQypAIp8mtknSHkX+l74N7niVXKreneLrt99e4ZWIyeuwNwr0HcGjoMEqM=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:59.088Z", + "default_api_key_id": "SK7jwHQBEiA0_DvkNuIq", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "OeriwHQBXUUrssdIvXGr", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.6.176/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:3363/64", + "fe80::250:56ff:feb1:3363/64" + ], + "hostname": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "mac": [ + "00:50:56:b1:33:63" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-118-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "c216aea0-58ba-40a3-b6fe-afa2f5457835" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:8e652110-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:43.499165-04:00", + "subtype": "RUNNING", + "agent_id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "message": "Application: endpoint-security--7.9.2[5460518c-10c7-4c25-b2ec-3f63eafb7d47]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:43.495361445Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[44,4,0,2,2,4,1,2,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":25.33265565,\"mean\":6.21698140807909}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58376192,\"mean\":46094231}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.32258064516129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.323624595469256},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.664451827242525},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":9.55882352941176},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":308,\"system\":3807934}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"b364a499-8e64-4d91-9770-6911c5d6964b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:18:18.145Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:80a6c1f0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:54.930717796-04:00", + "subtype": "RUNNING", + "agent_id": "c216aea0-58ba-40a3-b6fe-afa2f5457835", + "message": "Application: endpoint-security--7.9.2[c216aea0-58ba-40a3-b6fe-afa2f5457835]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:54.929290223Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":3,\"mean\":3.49666666666667}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49778688,\"mean\":31986824}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":3863}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"a15f0431-6835-41c4-a7ee-21a70d41cf5b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"20ccfdfa-323f-e33e-f2ef-3528edb1afea\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:55.087Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7bdc8fb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:45.675453579-04:00", + "subtype": "RUNNING", + "agent_id": "296c368b-35d3-4241-905f-75a24f52ec13", + "message": "Application: endpoint-security--7.9.2[296c368b-35d3-4241-905f-75a24f52ec13]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:45.674010613Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":2.8,\"mean\":3.17}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49278976,\"mean\":31884356}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":5000305}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6f0cb2fc-3e46-4435-8892-d9f7e71b23fd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:47.051Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:81e5aa90-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:40.138333-04:00", + "subtype": "RUNNING", + "agent_id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "message": "Application: endpoint-security--7.9.2[b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:40.134985503Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[55,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":10.21008368,\"mean\":1.91476589372881}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":71143424,\"mean\":53719456}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":3.08880308880309},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":302,\"system\":1901758}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"49f4e779-287a-4fa8-80e6-247b54c554f1\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"7d59b1a5-afa1-6531-07ea-691602558230\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:57.177Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:82b7eeb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:13.3157023-04:00", + "subtype": "RUNNING", + "agent_id": "ac0ab6c1-2317-478c-93d9-c514d845302d", + "message": "Application: endpoint-security--7.9.2[ac0ab6c1-2317-478c-93d9-c514d845302d]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:13.13714300Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[53,1,0,1,0,0,2,1,0,3,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":49.0526570938275,\"mean\":4.53577832211642}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":285802496,\"mean\":95647240}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":3.18021201413428},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":306,\"system\":3625}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6474b1bd-96bc-4bde-a770-0e6a7a5bf8c4\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:58.555Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7cbf9cb1-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:15.400204-04:00", + "subtype": "RUNNING", + "agent_id": "2d187287-658a-4cb6-84d8-d66d1b9a6299", + "message": "Application: endpoint-security--7.9.2[2d187287-658a-4cb6-84d8-d66d1b9a6299]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:15.96990100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[46,2,2,2,4,2,0,0,0,2,0,0,0,0,1,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":48.3070275492921,\"mean\":6.43134047264261}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":228757504,\"mean\":94594836}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":1.9672131147541},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":2.62295081967213},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.655737704918033},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":2.11267605633803},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":307,\"system\":3654}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"21d182a2-5a08-41bb-b601-5d2b4aba4ecd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"327d0e20-483e-95af-f4e4-7b065606e1aa\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:48.539Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json new file mode 100644 index 0000000000000..27aea27bebcd7 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json @@ -0,0 +1,2592 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "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" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json new file mode 100644 index 0000000000000..98488c85878b5 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json @@ -0,0 +1,533 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "sdv6996k+S1BiZ/12K3Wi6rb8Lsoh/+shwzKNqujwcmhdbeQ92ygLoO+tudJaJOnL129WT+hhanEf6OgH5PpQBezc03hl9v2AI+BlU+hssfce5OfgFRGLYg8S+ryNHwFhK6EJeN1aivoie+YholNpcpt2l/t+lQpevMI4QYGaMfUzofuivs5JM4=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:54.037Z", + "default_api_key_id": "lq7iwHQBEiA0_Dvk8-Fb", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "geriwHQBXUUrssdIqXB2", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::107d:2365:5a7c:8da/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.195/22", + "fdbb:cb5c:fb4:68:d4ef:63a5:8ffc:f933/64", + "fdbb:cb5c:fb4:68:b082:8681:cf85:27d0/64", + "fdbb:cb5c:fb4:68:7c3d:13f3:5339:be7b/64", + "fdbb:cb5c:fb4:68:19a4:2a63:cc88:6e59/64", + "fdbb:cb5c:fb4:68:494a:3867:57b8:4027/64", + "fdbb:cb5c:fb4:68:1c88:41e:6ce1:4be7/64", + "fdbb:cb5c:fb4:68:114:b84:8faf:b12b/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "mac": [ + "00:50:56:b1:e4:06" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:c216aea0-58ba-40a3-b6fe-afa2f5457835", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "tSCsI7HPfRVIcw3Yx3xUAl20Hfe9AdEIs/4IBBH9ZO1gxnMMjRkVb/hxhfcdg6dkW+RIc6Pc9Jz7rUvybq8fY0r/pTKGXTFr46dC2+E9jfb7rs/PmYhG2V0/Ei2p+ZQypAIp8mtknSHkX+l74N7niVXKreneLrt99e4ZWIyeuwNwr0HcGjoMEqM=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:59.088Z", + "default_api_key_id": "SK7jwHQBEiA0_DvkNuIq", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "OeriwHQBXUUrssdIvXGr", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.6.176/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:3363/64", + "fe80::250:56ff:feb1:3363/64" + ], + "hostname": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "mac": [ + "00:50:56:b1:33:63" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-118-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "c216aea0-58ba-40a3-b6fe-afa2f5457835" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:2d187287-658a-4cb6-84d8-d66d1b9a6299", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "fpQcy/QWSbafzl6avELe9goTtyojPwQX3id1pe+BBqDarSCB3k5QwWLQP2SXEl2rwJdywUrBz3gMySKi80RYWJFUoWHiipfaE/jXJRqJxZZvhBe8fdSP7YPkdIdLQl/3ktIWqAzjjS1CErqMb5K4HTZIp5FswDQB40SbDkQKPECl9o8pBhLjH/A=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:28.949Z", + "default_api_key_id": "aeriwHQBXUUrssdIdXAX", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sa7iwHQBEiA0_DvkR99k", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-2", + "ip": [ + "fdbb:cb5c:fb4:68:dda8:b7a:3e20:9ca0/64", + "fdbb:cb5c:fb4:68:e922:9626:5193:ef68/128", + "fe80::dda8:b7a:3e20:9ca0/64", + "10.0.6.96/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-2", + "mac": [ + "00:50:56:b1:26:07" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "2d187287-658a-4cb6-84d8-d66d1b9a6299" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:296c368b-35d3-4241-905f-75a24f52ec13", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "PEF8+bfiv21Yn5yj8I2/vIaQWMrUQK4PeBBwXsrvmVTsbuFejXM0IQtYVKXShBJAoY9CUEKPCRR4rIIdXWZc51i1ZneLoFw+yBw8BsSwhHfbQXvAVQowH7UqKHp0CiA5J9uGSgmw3Q55a4dv4IHih+sBKji7Qf2durs5gCWUJExrRCpMiU3OHSg=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:56.620Z", + "default_api_key_id": "xOrjwHQBXUUrssdIDnHH", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "S67iwHQBEiA0_Dvks-Cm", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "296c368b-35d3-4241-905f-75a24f52ec13" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "MngOEjmt4OWFSCvya8AWgDF9p0nPqiCZLpNrqntWdjcGl+vPcbVs+un3ilKC3GQKtKP6KLtMziLR/60teHpAJ0Ls1f+mbCP1PjjAfFL1ZBnGHsvkR099iRJ9q4rCxzmZtifGZQ/s2+t99DRUe8GkJhIj3VR1uN/EKPXmXDWZo0f+bTUDT7vGZVY=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:58.866Z", + "default_api_key_id": "ieriwHQBXUUrssdI83FW", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "E-riwHQBXUUrssdIvHEw", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:1cfe:7be7:f700:8810/64", + "fdbb:cb5c:fb4:68:257d:7303:389d:f335/64", + "fdbb:cb5c:fb4:68:7470:3bec:14b5:2caf/64", + "fdbb:cb5c:fb4:68:9c5f:eab7:8345:f711/64", + "fdbb:cb5c:fb4:68:dc96:8bac:67e0:99dd/64", + "fdbb:cb5c:fb4:68:60c6:73b6:1540:602/64", + "fdbb:cb5c:fb4:68:144:6a1b:1aae:a57d/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:ac0ab6c1-2317-478c-93d9-c514d845302d", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "UnSz8pAKTP/0DENATzn13Yo0jcdbWq70IiBJcDY+DF5M063+El91o+448KVaMHj3rCSrULfJboBf1Ao80UKU5WKz4CYJ3ZVjHm39/f8rXMZSah5lQAkl9Ak2v5wUCFd4KTEwUUEmnUKKSQGC53cBhnvoyPdzfNjt1ml96lZFZbxXt/VyU3u8vhQ=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:30.880Z", + "default_api_key_id": "Va7iwHQBEiA0_DvkcN-4", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sq7iwHQBEiA0_DvkT98X", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-1", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:6c9d:def9:bb8a:6695/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-1", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "ac0ab6c1-2317-478c-93d9-c514d845302d" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:81e5aa90-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:40.138333-04:00", + "subtype": "RUNNING", + "agent_id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "message": "Application: endpoint-security--7.9.2[b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:40.134985503Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[55,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":10.21008368,\"mean\":1.91476589372881}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":71143424,\"mean\":53719456}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":3.08880308880309},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":302,\"system\":1901758}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"49f4e779-287a-4fa8-80e6-247b54c554f1\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"7d59b1a5-afa1-6531-07ea-691602558230\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:57.177Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:8e652110-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:43.499165-04:00", + "subtype": "RUNNING", + "agent_id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "message": "Application: endpoint-security--7.9.2[5460518c-10c7-4c25-b2ec-3f63eafb7d47]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:43.495361445Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[44,4,0,2,2,4,1,2,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":25.33265565,\"mean\":6.21698140807909}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58376192,\"mean\":46094231}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.32258064516129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.323624595469256},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.664451827242525},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":9.55882352941176},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":308,\"system\":3807934}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"b364a499-8e64-4d91-9770-6911c5d6964b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:18:18.145Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:82b7eeb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:13.3157023-04:00", + "subtype": "RUNNING", + "agent_id": "ac0ab6c1-2317-478c-93d9-c514d845302d", + "message": "Application: endpoint-security--7.9.2[ac0ab6c1-2317-478c-93d9-c514d845302d]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:13.13714300Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[53,1,0,1,0,0,2,1,0,3,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":49.0526570938275,\"mean\":4.53577832211642}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":285802496,\"mean\":95647240}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":3.18021201413428},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":306,\"system\":3625}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6474b1bd-96bc-4bde-a770-0e6a7a5bf8c4\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:58.555Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:80a6c1f0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:54.930717796-04:00", + "subtype": "RUNNING", + "agent_id": "c216aea0-58ba-40a3-b6fe-afa2f5457835", + "message": "Application: endpoint-security--7.9.2[c216aea0-58ba-40a3-b6fe-afa2f5457835]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:54.929290223Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":3,\"mean\":3.49666666666667}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49778688,\"mean\":31986824}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":3863}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"a15f0431-6835-41c4-a7ee-21a70d41cf5b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"20ccfdfa-323f-e33e-f2ef-3528edb1afea\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:55.087Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7bdc8fb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:45.675453579-04:00", + "subtype": "RUNNING", + "agent_id": "296c368b-35d3-4241-905f-75a24f52ec13", + "message": "Application: endpoint-security--7.9.2[296c368b-35d3-4241-905f-75a24f52ec13]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:45.674010613Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":2.8,\"mean\":3.17}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49278976,\"mean\":31884356}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":5000305}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6f0cb2fc-3e46-4435-8892-d9f7e71b23fd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:47.051Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7cbf9cb1-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:15.400204-04:00", + "subtype": "RUNNING", + "agent_id": "2d187287-658a-4cb6-84d8-d66d1b9a6299", + "message": "Application: endpoint-security--7.9.2[2d187287-658a-4cb6-84d8-d66d1b9a6299]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:15.96990100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[46,2,2,2,4,2,0,0,0,2,0,0,0,0,1,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":48.3070275492921,\"mean\":6.43134047264261}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":228757504,\"mean\":94594836}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":1.9672131147541},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":2.62295081967213},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.655737704918033},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":2.11267605633803},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":307,\"system\":3654}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"21d182a2-5a08-41bb-b601-5d2b4aba4ecd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"327d0e20-483e-95af-f4e4-7b065606e1aa\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:48.539Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json new file mode 100644 index 0000000000000..27aea27bebcd7 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json @@ -0,0 +1,2592 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "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" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json new file mode 100644 index 0000000000000..dbcd2604aed15 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json @@ -0,0 +1,527 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:38.636Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "MngOEjmt4OWFSCvya8AWgDF9p0nPqiCZLpNrqntWdjcGl+vPcbVs+un3ilKC3GQKtKP6KLtMziLR/60teHpAJ0Ls1f+mbCP1PjjAfFL1ZBnGHsvkR099iRJ9q4rCxzmZtifGZQ/s2+t99DRUe8GkJhIj3VR1uN/EKPXmXDWZo0f+bTUDT7vGZVY=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:58.866Z", + "default_api_key_id": "ieriwHQBXUUrssdI83FW", + "last_checkin": "2020-09-24T16:29:07.071Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "E-riwHQBXUUrssdIvHEw", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:1cfe:7be7:f700:8810/64", + "fdbb:cb5c:fb4:68:257d:7303:389d:f335/64", + "fdbb:cb5c:fb4:68:7470:3bec:14b5:2caf/64", + "fdbb:cb5c:fb4:68:9c5f:eab7:8345:f711/64", + "fdbb:cb5c:fb4:68:dc96:8bac:67e0:99dd/64", + "fdbb:cb5c:fb4:68:60c6:73b6:1540:602/64", + "fdbb:cb5c:fb4:68:144:6a1b:1aae:a57d/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:ac0ab6c1-2317-478c-93d9-c514d845302d", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.974Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "UnSz8pAKTP/0DENATzn13Yo0jcdbWq70IiBJcDY+DF5M063+El91o+448KVaMHj3rCSrULfJboBf1Ao80UKU5WKz4CYJ3ZVjHm39/f8rXMZSah5lQAkl9Ak2v5wUCFd4KTEwUUEmnUKKSQGC53cBhnvoyPdzfNjt1ml96lZFZbxXt/VyU3u8vhQ=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:30.880Z", + "default_api_key_id": "Va7iwHQBEiA0_DvkcN-4", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sq7iwHQBEiA0_DvkT98X", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-1", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:6c9d:def9:bb8a:6695/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-1", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "ac0ab6c1-2317-478c-93d9-c514d845302d" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:c216aea0-58ba-40a3-b6fe-afa2f5457835", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.142Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "tSCsI7HPfRVIcw3Yx3xUAl20Hfe9AdEIs/4IBBH9ZO1gxnMMjRkVb/hxhfcdg6dkW+RIc6Pc9Jz7rUvybq8fY0r/pTKGXTFr46dC2+E9jfb7rs/PmYhG2V0/Ei2p+ZQypAIp8mtknSHkX+l74N7niVXKreneLrt99e4ZWIyeuwNwr0HcGjoMEqM=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:59.088Z", + "default_api_key_id": "SK7jwHQBEiA0_DvkNuIq", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "OeriwHQBXUUrssdIvXGr", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.6.176/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:3363/64", + "fe80::250:56ff:feb1:3363/64" + ], + "hostname": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "mac": [ + "00:50:56:b1:33:63" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-118-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "c216aea0-58ba-40a3-b6fe-afa2f5457835" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.072Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "sdv6996k+S1BiZ/12K3Wi6rb8Lsoh/+shwzKNqujwcmhdbeQ92ygLoO+tudJaJOnL129WT+hhanEf6OgH5PpQBezc03hl9v2AI+BlU+hssfce5OfgFRGLYg8S+ryNHwFhK6EJeN1aivoie+YholNpcpt2l/t+lQpevMI4QYGaMfUzofuivs5JM4=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:54.037Z", + "default_api_key_id": "lq7iwHQBEiA0_Dvk8-Fb", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "geriwHQBXUUrssdIqXB2", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::107d:2365:5a7c:8da/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.195/22", + "fdbb:cb5c:fb4:68:d4ef:63a5:8ffc:f933/64", + "fdbb:cb5c:fb4:68:b082:8681:cf85:27d0/64", + "fdbb:cb5c:fb4:68:7c3d:13f3:5339:be7b/64", + "fdbb:cb5c:fb4:68:19a4:2a63:cc88:6e59/64", + "fdbb:cb5c:fb4:68:494a:3867:57b8:4027/64", + "fdbb:cb5c:fb4:68:1c88:41e:6ce1:4be7/64", + "fdbb:cb5c:fb4:68:114:b84:8faf:b12b/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "mac": [ + "00:50:56:b1:e4:06" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:296c368b-35d3-4241-905f-75a24f52ec13", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.072Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "PEF8+bfiv21Yn5yj8I2/vIaQWMrUQK4PeBBwXsrvmVTsbuFejXM0IQtYVKXShBJAoY9CUEKPCRR4rIIdXWZc51i1ZneLoFw+yBw8BsSwhHfbQXvAVQowH7UqKHp0CiA5J9uGSgmw3Q55a4dv4IHih+sBKji7Qf2durs5gCWUJExrRCpMiU3OHSg=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:56.620Z", + "default_api_key_id": "xOrjwHQBXUUrssdIDnHH", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "S67iwHQBEiA0_Dvks-Cm", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "296c368b-35d3-4241-905f-75a24f52ec13" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:2d187287-658a-4cb6-84d8-d66d1b9a6299", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.072Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "fpQcy/QWSbafzl6avELe9goTtyojPwQX3id1pe+BBqDarSCB3k5QwWLQP2SXEl2rwJdywUrBz3gMySKi80RYWJFUoWHiipfaE/jXJRqJxZZvhBe8fdSP7YPkdIdLQl/3ktIWqAzjjS1CErqMb5K4HTZIp5FswDQB40SbDkQKPECl9o8pBhLjH/A=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:28.949Z", + "default_api_key_id": "aeriwHQBXUUrssdIdXAX", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sa7iwHQBEiA0_DvkR99k", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-2", + "ip": [ + "fdbb:cb5c:fb4:68:dda8:b7a:3e20:9ca0/64", + "fdbb:cb5c:fb4:68:e922:9626:5193:ef68/128", + "fe80::dda8:b7a:3e20:9ca0/64", + "10.0.6.96/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-2", + "mac": [ + "00:50:56:b1:26:07" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "2d187287-658a-4cb6-84d8-d66d1b9a6299" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:81e5aa90-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:40.138333-04:00", + "subtype": "RUNNING", + "agent_id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "message": "Application: endpoint-security--7.9.2[b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:40.134985503Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[55,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":10.21008368,\"mean\":1.91476589372881}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":71143424,\"mean\":53719456}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":3.08880308880309},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":302,\"system\":1901758}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"49f4e779-287a-4fa8-80e6-247b54c554f1\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"7d59b1a5-afa1-6531-07ea-691602558230\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:57.177Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:8e652110-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:43.499165-04:00", + "subtype": "RUNNING", + "agent_id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "message": "Application: endpoint-security--7.9.2[5460518c-10c7-4c25-b2ec-3f63eafb7d47]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:43.495361445Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[44,4,0,2,2,4,1,2,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":25.33265565,\"mean\":6.21698140807909}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58376192,\"mean\":46094231}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.32258064516129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.323624595469256},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.664451827242525},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":9.55882352941176},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":308,\"system\":3807934}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"b364a499-8e64-4d91-9770-6911c5d6964b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:18:18.145Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:82b7eeb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:13.3157023-04:00", + "subtype": "RUNNING", + "agent_id": "ac0ab6c1-2317-478c-93d9-c514d845302d", + "message": "Application: endpoint-security--7.9.2[ac0ab6c1-2317-478c-93d9-c514d845302d]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:13.13714300Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[53,1,0,1,0,0,2,1,0,3,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":49.0526570938275,\"mean\":4.53577832211642}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":285802496,\"mean\":95647240}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":3.18021201413428},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":306,\"system\":3625}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6474b1bd-96bc-4bde-a770-0e6a7a5bf8c4\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:58.555Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:80a6c1f0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:54.930717796-04:00", + "subtype": "RUNNING", + "agent_id": "c216aea0-58ba-40a3-b6fe-afa2f5457835", + "message": "Application: endpoint-security--7.9.2[c216aea0-58ba-40a3-b6fe-afa2f5457835]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:54.929290223Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":3,\"mean\":3.49666666666667}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49778688,\"mean\":31986824}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":3863}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"a15f0431-6835-41c4-a7ee-21a70d41cf5b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"20ccfdfa-323f-e33e-f2ef-3528edb1afea\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:55.087Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7bdc8fb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:45.675453579-04:00", + "subtype": "RUNNING", + "agent_id": "296c368b-35d3-4241-905f-75a24f52ec13", + "message": "Application: endpoint-security--7.9.2[296c368b-35d3-4241-905f-75a24f52ec13]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:45.674010613Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":2.8,\"mean\":3.17}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49278976,\"mean\":31884356}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":5000305}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6f0cb2fc-3e46-4435-8892-d9f7e71b23fd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:47.051Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7cbf9cb1-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:15.400204-04:00", + "subtype": "RUNNING", + "agent_id": "2d187287-658a-4cb6-84d8-d66d1b9a6299", + "message": "Application: endpoint-security--7.9.2[2d187287-658a-4cb6-84d8-d66d1b9a6299]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:15.96990100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[46,2,2,2,4,2,0,0,0,2,0,0,0,0,1,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":48.3070275492921,\"mean\":6.43134047264261}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":228757504,\"mean\":94594836}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":1.9672131147541},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":2.62295081967213},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.655737704918033},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":2.11267605633803},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":307,\"system\":3654}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"21d182a2-5a08-41bb-b601-5d2b4aba4ecd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"327d0e20-483e-95af-f4e4-7b065606e1aa\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:48.539Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json new file mode 100644 index 0000000000000..27aea27bebcd7 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json @@ -0,0 +1,2592 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "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" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts index 533ce49b14325..eb0cf4a34b2cc 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts @@ -157,5 +157,123 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + describe('when agents are connected with cloned endpoints', () => { + describe('with endpoint integration installed with malware enabled', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'cloned_endpoint_installed', + 'cloned_endpoint_test', + 0 + ); + await esArchiver.load('endpoint/telemetry/cloned_endpoint_test'); + await telemetryTestResources.deleteArchive('cloned_endpoint_test'); + }); + it('reports all endpoints and policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 6, + active_within_last_24_hours: 6, + os: [ + { + full_name: 'Ubuntu bionic(18.04.1 LTS (Bionic Beaver))', + platform: 'ubuntu', + version: '18.04.1 LTS (Bionic Beaver)', + count: 2, + }, + { + full_name: 'Mac OS X(10.14.1)', + platform: 'darwin', + version: '10.14.1', + count: 2, + }, + { + full_name: 'Windows 10 Pro(10.0)', + platform: 'windows', + version: '10.0', + count: 2, + }, + ], + policies: { + malware: { + active: 4, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + describe('with endpoint integration installed on half the endpoints with malware enabled', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'cloned_endpoint_different_states', + 'cloned_endpoint_test', + 0 + ); + await esArchiver.load('endpoint/telemetry/cloned_endpoint_test'); + await telemetryTestResources.deleteArchive('cloned_endpoint_test'); + }); + it('reports all endpoints and policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 3, + active_within_last_24_hours: 3, + os: [ + { + full_name: 'Mac OS X(10.14.1)', + platform: 'darwin', + version: '10.14.1', + count: 1, + }, + { + full_name: 'Ubuntu bionic(18.04.1 LTS (Bionic Beaver))', + platform: 'ubuntu', + version: '18.04.1 LTS (Bionic Beaver)', + count: 1, + }, + { + full_name: 'Windows 10 Pro(10.0)', + platform: 'windows', + version: '10.0', + count: 1, + }, + ], + policies: { + malware: { + active: 2, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + describe('with endpoint integration uninstalled', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'cloned_endpoint_uninstalled', + 'cloned_endpoint_test', + 0 + ); + await esArchiver.load('endpoint/telemetry/cloned_endpoint_test'); + await telemetryTestResources.deleteArchive('cloned_endpoint_test'); + }); + it('reports all endpoints and policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 0, + active_within_last_24_hours: 0, + os: [], + policies: { + malware: { + active: 0, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + }); }); } From 5da9650f21337591cf4e62b8a218425b47abb6b2 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 1 Dec 2020 12:39:42 -0600 Subject: [PATCH 13/37] Changes UI links for drilldowns (#83971) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/core/public/doc_links/doc_links_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6af14734444d1..15df6b34e22ff 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -39,9 +39,9 @@ export class DocLinksService { dashboard: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, - drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldown`, + drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldowns`, urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html`, - urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#variables`, + urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#url-template-variables`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, From 78faa107aabc2b0ce6eed88f19633617244e19a1 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 1 Dec 2020 12:47:57 -0600 Subject: [PATCH 14/37] Add routes for use in Sources Schema (#84579) --- .../routes/workplace_search/sources.test.ts | 272 ++++++++++++++++++ .../server/routes/workplace_search/sources.ts | 172 +++++++++++ 2 files changed, 444 insertions(+) diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index 62f4dceeac363..d97a587e57ff2 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -19,6 +19,9 @@ import { registerAccountPrepareSourcesRoute, registerAccountSourceSearchableRoute, registerAccountSourceDisplaySettingsConfig, + registerAccountSourceSchemasRoute, + registerAccountSourceReindexJobRoute, + registerAccountSourceReindexJobStatusRoute, registerOrgSourcesRoute, registerOrgSourcesStatusRoute, registerOrgSourceRoute, @@ -31,6 +34,9 @@ import { registerOrgPrepareSourcesRoute, registerOrgSourceSearchableRoute, registerOrgSourceDisplaySettingsConfig, + registerOrgSourceSchemasRoute, + registerOrgSourceReindexJobRoute, + registerOrgSourceReindexJobStatusRoute, registerOrgSourceOauthConfigurationsRoute, registerOrgSourceOauthConfigurationRoute, } from './sources'; @@ -523,6 +529,139 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/account/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{id}/schemas', + payload: 'params', + }); + + registerAccountSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + id: '123', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/schemas', + }); + }); + }); + + describe('POST /api/workplace_search/account/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/account/sources/{id}/schemas', + payload: 'body', + }); + + registerAccountSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + const mockRequest = { + params: { id: '123' }, + body: {}, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/schemas', + body: mockRequest.body, + }); + }); + }); + + describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + payload: 'params', + }); + + registerAccountSourceReindexJobRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/reindex_job/345', + }); + }); + }); + + describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + payload: 'params', + }); + + registerAccountSourceReindexJobStatusRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/reindex_job/345/status', + }); + }); + }); + describe('GET /api/workplace_search/org/sources', () => { let mockRouter: MockRouter; @@ -1000,6 +1139,139 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/org/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{id}/schemas', + payload: 'params', + }); + + registerOrgSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + id: '123', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/schemas', + }); + }); + }); + + describe('POST /api/workplace_search/org/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/org/sources/{id}/schemas', + payload: 'body', + }); + + registerOrgSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + const mockRequest = { + params: { id: '123' }, + body: {}, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/schemas', + body: mockRequest.body, + }); + }); + }); + + describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + payload: 'params', + }); + + registerOrgSourceReindexJobRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/reindex_job/345', + }); + }); + }); + + describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + payload: 'params', + }); + + registerOrgSourceReindexJobStatusRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/reindex_job/345/status', + }); + }); + }); + describe('GET /api/workplace_search/org/settings/connectors', () => { let mockRouter: MockRouter; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index d43a4252c7e1f..9beac109be510 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -339,6 +339,89 @@ export function registerAccountSourceDisplaySettingsConfig({ ); } +export function registerAccountSourceSchemasRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{id}/schemas', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.id}/schemas`, + })(context, request, response); + } + ); + + router.post( + { + path: '/api/workplace_search/account/sources/{id}/schemas', + validate: { + body: schema.object({}), + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.id}/schemas`, + body: request.body, + })(context, request, response); + } + ); +} + +export function registerAccountSourceReindexJobRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, + })(context, request, response); + } + ); +} + +export function registerAccountSourceReindexJobStatusRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, + })(context, request, response); + } + ); +} + export function registerOrgSourcesRoute({ router, enterpriseSearchRequestHandler, @@ -638,6 +721,89 @@ export function registerOrgSourceDisplaySettingsConfig({ ); } +export function registerOrgSourceSchemasRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{id}/schemas', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.id}/schemas`, + })(context, request, response); + } + ); + + router.post( + { + path: '/api/workplace_search/org/sources/{id}/schemas', + validate: { + body: schema.object({}), + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.id}/schemas`, + body: request.body, + })(context, request, response); + } + ); +} + +export function registerOrgSourceReindexJobRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, + })(context, request, response); + } + ); +} + +export function registerOrgSourceReindexJobStatusRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, + })(context, request, response); + } + ); +} + export function registerOrgSourceOauthConfigurationsRoute({ router, enterpriseSearchRequestHandler, @@ -741,6 +907,9 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerAccountPrepareSourcesRoute(dependencies); registerAccountSourceSearchableRoute(dependencies); registerAccountSourceDisplaySettingsConfig(dependencies); + registerAccountSourceSchemasRoute(dependencies); + registerAccountSourceReindexJobRoute(dependencies); + registerAccountSourceReindexJobStatusRoute(dependencies); registerOrgSourcesRoute(dependencies); registerOrgSourcesStatusRoute(dependencies); registerOrgSourceRoute(dependencies); @@ -753,6 +922,9 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerOrgPrepareSourcesRoute(dependencies); registerOrgSourceSearchableRoute(dependencies); registerOrgSourceDisplaySettingsConfig(dependencies); + registerOrgSourceSchemasRoute(dependencies); + registerOrgSourceReindexJobRoute(dependencies); + registerOrgSourceReindexJobStatusRoute(dependencies); registerOrgSourceOauthConfigurationsRoute(dependencies); registerOrgSourceOauthConfigurationRoute(dependencies); }; From 4b1ba0f3b261491f028c9b79d9163ff7c4000070 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 1 Dec 2020 12:10:14 -0700 Subject: [PATCH 15/37] [maps] remove fields from index-pattern test artifacts (#84379) * [maps] remove fields from index-pattern test artifacts * only remove non-scripted fields * fix data.json parse issues - darn trailing comma Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../es_archives/maps/kibana/data.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 71b4a85d63f08..5d6a355939d30 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -20,7 +20,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields" : "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"xss\"}}},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "fields" : "[{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, @@ -36,7 +36,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"geometry\",\"type\":\"geo_shape\",\"esTypes\":[\"geo_shape\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"prop1\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields" : "[]", "title": "geo_shapes*" }, "type": "index-pattern" @@ -51,7 +51,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"prop1\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"shape_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields" : "[]", "title": "meta_for_geo_shapes*" }, "type": "index-pattern" @@ -66,7 +66,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"location\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields" : "[]", "title": "antimeridian_points" }, "type": "index-pattern" @@ -81,7 +81,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"location\",\"type\":\"geo_shape\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false}]", + "fields" : "[]", "title": "antimeridian_shapes" }, "type": "index-pattern" @@ -96,8 +96,8 @@ "index": ".kibana", "source": { "index-pattern" : { - "title" : "flights", - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"altitude\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"heading\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + "fields" : "[]", + "title" : "flights" }, "type" : "index-pattern", "references" : [ ], @@ -116,8 +116,8 @@ "index": ".kibana", "source": { "index-pattern" : { - "title" : "connections", - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"destination\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + "fields" : "[]", + "title" : "connections" }, "type" : "index-pattern", "references" : [ ], From ae9df6902863d78c422f93d260a216e2da8d0a9c Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 1 Dec 2020 14:24:02 -0600 Subject: [PATCH 16/37] [Enterprise Search] Migrate shared Indexing Status component (#84571) * Add react-motion package This is needed to animate the loading progress bar in the Enterprise Search schema views * Add shared interface * Migrate IndexingStatusContent component This is a straight copy/paste with only linting changes and tests added * Migrate IndexingStatusErrors component This is a copy/paste with linting changes and tests added. Also changed out the Link component to our EuiLinkTo component for internal routing * Migrate IndexingStatus component This is a straight copy/paste with only linting changes and tests added * Migrate IndexingStatusFetcher component This is a copy/paste with some modifications. The http/axios code has been removed in favor of the HTTPLogic in Kibana. This is a WIP that I am merging to master until I can get it working in the UI. Without either Schema component in the UIs for App Search or Workplace Search this is only a POC. Will not backport until this is verified working and have written tests. * Add i18n * Revert "Add react-motion package" This reverts commit 92db929d2ab45bba36be7fd53acee495e4969981. * Remove motion dependency * Update copy Co-authored-by: Constance * Refactor per code review - Remove stui classes - Inline status Co-authored-by: Constance --- .../shared/indexing_status/constants.ts | 28 ++++++++ .../shared/indexing_status/index.ts | 7 ++ .../indexing_status/indexing_status.test.tsx | 51 +++++++++++++++ .../indexing_status/indexing_status.tsx | 44 +++++++++++++ .../indexing_status_content.test.tsx | 21 ++++++ .../indexing_status_content.tsx | 27 ++++++++ .../indexing_status_errors.test.tsx | 25 ++++++++ .../indexing_status_errors.tsx | 31 +++++++++ .../indexing_status_fetcher.tsx | 64 +++++++++++++++++++ .../public/applications/shared/types.ts | 11 ++++ 10 files changed, 309 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/types.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts new file mode 100644 index 0000000000000..b2b76d5b987b9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts @@ -0,0 +1,28 @@ +/* + * 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'; + +export const INDEXING_STATUS_PROGRESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.progress.title', + { + defaultMessage: 'Indexing progress', + } +); + +export const INDEXING_STATUS_HAS_ERRORS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.hasErrors.title', + { + defaultMessage: 'Several documents have field conversion errors.', + } +); + +export const INDEXING_STATUS_HAS_ERRORS_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.hasErrors.button', + { + defaultMessage: 'View errors', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts new file mode 100644 index 0000000000000..4a97f11e8f0ee --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/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 { IndexingStatus } from './indexing_status'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx new file mode 100644 index 0000000000000..097c3bbc8e9ff --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { EuiPanel } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; +import { IndexingStatusErrors } from './indexing_status_errors'; +import { IndexingStatusFetcher } from './indexing_status_fetcher'; +import { IndexingStatus } from './indexing_status'; + +describe('IndexingStatus', () => { + const getItemDetailPath = jest.fn(); + const getStatusPath = jest.fn(); + const onComplete = jest.fn(); + const setGlobalIndexingStatus = jest.fn(); + + const props = { + percentageComplete: 50, + numDocumentsWithErrors: 1, + activeReindexJobId: 12, + viewLinkPath: '/path', + itemId: '1', + getItemDetailPath, + getStatusPath, + onComplete, + setGlobalIndexingStatus, + }; + + it('renders', () => { + const wrapper = shallow(); + const fetcher = wrapper.find(IndexingStatusFetcher).prop('children')( + props.percentageComplete, + props.numDocumentsWithErrors + ); + + expect(shallow(fetcher).find(EuiPanel)).toHaveLength(1); + expect(shallow(fetcher).find(IndexingStatusContent)).toHaveLength(1); + }); + + it('renders errors', () => { + const wrapper = shallow(); + const fetcher = wrapper.find(IndexingStatusFetcher).prop('children')(100, 1); + expect(shallow(fetcher).find(IndexingStatusErrors)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx new file mode 100644 index 0000000000000..beec0babea590 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx @@ -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 React from 'react'; + +import { EuiPanel, EuiSpacer } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; +import { IndexingStatusErrors } from './indexing_status_errors'; +import { IndexingStatusFetcher } from './indexing_status_fetcher'; + +import { IIndexingStatus } from '../types'; + +export interface IIndexingStatusProps extends IIndexingStatus { + viewLinkPath: string; + itemId: string; + getItemDetailPath?(itemId: string): string; + getStatusPath(itemId: string, activeReindexJobId: number): string; + onComplete(numDocumentsWithErrors: number): void; + setGlobalIndexingStatus?(activeReindexJob: IIndexingStatus): void; +} + +export const IndexingStatus: React.FC = (props) => ( + + {(percentageComplete, numDocumentsWithErrors) => ( +
+ {percentageComplete < 100 && ( + + + + )} + {percentageComplete === 100 && numDocumentsWithErrors > 0 && ( + <> + + + + )} +
+ )} +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx new file mode 100644 index 0000000000000..9fe0e890e6943 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx @@ -0,0 +1,21 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { EuiProgress, EuiTitle } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; + +describe('IndexingStatusContent', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTitle)).toHaveLength(1); + expect(wrapper.find(EuiProgress)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx new file mode 100644 index 0000000000000..a0c67388621a8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx @@ -0,0 +1,27 @@ +/* + * 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 { EuiProgress, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { INDEXING_STATUS_PROGRESS_TITLE } from './constants'; + +interface IIndexingStatusContentProps { + percentageComplete: number; +} + +export const IndexingStatusContent: React.FC = ({ + percentageComplete, +}) => ( +
+ +

{INDEXING_STATUS_PROGRESS_TITLE}

+
+ + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx new file mode 100644 index 0000000000000..fc706aee659a5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { EuiCallOut, EuiButton } from '@elastic/eui'; + +import { EuiLinkTo } from '../react_router_helpers'; + +import { IndexingStatusErrors } from './indexing_status_errors'; + +describe('IndexingStatusErrors', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiButton)).toHaveLength(1); + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo).prop('to')).toEqual('/path'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx new file mode 100644 index 0000000000000..a928400b2338c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx @@ -0,0 +1,31 @@ +/* + * 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 { EuiButton, EuiCallOut } from '@elastic/eui'; + +import { EuiLinkTo } from '../react_router_helpers'; + +import { INDEXING_STATUS_HAS_ERRORS_TITLE, INDEXING_STATUS_HAS_ERRORS_BUTTON } from './constants'; + +interface IIndexingStatusErrorsProps { + viewLinkPath: string; +} + +export const IndexingStatusErrors: React.FC = ({ viewLinkPath }) => ( + +

{INDEXING_STATUS_HAS_ERRORS_TITLE}

+ + {INDEXING_STATUS_HAS_ERRORS_BUTTON} + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx new file mode 100644 index 0000000000000..cb7c82f91ed61 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx @@ -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. + */ + +import React, { useEffect, useState, useRef } from 'react'; + +import { HttpLogic } from '../http'; +import { flashAPIErrors } from '../flash_messages'; + +interface IIndexingStatusFetcherProps { + activeReindexJobId: number; + itemId: string; + percentageComplete: number; + numDocumentsWithErrors: number; + onComplete?(numDocumentsWithErrors: number): void; + getStatusPath(itemId: string, activeReindexJobId: number): string; + children(percentageComplete: number, numDocumentsWithErrors: number): JSX.Element; +} + +export const IndexingStatusFetcher: React.FC = ({ + activeReindexJobId, + children, + getStatusPath, + itemId, + numDocumentsWithErrors, + onComplete, + percentageComplete = 0, +}) => { + const [indexingStatus, setIndexingStatus] = useState({ + numDocumentsWithErrors, + percentageComplete, + }); + const pollingInterval = useRef(); + + useEffect(() => { + pollingInterval.current = window.setInterval(async () => { + try { + const response = await HttpLogic.values.http.get(getStatusPath(itemId, activeReindexJobId)); + if (response.percentageComplete >= 100) { + clearInterval(pollingInterval.current); + } + setIndexingStatus({ + percentageComplete: response.percentageComplete, + numDocumentsWithErrors: response.numDocumentsWithErrors, + }); + if (response.percentageComplete >= 100 && onComplete) { + onComplete(response.numDocumentsWithErrors); + } + } catch (e) { + flashAPIErrors(e); + } + }, 3000); + + return () => { + if (pollingInterval.current) { + clearInterval(pollingInterval.current); + } + }; + }, []); + + return children(indexingStatus.percentageComplete, indexingStatus.numDocumentsWithErrors); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts new file mode 100644 index 0000000000000..3866d1a7199e4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.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 interface IIndexingStatus { + percentageComplete: number; + numDocumentsWithErrors: number; + activeReindexJobId: number; +} From 4ae84a00a549ed834d618d19e00008666791f107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Tue, 1 Dec 2020 21:37:56 +0100 Subject: [PATCH 17/37] [CCR] Fix row actions in follower index and auto-follow pattern tables (#84433) * Fixed auto follow actions * Created a provider for all follower index table actions to fix modal auto-closing * Moved i18n texts into a const to avoid duplication * Removed unnecessary imports and added index.js file for follower_index_actions_provider imports * Fixed wrong imports deletion * Fixed wrong imports deletion Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../follower_index_actions_provider.js | 34 ++++ .../follower_index_pause_provider.js | 6 +- .../follower_index_resume_provider.js | 8 +- .../follower_index_unfollow_provider.js | 4 +- .../follower_index_actions_providers/index.js | 10 + .../public/app/components/index.js | 7 +- .../auto_follow_pattern_table.js | 178 ++++++++--------- .../follower_indices_table.js | 185 ++++++++---------- 8 files changed, 217 insertions(+), 215 deletions(-) create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js rename x-pack/plugins/cross_cluster_replication/public/app/components/{ => follower_index_actions_providers}/follower_index_pause_provider.js (95%) rename x-pack/plugins/cross_cluster_replication/public/app/components/{ => follower_index_actions_providers}/follower_index_resume_provider.js (95%) rename x-pack/plugins/cross_cluster_replication/public/app/components/{ => follower_index_actions_providers}/follower_index_unfollow_provider.js (97%) create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js new file mode 100644 index 0000000000000..df3017ebf92a4 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js @@ -0,0 +1,34 @@ +/* + * 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 { FollowerIndexPauseProvider } from './follower_index_pause_provider'; +import { FollowerIndexResumeProvider } from './follower_index_resume_provider'; +import { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; + +export const FollowerIndexActionsProvider = (props) => { + return ( + + {(pauseFollowerIndex) => ( + + {(resumeFollowerIndex) => ( + + {(unfollowLeaderIndex) => { + const { children } = props; + return children(() => ({ + pauseFollowerIndex, + resumeFollowerIndex, + unfollowLeaderIndex, + })); + }} + + )} + + )} + + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js similarity index 95% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js index 9c1e8255d069c..7d1168d831631 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js @@ -11,9 +11,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { pauseFollowerIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; -import { areAllSettingsDefault } from '../services/follower_index_default_settings'; +import { pauseFollowerIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; +import { areAllSettingsDefault } from '../../services/follower_index_default_settings'; class FollowerIndexPauseProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js similarity index 95% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js index 101e3df6bf710..86f8c0447e734 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js @@ -10,10 +10,10 @@ import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; -import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; -import { routing } from '../services/routing'; -import { resumeFollowerIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { routing } from '../../services/routing'; +import { resumeFollowerIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; class FollowerIndexResumeProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js similarity index 97% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js index 68b6b970ad90b..f9644aa20c2c2 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js @@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { unfollowLeaderIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; +import { unfollowLeaderIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; class FollowerIndexUnfollowProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js new file mode 100644 index 0000000000000..fe1a7d82a56a1 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js @@ -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. + */ + +export { FollowerIndexActionsProvider } from './follower_index_actions_provider'; +export { FollowerIndexPauseProvider } from './follower_index_pause_provider'; +export { FollowerIndexResumeProvider } from './follower_index_resume_provider'; +export { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js index 6d971bff03981..55609fa85fb11 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js @@ -12,9 +12,10 @@ export { AutoFollowPatternForm } from './auto_follow_pattern_form'; export { AutoFollowPatternDeleteProvider } from './auto_follow_pattern_delete_provider'; export { AutoFollowPatternPageTitle } from './auto_follow_pattern_page_title'; export { AutoFollowPatternIndicesPreview } from './auto_follow_pattern_indices_preview'; -export { FollowerIndexPauseProvider } from './follower_index_pause_provider'; -export { FollowerIndexResumeProvider } from './follower_index_resume_provider'; -export { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; +export { FollowerIndexPauseProvider } from './follower_index_actions_providers'; +export { FollowerIndexResumeProvider } from './follower_index_actions_providers'; +export { FollowerIndexUnfollowProvider } from './follower_index_actions_providers'; +export { FollowerIndexActionsProvider } from './follower_index_actions_providers'; export { FollowerIndexForm } from './follower_index_form'; export { FollowerIndexPageTitle } from './follower_index_page_title'; export { FormEntryRow } from './form_entry_row'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 2309dece3f92b..dd5fe6f212808 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { @@ -13,7 +13,6 @@ import { EuiLoadingKibana, EuiOverlayMask, EuiHealth, - EuiIcon, } from '@elastic/eui'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK } from '../../../../../constants'; import { @@ -23,6 +22,33 @@ import { import { routing } from '../../../../../services/routing'; import { trackUiMetric } from '../../../../../services/track_ui_metric'; +const actionI18nTexts = { + pause: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', + { + defaultMessage: 'Pause replication', + } + ), + resume: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', + { + defaultMessage: 'Resume replication', + } + ), + edit: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', + { + defaultMessage: 'Edit auto-follow pattern', + } + ), + delete: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', + { + defaultMessage: 'Delete auto-follow pattern', + } + ), +}; + const getFilteredPatterns = (autoFollowPatterns, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); @@ -93,7 +119,7 @@ export class AutoFollowPatternTable extends PureComponent { }); }; - getTableColumns() { + getTableColumns(deleteAutoFollowPattern) { const { selectAutoFollowPattern } = this.props; return [ @@ -200,88 +226,34 @@ export class AutoFollowPatternTable extends PureComponent { ), actions: [ { - render: ({ name, active }) => { - const label = active - ? i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', - { - defaultMessage: 'Pause replication', - } - ) - : i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', - { - defaultMessage: 'Resume replication', - } - ); - - return ( - { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (active) { - this.props.pauseAutoFollowPattern(name); - } else { - this.props.resumeAutoFollowPattern(name); - } - }} - data-test-subj={active ? 'contextMenuPauseButton' : 'contextMenuResumeButton'} - > - - {label} - - ); - }, + name: actionI18nTexts.pause, + description: actionI18nTexts.pause, + icon: 'pause', + onClick: (item) => this.props.pauseAutoFollowPattern(item.name), + available: (item) => item.active, + 'data-test-subj': 'contextMenuPauseButton', }, { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', - { - defaultMessage: 'Edit auto-follow pattern', - } - ); - - return ( - routing.navigate(routing.getAutoFollowPatternPath(name))} - data-test-subj="contextMenuEditButton" - > - - {label} - - ); - }, + name: actionI18nTexts.resume, + description: actionI18nTexts.resume, + icon: 'play', + onClick: (item) => this.props.resumeAutoFollowPattern(item.name), + available: (item) => !item.active, + 'data-test-subj': 'contextMenuResumeButton', }, { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', - { - defaultMessage: 'Delete auto-follow pattern', - } - ); - - return ( - - {(deleteAutoFollowPattern) => ( - deleteAutoFollowPattern(name)} - data-test-subj="contextMenuDeleteButton" - > - - {label} - - )} - - ); - }, + name: actionI18nTexts.edit, + description: actionI18nTexts.edit, + icon: 'pencil', + onClick: (item) => routing.navigate(routing.getAutoFollowPatternPath(item.name)), + 'data-test-subj': 'contextMenuEditButton', + }, + { + name: actionI18nTexts.delete, + description: actionI18nTexts.delete, + icon: 'trash', + onClick: (item) => deleteAutoFollowPattern(item.name), + 'data-test-subj': 'contextMenuDeleteButton', }, ], width: '100px', @@ -339,26 +311,30 @@ export class AutoFollowPatternTable extends PureComponent { }; return ( - - ({ - 'data-test-subj': 'row', - })} - cellProps={(item, column) => ({ - 'data-test-subj': `cell_${column.field}`, - })} - data-test-subj="autoFollowPatternListTable" - /> - {this.renderLoading()} - + + {(deleteAutoFollowPattern) => ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={(item, column) => ({ + 'data-test-subj': `cell_${column.field}`, + })} + data-test-subj="autoFollowPatternListTable" + /> + {this.renderLoading()} + + )} + ); } } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 0c57b3f7330cf..2ea73e272b24e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiIcon, EuiHealth, EuiInMemoryTable, EuiLink, @@ -17,15 +16,38 @@ import { EuiOverlayMask, } from '@elastic/eui'; import { API_STATUS, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK } from '../../../../../constants'; -import { - FollowerIndexPauseProvider, - FollowerIndexResumeProvider, - FollowerIndexUnfollowProvider, -} from '../../../../../components'; +import { FollowerIndexActionsProvider } from '../../../../../components'; import { routing } from '../../../../../services/routing'; import { trackUiMetric } from '../../../../../services/track_ui_metric'; import { ContextMenu } from '../context_menu'; +const actionI18nTexts = { + pause: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription', + { + defaultMessage: 'Pause replication', + } + ), + resume: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription', + { + defaultMessage: 'Resume replication', + } + ), + edit: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', + { + defaultMessage: 'Edit follower index', + } + ), + unfollow: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription', + { + defaultMessage: 'Unfollow leader index', + } + ), +}; + const getFilteredIndices = (followerIndices, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); @@ -101,91 +123,43 @@ export class FollowerIndicesTable extends PureComponent { routing.navigate(uri); }; - getTableColumns() { + getTableColumns(actionHandlers) { const { selectFollowerIndex } = this.props; const actions = [ - /* Pause or resume follower index */ + /* Pause follower index */ { - render: (followerIndex) => { - const { name, isPaused } = followerIndex; - const label = isPaused - ? i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription', - { - defaultMessage: 'Resume replication', - } - ) - : i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription', - { - defaultMessage: 'Pause replication', - } - ); - - return isPaused ? ( - - {(resumeFollowerIndex) => ( - resumeFollowerIndex(name)} data-test-subj="resumeButton"> - - {label} - - )} - - ) : ( - - {(pauseFollowerIndex) => ( - pauseFollowerIndex(followerIndex)} - data-test-subj="pauseButton" - > - - {label} - - )} - - ); - }, + name: actionI18nTexts.pause, + description: actionI18nTexts.pause, + icon: 'pause', + onClick: (item) => actionHandlers.pauseFollowerIndex(item), + available: (item) => !item.isPaused, + 'data-test-subj': 'pauseButton', + }, + /* Resume follower index */ + { + name: actionI18nTexts.resume, + description: actionI18nTexts.resume, + icon: 'play', + onClick: (item) => actionHandlers.resumeFollowerIndex(item.name), + available: (item) => item.isPaused, + 'data-test-subj': 'resumeButton', }, /* Edit follower index */ { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', - { - defaultMessage: 'Edit follower index', - } - ); - - return ( - this.editFollowerIndex(name)} data-test-subj="editButton"> - - {label} - - ); - }, + name: actionI18nTexts.edit, + description: actionI18nTexts.edit, + onClick: (item) => this.editFollowerIndex(item.name), + icon: 'pencil', + 'data-test-subj': 'editButton', }, /* Unfollow leader index */ { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription', - { - defaultMessage: 'Unfollow leader index', - } - ); - - return ( - - {(unfollowLeaderIndex) => ( - unfollowLeaderIndex(name)} data-test-subj="unfollowButton"> - - {label} - - )} - - ); - }, + name: actionI18nTexts.unfollow, + description: actionI18nTexts.unfollow, + onClick: (item) => actionHandlers.unfollowLeaderIndex(item.name), + icon: 'indexFlush', + 'data-test-subj': 'unfollowButton', }, ]; @@ -321,26 +295,33 @@ export class FollowerIndicesTable extends PureComponent { }; return ( - - ({ - 'data-test-subj': 'row', - })} - cellProps={(item, column) => ({ - 'data-test-subj': `cell-${column.field}`, - })} - data-test-subj="followerIndexListTable" - /> - {this.renderLoading()} - + + {(getActionHandlers) => { + const actionHandlers = getActionHandlers(); + return ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={(item, column) => ({ + 'data-test-subj': `cell-${column.field}`, + })} + data-test-subj="followerIndexListTable" + /> + {this.renderLoading()} + + ); + }} + ); } } From 49f0ca082756673d9613bdf576023e7a00a305ed Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 1 Dec 2020 16:33:16 -0500 Subject: [PATCH 18/37] [Maps] Support runtime fields in tooltips (#84377) --- .../classes/sources/es_search_source/es_search_source.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index d31f6ee626245..1c0645ae797ec 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -487,8 +487,14 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return {}; } + const { docValueFields } = getDocValueAndSourceFields( + indexPattern, + this._getTooltipPropertyNames() + ); + + const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source const searchService = getSearchService(); - const searchSource = searchService.searchSource.createEmpty(); + const searchSource = await searchService.searchSource.create(initialSearchContext as object); searchSource.setField('index', indexPattern); searchSource.setField('size', 1); From b9a64ba7d5f859e4803f38544a471040111a4100 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 1 Dec 2020 23:39:27 +0200 Subject: [PATCH 19/37] [Security Solutino][Case] Case connector alert UI (#82405) Co-authored-by: Patryk Kopycinski Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security_solution/common/constants.ts | 6 + .../components/add_comment/index.test.tsx | 94 ++-- .../cases/components/add_comment/index.tsx | 18 +- .../all_cases/table_filters.test.tsx | 1 - .../configure_cases/connectors_dropdown.tsx | 2 +- .../configure_cases/field_mapping.test.tsx | 3 +- .../configure_cases/field_mapping.tsx | 14 +- .../configure_cases/field_mapping_row.tsx | 2 +- .../components/configure_cases/index.tsx | 2 +- .../connector_selector/form.test.tsx | 68 +++ .../components/connector_selector/form.tsx | 24 +- .../connectors/case/cases_dropdown.tsx | 72 +++ .../connectors/case/existing_case.tsx | 54 +++ .../components/connectors/case/fields.tsx | 101 ++++ .../components}/connectors/case/index.ts | 22 +- .../connectors/case/translations.ts | 86 ++++ .../cases/components/connectors/case/types.ts | 17 + .../components}/connectors/config.ts | 0 .../components}/connectors/index.ts | 4 + .../components}/connectors/types.ts | 0 .../components}/connectors/utils.ts | 0 .../components/create/connector.test.tsx | 186 ++++++++ .../cases/components/create/connector.tsx | 83 ++++ .../components/create/description.test.tsx | 57 +++ .../cases/components/create/description.tsx | 31 ++ .../cases/components/create/form.test.tsx | 66 +++ .../public/cases/components/create/form.tsx | 94 ++++ .../cases/components/create/form_context.tsx | 66 +++ .../cases/components/create/index.test.tsx | 443 +++++++++++++----- .../public/cases/components/create/index.tsx | 342 ++------------ .../optional_field_label/index.test.tsx | 18 + .../create/optional_field_label/index.tsx | 2 +- .../public/cases/components/create/schema.tsx | 7 +- .../components/create/submit_button.test.tsx | 83 ++++ .../cases/components/create/submit_button.tsx | 30 ++ .../cases/components/create/tags.test.tsx | 77 +++ .../public/cases/components/create/tags.tsx | 48 ++ .../cases/components/create/title.test.tsx | 67 +++ .../public/cases/components/create/title.tsx | 32 ++ .../public/cases/components/settings/card.tsx | 2 +- .../cases/components/settings/fields_form.tsx | 23 +- .../cases/components/tag_list/index.tsx | 5 +- .../use_all_cases_modal/all_cases_modal.tsx | 5 +- .../create_case_modal.tsx | 68 +++ .../use_create_case_modal/index.tsx | 45 ++ .../components/use_insert_timeline/index.tsx | 63 +++ .../lib/connectors/case/translations.ts | 21 - .../security_solution/public/plugin.tsx | 2 +- .../use_insert_timeline.tsx | 70 --- 49 files changed, 2018 insertions(+), 608 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/cases/components/connector_selector/form.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx rename x-pack/plugins/security_solution/public/{common/lib => cases/components}/connectors/case/index.ts (57%) create mode 100644 x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts create mode 100644 x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts rename x-pack/plugins/security_solution/public/{common/lib => cases/components}/connectors/config.ts (100%) rename x-pack/plugins/security_solution/public/{common/lib => cases/components}/connectors/index.ts (79%) rename x-pack/plugins/security_solution/public/{common/lib => cases/components}/connectors/types.ts (100%) rename x-pack/plugins/security_solution/public/{common/lib => cases/components}/connectors/utils.ts (100%) create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/connector.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/description.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/form.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/tags.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/title.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e58aed15a8a10..c47ec70341845 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -160,6 +160,7 @@ export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID]; /* Rule notifications options */ +export const ENABLE_CASE_CONNECTOR = false; export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.email', '.slack', @@ -169,6 +170,11 @@ export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.jira', '.resilient', ]; + +if (ENABLE_CASE_CONNECTOR) { + NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.push('.case'); +} + export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions'; export const NOTIFICATION_THROTTLE_RULE = 'rule'; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index 2c8051f902b17..50e139bcd215f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -6,42 +6,24 @@ import React from 'react'; import { mount } from 'enzyme'; +import { waitFor, act } from '@testing-library/react'; import { AddComment, AddCommentRefObject } from '.'; import { TestProviders } from '../../../common/mock'; -import { getFormMock } from '../__mock__/form'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; import { CommentRequest, CommentType } from '../../../../../case/common/api'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; +import { useInsertTimeline } from '../use_insert_timeline'; import { usePostComment } from '../../containers/use_post_comment'; -import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; -import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; -import { waitFor } from '@testing-library/react'; - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' -); - -jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); jest.mock('../../containers/use_post_comment'); +jest.mock('../use_insert_timeline'); -const useFormMock = useForm as jest.Mock; -const useFormDataMock = useFormData as jest.Mock; - -const useInsertTimelineMock = useInsertTimeline as jest.Mock; const usePostCommentMock = usePostComment as jest.Mock; - +const useInsertTimelineMock = useInsertTimeline as jest.Mock; const onCommentSaving = jest.fn(); const onCommentPosted = jest.fn(); const postComment = jest.fn(); -const handleCursorChange = jest.fn(); -const handleOnTimelineChange = jest.fn(); const addCommentProps = { caseId: '1234', @@ -52,15 +34,6 @@ const addCommentProps = { showLoading: false, }; -const defaultInsertTimeline = { - cursorPosition: { - start: 0, - end: 0, - }, - handleCursorChange, - handleOnTimelineChange, -}; - const defaultPostCommment = { isLoading: false, isError: false, @@ -73,14 +46,9 @@ const sampleData: CommentRequest = { }; describe('AddComment ', () => { - const formHookMock = getFormMock(sampleData); - beforeEach(() => { jest.resetAllMocks(); - useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); usePostCommentMock.mockImplementation(() => defaultPostCommment); - useFormMock.mockImplementation(() => ({ form: formHookMock })); - useFormDataMock.mockImplementation(() => [{ comment: sampleData.comment }]); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); }); @@ -92,14 +60,25 @@ describe('AddComment ', () => { ); + + await act(async () => { + wrapper + .find(`[data-test-subj="add-comment"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.comment } }); + }); + expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy(); - wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); + await act(async () => { + wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); + }); + await waitFor(() => { expect(onCommentSaving).toBeCalled(); expect(postComment).toBeCalledWith(sampleData, onCommentPosted); - expect(formHookMock.reset).toBeCalled(); + expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe(''); }); }); @@ -112,6 +91,7 @@ describe('AddComment ', () => { ); + expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy(); expect( wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled') @@ -127,15 +107,16 @@ describe('AddComment ', () => { ); + expect( wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled') ).toBeTruthy(); }); - it('should insert a quote', () => { + it('should insert a quote', async () => { const sampleQuote = 'what a cool quote'; const ref = React.createRef(); - mount( + const wrapper = mount( @@ -143,10 +124,37 @@ describe('AddComment ', () => { ); - ref.current!.addQuote(sampleQuote); - expect(formHookMock.setFieldValue).toBeCalledWith( - 'comment', + await act(async () => { + wrapper + .find(`[data-test-subj="add-comment"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.comment } }); + }); + + await act(async () => { + ref.current!.addQuote(sampleQuote); + }); + + expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe( `${sampleData.comment}\n\n${sampleQuote}` ); }); + + it('it should insert a timeline', async () => { + useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => { + onTimelineAttached(`[title](url)`); + }); + + const wrapper = mount( + + + + + + ); + + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx index 859ba3d1a0951..daa7c24858b94 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx @@ -12,12 +12,11 @@ import { CommentType } from '../../../../../case/common/api'; import { usePostComment } from '../../containers/use_post_comment'; import { Case } from '../../containers/types'; import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; import { Form, useForm, UseField, useFormData } from '../../../shared_imports'; import * as i18n from './translations'; import { schema, AddCommentFormSchema } from './schema'; -import { useTimelineClick } from '../../../common/utils/timeline/use_timeline_click'; +import { useInsertTimeline } from '../use_insert_timeline'; const MySpinner = styled(EuiLoadingSpinner)` position: absolute; @@ -56,12 +55,6 @@ export const AddComment = React.memo( const { setFieldValue, reset, submit } = form; const [{ comment }] = useFormData<{ comment: string }>({ form, watch: [fieldName] }); - const onCommentChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [ - setFieldValue, - ]); - - const { handleCursorChange } = useInsertTimeline(comment, onCommentChange); - const addQuote = useCallback( (quote) => { setFieldValue(fieldName, `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`); @@ -73,7 +66,12 @@ export const AddComment = React.memo( addQuote, })); - const handleTimelineClick = useTimelineClick(); + const onTimelineAttached = useCallback( + (newValue: string) => setFieldValue(fieldName, newValue), + [setFieldValue] + ); + + useInsertTimeline(comment ?? '', onTimelineAttached); const onSubmit = useCallback(async () => { const { isValid, data } = await submit(); @@ -98,8 +96,6 @@ export const AddComment = React.memo( isDisabled: isLoading, dataTestSubj: 'add-comment', placeholder: i18n.ADD_COMMENT_HELP_TEXT, - onCursorPositionUpdate: handleCursorChange, - onClickTimeline: handleTimelineClick, bottomRightContent: ( { + const formHookMock = getFormMock({ connectorId: connectorsMock[0].id }); + + beforeEach(() => { + jest.resetAllMocks(); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + }); + + it('it should render', async () => { + const wrapper = mount( +
+ + + ); + + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); + }); + + it('it should not render when is not in edit mode', async () => { + const wrapper = mount( +
+ + + ); + + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx index 7de7b3d6b2a96..9017365eea02b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { isEmpty } from 'lodash/fp'; import { EuiFormRow } from '@elastic/eui'; -import React, { useCallback, useEffect } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../shared_imports'; import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown'; @@ -14,9 +15,8 @@ import { ActionConnector } from '../../../../../case/common/api/cases'; interface ConnectorSelectorProps { connectors: ActionConnector[]; dataTestSubj: string; - defaultValue?: ActionConnector; disabled: boolean; - field: FieldHook; + field: FieldHook; idAria: string; isEdit: boolean; isLoading: boolean; @@ -24,7 +24,6 @@ interface ConnectorSelectorProps { export const ConnectorSelector = ({ connectors, dataTestSubj, - defaultValue, disabled = false, field, idAria, @@ -32,19 +31,6 @@ export const ConnectorSelector = ({ isLoading = false, }: ConnectorSelectorProps) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - - useEffect(() => { - field.setValue(defaultValue); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultValue]); - - const handleContentChange = useCallback( - (newConnector: string) => { - field.setValue(newConnector); - }, - [field] - ); - return isEdit ? ( ) : null; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx new file mode 100644 index 0000000000000..931e23e811b1b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx @@ -0,0 +1,72 @@ +/* + * 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 { EuiFormRow, EuiSuperSelect, EuiSuperSelectOption } from '@elastic/eui'; +import React, { memo, useMemo, useCallback } from 'react'; +import { Case } from '../../../containers/types'; + +import * as i18n from './translations'; + +interface CaseDropdownProps { + isLoading: boolean; + cases: Case[]; + selectedCase?: string; + onCaseChanged: (id: string) => void; +} + +export const ADD_CASE_BUTTON_ID = 'add-case'; + +const addNewCase = { + value: ADD_CASE_BUTTON_ID, + inputDisplay: ( + + {i18n.CASE_CONNECTOR_ADD_NEW_CASE} + + ), + 'data-test-subj': 'dropdown-connector-add-connector', +}; + +const CasesDropdownComponent: React.FC = ({ + isLoading, + cases, + selectedCase, + onCaseChanged, +}) => { + const caseOptions: Array> = useMemo( + () => + cases.reduce>>( + (acc, theCase) => [ + ...acc, + { + value: theCase.id, + inputDisplay: {theCase.title}, + 'data-test-subj': `case-connector-cases-dropdown-${theCase.id}`, + }, + ], + [] + ), + [cases] + ); + + const options = useMemo(() => [...caseOptions, addNewCase], [caseOptions]); + const onChange = useCallback((id: string) => onCaseChanged(id), [onCaseChanged]); + + return ( + + + + ); +}; + +export const CasesDropdown = memo(CasesDropdownComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx new file mode 100644 index 0000000000000..28e051a713bf4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx @@ -0,0 +1,54 @@ +/* + * 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, { memo, useMemo, useCallback } from 'react'; +import { useGetCases } from '../../../containers/use_get_cases'; +import { useCreateCaseModal } from '../../use_create_case_modal'; +import { CasesDropdown, ADD_CASE_BUTTON_ID } from './cases_dropdown'; + +interface ExistingCaseProps { + selectedCase: string | null; + onCaseChanged: (id: string) => void; +} + +const ExistingCaseComponent: React.FC = ({ onCaseChanged, selectedCase }) => { + const { data: cases, loading: isLoadingCases, refetchCases } = useGetCases(); + + const onCaseCreated = useCallback(() => refetchCases(), [refetchCases]); + + const { Modal: CreateCaseModal, openModal } = useCreateCaseModal({ onCaseCreated }); + + const onChange = useCallback( + (id: string) => { + if (id === ADD_CASE_BUTTON_ID) { + openModal(); + return; + } + + onCaseChanged(id); + }, + [onCaseChanged, openModal] + ); + + const isCasesLoading = useMemo( + () => isLoadingCases.includes('cases') || isLoadingCases.includes('caseUpdate'), + [isLoadingCases] + ); + + return ( + <> + + + + ); +}; + +export const ExistingCase = memo(ExistingCaseComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx new file mode 100644 index 0000000000000..91087138e52d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx @@ -0,0 +1,101 @@ +/* + * 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 @kbn/eslint/no-restricted-paths */ + +import React, { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { EuiCallOut } from '@elastic/eui'; + +import { ActionParamsProps } from '../../../../../../triggers_actions_ui/public/types'; +import { CommentType } from '../../../../../../case/common/api'; + +import { CaseActionParams } from './types'; +import { ExistingCase } from './existing_case'; + +import * as i18n from './translations'; + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui?.euiSize ?? '16px'}; + `} +`; + +const defaultAlertComment = { + type: CommentType.alert, + alertId: '{{context.rule.id}}', + index: '{{context.rule.output_index}}', +}; + +const CaseParamsFields: React.FunctionComponent> = ({ + actionParams, + editAction, + index, + errors, + messageVariables, + actionConnector, +}) => { + const { caseId = null, comment = defaultAlertComment } = actionParams.subActionParams ?? {}; + + const [selectedCase, setSelectedCase] = useState(null); + + const editSubActionProperty = useCallback( + (key: string, value: unknown) => { + const newProps = { ...actionParams.subActionParams, [key]: value }; + editAction('subActionParams', newProps, index); + }, + [actionParams.subActionParams, editAction, index] + ); + + const onCaseChanged = useCallback( + (id: string) => { + setSelectedCase(id); + editSubActionProperty('caseId', id); + }, + [editSubActionProperty] + ); + + useEffect(() => { + if (!actionParams.subAction) { + editAction('subAction', 'addComment', index); + } + + if (!actionParams.subActionParams?.caseId) { + editSubActionProperty('caseId', caseId); + } + + if (!actionParams.subActionParams?.comment) { + editSubActionProperty('comment', comment); + } + + if (caseId != null) { + setSelectedCase((prevCaseId) => (prevCaseId !== caseId ? caseId : prevCaseId)); + } + + // editAction creates an infinity loop. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + actionConnector, + index, + actionParams.subActionParams?.caseId, + actionParams.subActionParams?.comment, + caseId, + comment, + actionParams.subAction, + ]); + + return ( + <> + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { CaseParamsFields as default }; diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/case/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts similarity index 57% rename from x-pack/plugins/security_solution/public/common/lib/connectors/case/index.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts index 271b1bfd2e3de..0aacd61991771 100644 --- a/x-pack/plugins/security_solution/public/common/lib/connectors/case/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts @@ -3,11 +3,29 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { lazy } from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ActionTypeModel } from '../../../../../../triggers_actions_ui/public/types'; +import { CaseActionParams } from './types'; import * as i18n from './translations'; +interface ValidationResult { + errors: { + caseId: string[]; + }; +} + +const validateParams = (actionParams: CaseActionParams) => { + const validationResult: ValidationResult = { errors: { caseId: [] } }; + + if (actionParams.subActionParams && !actionParams.subActionParams.caseId) { + validationResult.errors.caseId.push(i18n.CASE_CONNECTOR_CASE_REQUIRED); + } + + return validationResult; +}; + export function getActionType(): ActionTypeModel { return { id: '.case', @@ -15,8 +33,8 @@ export function getActionType(): ActionTypeModel { selectMessage: i18n.CASE_CONNECTOR_DESC, actionTypeTitle: i18n.CASE_CONNECTOR_TITLE, validateConnector: () => ({ errors: {} }), - validateParams: () => ({ errors: {} }), + validateParams, actionConnectorFields: null, - actionParamsFields: null, + actionParamsFields: lazy(() => import('./fields')), }; } diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts new file mode 100644 index 0000000000000..8cfcf2b9a073b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.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 { i18n } from '@kbn/i18n'; + +export * from '../../../translations'; + +export const CASE_CONNECTOR_DESC = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.selectMessageText', + { + defaultMessage: 'Create or update a case.', + } +); + +export const CASE_CONNECTOR_TITLE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.actionTypeTitle', + { + defaultMessage: 'Cases', + } +); + +export const CASE_CONNECTOR_COMMENT_LABEL = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.commentLabel', + { + defaultMessage: 'Comment', + } +); + +export const CASE_CONNECTOR_COMMENT_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.commentRequired', + { + defaultMessage: 'Comment is required.', + } +); + +export const CASE_CONNECTOR_CASES_DROPDOWN_ROW_LABEL = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.casesDropdownRowLabel', + { + defaultMessage: 'Case', + } +); + +export const CASE_CONNECTOR_CASES_DROPDOWN_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.casesDropdownPlaceholder', + { + defaultMessage: 'Select case', + } +); + +export const CASE_CONNECTOR_CASES_OPTION_NEW_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.optionAddNewCase', + { + defaultMessage: 'Add to a new case', + } +); + +export const CASE_CONNECTOR_CASES_OPTION_EXISTING_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.optionAddToExistingCase', + { + defaultMessage: 'Add to existing case', + } +); + +export const CASE_CONNECTOR_CASE_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.caseRequired', + { + defaultMessage: 'You must select a case.', + } +); + +export const CASE_CONNECTOR_CALL_OUT_INFO = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.callOutInfo', + { + defaultMessage: 'All alerts after rule creation will be appended to the selected case.', + } +); + +export const CASE_CONNECTOR_ADD_NEW_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.addNewCaseOption', + { + defaultMessage: 'Add new case', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts new file mode 100644 index 0000000000000..8173a814c2d89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts @@ -0,0 +1,17 @@ +/* + * 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 interface CaseActionParams { + subAction: string; + subActionParams: { + caseId: string; + comment: { + alertId: string; + index: string; + type: 'alert'; + }; + }; +} diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/config.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/connectors/config.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/config.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts similarity index 79% rename from x-pack/plugins/security_solution/public/common/lib/connectors/index.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/index.ts index 58d7e89e080e7..e77aa9bdd84b1 100644 --- a/x-pack/plugins/security_solution/public/common/lib/connectors/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts @@ -5,3 +5,7 @@ */ export { getActionType as getCaseConnectorUI } from './case'; + +export * from './config'; +export * from './types'; +export * from './utils'; diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/types.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/connectors/types.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/types.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/utils.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/utils.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/connectors/utils.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/utils.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx new file mode 100644 index 0000000000000..89b9e3b30ede1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx @@ -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 React from 'react'; +import { mount } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; + +import { useForm, Form, FormHook } from '../../../shared_imports'; +import { connectorsMock } from '../../containers/mock'; +import { Connector } from './connector'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types'; +import { useGetSeverity } from '../settings/resilient/use_get_severity'; + +jest.mock('../../../common/lib/kibana', () => { + return { + useKibana: () => ({ + services: { + notifications: {}, + http: {}, + }, + }), + }; +}); +jest.mock('../../containers/configure/use_connectors'); +jest.mock('../settings/resilient/use_get_incident_types'); +jest.mock('../settings/resilient/use_get_severity'); + +const useConnectorsMock = useConnectors as jest.Mock; +const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; +const useGetSeverityMock = useGetSeverity as jest.Mock; + +const useGetIncidentTypesResponse = { + isLoading: false, + incidentTypes: [ + { + id: 19, + name: 'Malware', + }, + { + id: 21, + name: 'Denial of Service', + }, + ], +}; + +const useGetSeverityResponse = { + isLoading: false, + severity: [ + { + id: 4, + name: 'Low', + }, + { + id: 5, + name: 'Medium', + }, + { + id: 6, + name: 'High', + }, + ], +}; + +describe('Connector', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ connectorId: string; fields: Record | null }>({ + defaultValue: { connectorId: connectorsMock[0].id, fields: null }, + }); + + globalForm = form; + + return
{children}
; + }; + + beforeEach(() => { + jest.resetAllMocks(); + useConnectorsMock.mockReturnValue({ loading: false, connectors: connectorsMock }); + useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); + useGetSeverityMock.mockReturnValue(useGetSeverityResponse); + }); + + it('it renders', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="connector-settings"]`).exists()).toBeTruthy(); + + waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy(); + }); + }); + + it('it is loading when fetching connectors', async () => { + useConnectorsMock.mockReturnValue({ loading: true, connectors: connectorsMock }); + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') + ).toEqual(true); + }); + + it('it is disabled when fetching connectors', async () => { + useConnectorsMock.mockReturnValue({ loading: true, connectors: connectorsMock }); + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( + true + ); + }); + + it('it is disabled and loading when passing loading as true', async () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') + ).toEqual(true); + expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( + true + ); + }); + + it(`it should change connector`, async () => { + const wrapper = mount( + + + + ); + + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()).toBeTruthy(); + }); + + act(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ value: '19', label: 'Denial of Service' }]); + }); + + act(() => { + wrapper + .find('select[data-test-subj="severitySelect"]') + .first() + .simulate('change', { + target: { value: '4' }, + }); + }); + + await waitFor(() => { + expect(globalForm.getFormData()).toEqual({ + connectorId: 'resilient-2', + fields: { incidentTypes: ['19'], severityCode: '4' }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx new file mode 100644 index 0000000000000..b2a0f3c351552 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx @@ -0,0 +1,83 @@ +/* + * 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, { memo, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { ConnectorTypeFields } from '../../../../../case/common/api/connectors'; +import { UseField, useFormData, FieldHook } from '../../../shared_imports'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { ConnectorSelector } from '../connector_selector/form'; +import { SettingFieldsForm } from '../settings/fields_form'; +import { ActionConnector } from '../../containers/types'; +import { getConnectorById } from '../configure_cases/utils'; + +interface Props { + isLoading: boolean; +} + +interface SettingsFieldProps { + connectors: ActionConnector[]; + field: FieldHook; + isEdit: boolean; +} + +const SettingsField = ({ connectors, isEdit, field }: SettingsFieldProps) => { + const [{ connectorId }] = useFormData({ watch: ['connectorId'] }); + const { setValue } = field; + const connector = getConnectorById(connectorId, connectors) ?? null; + + useEffect(() => { + if (connectorId) { + setValue(null); + } + }, [setValue, connectorId]); + + return ( + + ); +}; + +const ConnectorComponent: React.FC = ({ isLoading }) => { + const { loading: isLoadingConnectors, connectors } = useConnectors(); + + return ( + + + + + + + + + ); +}; + +ConnectorComponent.displayName = 'ConnectorComponent'; + +export const Connector = memo(ConnectorComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx new file mode 100644 index 0000000000000..201a61febc628 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx @@ -0,0 +1,57 @@ +/* + * 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 } from 'enzyme'; +import { act } from '@testing-library/react'; + +import { useForm, Form, FormHook } from '../../../shared_imports'; +import { Description } from './description'; + +describe('Description', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ description: string }>({ + defaultValue: { description: 'My description' }, + }); + + globalForm = form; + + return
{children}
; + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('it renders', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="caseDescription"]`).exists()).toBeTruthy(); + }); + + it('it changes the description', async () => { + const wrapper = mount( + + + + ); + + await act(async () => { + wrapper + .find(`[data-test-subj="caseDescription"] textarea`) + .first() + .simulate('change', { target: { value: 'My new description' } }); + }); + + expect(globalForm.getFormData()).toEqual({ description: 'My new description' }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/description.tsx b/x-pack/plugins/security_solution/public/cases/components/create/description.tsx new file mode 100644 index 0000000000000..f130bd14644f8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/description.tsx @@ -0,0 +1,31 @@ +/* + * 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, { memo } from 'react'; +import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; +import { UseField } from '../../../shared_imports'; + +interface Props { + isLoading: boolean; +} + +export const fieldName = 'description'; + +const DescriptionComponent: React.FC = ({ isLoading }) => ( + +); + +DescriptionComponent.displayName = 'DescriptionComponent'; + +export const Description = memo(DescriptionComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx new file mode 100644 index 0000000000000..e64b2b3a05080 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx @@ -0,0 +1,66 @@ +/* + * 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 } from 'enzyme'; + +import { useForm, Form } from '../../../shared_imports'; +import { useGetTags } from '../../containers/use_get_tags'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { connectorsMock } from '../../containers/mock'; +import { schema, FormProps } from './schema'; +import { CreateCaseForm } from './form'; + +jest.mock('../../containers/use_get_tags'); +jest.mock('../../containers/configure/use_connectors'); +const useGetTagsMock = useGetTags as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; + +const initialCaseValue: FormProps = { + description: '', + tags: [], + title: '', + connectorId: 'none', + fields: null, +}; + +describe('CreateCaseForm', () => { + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm({ + defaultValue: initialCaseValue, + options: { stripEmptyFields: false }, + schema, + }); + + return
{children}
; + }; + + beforeEach(() => { + jest.resetAllMocks(); + useGetTagsMock.mockReturnValue({ tags: ['test'] }); + useConnectorsMock.mockReturnValue({ loading: false, connectors: connectorsMock }); + }); + + it('it renders with steps', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeTruthy(); + }); + + it('it renders without steps', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form.tsx new file mode 100644 index 0000000000000..40db4d792c1c8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/form.tsx @@ -0,0 +1,94 @@ +/* + * 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, { useMemo } from 'react'; +import { EuiLoadingSpinner, EuiSteps } from '@elastic/eui'; +import styled, { css } from 'styled-components'; + +import { useFormContext } from '../../../shared_imports'; + +import { Title } from './title'; +import { Description } from './description'; +import { Tags } from './tags'; +import { Connector } from './connector'; +import * as i18n from './translations'; + +interface ContainerProps { + big?: boolean; +} + +const Container = styled.div.attrs((props) => props)` + ${({ big, theme }) => css` + margin-top: ${big ? theme.eui?.euiSizeXL ?? '32px' : theme.eui?.euiSize ?? '16px'}; + `} +`; + +const MySpinner = styled(EuiLoadingSpinner)` + position: absolute; + top: 50%; + left: 50%; + z-index: 99; +`; + +interface Props { + withSteps?: boolean; +} + +export const CreateCaseForm: React.FC = React.memo(({ withSteps = true }) => { + const { isSubmitting } = useFormContext(); + + const firstStep = useMemo( + () => ({ + title: i18n.STEP_ONE_TITLE, + children: ( + <> + + <Container> + <Tags isLoading={isSubmitting} /> + </Container> + <Container big> + <Description isLoading={isSubmitting} /> + </Container> + </> + ), + }), + [isSubmitting] + ); + + const secondStep = useMemo( + () => ({ + title: i18n.STEP_TWO_TITLE, + children: ( + <Container> + <Connector isLoading={isSubmitting} /> + </Container> + ), + }), + [isSubmitting] + ); + + const allSteps = useMemo(() => [firstStep, secondStep], [firstStep, secondStep]); + + return ( + <> + {isSubmitting && <MySpinner data-test-subj="create-case-loading-spinner" size="xl" />} + {withSteps ? ( + <EuiSteps + headingElement="h2" + steps={allSteps} + data-test-subj={'case-creation-form-steps'} + /> + ) : ( + <> + {firstStep.children} + {secondStep.children} + </> + )} + </> + ); +}); + +CreateCaseForm.displayName = 'CreateCaseForm'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx new file mode 100644 index 0000000000000..e11e508b60ebf --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx @@ -0,0 +1,66 @@ +/* + * 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, { useCallback, useEffect } from 'react'; + +import { schema, FormProps } from './schema'; +import { Form, useForm } from '../../../shared_imports'; +import { + getConnectorById, + getNoneConnector, + normalizeActionConnector, +} from '../configure_cases/utils'; +import { usePostCase } from '../../containers/use_post_case'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { Case } from '../../containers/types'; + +const initialCaseValue: FormProps = { + description: '', + tags: [], + title: '', + connectorId: 'none', + fields: null, +}; + +interface Props { + onSuccess?: (theCase: Case) => void; +} + +export const FormContext: React.FC<Props> = ({ children, onSuccess }) => { + const { connectors } = useConnectors(); + const { caseData, postCase } = usePostCase(); + + const submitCase = useCallback( + async ({ connectorId: dataConnectorId, fields, ...dataWithoutConnectorId }, isValid) => { + if (isValid) { + const caseConnector = getConnectorById(dataConnectorId, connectors); + const connectorToUpdate = caseConnector + ? normalizeActionConnector(caseConnector, fields) + : getNoneConnector(); + + await postCase({ ...dataWithoutConnectorId, connector: connectorToUpdate }); + } + }, + [postCase, connectors] + ); + + const { form } = useForm<FormProps>({ + defaultValue: initialCaseValue, + options: { stripEmptyFields: false }, + schema, + onSubmit: submitCase, + }); + + useEffect(() => { + if (caseData && onSuccess) { + onSuccess(caseData); + } + }, [caseData, onSuccess]); + + return <Form form={form}>{children}</Form>; +}; + +FormContext.displayName = 'FormContext'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx index 7902c7065d9a3..29073e7774158 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -5,71 +5,40 @@ */ import React from 'react'; -import { mount } from 'enzyme'; -import { Create } from '.'; +import { mount, ReactWrapper } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { TestProviders } from '../../../common/mock'; -import { getFormMock } from '../__mock__/form'; -import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; import { usePostCase } from '../../containers/use_post_case'; import { useGetTags } from '../../containers/use_get_tags'; - -import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; -import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; - -import { waitFor } from '@testing-library/react'; import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/configure/mock'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types'; +import { useGetSeverity } from '../settings/resilient/use_get_severity'; +import { useGetIssueTypes } from '../settings/jira/use_get_issue_types'; +import { useGetFieldsByIssueType } from '../settings/jira/use_get_fields_by_issue_type'; +import { Create } from '.'; -jest.mock('@elastic/eui', () => { - const original = jest.requireActual('@elastic/eui'); - return { - ...original, - // eslint-disable-next-line react/display-name - EuiFieldText: () => <input />, - }; -}); -jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); jest.mock('../../containers/use_post_case'); - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' -); - jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/configure/use_connectors'); -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider', - () => ({ - FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) => - children({ tags: ['rad', 'dude'] }), - }) -); -const useConnectorsMock = useConnectors as jest.Mock; -const useFormMock = useForm as jest.Mock; -const useFormDataMock = useFormData as jest.Mock; +jest.mock('../settings/resilient/use_get_incident_types'); +jest.mock('../settings/resilient/use_get_severity'); +jest.mock('../settings/jira/use_get_issue_types'); +jest.mock('../settings/jira/use_get_fields_by_issue_type'); +jest.mock('../settings/jira/use_get_single_issue'); +jest.mock('../settings/jira/use_get_issues'); -const useInsertTimelineMock = useInsertTimeline as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; const usePostCaseMock = usePostCase as jest.Mock; - +const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; +const useGetSeverityMock = useGetSeverity as jest.Mock; +const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; +const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; const postCase = jest.fn(); -const handleCursorChange = jest.fn(); -const handleOnTimelineChange = jest.fn(); - -const defaultInsertTimeline = { - cursorPosition: { - start: 0, - end: 0, - }, - handleCursorChange, - handleOnTimelineChange, -}; const sampleTags = ['coke', 'pepsi']; const sampleData = { @@ -83,27 +52,117 @@ const sampleData = { type: ConnectorTypes.none, }, }; + const defaultPostCase = { isLoading: false, isError: false, caseData: null, postCase, }; + const sampleConnectorData = { loading: false, connectors: [] }; + +const useGetIncidentTypesResponse = { + isLoading: false, + incidentTypes: [ + { + id: 19, + name: 'Malware', + }, + { + id: 21, + name: 'Denial of Service', + }, + ], +}; + +const useGetSeverityResponse = { + isLoading: false, + severity: [ + { + id: 4, + name: 'Low', + }, + { + id: 5, + name: 'Medium', + }, + { + id: 6, + name: 'High', + }, + ], +}; + +const useGetIssueTypesResponse = { + isLoading: false, + issueTypes: [ + { + id: '10006', + name: 'Task', + }, + { + id: '10007', + name: 'Bug', + }, + ], +}; + +const useGetFieldsByIssueTypeResponse = { + isLoading: false, + fields: { + summary: { allowedValues: [], defaultValue: {} }, + labels: { allowedValues: [], defaultValue: {} }, + description: { allowedValues: [], defaultValue: {} }, + priority: { + allowedValues: [ + { + name: 'Medium', + id: '3', + }, + { + name: 'Low', + id: '2', + }, + ], + defaultValue: { name: 'Medium', id: '3' }, + }, + }, +}; + +const fillForm = async (wrapper: ReactWrapper) => { + await act(async () => { + wrapper + .find(`[data-test-subj="caseTitle"] input`) + .first() + .simulate('change', { target: { value: sampleData.title } }); + }); + + await act(async () => { + wrapper + .find(`[data-test-subj="caseDescription"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.description } }); + }); + + await waitFor(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange(sampleTags.map((tag) => ({ label: tag }))); + }); +}; + describe('Create case', () => { const fetchTags = jest.fn(); - const formHookMock = getFormMock(sampleData); beforeEach(() => { jest.resetAllMocks(); - useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); usePostCaseMock.mockImplementation(() => defaultPostCase); - useFormMock.mockImplementation(() => ({ form: formHookMock })); - useFormDataMock.mockImplementation(() => [ - { - description: sampleData.description, - }, - ]); useConnectorsMock.mockReturnValue(sampleConnectorData); + useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); + useGetSeverityMock.mockReturnValue(useGetSeverityResponse); + useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse); + useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); (useGetTags as jest.Mock).mockImplementation(() => ({ tags: sampleTags, @@ -112,7 +171,32 @@ describe('Create case', () => { }); describe('Step 1 - Case Fields', () => { + it('it renders', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + + expect(wrapper.find(`[data-test-subj="caseTitle"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseDescription"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseTags"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="create-case-submit"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).first().exists()).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="case-creation-form-steps"]`).first().exists() + ).toBeTruthy(); + }); + it('should post case on submit click', async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -120,7 +204,13 @@ describe('Create case', () => { </Router> </TestProviders> ); - wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + + await fillForm(wrapper); + wrapper.update(); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); await waitFor(() => expect(postCase).toBeCalledWith(sampleData)); }); @@ -132,15 +222,18 @@ describe('Create case', () => { </Router> </TestProviders> ); + wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click'); await waitFor(() => expect(mockHistory.push).toHaveBeenCalledWith('/')); }); + it('should redirect to new case when caseData is there', async () => { - const sampleId = '777777'; + const sampleId = 'case-id'; usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, caseData: { id: sampleId }, })); + mount( <TestProviders> <Router history={mockHistory}> @@ -148,11 +241,11 @@ describe('Create case', () => { </Router> </TestProviders> ); - await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/777777')); + + await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/case-id')); }); it('should render spinner when loading', async () => { - usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, isLoading: true })); const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -160,11 +253,87 @@ describe('Create case', () => { </Router> </TestProviders> ); + + await fillForm(wrapper); + await act(async () => { + await wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + wrapper.update(); + expect( + wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists() + ).toBeTruthy(); + }); + }); + }); + + describe('Step 2 - Connector Fields', () => { + it(`it should submit a Jira connector`, async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + + await fillForm(wrapper); + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-jira-1"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeTruthy(); + }); + + act(() => { + wrapper + .find('select[data-test-subj="issueTypeSelect"]') + .first() + .simulate('change', { + target: { value: '10007' }, + }); + }); + + act(() => { + wrapper + .find('select[data-test-subj="prioritySelect"]') + .first() + .simulate('change', { + target: { value: '2' }, + }); + }); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + await waitFor(() => - expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy() + expect(postCase).toBeCalledWith({ + ...sampleData, + connector: { + id: 'jira-1', + name: 'Jira', + type: '.jira', + fields: { issueType: '10007', parent: null, priority: '2' }, + }, + }) ); }); - it('Tag options render with new tags added', async () => { + + it(`it should submit a resilient connector`, async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -172,63 +341,109 @@ describe('Create case', () => { </Router> </TestProviders> ); - await waitFor(() => + + await fillForm(wrapper); + await waitFor(() => { expect( - wrapper - .find(`[data-test-subj="caseTags"] [data-test-subj="input"]`) - .first() - .prop('options') - ).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]) + wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists() + ).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect( + wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists() + ).toBeTruthy(); + }); + + act(() => { + ((wrapper.find(EuiComboBox).at(1).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ value: '19', label: 'Denial of Service' }]); + }); + + act(() => { + wrapper + .find('select[data-test-subj="severitySelect"]') + .first() + .simulate('change', { + target: { value: '4' }, + }); + }); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => + expect(postCase).toBeCalledWith({ + ...sampleData, + connector: { + id: 'resilient-2', + name: 'My Connector 2', + type: '.resilient', + fields: { incidentTypes: ['19'], severityCode: '4' }, + }, + }) ); }); - }); - // FAILED ES PROMOTION: https://github.com/elastic/kibana/issues/84145 - describe.skip('Step 2 - Connector Fields', () => { - const connectorTypes = [ - { - label: 'Jira', - testId: 'jira-1', - dataTestSubj: 'connector-settings-jira', - }, - { - label: 'Resilient', - testId: 'resilient-2', - dataTestSubj: 'connector-settings-resilient', - }, - { - label: 'ServiceNow', - testId: 'servicenow-1', - dataTestSubj: 'connector-settings-sn', - }, - ]; - connectorTypes.forEach(({ label, testId, dataTestSubj }) => { - it(`should change from none to ${label} connector fields`, async () => { - useConnectorsMock.mockReturnValue({ - ...sampleConnectorData, - connectors: connectorsMock, - }); + it(`it should submit a servicenow connector`, async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); - const wrapper = mount( - <TestProviders> - <Router history={mockHistory}> - <Create /> - </Router> - </TestProviders> - ); - - await waitFor(() => { - expect(wrapper.find(`[data-test-subj="${dataTestSubj}"]`).exists()).toBeFalsy(); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.find(`button[data-test-subj="dropdown-connector-${testId}"]`).simulate('click'); - wrapper.update(); - }); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + + await fillForm(wrapper); + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-servicenow-1"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy(); + }); - await waitFor(() => { - wrapper.update(); - expect(wrapper.find(`[data-test-subj="${dataTestSubj}"]`).exists()).toBeTruthy(); + ['severitySelect', 'urgencySelect', 'impactSelect'].forEach((subj) => { + act(() => { + wrapper + .find(`select[data-test-subj="${subj}"]`) + .first() + .simulate('change', { + target: { value: '2' }, + }); }); }); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => + expect(postCase).toBeCalledWith({ + ...sampleData, + connector: { + id: 'servicenow-1', + name: 'My Connector', + type: '.servicenow', + fields: { impact: '2', severity: '2', urgency: '2' }, + }, + }) + ); }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 42633c5d2ccf8..5c50c37723083 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -3,319 +3,81 @@ * 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, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPanel, - EuiSteps, -} from '@elastic/eui'; -import styled, { css } from 'styled-components'; + +import React, { useCallback } from 'react'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; -import { isEqual } from 'lodash/fp'; -import { - Field, - Form, - FormDataProvider, - getUseField, - UseField, - useForm, - useFormData, -} from '../../../shared_imports'; -import { usePostCase } from '../../containers/use_post_case'; -import { schema, FormProps } from './schema'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; -import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; -import { useGetTags } from '../../containers/use_get_tags'; +import { Field, getUseField, useFormContext } from '../../../shared_imports'; import { getCaseDetailsUrl } from '../../../common/components/link_to'; -import { useTimelineClick } from '../../../common/utils/timeline/use_timeline_click'; -import { SettingFieldsForm } from '../settings/fields_form'; -import { useConnectors } from '../../containers/configure/use_connectors'; -import { ConnectorSelector } from '../connector_selector/form'; -import { useCaseConfigure } from '../../containers/configure/use_configure'; -import { - normalizeCaseConnector, - getConnectorById, - getNoneConnector, - normalizeActionConnector, -} from '../configure_cases/utils'; -import { ActionConnector } from '../../containers/types'; -import { ConnectorFields } from '../../../../../case/common/api/connectors'; import * as i18n from './translations'; +import { CreateCaseForm } from './form'; +import { FormContext } from './form_context'; +import { useInsertTimeline } from '../use_insert_timeline'; +import { fieldName as descriptionFieldName } from './description'; +import { SubmitCaseButton } from './submit_button'; export const CommonUseField = getUseField({ component: Field }); -interface ContainerProps { - big?: boolean; -} - -const Container = styled.div.attrs((props) => props)<ContainerProps>` - ${({ big, theme }) => css` - margin-top: ${big ? theme.eui.euiSizeXL : theme.eui.euiSize}; +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; `} `; -const MySpinner = styled(EuiLoadingSpinner)` - position: absolute; - top: 50%; - left: 50%; - z-index: 99; -`; - -const initialCaseValue: FormProps = { - description: '', - tags: [], - title: '', - connectorId: 'none', +const InsertTimeline = () => { + const { setFieldValue, getFormData } = useFormContext(); + const formData = getFormData(); + const onTimelineAttached = useCallback( + (newValue: string) => setFieldValue(descriptionFieldName, newValue), + [setFieldValue] + ); + useInsertTimeline(formData[descriptionFieldName] ?? '', onTimelineAttached); + return null; }; export const Create = React.memo(() => { const history = useHistory(); - const { caseData, isLoading, postCase } = usePostCase(); - const { loading: isLoadingConnectors, connectors } = useConnectors(); - const { connector: configureConnector, loading: isLoadingCaseConfigure } = useCaseConfigure(); - const { tags: tagOptions } = useGetTags(); - - const [connector, setConnector] = useState<ActionConnector | null>(null); - const [options, setOptions] = useState( - tagOptions.map((label) => ({ - label, - })) - ); - - // This values uses useEffect to update, not useMemo, - // because we need to setState on it from the jsx - useEffect( - () => - setOptions( - tagOptions.map((label) => ({ - label, - })) - ), - [tagOptions] - ); - - const [fields, setFields] = useState<ConnectorFields>(null); - - const { form } = useForm<FormProps>({ - defaultValue: initialCaseValue, - options: { stripEmptyFields: false }, - schema, - }); - const currentConnectorId = useMemo( - () => - !isLoadingCaseConfigure - ? normalizeCaseConnector(connectors, configureConnector)?.id ?? 'none' - : null, - [configureConnector, connectors, isLoadingCaseConfigure] - ); - const { submit, setFieldValue } = form; - const [{ description }] = useFormData<{ - description: string; - }>({ - form, - watch: ['description'], - }); - const onChangeConnector = useCallback( - (newConnectorId) => { - if (connector == null || connector.id !== newConnectorId) { - setConnector(getConnectorById(newConnectorId, connectors) ?? null); - // Reset setting fields when changing connector - setFields(null); - } + const onSuccess = useCallback( + ({ id }) => { + history.push(getCaseDetailsUrl({ id })); }, - [connector, connectors] + [history] ); - const onDescriptionChange = useCallback((newValue) => setFieldValue('description', newValue), [ - setFieldValue, - ]); - - const { handleCursorChange } = useInsertTimeline(description, onDescriptionChange); - - const handleTimelineClick = useTimelineClick(); - - const onSubmit = useCallback(async () => { - const { isValid, data } = await submit(); - if (isValid) { - const { connectorId: dataConnectorId, ...dataWithoutConnectorId } = data; - const caseConnector = getConnectorById(dataConnectorId, connectors); - const connectorToUpdate = caseConnector - ? normalizeActionConnector(caseConnector, fields) - : getNoneConnector(); - - await postCase({ ...dataWithoutConnectorId, connector: connectorToUpdate }); - } - }, [submit, postCase, fields, connectors]); - const handleSetIsCancel = useCallback(() => { history.push('/'); }, [history]); - const firstStep = useMemo( - () => ({ - title: i18n.STEP_ONE_TITLE, - children: ( - <> - <CommonUseField - path="title" - componentProps={{ - idAria: 'caseTitle', - 'data-test-subj': 'caseTitle', - euiFieldProps: { - fullWidth: false, - disabled: isLoading, - }, - }} - /> - <Container> - <CommonUseField - path="tags" - componentProps={{ - idAria: 'caseTags', - 'data-test-subj': 'caseTags', - euiFieldProps: { - fullWidth: true, - placeholder: '', - disabled: isLoading, - options, - noSuggestions: false, - }, - }} - /> - <FormDataProvider pathsToWatch="tags"> - {({ tags: anotherTags }) => { - const current: string[] = options.map((opt) => opt.label); - const newOptions = anotherTags.reduce((acc: string[], item: string) => { - if (!acc.includes(item)) { - return [...acc, item]; - } - return acc; - }, current); - if (!isEqual(current, newOptions)) { - setOptions( - newOptions.map((label: string) => ({ - label, - })) - ); - } - return null; - }} - </FormDataProvider> - </Container> - <Container big> - <UseField - path={'description'} - component={MarkdownEditorForm} - componentProps={{ - dataTestSubj: 'caseDescription', - idAria: 'caseDescription', - isDisabled: isLoading, - onClickTimeline: handleTimelineClick, - onCursorPositionUpdate: handleCursorChange, - }} - /> - </Container> - </> - ), - }), - [isLoading, options, handleCursorChange, handleTimelineClick] - ); - - const secondStep = useMemo( - () => ({ - title: i18n.STEP_TWO_TITLE, - children: ( - <EuiFlexGroup> - <EuiFlexItem> - <Container> - <UseField - path="connectorId" - component={ConnectorSelector} - componentProps={{ - connectors, - dataTestSubj: 'caseConnectors', - defaultValue: currentConnectorId, - disabled: isLoadingConnectors, - idAria: 'caseConnectors', - isLoading, - }} - onChange={onChangeConnector} - /> - </Container> - </EuiFlexItem> - <EuiFlexItem> - <Container> - <SettingFieldsForm - connector={connector} - fields={fields} - isEdit={true} - onChange={setFields} - /> - </Container> - </EuiFlexItem> - </EuiFlexGroup> - ), - }), - [ - connector, - connectors, - currentConnectorId, - fields, - isLoading, - isLoadingConnectors, - onChangeConnector, - ] - ); - - const allSteps = useMemo(() => [firstStep, secondStep], [firstStep, secondStep]); - - if (caseData != null && caseData.id) { - history.push(getCaseDetailsUrl({ id: caseData.id })); - return null; - } - return ( <EuiPanel> - {isLoading && <MySpinner data-test-subj="create-case-loading-spinner" size="xl" />} - <Form form={form}> - <EuiSteps headingElement="h2" steps={allSteps} /> - </Form> - <Container> - <EuiFlexGroup - alignItems="center" - justifyContent="flexEnd" - gutterSize="xs" - responsive={false} - > - <EuiFlexItem grow={false}> - <EuiButtonEmpty - data-test-subj="create-case-cancel" - size="s" - onClick={handleSetIsCancel} - iconType="cross" - > - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton - data-test-subj="create-case-submit" - fill - iconType="plusInCircle" - isDisabled={isLoading} - isLoading={isLoading} - onClick={onSubmit} - > - {i18n.CREATE_CASE} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </Container> + <FormContext onSuccess={onSuccess}> + <CreateCaseForm /> + <Container> + <EuiFlexGroup + alignItems="center" + justifyContent="flexEnd" + gutterSize="xs" + responsive={false} + > + <EuiFlexItem grow={false}> + <EuiButtonEmpty + data-test-subj="create-case-cancel" + size="s" + onClick={handleSetIsCancel} + iconType="cross" + > + {i18n.CANCEL} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <SubmitCaseButton /> + </EuiFlexItem> + </EuiFlexGroup> + </Container> + <InsertTimeline /> + </FormContext> </EuiPanel> ); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx new file mode 100644 index 0000000000000..3bbdb1eafd47c --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx @@ -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 { mount } from 'enzyme'; + +import { OptionalFieldLabel } from '.'; + +describe('OptionalFieldLabel', () => { + it('it renders correctly', async () => { + const wrapper = mount(OptionalFieldLabel); + expect(wrapper.find('[data-test-subj="form-optional-field-label"]').first().text()).toBe( + 'Optional' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx index b86198e09ceac..4a491eac35d90 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import * as i18n from '../../../translations'; export const OptionalFieldLabel = ( - <EuiText color="subdued" size="xs"> + <EuiText color="subdued" size="xs" data-test-subj="form-optional-field-label"> {i18n.OPTIONAL} </EuiText> ); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx index 5abac04d6ef37..a336860121c94 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CasePostRequest } from '../../../../../case/common/api'; +import { CasePostRequest, ConnectorTypeFields } from '../../../../../case/common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; import * as i18n from '../../translations'; @@ -18,7 +18,10 @@ export const schemaTags = { labelAppend: OptionalFieldLabel, }; -export type FormProps = Omit<CasePostRequest, 'connector'> & { connectorId: string }; +export type FormProps = Omit<CasePostRequest, 'connector'> & { + connectorId: string; + fields: ConnectorTypeFields['fields']; +}; export const schema: FormSchema<FormProps> = { title: { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx new file mode 100644 index 0000000000000..c8f6ebc05582f --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; + +import { useForm, Form } from '../../../shared_imports'; +import { SubmitCaseButton } from './submit_button'; + +describe('SubmitCaseButton', () => { + const onSubmit = jest.fn(); + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ title: string }>({ + defaultValue: { title: 'My title' }, + onSubmit, + }); + + return <Form form={form}>{children}</Form>; + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('it renders', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy(); + }); + + it('it submits', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => expect(onSubmit).toBeCalled()); + }); + + it('it disables when submitting', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="create-case-submit"]`).first().prop('isDisabled') + ).toBeTruthy(); + }); + }); + + it('it is loading when submitting', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="create-case-submit"]`).first().prop('isLoading') + ).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx new file mode 100644 index 0000000000000..8cffce290ff11 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx @@ -0,0 +1,30 @@ +/* + * 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, { memo } from 'react'; +import { EuiButton } from '@elastic/eui'; + +import { useFormContext } from '../../../shared_imports'; +import * as i18n from './translations'; + +const SubmitCaseButtonComponent: React.FC = () => { + const { submit, isSubmitting } = useFormContext(); + + return ( + <EuiButton + data-test-subj="create-case-submit" + fill + iconType="plusInCircle" + isDisabled={isSubmitting} + isLoading={isSubmitting} + onClick={submit} + > + {i18n.CREATE_CASE} + </EuiButton> + ); +}; + +export const SubmitCaseButton = memo(SubmitCaseButtonComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx new file mode 100644 index 0000000000000..c06ac011a035b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 } from 'enzyme'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { waitFor } from '@testing-library/react'; + +import { useForm, Form, FormHook, FIELD_TYPES } from '../../../shared_imports'; +import { useGetTags } from '../../containers/use_get_tags'; +import { Tags } from './tags'; + +jest.mock('../../containers/use_get_tags'); +const useGetTagsMock = useGetTags as jest.Mock; + +describe('Tags', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ tags: string[] }>({ + defaultValue: { tags: [] }, + schema: { + tags: { type: FIELD_TYPES.COMBO_BOX }, + }, + }); + + globalForm = form; + + return <Form form={form}>{children}</Form>; + }; + + beforeEach(() => { + jest.resetAllMocks(); + useGetTagsMock.mockReturnValue({ tags: ['test'] }); + }); + + it('it renders', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Tags isLoading={false} /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="caseTags"]`).exists()).toBeTruthy(); + }); + }); + + it('it disables the input when loading', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Tags isLoading={true} /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(EuiComboBox).prop('disabled')).toBeTruthy(); + }); + + it('it changes the tags', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Tags isLoading={false} /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange(['test', 'case'].map((tag) => ({ label: tag }))); + }); + + expect(globalForm.getFormData()).toEqual({ tags: ['test', 'case'] }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx b/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx new file mode 100644 index 0000000000000..8a7b4a6e5f760 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx @@ -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 React, { memo, useMemo } from 'react'; + +import { Field, getUseField } from '../../../shared_imports'; +import { useGetTags } from '../../containers/use_get_tags'; + +const CommonUseField = getUseField({ component: Field }); + +interface Props { + isLoading: boolean; +} + +const TagsComponent: React.FC<Props> = ({ isLoading }) => { + const { tags: tagOptions, isLoading: isLoadingTags } = useGetTags(); + const options = useMemo( + () => + tagOptions.map((label) => ({ + label, + })), + [tagOptions] + ); + + return ( + <CommonUseField + path="tags" + componentProps={{ + idAria: 'caseTags', + 'data-test-subj': 'caseTags', + euiFieldProps: { + fullWidth: true, + placeholder: '', + disabled: isLoading || isLoadingTags, + options, + noSuggestions: false, + }, + }} + /> + ); +}; + +TagsComponent.displayName = 'TagsComponent'; + +export const Tags = memo(TagsComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx new file mode 100644 index 0000000000000..54a4e665a56e5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/title.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 } from 'enzyme'; +import { act } from '@testing-library/react'; + +import { useForm, Form, FormHook } from '../../../shared_imports'; +import { Title } from './title'; + +describe('Title', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ title: string }>({ + defaultValue: { title: 'My title' }, + }); + + globalForm = form; + + return <Form form={form}>{children}</Form>; + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('it renders', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Title isLoading={false} /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(`[data-test-subj="caseTitle"]`).exists()).toBeTruthy(); + }); + + it('it disables the input when loading', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Title isLoading={true} /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(`[data-test-subj="caseTitle"] input`).prop('disabled')).toBeTruthy(); + }); + + it('it changes the title', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Title isLoading={false} /> + </MockHookWrapperComponent> + ); + + await act(async () => { + wrapper + .find(`[data-test-subj="caseTitle"] input`) + .first() + .simulate('change', { target: { value: 'My new title' } }); + }); + + expect(globalForm.getFormData()).toEqual({ title: 'My new title' }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/title.tsx b/x-pack/plugins/security_solution/public/cases/components/create/title.tsx new file mode 100644 index 0000000000000..2daeb9b738e23 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/title.tsx @@ -0,0 +1,32 @@ +/* + * 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, { memo } from 'react'; +import { Field, getUseField } from '../../../shared_imports'; + +const CommonUseField = getUseField({ component: Field }); + +interface Props { + isLoading: boolean; +} + +const TitleComponent: React.FC<Props> = ({ isLoading }) => ( + <CommonUseField + path="title" + componentProps={{ + idAria: 'caseTitle', + 'data-test-subj': 'caseTitle', + euiFieldProps: { + fullWidth: true, + disabled: isLoading, + }, + }} + /> +); + +TitleComponent.displayName = 'TitleComponent'; + +export const Title = memo(TitleComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx index 344ca88f5ab37..f5be9740bc4f1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx @@ -8,7 +8,7 @@ import React, { memo, useMemo } from 'react'; import { EuiCard, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; -import { connectorsConfiguration } from '../../../common/lib/connectors/config'; +import { connectorsConfiguration } from '../connectors'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; interface ConnectorCardProps { diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx index 87536b62747e8..79ae87355b5fb 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, Suspense, useCallback } from 'react'; +import React, { memo, Suspense } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { CaseSettingsConnector, SettingFieldsProps } from './types'; @@ -18,13 +18,6 @@ interface Props extends Omit<SettingFieldsProps<ConnectorTypeFields['fields']>, const SettingFieldsFormComponent: React.FC<Props> = ({ connector, isEdit, onChange, fields }) => { const { caseSettingsRegistry } = getCaseSettings(); - const onFieldsChange = useCallback( - (newFields) => { - onChange(newFields); - }, - [onChange] - ); - if (connector == null || connector.actionTypeId == null || connector.actionTypeId === '.none') { return null; } @@ -45,12 +38,14 @@ const SettingFieldsFormComponent: React.FC<Props> = ({ connector, isEdit, onChan </EuiFlexGroup> } > - <FieldsComponent - isEdit={isEdit} - fields={fields} - connector={connector} - onChange={onFieldsChange} - /> + <div data-test-subj={'connector-settings'}> + <FieldsComponent + isEdit={isEdit} + fields={fields} + connector={connector} + onChange={onChange} + /> + </div> </Suspense> ) : null} </> diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx index a04450b3c4198..83afa4c4f5ed3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx @@ -18,13 +18,14 @@ import { import styled, { css } from 'styled-components'; import { isEqual } from 'lodash/fp'; import * as i18n from './translations'; -import { Form, FormDataProvider, useForm } from '../../../shared_imports'; +import { Form, FormDataProvider, useForm, getUseField, Field } from '../../../shared_imports'; import { schema } from './schema'; -import { CommonUseField } from '../create'; import { useGetTags } from '../../containers/use_get_tags'; import { Tags } from './tags'; +const CommonUseField = getUseField({ component: Field }); + interface TagListProps { disabled?: boolean; isLoading: boolean; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx index 7a12f9211e969..b5885b330a822 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx @@ -22,10 +22,7 @@ export interface AllCasesModalProps { onRowClick: (id?: string) => void; } -const AllCasesModalComponent: React.FC<AllCasesModalProps> = ({ - onCloseCaseModal, - onRowClick, -}: AllCasesModalProps) => { +const AllCasesModalComponent: React.FC<AllCasesModalProps> = ({ onCloseCaseModal, onRowClick }) => { const userPermissions = useGetUserSavedObjectPermissions(); const userCanCrud = userPermissions?.crud ?? false; return ( diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx new file mode 100644 index 0000000000000..68446fc5b3171 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx @@ -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 React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; +import { + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, +} from '@elastic/eui'; + +import { FormContext } from '../create/form_context'; +import { CreateCaseForm } from '../create/form'; +import { SubmitCaseButton } from '../create/submit_button'; +import { Case } from '../../containers/types'; +import * as i18n from '../../translations'; + +export interface CreateCaseModalProps { + onCloseCaseModal: () => void; + onCaseCreated: (theCase: Case) => void; +} + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; + text-align: right; + `} +`; + +const CreateModalComponent: React.FC<CreateCaseModalProps> = ({ + onCloseCaseModal, + onCaseCreated, +}) => { + const onSuccess = useCallback( + (theCase) => { + onCaseCreated(theCase); + onCloseCaseModal(); + }, + [onCaseCreated, onCloseCaseModal] + ); + + return ( + <EuiOverlayMask data-test-subj="all-cases-modal"> + <EuiModal onClose={onCloseCaseModal}> + <EuiModalHeader> + <EuiModalHeaderTitle>{i18n.CREATE_TITLE}</EuiModalHeaderTitle> + </EuiModalHeader> + <EuiModalBody> + <FormContext onSuccess={onSuccess}> + <CreateCaseForm withSteps={false} /> + <Container> + <SubmitCaseButton /> + </Container> + </FormContext> + </EuiModalBody> + </EuiModal> + </EuiOverlayMask> + ); +}; + +export const CreateCaseModal = memo(CreateModalComponent); + +CreateCaseModal.displayName = 'CreateCaseModal'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx new file mode 100644 index 0000000000000..f07be3cc60821 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx @@ -0,0 +1,45 @@ +/* + * 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, { useState, useCallback, useMemo } from 'react'; +import { Case } from '../../containers/types'; +import { CreateCaseModal } from './create_case_modal'; + +interface Props { + onCaseCreated: (theCase: Case) => void; +} +export interface UseAllCasesModalReturnedValues { + Modal: React.FC; + isModalOpen: boolean; + closeModal: () => void; + openModal: () => void; +} + +export const useCreateCaseModal = ({ onCaseCreated }: Props) => { + const [isModalOpen, setIsModalOpen] = useState<boolean>(false); + const closeModal = useCallback(() => setIsModalOpen(false), []); + const openModal = useCallback(() => setIsModalOpen(true), []); + + const Modal: React.FC = useCallback( + () => + isModalOpen ? ( + <CreateCaseModal onCloseCaseModal={closeModal} onCaseCreated={onCaseCreated} /> + ) : null, + [closeModal, isModalOpen, onCaseCreated] + ); + + const state = useMemo( + () => ({ + Modal, + isModalOpen, + closeModal, + openModal, + }), + [isModalOpen, closeModal, openModal, Modal] + ); + + return state; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.tsx new file mode 100644 index 0000000000000..c44193dc363a4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.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 { useCallback, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { isEmpty } from 'lodash/fp'; + +import { getTimelineUrl, useFormatUrl } from '../../../common/components/link_to'; +import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; +import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline'; +import { SecurityPageName } from '../../../app/types'; +import { setInsertTimeline } from '../../../timelines/store/timeline/actions'; + +interface UseInsertTimelineReturn { + handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void; +} + +export const useInsertTimeline = ( + value: string, + onChange: (newValue: string) => void +): UseInsertTimelineReturn => { + const dispatch = useDispatch(); + const { formatUrl } = useFormatUrl(SecurityPageName.timelines); + + const insertTimeline = useShallowEqualSelector(timelineSelectors.selectInsertTimeline); + + const handleOnTimelineChange = useCallback( + (title: string, id: string | null, graphEventId?: string) => { + const url = formatUrl(getTimelineUrl(id ?? '', graphEventId), { + absolute: true, + skipSearch: true, + }); + + let newValue = `[${title}](${url})`; + // Leave a space between the previous value and the timeline url if the value is not empty. + if (!isEmpty(value)) { + newValue = `${value} ${newValue}`; + } + + onChange(newValue); + }, + [value, onChange, formatUrl] + ); + + useEffect(() => { + if (insertTimeline != null && value != null) { + dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false })); + handleOnTimelineChange( + insertTimeline.timelineTitle, + insertTimeline.timelineSavedObjectId, + insertTimeline.graphEventId + ); + dispatch(setInsertTimeline(null)); + } + }, [insertTimeline, dispatch, handleOnTimelineChange, value]); + + return { + handleOnTimelineChange, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts deleted file mode 100644 index a39e04acc1bf3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const CASE_CONNECTOR_DESC = i18n.translate( - 'xpack.securitySolution.case.components.case.selectMessageText', - { - defaultMessage: 'Create or update a case.', - } -); - -export const CASE_CONNECTOR_TITLE = i18n.translate( - 'xpack.securitySolution.case.components.case.actionTypeTitle', - { - defaultMessage: 'Cases', - } -); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index f97bec65d269a..b81be3249953e 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -59,7 +59,7 @@ import { IndexFieldsStrategyResponse, } from '../common/search_strategy/index_fields'; import { SecurityAppStore } from './common/store/store'; -import { getCaseConnectorUI } from './common/lib/connectors'; +import { getCaseConnectorUI } from './cases/components/connectors'; import { licenseService } from './common/hooks/use_license'; import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx deleted file mode 100644 index 6a65819a764eb..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useCallback, useState, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; - -import { useShallowEqualSelector } from '../../../../common/hooks/use_selector'; -import { SecurityPageName } from '../../../../../common/constants'; -import { getTimelineUrl, useFormatUrl } from '../../../../common/components/link_to'; -import { CursorPosition } from '../../../../common/components/markdown_editor'; -import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; -import { setInsertTimeline } from '../../../store/timeline/actions'; - -export const useInsertTimeline = (value: string, onChange: (newValue: string) => void) => { - const dispatch = useDispatch(); - const { formatUrl } = useFormatUrl(SecurityPageName.timelines); - const [cursorPosition, setCursorPosition] = useState<CursorPosition>({ - start: 0, - end: 0, - }); - - const insertTimeline = useShallowEqualSelector(timelineSelectors.selectInsertTimeline); - - const handleOnTimelineChange = useCallback( - (title: string, id: string | null, graphEventId?: string) => { - const url = formatUrl(getTimelineUrl(id ?? '', graphEventId), { - absolute: true, - skipSearch: true, - }); - - const newValue: string = [ - value.slice(0, cursorPosition.start), - cursorPosition.start === cursorPosition.end - ? `[${title}](${url})` - : `[${value.slice(cursorPosition.start, cursorPosition.end)}](${url})`, - value.slice(cursorPosition.end), - ].join(''); - - onChange(newValue); - }, - [value, onChange, cursorPosition, formatUrl] - ); - - const handleCursorChange = useCallback((cp: CursorPosition) => { - setCursorPosition(cp); - }, []); - - // insertTimeline selector is defined to attached a timeline to a case outside of the case page. - // FYI, if you are in the case page we only use handleOnTimelineChange to attach a timeline to a case. - useEffect(() => { - if (insertTimeline != null && value != null) { - dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false })); - handleOnTimelineChange( - insertTimeline.timelineTitle, - insertTimeline.timelineSavedObjectId, - insertTimeline.graphEventId - ); - dispatch(setInsertTimeline(null)); - } - }, [insertTimeline, dispatch, handleOnTimelineChange, value]); - - return { - cursorPosition, - handleCursorChange, - handleOnTimelineChange, - }; -}; From 00a5b60779844880762af433f2ec83976ee572b3 Mon Sep 17 00:00:00 2001 From: Constance <constancecchen@users.noreply.github.com> Date: Tue, 1 Dec 2020 14:59:27 -0800 Subject: [PATCH 20/37] Attempt to more granularly separate App Search vs Workplace Search vs shared GitHub notifications (#84713) --- .github/CODEOWNERS | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 516de74378cbc..0993876f98a6a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -262,8 +262,31 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Enterprise Search # Shared -/x-pack/plugins/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/common/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/shared/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/__mocks__/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/lib/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/__mocks__/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/lib/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/enterprise_search/ @elastic/enterprise-search-frontend /x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +# App Search +/x-pack/plugins/enterprise_search/public/applications/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/routes/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/app_search/ @elastic/app-search-frontend +# Workplace Search +/x-pack/plugins/enterprise_search/public/applications/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/routes/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/workplace_search/ @elastic/workplace-search-frontend # Elasticsearch UI /src/plugins/dev_tools/ @elastic/es-ui From 2c084b796facc19818eeb38301f36f79e506d299 Mon Sep 17 00:00:00 2001 From: Daniil <daniil_suleiman@epam.com> Date: Wed, 2 Dec 2020 11:02:31 +0300 Subject: [PATCH 21/37] [Input Control] Custom renderer (#84423) * Create custom renderer * Reduce initial bundle size * Fix tests * Add unit test * Remove injectI18n usage Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../input_control_fn.test.ts.snap | 2 +- .../public/__snapshots__/to_ast.test.ts.snap | 18 + .../public/components/editor/_index.scss | 1 - ...ontrol_editor.scss => control_editor.scss} | 0 .../components/editor/control_editor.tsx | 2 + .../components/editor/controls_tab.test.tsx | 18 +- .../public/components/editor/controls_tab.tsx | 68 +- .../public/components/editor/index.tsx | 34 + .../components/editor/options_tab.test.tsx | 6 +- .../public/components/editor/options_tab.tsx | 18 +- .../input_control_vis.test.tsx.snap | 610 +++++++++--------- .../public/components/vis/_index.scss | 1 - .../public/components/vis/_vis.scss | 5 - .../components/vis/input_control_vis.scss | 13 + .../components/vis/input_control_vis.tsx | 10 +- .../input_control_vis/public/index.scss | 9 - src/plugins/input_control_vis/public/index.ts | 2 - .../public/input_control_fn.ts | 19 +- .../public/input_control_vis_renderer.tsx | 51 ++ .../public/input_control_vis_type.ts | 14 +- .../input_control_vis/public/plugin.ts | 2 + .../input_control_vis/public/to_ast.test.ts | 54 ++ .../input_control_vis/public/to_ast.ts | 36 ++ src/plugins/input_control_vis/public/types.ts | 27 + .../public/vis_controller.tsx | 65 +- .../__snapshots__/build_pipeline.test.ts.snap | 2 - .../public/legacy/build_pipeline.test.ts | 9 - .../public/legacy/build_pipeline.ts | 3 - 28 files changed, 663 insertions(+), 436 deletions(-) create mode 100644 src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap delete mode 100644 src/plugins/input_control_vis/public/components/editor/_index.scss rename src/plugins/input_control_vis/public/components/editor/{_control_editor.scss => control_editor.scss} (100%) create mode 100644 src/plugins/input_control_vis/public/components/editor/index.tsx delete mode 100644 src/plugins/input_control_vis/public/components/vis/_index.scss delete mode 100644 src/plugins/input_control_vis/public/components/vis/_vis.scss create mode 100644 src/plugins/input_control_vis/public/components/vis/input_control_vis.scss delete mode 100644 src/plugins/input_control_vis/public/index.scss create mode 100644 src/plugins/input_control_vis/public/input_control_vis_renderer.tsx create mode 100644 src/plugins/input_control_vis/public/to_ast.test.ts create mode 100644 src/plugins/input_control_vis/public/to_ast.ts create mode 100644 src/plugins/input_control_vis/public/types.ts diff --git a/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap b/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap index 35349b4719676..696b74d040e0c 100644 --- a/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap +++ b/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap @@ -2,7 +2,7 @@ exports[`interpreter/functions#input_control_vis returns an object with the correct structure 1`] = ` Object { - "as": "visualization", + "as": "input_control_vis", "type": "render", "value": Object { "visConfig": Object { diff --git a/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap new file mode 100644 index 0000000000000..edd44d8dd0337 --- /dev/null +++ b/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`input_control_vis toExpressionAst should build an expression based on vis.params 1`] = ` +Object { + "chain": Array [ + Object { + "arguments": Object { + "visConfig": Array [ + "{\\"controls\\":[{\\"id\\":\\"1536977437774\\",\\"fieldName\\":\\"manufacturer.keyword\\",\\"parent\\":\\"\\",\\"label\\":\\"Manufacturer\\",\\"type\\":\\"list\\",\\"options\\":{\\"type\\":\\"terms\\",\\"multiselect\\":true,\\"dynamicOptions\\":true,\\"size\\":5,\\"order\\":\\"desc\\"},\\"indexPattern\\":\\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\\"}],\\"updateFiltersOnChange\\":false,\\"useTimeFilter\\":true,\\"pinFilters\\":false}", + ], + }, + "function": "input_control_vis", + "type": "function", + }, + ], + "type": "expression", +} +`; diff --git a/src/plugins/input_control_vis/public/components/editor/_index.scss b/src/plugins/input_control_vis/public/components/editor/_index.scss deleted file mode 100644 index 9af8f8d6e8222..0000000000000 --- a/src/plugins/input_control_vis/public/components/editor/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './control_editor'; diff --git a/src/plugins/input_control_vis/public/components/editor/_control_editor.scss b/src/plugins/input_control_vis/public/components/editor/control_editor.scss similarity index 100% rename from src/plugins/input_control_vis/public/components/editor/_control_editor.scss rename to src/plugins/input_control_vis/public/components/editor/control_editor.scss diff --git a/src/plugins/input_control_vis/public/components/editor/control_editor.tsx b/src/plugins/input_control_vis/public/components/editor/control_editor.tsx index aa473095aaf3f..109237f8db4ec 100644 --- a/src/plugins/input_control_vis/public/components/editor/control_editor.tsx +++ b/src/plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -36,6 +36,8 @@ import { getTitle, ControlParams, CONTROL_TYPES, ControlParamsOptions } from '.. import { IIndexPattern } from '../../../../data/public'; import { InputControlVisDependencies } from '../../plugin'; +import './control_editor.scss'; + interface ControlEditorUiProps { controlIndex: number; controlParams: ControlParams; diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index a85f98c7b89ba..c05dec8fccbe1 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -21,16 +21,16 @@ import React from 'react'; import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; import { findTestSubject } from '@elastic/eui/lib/test'; import { getDepsMock, getIndexPatternMock } from '../../test_utils'; -import { ControlsTab, ControlsTabUiProps } from './controls_tab'; +import ControlsTab, { ControlsTabProps } from './controls_tab'; import { Vis } from '../../../../visualizations/public'; const indexPatternsMock = { get: getIndexPatternMock, }; -let props: ControlsTabUiProps; +let props: ControlsTabProps; beforeEach(() => { - props = { + props = ({ deps: getDepsMock(), vis: ({ API: { @@ -78,18 +78,18 @@ beforeEach(() => { }, setValue: jest.fn(), intl: null as any, - }; + } as unknown) as ControlsTabProps; }); test('renders ControlsTab', () => { - const component = shallowWithIntl(<ControlsTab.WrappedComponent {...props} />); + const component = shallowWithIntl(<ControlsTab {...props} />); expect(component).toMatchSnapshot(); }); describe('behavior', () => { test('add control button', () => { - const component = mountWithIntl(<ControlsTab.WrappedComponent {...props} />); + const component = mountWithIntl(<ControlsTab {...props} />); findTestSubject(component, 'inputControlEditorAddBtn').simulate('click'); @@ -102,7 +102,7 @@ describe('behavior', () => { }); test('remove control button', () => { - const component = mountWithIntl(<ControlsTab.WrappedComponent {...props} />); + const component = mountWithIntl(<ControlsTab {...props} />); findTestSubject(component, 'inputControlEditorRemoveControl0').simulate('click'); const expectedParams = [ 'controls', @@ -125,7 +125,7 @@ describe('behavior', () => { }); test('move down control button', () => { - const component = mountWithIntl(<ControlsTab.WrappedComponent {...props} />); + const component = mountWithIntl(<ControlsTab {...props} />); findTestSubject(component, 'inputControlEditorMoveDownControl0').simulate('click'); const expectedParams = [ 'controls', @@ -162,7 +162,7 @@ describe('behavior', () => { }); test('move up control button', () => { - const component = mountWithIntl(<ControlsTab.WrappedComponent {...props} />); + const component = mountWithIntl(<ControlsTab {...props} />); findTestSubject(component, 'inputControlEditorMoveUpControl1').simulate('click'); const expectedParams = [ 'controls', diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx index a9f04a86f8d03..0e622e08c529f 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -18,7 +18,8 @@ */ import React, { PureComponent } from 'react'; -import { injectI18n, FormattedMessage, InjectedIntlProps } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -44,22 +45,17 @@ import { } from '../../editor_utils'; import { getLineageMap, getParentCandidates } from '../../lineage'; import { InputControlVisDependencies } from '../../plugin'; +import { InputControlVisParams } from '../../types'; interface ControlsTabUiState { type: CONTROL_TYPES; } -interface ControlsTabUiParams { - controls: ControlParams[]; -} -type ControlsTabUiInjectedProps = InjectedIntlProps & - Pick<VisOptionsProps<ControlsTabUiParams>, 'vis' | 'stateParams' | 'setValue'> & { - deps: InputControlVisDependencies; - }; +export type ControlsTabProps = VisOptionsProps<InputControlVisParams> & { + deps: InputControlVisDependencies; +}; -export type ControlsTabUiProps = ControlsTabUiInjectedProps; - -class ControlsTabUi extends PureComponent<ControlsTabUiProps, ControlsTabUiState> { +class ControlsTab extends PureComponent<ControlsTabProps, ControlsTabUiState> { state = { type: CONTROL_TYPES.LIST, }; @@ -161,8 +157,6 @@ class ControlsTabUi extends PureComponent<ControlsTabUiProps, ControlsTabUiState } render() { - const { intl } = this.props; - return ( <div> {this.renderControls()} @@ -176,25 +170,31 @@ class ControlsTabUi extends PureComponent<ControlsTabUiProps, ControlsTabUiState options={[ { value: CONTROL_TYPES.RANGE, - text: intl.formatMessage({ - id: 'inputControl.editor.controlsTab.select.rangeDropDownOptionLabel', - defaultMessage: 'Range slider', - }), + text: i18n.translate( + 'inputControl.editor.controlsTab.select.rangeDropDownOptionLabel', + { + defaultMessage: 'Range slider', + } + ), }, { value: CONTROL_TYPES.LIST, - text: intl.formatMessage({ - id: 'inputControl.editor.controlsTab.select.listDropDownOptionLabel', - defaultMessage: 'Options list', - }), + text: i18n.translate( + 'inputControl.editor.controlsTab.select.listDropDownOptionLabel', + { + defaultMessage: 'Options list', + } + ), }, ]} value={this.state.type} onChange={(event) => this.setState({ type: event.target.value as CONTROL_TYPES })} - aria-label={intl.formatMessage({ - id: 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', - defaultMessage: 'Select control type', - })} + aria-label={i18n.translate( + 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', + { + defaultMessage: 'Select control type', + } + )} /> </EuiFormRow> </EuiFlexItem> @@ -205,10 +205,12 @@ class ControlsTabUi extends PureComponent<ControlsTabUiProps, ControlsTabUiState onClick={this.handleAddControl} iconType="plusInCircle" data-test-subj="inputControlEditorAddBtn" - aria-label={intl.formatMessage({ - id: 'inputControl.editor.controlsTab.select.addControlAriaLabel', - defaultMessage: 'Add control', - })} + aria-label={i18n.translate( + 'inputControl.editor.controlsTab.select.addControlAriaLabel', + { + defaultMessage: 'Add control', + } + )} > <FormattedMessage id="inputControl.editor.controlsTab.addButtonLabel" @@ -224,8 +226,6 @@ class ControlsTabUi extends PureComponent<ControlsTabUiProps, ControlsTabUiState } } -export const ControlsTab = injectI18n(ControlsTabUi); - -export const getControlsTab = (deps: InputControlVisDependencies) => ( - props: Omit<ControlsTabUiProps, 'core'> -) => <ControlsTab {...props} deps={deps} />; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { ControlsTab as default }; diff --git a/src/plugins/input_control_vis/public/components/editor/index.tsx b/src/plugins/input_control_vis/public/components/editor/index.tsx new file mode 100644 index 0000000000000..11b3c2ea4ee8a --- /dev/null +++ b/src/plugins/input_control_vis/public/components/editor/index.tsx @@ -0,0 +1,34 @@ +/* + * 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, { lazy } from 'react'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { InputControlVisDependencies } from '../../plugin'; +import { InputControlVisParams } from '../../types'; + +const ControlsTab = lazy(() => import('./controls_tab')); +const OptionsTab = lazy(() => import('./options_tab')); + +export const getControlsTab = (deps: InputControlVisDependencies) => ( + props: VisOptionsProps<InputControlVisParams> +) => <ControlsTab {...props} deps={deps} />; + +export const OptionsTabLazy = (props: VisOptionsProps<InputControlVisParams>) => ( + <OptionsTab {...props} /> +); diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx index 0f126e915a68c..0970d1cd3c298 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -22,13 +22,13 @@ import { shallow } from 'enzyme'; import { mountWithIntl } from '@kbn/test/jest'; import { Vis } from '../../../../visualizations/public'; -import { OptionsTab, OptionsTabProps } from './options_tab'; +import OptionsTab, { OptionsTabProps } from './options_tab'; describe('OptionsTab', () => { let props: OptionsTabProps; beforeEach(() => { - props = { + props = ({ vis: {} as Vis, stateParams: { updateFiltersOnChange: false, @@ -36,7 +36,7 @@ describe('OptionsTab', () => { pinFilters: false, }, setValue: jest.fn(), - }; + } as unknown) as OptionsTabProps; }); it('should renders OptionsTab', () => { diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.tsx index cdff6cabad8ba..306d1141e75bd 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.tsx @@ -24,20 +24,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSwitchEvent } from '@elastic/eui'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { InputControlVisParams } from '../../types'; -interface OptionsTabParams { - updateFiltersOnChange: boolean; - useTimeFilter: boolean; - pinFilters: boolean; -} -type OptionsTabInjectedProps = Pick< - VisOptionsProps<OptionsTabParams>, - 'vis' | 'setValue' | 'stateParams' ->; - -export type OptionsTabProps = OptionsTabInjectedProps; +export type OptionsTabProps = VisOptionsProps<InputControlVisParams>; -export class OptionsTab extends PureComponent<OptionsTabProps> { +class OptionsTab extends PureComponent<OptionsTabProps> { handleUpdateFiltersChange = (event: EuiSwitchEvent) => { this.props.setValue('updateFiltersOnChange', event.target.checked); }; @@ -98,3 +89,6 @@ export class OptionsTab extends PureComponent<OptionsTabProps> { ); } } +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { OptionsTab as default }; diff --git a/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap b/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap index 5a76967c71fbb..5e1f25993616b 100644 --- a/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap +++ b/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap @@ -2,355 +2,371 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = ` <div - className="icvContainer" + className="icvContainer__wrapper" > - <EuiFlexGroup - wrap={true} + <div + className="icvContainer" > - <EuiFlexItem - data-test-subj="inputControlItem" - key="mock-list-control" - style={ - Object { - "minWidth": "250px", - } - } + <EuiFlexGroup + wrap={true} > - <InjectIntl(ListControlUi) - controlIndex={0} - fetchOptions={[Function]} - formatOptionLabel={[Function]} - id="mock-list-control" - label="list control" - multiselect={true} - options={ - Array [ - "choice1", - "choice2", - ] + <EuiFlexItem + data-test-subj="inputControlItem" + key="mock-list-control" + style={ + Object { + "minWidth": "250px", + } } - selectedOptions={Array []} - stageFilter={[Function]} - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup - wrap={true} - > - <EuiFlexItem - grow={false} - > - <EuiButton - data-test-subj="inputControlSubmitBtn" - disabled={false} - fill={true} - onClick={[Function]} > - <FormattedMessage - defaultMessage="Apply changes" - id="inputControl.vis.inputControlVis.applyChangesButtonLabel" - values={Object {}} + <InjectIntl(ListControlUi) + controlIndex={0} + fetchOptions={[Function]} + formatOptionLabel={[Function]} + id="mock-list-control" + label="list control" + multiselect={true} + options={ + Array [ + "choice1", + "choice2", + ] + } + selectedOptions={Array []} + stageFilter={[Function]} /> - </EuiButton> - </EuiFlexItem> - <EuiFlexItem - grow={false} + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup + wrap={true} > - <EuiButtonEmpty - data-test-subj="inputControlCancelBtn" - disabled={false} - onClick={[Function]} + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Cancel changes" - id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem - grow={false} - > - <EuiButtonEmpty - data-test-subj="inputControlClearBtn" - disabled={true} - onClick={[Function]} + <EuiButton + data-test-subj="inputControlSubmitBtn" + disabled={false} + fill={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Apply changes" + id="inputControl.vis.inputControlVis.applyChangesButtonLabel" + values={Object {}} + /> + </EuiButton> + </EuiFlexItem> + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Clear form" - id="inputControl.vis.inputControlVis.clearFormButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> + <EuiButtonEmpty + data-test-subj="inputControlCancelBtn" + disabled={false} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Cancel changes" + id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem + grow={false} + > + <EuiButtonEmpty + data-test-subj="inputControlClearBtn" + disabled={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Clear form" + id="inputControl.vis.inputControlVis.clearFormButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + </div> </div> `; exports[`Clear btns enabled when there are values 1`] = ` <div - className="icvContainer" + className="icvContainer__wrapper" > - <EuiFlexGroup - wrap={true} + <div + className="icvContainer" > - <EuiFlexItem - data-test-subj="inputControlItem" - key="mock-list-control" - style={ - Object { - "minWidth": "250px", - } - } + <EuiFlexGroup + wrap={true} > - <InjectIntl(ListControlUi) - controlIndex={0} - fetchOptions={[Function]} - formatOptionLabel={[Function]} - id="mock-list-control" - label="list control" - multiselect={true} - options={ - Array [ - "choice1", - "choice2", - ] + <EuiFlexItem + data-test-subj="inputControlItem" + key="mock-list-control" + style={ + Object { + "minWidth": "250px", + } } - selectedOptions={Array []} - stageFilter={[Function]} - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup - wrap={true} - > - <EuiFlexItem - grow={false} - > - <EuiButton - data-test-subj="inputControlSubmitBtn" - disabled={true} - fill={true} - onClick={[Function]} > - <FormattedMessage - defaultMessage="Apply changes" - id="inputControl.vis.inputControlVis.applyChangesButtonLabel" - values={Object {}} + <InjectIntl(ListControlUi) + controlIndex={0} + fetchOptions={[Function]} + formatOptionLabel={[Function]} + id="mock-list-control" + label="list control" + multiselect={true} + options={ + Array [ + "choice1", + "choice2", + ] + } + selectedOptions={Array []} + stageFilter={[Function]} /> - </EuiButton> - </EuiFlexItem> - <EuiFlexItem - grow={false} + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup + wrap={true} > - <EuiButtonEmpty - data-test-subj="inputControlCancelBtn" - disabled={true} - onClick={[Function]} + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Cancel changes" - id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem - grow={false} - > - <EuiButtonEmpty - data-test-subj="inputControlClearBtn" - disabled={false} - onClick={[Function]} + <EuiButton + data-test-subj="inputControlSubmitBtn" + disabled={true} + fill={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Apply changes" + id="inputControl.vis.inputControlVis.applyChangesButtonLabel" + values={Object {}} + /> + </EuiButton> + </EuiFlexItem> + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Clear form" - id="inputControl.vis.inputControlVis.clearFormButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> + <EuiButtonEmpty + data-test-subj="inputControlCancelBtn" + disabled={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Cancel changes" + id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem + grow={false} + > + <EuiButtonEmpty + data-test-subj="inputControlClearBtn" + disabled={false} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Clear form" + id="inputControl.vis.inputControlVis.clearFormButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + </div> </div> `; exports[`Renders list control 1`] = ` <div - className="icvContainer" + className="icvContainer__wrapper" > - <EuiFlexGroup - wrap={true} + <div + className="icvContainer" > - <EuiFlexItem - data-test-subj="inputControlItem" - key="mock-list-control" - style={ - Object { - "minWidth": "250px", - } - } + <EuiFlexGroup + wrap={true} > - <InjectIntl(ListControlUi) - controlIndex={0} - fetchOptions={[Function]} - formatOptionLabel={[Function]} - id="mock-list-control" - label="list control" - multiselect={true} - options={ - Array [ - "choice1", - "choice2", - ] + <EuiFlexItem + data-test-subj="inputControlItem" + key="mock-list-control" + style={ + Object { + "minWidth": "250px", + } } - selectedOptions={Array []} - stageFilter={[Function]} - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup - wrap={true} - > - <EuiFlexItem - grow={false} - > - <EuiButton - data-test-subj="inputControlSubmitBtn" - disabled={true} - fill={true} - onClick={[Function]} > - <FormattedMessage - defaultMessage="Apply changes" - id="inputControl.vis.inputControlVis.applyChangesButtonLabel" - values={Object {}} + <InjectIntl(ListControlUi) + controlIndex={0} + fetchOptions={[Function]} + formatOptionLabel={[Function]} + id="mock-list-control" + label="list control" + multiselect={true} + options={ + Array [ + "choice1", + "choice2", + ] + } + selectedOptions={Array []} + stageFilter={[Function]} /> - </EuiButton> - </EuiFlexItem> - <EuiFlexItem - grow={false} + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup + wrap={true} > - <EuiButtonEmpty - data-test-subj="inputControlCancelBtn" - disabled={true} - onClick={[Function]} + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Cancel changes" - id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem - grow={false} - > - <EuiButtonEmpty - data-test-subj="inputControlClearBtn" - disabled={true} - onClick={[Function]} + <EuiButton + data-test-subj="inputControlSubmitBtn" + disabled={true} + fill={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Apply changes" + id="inputControl.vis.inputControlVis.applyChangesButtonLabel" + values={Object {}} + /> + </EuiButton> + </EuiFlexItem> + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Clear form" - id="inputControl.vis.inputControlVis.clearFormButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> + <EuiButtonEmpty + data-test-subj="inputControlCancelBtn" + disabled={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Cancel changes" + id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem + grow={false} + > + <EuiButtonEmpty + data-test-subj="inputControlClearBtn" + disabled={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Clear form" + id="inputControl.vis.inputControlVis.clearFormButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + </div> </div> `; exports[`Renders range control 1`] = ` <div - className="icvContainer" + className="icvContainer__wrapper" > - <EuiFlexGroup - wrap={true} + <div + className="icvContainer" > - <EuiFlexItem - data-test-subj="inputControlItem" - key="mock-range-control" - style={ - Object { - "minWidth": "250px", - } - } + <EuiFlexGroup + wrap={true} > - <RangeControl - control={ + <EuiFlexItem + data-test-subj="inputControlItem" + key="mock-range-control" + style={ Object { - "format": [Function], - "id": "mock-range-control", - "isEnabled": [Function], - "label": "range control", - "max": 100, - "min": 0, - "options": Object { - "decimalPlaces": 0, - "step": 1, - }, - "type": "range", - "value": Object { - "max": 0, - "min": 0, - }, + "minWidth": "250px", } } - controlIndex={0} - stageFilter={[Function]} - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup - wrap={true} - > - <EuiFlexItem - grow={false} - > - <EuiButton - data-test-subj="inputControlSubmitBtn" - disabled={true} - fill={true} - onClick={[Function]} > - <FormattedMessage - defaultMessage="Apply changes" - id="inputControl.vis.inputControlVis.applyChangesButtonLabel" - values={Object {}} + <RangeControl + control={ + Object { + "format": [Function], + "id": "mock-range-control", + "isEnabled": [Function], + "label": "range control", + "max": 100, + "min": 0, + "options": Object { + "decimalPlaces": 0, + "step": 1, + }, + "type": "range", + "value": Object { + "max": 0, + "min": 0, + }, + } + } + controlIndex={0} + stageFilter={[Function]} /> - </EuiButton> - </EuiFlexItem> - <EuiFlexItem - grow={false} + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup + wrap={true} > - <EuiButtonEmpty - data-test-subj="inputControlCancelBtn" - disabled={true} - onClick={[Function]} + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Cancel changes" - id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem - grow={false} - > - <EuiButtonEmpty - data-test-subj="inputControlClearBtn" - disabled={true} - onClick={[Function]} + <EuiButton + data-test-subj="inputControlSubmitBtn" + disabled={true} + fill={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Apply changes" + id="inputControl.vis.inputControlVis.applyChangesButtonLabel" + values={Object {}} + /> + </EuiButton> + </EuiFlexItem> + <EuiFlexItem + grow={false} > - <FormattedMessage - defaultMessage="Clear form" - id="inputControl.vis.inputControlVis.clearFormButtonLabel" - values={Object {}} - /> - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> + <EuiButtonEmpty + data-test-subj="inputControlCancelBtn" + disabled={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Cancel changes" + id="inputControl.vis.inputControlVis.cancelChangesButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem + grow={false} + > + <EuiButtonEmpty + data-test-subj="inputControlClearBtn" + disabled={true} + onClick={[Function]} + > + <FormattedMessage + defaultMessage="Clear form" + id="inputControl.vis.inputControlVis.clearFormButtonLabel" + values={Object {}} + /> + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + </div> </div> `; diff --git a/src/plugins/input_control_vis/public/components/vis/_index.scss b/src/plugins/input_control_vis/public/components/vis/_index.scss deleted file mode 100644 index a428a7c1782e3..0000000000000 --- a/src/plugins/input_control_vis/public/components/vis/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './vis'; diff --git a/src/plugins/input_control_vis/public/components/vis/_vis.scss b/src/plugins/input_control_vis/public/components/vis/_vis.scss deleted file mode 100644 index d42c2c5f263c7..0000000000000 --- a/src/plugins/input_control_vis/public/components/vis/_vis.scss +++ /dev/null @@ -1,5 +0,0 @@ -.icvContainer { - width: 100%; - margin: 0 $euiSizeXS; - padding: $euiSizeS; -} diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss b/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss new file mode 100644 index 0000000000000..322573446f762 --- /dev/null +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss @@ -0,0 +1,13 @@ +.icvContainer__wrapper { + @include euiScrollBar; + min-height: 0; + flex: 1 1 0; + display: flex; + overflow: auto; +} + +.icvContainer { + width: 100%; + margin: 0 $euiSizeXS; + padding: $euiSizeS; +} diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx index 95edb4a35bc22..058f39cb8a6d4 100644 --- a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx @@ -26,6 +26,8 @@ import { RangeControl } from '../../control/range_control_factory'; import { ListControl as ListControlComponent } from './list_control'; import { RangeControl as RangeControlComponent } from './range_control'; +import './input_control_vis.scss'; + function isListControl(control: RangeControl | ListControl): control is ListControl { return control.type === CONTROL_TYPES.LIST; } @@ -165,9 +167,11 @@ export class InputControlVis extends Component<InputControlVisProps> { } return ( - <div className="icvContainer"> - <EuiFlexGroup wrap>{this.renderControls()}</EuiFlexGroup> - {stagingButtons} + <div className="icvContainer__wrapper"> + <div className="icvContainer"> + <EuiFlexGroup wrap>{this.renderControls()}</EuiFlexGroup> + {stagingButtons} + </div> </div> ); } diff --git a/src/plugins/input_control_vis/public/index.scss b/src/plugins/input_control_vis/public/index.scss deleted file mode 100644 index 42fded23d7761..0000000000000 --- a/src/plugins/input_control_vis/public/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Prefix all styles with "icv" to avoid conflicts. -// Examples -// icvChart -// icvChart__legend -// icvChart__legend--small -// icvChart__legend-isLoading - -@import './components/editor/index'; -@import './components/vis/index'; diff --git a/src/plugins/input_control_vis/public/index.ts b/src/plugins/input_control_vis/public/index.ts index 8edd3fd9996c3..b6fee12f6d9cb 100644 --- a/src/plugins/input_control_vis/public/index.ts +++ b/src/plugins/input_control_vis/public/index.ts @@ -17,8 +17,6 @@ * under the License. */ -import './index.scss'; - import { PluginInitializerContext } from '../../../core/public'; import { InputControlVisPlugin as Plugin } from './plugin'; diff --git a/src/plugins/input_control_vis/public/input_control_fn.ts b/src/plugins/input_control_vis/public/input_control_fn.ts index 1664555b916b6..46fba66264bcb 100644 --- a/src/plugins/input_control_vis/public/input_control_fn.ts +++ b/src/plugins/input_control_vis/public/input_control_fn.ts @@ -20,24 +20,25 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; +import { InputControlVisParams } from './types'; interface Arguments { visConfig: string; } -type VisParams = Required<Arguments>; - -interface RenderValue { +export interface InputControlRenderValue { visType: 'input_control_vis'; - visConfig: VisParams; + visConfig: InputControlVisParams; } -export const createInputControlVisFn = (): ExpressionFunctionDefinition< +export type InputControlExpressionFunctionDefinition = ExpressionFunctionDefinition< 'input_control_vis', Datatable, Arguments, - Render<RenderValue> -> => ({ + Render<InputControlRenderValue> +>; + +export const createInputControlVisFn = (): InputControlExpressionFunctionDefinition => ({ name: 'input_control_vis', type: 'render', inputTypes: [], @@ -52,10 +53,10 @@ export const createInputControlVisFn = (): ExpressionFunctionDefinition< }, }, fn(input, args) { - const params = JSON.parse(args.visConfig); + const params: InputControlVisParams = JSON.parse(args.visConfig); return { type: 'render', - as: 'visualization', + as: 'input_control_vis', value: { visType: 'input_control_vis', visConfig: params, diff --git a/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx new file mode 100644 index 0000000000000..6431ed6ebed1e --- /dev/null +++ b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx @@ -0,0 +1,51 @@ +/* + * 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 { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { InputControlVisDependencies } from './plugin'; +import { InputControlRenderValue } from './input_control_fn'; +import type { InputControlVisControllerType } from './vis_controller'; + +const inputControlVisRegistry = new Map<HTMLElement, InputControlVisControllerType>(); + +export const getInputControlVisRenderer: ( + deps: InputControlVisDependencies +) => ExpressionRenderDefinition<InputControlRenderValue> = (deps) => ({ + name: 'input_control_vis', + reuseDomNode: true, + render: async (domNode, { visConfig }, handlers) => { + let registeredController = inputControlVisRegistry.get(domNode); + + if (!registeredController) { + const { createInputControlVisController } = await import('./vis_controller'); + + const Controller = createInputControlVisController(deps, handlers); + registeredController = new Controller(domNode); + inputControlVisRegistry.set(domNode, registeredController); + + handlers.onDestroy(() => { + registeredController?.destroy(); + inputControlVisRegistry.delete(domNode); + }); + } + + await registeredController.render(visConfig); + handlers.done(); + }, +}); diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts index 6e33e18c1603b..686327a1ba774 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts @@ -19,15 +19,14 @@ import { i18n } from '@kbn/i18n'; import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public'; -import { createInputControlVisController } from './vis_controller'; -import { getControlsTab } from './components/editor/controls_tab'; -import { OptionsTab } from './components/editor/options_tab'; +import { getControlsTab, OptionsTabLazy } from './components/editor'; import { InputControlVisDependencies } from './plugin'; +import { toExpressionAst } from './to_ast'; +import { InputControlVisParams } from './types'; export function createInputControlVisTypeDefinition( deps: InputControlVisDependencies -): BaseVisTypeOptions { - const InputControlVisController = createInputControlVisController(deps); +): BaseVisTypeOptions<InputControlVisParams> { const ControlsTab = getControlsTab(deps); return { @@ -41,7 +40,6 @@ export function createInputControlVisTypeDefinition( defaultMessage: 'Add dropdown menus and range sliders to your dashboard.', }), stage: 'experimental', - visualization: InputControlVisController, visConfig: { defaults: { controls: [], @@ -64,12 +62,12 @@ export function createInputControlVisTypeDefinition( title: i18n.translate('inputControl.register.tabs.optionsTitle', { defaultMessage: 'Options', }), - editor: OptionsTab, + editor: OptionsTabLazy, }, ], }, inspectorAdapters: {}, requestHandler: 'none', - responseHandler: 'none', + toExpressionAst, }; } diff --git a/src/plugins/input_control_vis/public/plugin.ts b/src/plugins/input_control_vis/public/plugin.ts index 2c93a529c25b1..afaaa27d74c82 100644 --- a/src/plugins/input_control_vis/public/plugin.ts +++ b/src/plugins/input_control_vis/public/plugin.ts @@ -22,6 +22,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/p import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; import { createInputControlVisFn } from './input_control_fn'; +import { getInputControlVisRenderer } from './input_control_vis_renderer'; import { createInputControlVisTypeDefinition } from './input_control_vis_type'; type InputControlVisCoreSetup = CoreSetup<InputControlVisPluginStartDependencies, void>; @@ -76,6 +77,7 @@ export class InputControlVisPlugin implements Plugin<void, void> { }; expressions.registerFunction(createInputControlVisFn); + expressions.registerRenderer(getInputControlVisRenderer(visualizationDependencies)); visualizations.createBaseVisualization( createInputControlVisTypeDefinition(visualizationDependencies) ); diff --git a/src/plugins/input_control_vis/public/to_ast.test.ts b/src/plugins/input_control_vis/public/to_ast.test.ts new file mode 100644 index 0000000000000..fbeb78ee93a1e --- /dev/null +++ b/src/plugins/input_control_vis/public/to_ast.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { Vis } from '../../visualizations/public'; +import { InputControlVisParams } from './types'; +import { toExpressionAst } from './to_ast'; + +describe('input_control_vis toExpressionAst', () => { + const vis = { + params: { + controls: [ + { + id: '1536977437774', + fieldName: 'manufacturer.keyword', + parent: '', + label: 'Manufacturer', + type: 'list', + options: { + type: 'terms', + multiselect: true, + dynamicOptions: true, + size: 5, + order: 'desc', + }, + indexPattern: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + ], + updateFiltersOnChange: false, + useTimeFilter: true, + pinFilters: false, + }, + } as Vis<InputControlVisParams>; + + it('should build an expression based on vis.params', () => { + const expression = toExpressionAst(vis); + expect(expression).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/input_control_vis/public/to_ast.ts b/src/plugins/input_control_vis/public/to_ast.ts new file mode 100644 index 0000000000000..93c0b4a87cfe6 --- /dev/null +++ b/src/plugins/input_control_vis/public/to_ast.ts @@ -0,0 +1,36 @@ +/* + * 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 { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { Vis } from '../../visualizations/public'; +import { InputControlExpressionFunctionDefinition } from './input_control_fn'; +import { InputControlVisParams } from './types'; + +export const toExpressionAst = (vis: Vis<InputControlVisParams>) => { + const inputControl = buildExpressionFunction<InputControlExpressionFunctionDefinition>( + 'input_control_vis', + { + visConfig: JSON.stringify(vis.params), + } + ); + + const ast = buildExpression([inputControl]); + + return ast.toAst(); +}; diff --git a/src/plugins/input_control_vis/public/types.ts b/src/plugins/input_control_vis/public/types.ts new file mode 100644 index 0000000000000..2898ab49590ed --- /dev/null +++ b/src/plugins/input_control_vis/public/types.ts @@ -0,0 +1,27 @@ +/* + * 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 { ControlParams } from './editor_utils'; + +export interface InputControlVisParams { + controls: ControlParams[]; + pinFilters: boolean; + updateFiltersOnChange: boolean; + useTimeFilter: boolean; +} diff --git a/src/plugins/input_control_vis/public/vis_controller.tsx b/src/plugins/input_control_vis/public/vis_controller.tsx index 6f35e17866120..8e762a38671e9 100644 --- a/src/plugins/input_control_vis/public/vis_controller.tsx +++ b/src/plugins/input_control_vis/public/vis_controller.tsx @@ -20,20 +20,29 @@ import React from 'react'; import { isEqual } from 'lodash'; import { render, unmountComponentAtNode } from 'react-dom'; - import { Subscription } from 'rxjs'; + import { I18nStart } from 'kibana/public'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { VisualizationContainer } from '../../visualizations/public'; +import { FilterManager, Filter } from '../../data/public'; + import { InputControlVis } from './components/vis/input_control_vis'; import { getControlFactory } from './control/control_factory'; import { getLineageMap } from './lineage'; -import { ControlParams } from './editor_utils'; import { RangeControl } from './control/range_control_factory'; import { ListControl } from './control/list_control_factory'; import { InputControlVisDependencies } from './plugin'; -import { FilterManager, Filter } from '../../data/public'; -import { VisParams, ExprVis } from '../../visualizations/public'; +import { InputControlVisParams } from './types'; -export const createInputControlVisController = (deps: InputControlVisDependencies) => { +export type InputControlVisControllerType = InstanceType< + ReturnType<typeof createInputControlVisController> +>; + +export const createInputControlVisController = ( + deps: InputControlVisDependencies, + handlers: IInterpreterRenderHandlers +) => { return class InputControlVisController { private I18nContext?: I18nStart['Context']; private _isLoaded = false; @@ -43,9 +52,9 @@ export const createInputControlVisController = (deps: InputControlVisDependencie filterManager: FilterManager; updateSubsciption: any; timeFilterSubscription: Subscription; - visParams?: VisParams; + visParams?: InputControlVisParams; - constructor(public el: Element, public vis: ExprVis) { + constructor(public el: Element) { this.controls = []; this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); @@ -63,7 +72,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie }); } - async render(visData: any, visParams: VisParams) { + async render(visParams: InputControlVisParams) { if (!this.I18nContext) { const [{ i18n }] = await deps.core.getStartServices(); this.I18nContext = i18n.Context; @@ -71,7 +80,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie if (!this._isLoaded || !isEqual(visParams, this.visParams)) { this.visParams = visParams; this.controls = []; - this.controls = await this.initControls(); + this.controls = await this.initControls(visParams); this._isLoaded = true; } this.drawVis(); @@ -91,34 +100,34 @@ export const createInputControlVisController = (deps: InputControlVisDependencie render( <this.I18nContext> - <InputControlVis - updateFiltersOnChange={this.visParams?.updateFiltersOnChange} - controls={this.controls} - stageFilter={this.stageFilter} - submitFilters={this.submitFilters} - resetControls={this.updateControlsFromKbn} - clearControls={this.clearControls} - hasChanges={this.hasChanges} - hasValues={this.hasValues} - refreshControl={this.refreshControl} - /> + <VisualizationContainer handlers={handlers}> + <InputControlVis + updateFiltersOnChange={this.visParams?.updateFiltersOnChange} + controls={this.controls} + stageFilter={this.stageFilter} + submitFilters={this.submitFilters} + resetControls={this.updateControlsFromKbn} + clearControls={this.clearControls} + hasChanges={this.hasChanges} + hasValues={this.hasValues} + refreshControl={this.refreshControl} + /> + </VisualizationContainer> </this.I18nContext>, this.el ); }; - async initControls() { - const controlParamsList = (this.visParams?.controls as ControlParams[])?.filter( - (controlParams) => { - // ignore controls that do not have indexPattern or field - return controlParams.indexPattern && controlParams.fieldName; - } - ); + async initControls(visParams: InputControlVisParams) { + const controlParamsList = visParams.controls.filter((controlParams) => { + // ignore controls that do not have indexPattern or field + return controlParams.indexPattern && controlParams.fieldName; + }); const controlFactoryPromises = controlParamsList.map((controlParams) => { const factory = getControlFactory(controlParams); - return factory(controlParams, this.visParams?.useTimeFilter, deps); + return factory(controlParams, visParams.useTimeFilter, deps); }); const controls = await Promise.all<RangeControl | ListControl>(controlFactoryPromises); diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index 03a355c604c4d..3ff0c83961e2a 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -2,8 +2,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles input_control_vis function 1`] = `"input_control_vis visConfig='{\\"some\\":\\"nested\\",\\"data\\":{\\"here\\":true}}' "`; - exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index 653542bd8837d..57c58a99f09ea 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -92,15 +92,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { uiState = {}; }); - it('handles input_control_vis function', () => { - const params = { - some: 'nested', - data: { here: true }, - }; - const actual = buildPipelineVisFunction.input_control_vis(params, schemasDef, uiState); - expect(actual).toMatchSnapshot(); - }); - describe('handles region_map function', () => { it('without buckets', () => { const params = { metric: {} }; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index 0c244876ca6a3..29f6ec9b069a7 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -219,9 +219,6 @@ export const prepareDimension = (variable: string, data: any) => { }; export const buildPipelineVisFunction: BuildPipelineVisFunction = { - input_control_vis: (params) => { - return `input_control_vis ${prepareJson('visConfig', params)}`; - }, region_map: (params, schemas) => { const visConfig = { ...params, From b454f2a1c2f9388be752eda826d470d698d0c094 Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Wed, 2 Dec 2020 09:26:22 +0100 Subject: [PATCH 22/37] migrate away from rest_total_hits_as_int (#84508) --- x-pack/plugins/graph/public/angular/graph_client_workspace.js | 2 +- x-pack/plugins/graph/server/routes/search.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/graph/public/angular/graph_client_workspace.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.js index 5cc06bad4c423..785e221b79865 100644 --- a/x-pack/plugins/graph/public/angular/graph_client_workspace.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.js @@ -1187,7 +1187,7 @@ function GraphWorkspace(options) { // Search for connections between the selected nodes. searcher(self.options.indexName, searchReq, function (data) { - const numDocsMatched = data.hits.total; + const numDocsMatched = data.hits.total.value; const buckets = data.aggregations.matrix.buckets; const vertices = nodesForLinking.map(function (existingNode) { return { diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index 1bd2861687281..7d05f9ab6888c 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -47,7 +47,7 @@ export function registerSearchRoute({ await esClient.asCurrentUser.search({ index: request.body.index, body: request.body.body, - rest_total_hits_as_int: true, + track_total_hits: true, ignore_throttled: !includeFrozen, }) ).body, From 717a66fc6c41b484bd61071cc183313f3c0c3802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Wed, 2 Dec 2020 09:26:45 +0100 Subject: [PATCH 23/37] TelemetryCollectionManager: Use X-Pack strategy as an OSS overwrite (#84477) --- src/plugins/telemetry/server/fetcher.ts | 2 - src/plugins/telemetry/server/index.ts | 1 - src/plugins/telemetry/server/plugin.ts | 15 +- .../telemetry_collection/get_local_stats.ts | 2 +- .../server/telemetry_collection/index.ts | 1 - .../register_collection.ts | 14 +- .../server/index.ts | 2 - .../server/plugin.ts | 157 +++++++----------- .../server/types.ts | 74 ++------- .../apis/telemetry/telemetry_local.js | 2 + .../telemetry_collection/get_licenses.ts | 2 +- .../common/index.ts | 8 - .../server/index.ts | 7 +- .../server/plugin.ts | 26 +-- .../get_stats_with_xpack.test.ts.snap | 6 + .../telemetry_collection/get_license.test.ts | 82 +++++++++ .../telemetry_collection/get_license.ts | 45 +++-- .../get_stats_with_xpack.ts | 23 ++- .../server/telemetry_collection/index.ts | 1 + .../apis/telemetry/telemetry_local.js | 3 + 20 files changed, 218 insertions(+), 255 deletions(-) delete mode 100644 x-pack/plugins/telemetry_collection_xpack/common/index.ts create mode 100644 x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts rename src/plugins/telemetry/server/telemetry_collection/get_local_license.ts => x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts (50%) diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index a3649f51577ac..820f2c7c4c4af 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -17,7 +17,6 @@ * under the License. */ -import moment from 'moment'; import { Observable, Subscription, timer } from 'rxjs'; import { take } from 'rxjs/operators'; // @ts-ignore @@ -213,7 +212,6 @@ export class FetcherTask { private async fetchTelemetry() { return await this.telemetryCollectionManager!.getStats({ unencrypted: false, - timestamp: moment().valueOf(), }); } diff --git a/src/plugins/telemetry/server/index.ts b/src/plugins/telemetry/server/index.ts index e9887456e2f36..326c87a75b0ea 100644 --- a/src/plugins/telemetry/server/index.ts +++ b/src/plugins/telemetry/server/index.ts @@ -44,7 +44,6 @@ export const plugin = (initializerContext: PluginInitializerContext<TelemetryCon export { constants }; export { getClusterUuids, - getLocalLicense, getLocalStats, TelemetryLocalStats, DATA_TELEMETRY_ID, diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 037f97fb63ac6..f40c38b2cbbd0 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -33,9 +33,7 @@ import { SavedObjectsClient, Plugin, Logger, - IClusterClient, UiSettingsServiceStart, - SavedObjectsServiceStart, } from '../../../core/server'; import { registerRoutes } from './routes'; import { registerCollection } from './telemetry_collection'; @@ -88,8 +86,6 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl */ private readonly oldUiSettingsHandled$ = new AsyncSubject(); private savedObjectsClient?: ISavedObjectsRepository; - private elasticsearchClient?: IClusterClient; - private savedObjectsService?: SavedObjectsServiceStart; constructor(initializerContext: PluginInitializerContext<TelemetryConfigType>) { this.logger = initializerContext.logger.get(); @@ -109,12 +105,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl const currentKibanaVersion = this.currentKibanaVersion; const config$ = this.config$; const isDev = this.isDev; - registerCollection( - telemetryCollectionManager, - elasticsearch.legacy.client, - () => this.elasticsearchClient, - () => this.savedObjectsService - ); + registerCollection(telemetryCollectionManager); const router = http.createRouter(); registerRoutes({ @@ -138,11 +129,9 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl } public start(core: CoreStart, { telemetryCollectionManager }: TelemetryPluginsDepsStart) { - const { savedObjects, uiSettings, elasticsearch } = core; + const { savedObjects, uiSettings } = core; const savedObjectsInternalRepository = savedObjects.createInternalRepository(); this.savedObjectsClient = savedObjectsInternalRepository; - this.elasticsearchClient = elasticsearch.client; - this.savedObjectsService = savedObjects; // Not catching nor awaiting these promises because they should never reject this.handleOldUiSettings(uiSettings); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts index a3666683a05a1..12245ce62305e 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -66,7 +66,7 @@ export type TelemetryLocalStats = ReturnType<typeof handleLocalStats>; * @param {Object} config contains the usageCollection, callCluster (deprecated), the esClient and Saved Objects client scoped to the request or the internal repository, and the kibana request * @param {Object} StatsCollectionContext contains logger and version (string) */ -export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async ( +export const getLocalStats: StatsGetter<TelemetryLocalStats> = async ( clustersDetails, config, context diff --git a/src/plugins/telemetry/server/telemetry_collection/index.ts b/src/plugins/telemetry/server/telemetry_collection/index.ts index 40cbf0e4caa1d..77894091f6133 100644 --- a/src/plugins/telemetry/server/telemetry_collection/index.ts +++ b/src/plugins/telemetry/server/telemetry_collection/index.ts @@ -24,6 +24,5 @@ export { buildDataTelemetryPayload, } from './get_data_telemetry'; export { getLocalStats, TelemetryLocalStats } from './get_local_stats'; -export { getLocalLicense } from './get_local_license'; export { getClusterUuids } from './get_cluster_stats'; export { registerCollection } from './register_collection'; diff --git a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts index 27ca5ae746512..fac315b01493e 100644 --- a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts +++ b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts @@ -36,27 +36,17 @@ * under the License. */ -import { ILegacyClusterClient, SavedObjectsServiceStart } from 'kibana/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; -import { IClusterClient } from '../../../../../src/core/server'; import { getLocalStats } from './get_local_stats'; import { getClusterUuids } from './get_cluster_stats'; -import { getLocalLicense } from './get_local_license'; export function registerCollection( - telemetryCollectionManager: TelemetryCollectionManagerPluginSetup, - esCluster: ILegacyClusterClient, - esClientGetter: () => IClusterClient | undefined, - soServiceGetter: () => SavedObjectsServiceStart | undefined + telemetryCollectionManager: TelemetryCollectionManagerPluginSetup ) { - telemetryCollectionManager.setCollection({ - esCluster, - esClientGetter, - soServiceGetter, + telemetryCollectionManager.setCollectionStrategy({ title: 'local', priority: 0, statsGetter: getLocalStats, clusterDetailsGetter: getClusterUuids, - licenseGetter: getLocalLicense, }); } diff --git a/src/plugins/telemetry_collection_manager/server/index.ts b/src/plugins/telemetry_collection_manager/server/index.ts index 36ab64731fe58..de2080059c80b 100644 --- a/src/plugins/telemetry_collection_manager/server/index.ts +++ b/src/plugins/telemetry_collection_manager/server/index.ts @@ -30,13 +30,11 @@ export function plugin(initializerContext: PluginInitializerContext) { export { TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart, - ESLicense, StatsCollectionConfig, StatsGetter, StatsGetterConfig, StatsCollectionContext, ClusterDetails, ClusterDetailsGetter, - LicenseGetter, UsageStatsPayload, } from './types'; diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index bc33e9fbc82c5..a135f4b115b21 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -26,14 +26,15 @@ import { Logger, IClusterClient, SavedObjectsServiceStart, -} from '../../../core/server'; + ILegacyClusterClient, +} from 'src/core/server'; import { TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart, BasicStatsPayload, - CollectionConfig, - Collection, + CollectionStrategyConfig, + CollectionStrategy, StatsGetterConfig, StatsCollectionConfig, UsageStatsPayload, @@ -49,9 +50,12 @@ interface TelemetryCollectionPluginsDepsSetup { export class TelemetryCollectionManagerPlugin implements Plugin<TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart> { private readonly logger: Logger; - private readonly collections: Array<Collection<any>> = []; + private collectionStrategy: CollectionStrategy<any> | undefined; private usageGetterMethodPriority = -1; private usageCollection?: UsageCollectionSetup; + private legacyElasticsearchClient?: ILegacyClusterClient; + private elasticsearchClient?: IClusterClient; + private savedObjectsService?: SavedObjectsServiceStart; private readonly isDistributable: boolean; private readonly version: string; @@ -65,7 +69,7 @@ export class TelemetryCollectionManagerPlugin this.usageCollection = usageCollection; return { - setCollection: this.setCollection.bind(this), + setCollectionStrategy: this.setCollectionStrategy.bind(this), getOptInStats: this.getOptInStats.bind(this), getStats: this.getStats.bind(this), areAllCollectorsReady: this.areAllCollectorsReady.bind(this), @@ -73,8 +77,11 @@ export class TelemetryCollectionManagerPlugin } public start(core: CoreStart) { + this.legacyElasticsearchClient = core.elasticsearch.legacy.client; // TODO: Remove when all the collectors have migrated + this.elasticsearchClient = core.elasticsearch.client; + this.savedObjectsService = core.savedObjects; + return { - setCollection: this.setCollection.bind(this), getOptInStats: this.getOptInStats.bind(this), getStats: this.getStats.bind(this), areAllCollectorsReady: this.areAllCollectorsReady.bind(this), @@ -83,19 +90,10 @@ export class TelemetryCollectionManagerPlugin public stop() {} - private setCollection<CustomContext extends Record<string, any>, T extends BasicStatsPayload>( - collectionConfig: CollectionConfig<CustomContext, T> + private setCollectionStrategy<T extends BasicStatsPayload>( + collectionConfig: CollectionStrategyConfig<T> ) { - const { - title, - priority, - esCluster, - esClientGetter, - soServiceGetter, - statsGetter, - clusterDetailsGetter, - licenseGetter, - } = collectionConfig; + const { title, priority, statsGetter, clusterDetailsGetter } = collectionConfig; if (typeof priority !== 'number') { throw new Error('priority must be set.'); @@ -108,78 +106,58 @@ export class TelemetryCollectionManagerPlugin if (!statsGetter) { throw Error('Stats getter method not set.'); } - if (!esCluster) { - throw Error('esCluster name must be set for the getCluster method.'); - } - if (!esClientGetter) { - throw Error('esClientGetter method not set.'); - } - if (!soServiceGetter) { - throw Error('soServiceGetter method not set.'); - } if (!clusterDetailsGetter) { throw Error('Cluster UUIds method is not set.'); } - if (!licenseGetter) { - throw Error('License getter method not set.'); - } - this.collections.unshift({ - licenseGetter, - statsGetter, - clusterDetailsGetter, - esCluster, - title, - esClientGetter, - soServiceGetter, - }); + this.logger.debug(`Setting ${title} as the telemetry collection strategy`); + + // Overwrite the collection strategy + this.collectionStrategy = collectionConfig; this.usageGetterMethodPriority = priority; } } + /** + * Returns the context to provide to the Collection Strategies. + * It may return undefined if the ES and SO clients are not initialised yet. + * @param config {@link StatsGetterConfig} + * @param usageCollection {@link UsageCollectionSetup} + * @private + */ private getStatsCollectionConfig( config: StatsGetterConfig, - collection: Collection, - collectionEsClient: IClusterClient, - collectionSoService: SavedObjectsServiceStart, usageCollection: UsageCollectionSetup - ): StatsCollectionConfig { - const { request } = config; - + ): StatsCollectionConfig | undefined { const callCluster = config.unencrypted - ? collection.esCluster.asScoped(request).callAsCurrentUser - : collection.esCluster.callAsInternalUser; + ? this.legacyElasticsearchClient?.asScoped(config.request).callAsCurrentUser + : this.legacyElasticsearchClient?.callAsInternalUser; // Scope the new elasticsearch Client appropriately and pass to the stats collection config const esClient = config.unencrypted - ? collectionEsClient.asScoped(config.request).asCurrentUser - : collectionEsClient.asInternalUser; + ? this.elasticsearchClient?.asScoped(config.request).asCurrentUser + : this.elasticsearchClient?.asInternalUser; // Scope the saved objects client appropriately and pass to the stats collection config const soClient = config.unencrypted - ? collectionSoService.getScopedClient(config.request) - : collectionSoService.createInternalRepository(); + ? this.savedObjectsService?.getScopedClient(config.request) + : this.savedObjectsService?.createInternalRepository(); // Provide the kibanaRequest so opted-in plugins can scope their custom clients only if the request is not encrypted - const kibanaRequest = config.unencrypted ? request : void 0; + const kibanaRequest = config.unencrypted ? config.request : void 0; - return { callCluster, usageCollection, esClient, soClient, kibanaRequest }; + if (callCluster && esClient && soClient) { + return { callCluster, usageCollection, esClient, soClient, kibanaRequest }; + } } private async getOptInStats(optInStatus: boolean, config: StatsGetterConfig) { if (!this.usageCollection) { return []; } - for (const collection of this.collections) { - // first fetch the client and make sure it's not undefined. - const collectionEsClient = collection.esClientGetter(); - const collectionSoService = collection.soServiceGetter(); - if (collectionEsClient !== undefined && collectionSoService !== undefined) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - collectionEsClient, - collectionSoService, - this.usageCollection - ); + const collection = this.collectionStrategy; + if (collection) { + // Build the context (clients and others) to send to the CollectionStrategies + const statsCollectionConfig = this.getStatsCollectionConfig(config, this.usageCollection); + if (statsCollectionConfig) { try { const optInStats = await this.getOptInStatsForCollection( collection, @@ -194,8 +172,9 @@ export class TelemetryCollectionManagerPlugin return encryptTelemetry(optInStats, { useProdKey: this.isDistributable }); } } catch (err) { - this.logger.debug(`Failed to collect any opt in stats with registered collections.`); - // swallow error to try next collection; + this.logger.debug( + `Failed to collect any opt in stats with collection ${collection.title}.` + ); } } } @@ -203,19 +182,18 @@ export class TelemetryCollectionManagerPlugin return []; } - private areAllCollectorsReady = async () => { + private async areAllCollectorsReady() { return await this.usageCollection?.areAllCollectorsReady(); - }; + } private getOptInStatsForCollection = async ( - collection: Collection, + collection: CollectionStrategy, optInStatus: boolean, statsCollectionConfig: StatsCollectionConfig ) => { const context: StatsCollectionContext = { logger: this.logger.get(collection.title), version: this.version, - ...collection.customContext, }; const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig, context); @@ -229,17 +207,11 @@ export class TelemetryCollectionManagerPlugin if (!this.usageCollection) { return []; } - for (const collection of this.collections) { - const collectionEsClient = collection.esClientGetter(); - const collectionSavedObjectsService = collection.soServiceGetter(); - if (collectionEsClient !== undefined && collectionSavedObjectsService !== undefined) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - collectionEsClient, - collectionSavedObjectsService, - this.usageCollection - ); + const collection = this.collectionStrategy; + if (collection) { + // Build the context (clients and others) to send to the CollectionStrategies + const statsCollectionConfig = this.getStatsCollectionConfig(config, this.usageCollection); + if (statsCollectionConfig) { try { const usageData = await this.getUsageForCollection(collection, statsCollectionConfig); if (usageData.length) { @@ -256,7 +228,6 @@ export class TelemetryCollectionManagerPlugin this.logger.debug( `Failed to collect any usage with registered collection ${collection.title}.` ); - // swallow error to try next collection; } } } @@ -265,34 +236,24 @@ export class TelemetryCollectionManagerPlugin } private async getUsageForCollection( - collection: Collection, + collection: CollectionStrategy, statsCollectionConfig: StatsCollectionConfig ): Promise<UsageStatsPayload[]> { const context: StatsCollectionContext = { logger: this.logger.get(collection.title), version: this.version, - ...collection.customContext, }; const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig, context); if (clustersDetails.length === 0) { - // don't bother doing a further lookup, try next collection. + // don't bother doing a further lookup. return []; } - const [stats, licenses] = await Promise.all([ - collection.statsGetter(clustersDetails, statsCollectionConfig, context), - collection.licenseGetter(clustersDetails, statsCollectionConfig, context), - ]); + const stats = await collection.statsGetter(clustersDetails, statsCollectionConfig, context); - return stats.map((stat) => { - const license = licenses[stat.cluster_uuid]; - return { - collectionSource: collection.title, - ...(license ? { license } : {}), - ...stat, - }; - }); + // Add the `collectionSource` to the resulting payload + return stats.map((stat) => ({ collectionSource: collection.title, ...stat })); } } diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts index a6cf1a9e5aaf9..05641d5064593 100644 --- a/src/plugins/telemetry_collection_manager/server/types.ts +++ b/src/plugins/telemetry_collection_manager/server/types.ts @@ -19,21 +19,18 @@ import { LegacyAPICaller, + ElasticsearchClient, Logger, KibanaRequest, - ILegacyClusterClient, - IClusterClient, - SavedObjectsServiceStart, SavedObjectsClientContract, ISavedObjectsRepository, -} from 'kibana/server'; +} from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { ElasticsearchClient } from '../../../../src/core/server'; import { TelemetryCollectionManagerPlugin } from './plugin'; export interface TelemetryCollectionManagerPluginSetup { - setCollection: <CustomContext extends Record<string, any>, T extends BasicStatsPayload>( - collectionConfig: CollectionConfig<CustomContext, T> + setCollectionStrategy: <T extends BasicStatsPayload>( + collectionConfig: CollectionStrategyConfig<T> ) => void; getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats']; getStats: TelemetryCollectionManagerPlugin['getStats']; @@ -41,9 +38,6 @@ export interface TelemetryCollectionManagerPluginSetup { } export interface TelemetryCollectionManagerPluginStart { - setCollection: <CustomContext extends Record<string, any>, T extends BasicStatsPayload>( - collectionConfig: CollectionConfig<CustomContext, T> - ) => void; getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats']; getStats: TelemetryCollectionManagerPlugin['getStats']; areAllCollectorsReady: TelemetryCollectionManagerPlugin['areAllCollectorsReady']; @@ -91,74 +85,34 @@ export interface BasicStatsPayload { } export interface UsageStatsPayload extends BasicStatsPayload { - license?: ESLicense; collectionSource: string; } -// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html -export interface ESLicense { - status: string; - uid: string; - type: string; - issue_date: string; - issue_date_in_millis: number; - expiry_date: string; - expirty_date_in_millis: number; - max_nodes: number; - issued_to: string; - issuer: string; - start_date_in_millis: number; -} - export interface StatsCollectionContext { logger: Logger | Console; version: string; } export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig; -export type ClusterDetailsGetter<CustomContext extends Record<string, any> = {}> = ( +export type ClusterDetailsGetter = ( config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext + context: StatsCollectionContext ) => Promise<ClusterDetails[]>; -export type StatsGetter< - CustomContext extends Record<string, any> = {}, - T extends BasicStatsPayload = BasicStatsPayload -> = ( +export type StatsGetter<T extends BasicStatsPayload = BasicStatsPayload> = ( clustersDetails: ClusterDetails[], config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext + context: StatsCollectionContext ) => Promise<T[]>; -export type LicenseGetter<CustomContext extends Record<string, any> = {}> = ( - clustersDetails: ClusterDetails[], - config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext -) => Promise<{ [clusterUuid: string]: ESLicense | undefined }>; -export interface CollectionConfig< - CustomContext extends Record<string, any> = {}, - T extends BasicStatsPayload = BasicStatsPayload -> { +export interface CollectionStrategyConfig<T extends BasicStatsPayload = BasicStatsPayload> { title: string; priority: number; - esCluster: ILegacyClusterClient; - esClientGetter: () => IClusterClient | undefined; // --> by now we know that the client getter will return the IClusterClient but we assure that through a code check - soServiceGetter: () => SavedObjectsServiceStart | undefined; // --> by now we know that the service getter will return the SavedObjectsServiceStart but we assure that through a code check - statsGetter: StatsGetter<CustomContext, T>; - clusterDetailsGetter: ClusterDetailsGetter<CustomContext>; - licenseGetter: LicenseGetter<CustomContext>; - customContext?: CustomContext; + statsGetter: StatsGetter<T>; + clusterDetailsGetter: ClusterDetailsGetter; } -export interface Collection< - CustomContext extends Record<string, any> = {}, - T extends BasicStatsPayload = BasicStatsPayload -> { - customContext?: CustomContext; - statsGetter: StatsGetter<CustomContext, T>; - licenseGetter: LicenseGetter<CustomContext>; - clusterDetailsGetter: ClusterDetailsGetter<CustomContext>; - esCluster: ILegacyClusterClient; - esClientGetter: () => IClusterClient | undefined; // the collection could still return undefined for the es client getter. - soServiceGetter: () => SavedObjectsServiceStart | undefined; // the collection could still return undefined for the Saved Objects Service getter. +export interface CollectionStrategy<T extends BasicStatsPayload = BasicStatsPayload> { + statsGetter: StatsGetter<T>; + clusterDetailsGetter: ClusterDetailsGetter; title: string; } diff --git a/test/api_integration/apis/telemetry/telemetry_local.js b/test/api_integration/apis/telemetry/telemetry_local.js index b3d34d5910fc3..a025a65779e9c 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.js +++ b/test/api_integration/apis/telemetry/telemetry_local.js @@ -62,6 +62,8 @@ export default function ({ getService }) { expect(body.length).to.be(1); const stats = body[0]; expect(stats.collection).to.be('local'); + expect(stats.collectionSource).to.be('local'); + expect(stats.license).to.be.undefined; // OSS cannot get the license expect(stats.stack_stats.kibana.count).to.be.a('number'); expect(stats.stack_stats.kibana.indices).to.be.a('number'); expect(stats.stack_stats.kibana.os.platforms[0].platform).to.be.a('string'); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts index 7b1b877c51278..792389485164d 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts @@ -5,8 +5,8 @@ */ import { SearchResponse } from 'elasticsearch'; -import { ESLicense } from 'src/plugins/telemetry_collection_manager/server'; import { LegacyAPICaller } from 'kibana/server'; +import { ESLicense } from '../../../telemetry_collection_xpack/server'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; /** diff --git a/x-pack/plugins/telemetry_collection_xpack/common/index.ts b/x-pack/plugins/telemetry_collection_xpack/common/index.ts deleted file mode 100644 index 2b08ebe2e7bbf..0000000000000 --- a/x-pack/plugins/telemetry_collection_xpack/common/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const PLUGIN_ID = 'telemetryCollectionXpack'; -export const PLUGIN_NAME = 'telemetry_collection_xpack'; diff --git a/x-pack/plugins/telemetry_collection_xpack/server/index.ts b/x-pack/plugins/telemetry_collection_xpack/server/index.ts index 249d16c331c39..de39089fe0e03 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/index.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/index.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'kibana/server'; import { TelemetryCollectionXpackPlugin } from './plugin'; +export { ESLicense } from './telemetry_collection'; + // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. -export function plugin(initializerContext: PluginInitializerContext) { - return new TelemetryCollectionXpackPlugin(initializerContext); +export function plugin() { + return new TelemetryCollectionXpackPlugin(); } diff --git a/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts b/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts index 524b4c5616c73..e6d72f5813163 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts @@ -4,16 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - IClusterClient, - SavedObjectsServiceStart, -} from 'kibana/server'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; -import { getClusterUuids, getLocalLicense } from '../../../../src/plugins/telemetry/server'; +import { getClusterUuids } from '../../../../src/plugins/telemetry/server'; import { getStatsWithXpack } from './telemetry_collection'; interface TelemetryCollectionXpackDepsSetup { @@ -21,25 +14,16 @@ interface TelemetryCollectionXpackDepsSetup { } export class TelemetryCollectionXpackPlugin implements Plugin { - private elasticsearchClient?: IClusterClient; - private savedObjectsService?: SavedObjectsServiceStart; - constructor(initializerContext: PluginInitializerContext) {} + constructor() {} public setup(core: CoreSetup, { telemetryCollectionManager }: TelemetryCollectionXpackDepsSetup) { - telemetryCollectionManager.setCollection({ - esCluster: core.elasticsearch.legacy.client, - esClientGetter: () => this.elasticsearchClient, - soServiceGetter: () => this.savedObjectsService, + telemetryCollectionManager.setCollectionStrategy({ title: 'local_xpack', priority: 1, statsGetter: getStatsWithXpack, clusterDetailsGetter: getClusterUuids, - licenseGetter: getLocalLicense, }); } - public start(core: CoreStart) { - this.elasticsearchClient = core.elasticsearch.client; - this.savedObjectsService = core.savedObjects; - } + public start(core: CoreStart) {} } diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap index b68186c0c343d..836b5276615ef 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap @@ -104,6 +104,9 @@ Object { }, "cluster_uuid": "test", "collection": "local", + "license": Object { + "type": "basic", + }, "stack_stats": Object { "data": Array [], "kibana": Object { @@ -183,6 +186,9 @@ Object { }, "cluster_uuid": "test", "collection": "local", + "license": Object { + "type": "basic", + }, "stack_stats": Object { "data": Array [], "kibana": Object { diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts new file mode 100644 index 0000000000000..c5c6832aa84ac --- /dev/null +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { elasticsearchServiceMock } from 'src/core/server/mocks'; +import { getLicenseFromLocalOrMaster } from './get_license'; + +describe('getLicenseFromLocalOrMaster', () => { + test('return an undefined license if it fails to get the license on the first attempt and it does not have a cached license yet', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The local fetch fails + esClient.license.get.mockRejectedValue(new Error('Something went terribly wrong')); + + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toBeUndefined(); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(1); + }); + + test('returns the license it fetches from Elasticsearch', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The local fetch succeeds + esClient.license.get.mockResolvedValue({ body: { license: { type: 'basic' } } } as any); + + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toStrictEqual({ type: 'basic' }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(1); + }); + + test('after the first successful attempt, if the local request fails, it will try with the master request (failed case)', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const error = new Error('Something went terribly wrong'); + // The requests fail with an error + esClient.license.get.mockRejectedValue(error); + + await expect(getLicenseFromLocalOrMaster(esClient)).rejects.toStrictEqual(error); + + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: false, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(2); + }); + + test('after the first successful attempt, if the local request fails, it will try with the master request (success case)', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The local fetch fails + esClient.license.get.mockRejectedValueOnce(new Error('Something went terribly wrong')); + // The master fetch succeeds + esClient.license.get.mockResolvedValue({ body: { license: { type: 'basic' } } } as any); + + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toStrictEqual({ type: 'basic' }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: false, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(2); + }); + + test('after the first successful attempt, if the local request fails, it will try with the master request (clearing cached license)', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The requests fail with 400 + esClient.license.get.mockRejectedValue({ statusCode: 400 }); + + // First attempt goes through 2 requests: local and master + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toBeUndefined(); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: false, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(2); + + // Now the cached license is cleared, next request only goes for local and gives up when failed + esClient.license.get.mockClear(); + await expect(getLicenseFromLocalOrMaster(esClient)).resolves.toBeUndefined(); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts similarity index 50% rename from src/plugins/telemetry/server/telemetry_collection/get_local_license.ts rename to x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts index 879416cda62fc..9ffbf5d1bf6d7 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts @@ -1,29 +1,30 @@ /* - * 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. + * 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 { ESLicense, LicenseGetter } from 'src/plugins/telemetry_collection_manager/server'; import { ElasticsearchClient } from 'src/core/server'; +// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html +export interface ESLicense { + status: string; + uid: string; + type: string; + issue_date: string; + issue_date_in_millis: number; + expiry_date: string; + expirty_date_in_millis: number; + max_nodes: number; + issued_to: string; + issuer: string; + start_date_in_millis: number; +} + let cachedLicense: ESLicense | undefined; async function fetchLicense(esClient: ElasticsearchClient, local: boolean) { - const { body } = await esClient.license.get({ + const { body } = await esClient.license.get<{ license: ESLicense }>({ local, // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. accept_enterprise: true, @@ -39,7 +40,7 @@ async function fetchLicense(esClient: ElasticsearchClient, local: boolean) { * * In OSS we'll get a 400 response using the new elasticsearch client. */ -async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { +export async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { // Fetching the local license is cheaper than getting it from the master node and good enough const { license } = await fetchLicense(esClient, true).catch(async (err) => { if (cachedLicense) { @@ -64,9 +65,3 @@ async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { } return license; } - -export const getLocalLicense: LicenseGetter = async (clustersDetails, { esClient }) => { - const license = await getLicenseFromLocalOrMaster(esClient); - // It should be called only with 1 cluster element in the clustersDetails array, but doing reduce just in case. - return clustersDetails.reduce((acc, { clusterUuid }) => ({ ...acc, [clusterUuid]: license }), {}); -}; diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts index c0e55274b08df..bf1e7c3aaae17 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts @@ -4,33 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ +import { StatsGetter } from 'src/plugins/telemetry_collection_manager/server'; import { TelemetryLocalStats, getLocalStats } from '../../../../../src/plugins/telemetry/server'; -import { StatsGetter } from '../../../../../src/plugins/telemetry_collection_manager/server'; import { getXPackUsage } from './get_xpack'; +import { ESLicense, getLicenseFromLocalOrMaster } from './get_license'; export type TelemetryAggregatedStats = TelemetryLocalStats & { stack_stats: { xpack?: object }; + license?: ESLicense; }; -export const getStatsWithXpack: StatsGetter<{}, TelemetryAggregatedStats> = async function ( +export const getStatsWithXpack: StatsGetter<TelemetryAggregatedStats> = async function ( clustersDetails, config, context ) { const { esClient } = config; - const clustersLocalStats = await getLocalStats(clustersDetails, config, context); - const xpack = await getXPackUsage(esClient).catch(() => undefined); // We want to still report something (and do not lose the license) even when this method fails. + const [clustersLocalStats, license, xpack] = await Promise.all([ + getLocalStats(clustersDetails, config, context), + getLicenseFromLocalOrMaster(esClient), + getXPackUsage(esClient).catch(() => undefined), // We want to still report something (and do not lose the license) even when this method fails. + ]); return clustersLocalStats .map((localStats) => { + const localStatsWithLicense: TelemetryAggregatedStats = { + ...localStats, + ...(license && { license }), + }; if (xpack) { return { - ...localStats, - stack_stats: { ...localStats.stack_stats, xpack }, + ...localStatsWithLicense, + stack_stats: { ...localStatsWithLicense.stack_stats, xpack }, }; } - return localStats; + return localStatsWithLicense; }) .reduce((acc, stats) => { // Concatenate the telemetry reported via monitoring as additional payloads instead of reporting it inside of stack_stats.kibana.plugins.monitoringTelemetry diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts index 553f8dc0c4188..bcd011ae750a6 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export { ESLicense } from './get_license'; export { getStatsWithXpack } from './get_stats_with_xpack'; diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js index 6a5a7db4d2560..e7014ff92d9b9 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js @@ -55,6 +55,9 @@ export default function ({ getService }) { const stats = body[0]; expect(stats.collection).to.be('local'); + expect(stats.collectionSource).to.be('local_xpack'); + + // License should exist in X-Pack expect(stats.license.issuer).to.be.a('string'); expect(stats.license.status).to.be('active'); From 8981d0e9e23d96592a7ceaddfba735639aa620a5 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin <aleh.zasypkin@gmail.com> Date: Wed, 2 Dec 2020 09:32:49 +0100 Subject: [PATCH 24/37] Make it possible to use Kibana anonymous authentication provider with ES anonymous access. (#84074) --- .../providers/anonymous.test.ts | 74 +++++++++++++------ .../authentication/providers/anonymous.ts | 51 ++++++++++--- x-pack/plugins/security/server/config.test.ts | 54 ++++++++++++-- x-pack/plugins/security/server/config.ts | 1 + .../anonymous_es_anonymous.config.ts | 40 ++++++++++ .../tests/anonymous/login.ts | 28 ++++--- 6 files changed, 200 insertions(+), 48 deletions(-) create mode 100644 x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts index c296cb9c8e94d..9674181e18750 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts @@ -29,32 +29,48 @@ function expectAuthenticateCall( expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); } +enum CredentialsType { + Basic = 'Basic', + ApiKey = 'ApiKey', + None = 'ES native anonymous', +} + describe('AnonymousAuthenticationProvider', () => { const user = mockAuthenticatedUser({ authentication_provider: { type: 'anonymous', name: 'anonymous1' }, }); - for (const useBasicCredentials of [true, false]) { - describe(`with ${useBasicCredentials ? '`Basic`' : '`ApiKey`'} credentials`, () => { + for (const credentialsType of [ + CredentialsType.Basic, + CredentialsType.ApiKey, + CredentialsType.None, + ]) { + describe(`with ${credentialsType} credentials`, () => { let provider: AnonymousAuthenticationProvider; let mockOptions: ReturnType<typeof mockAuthenticationProviderOptions>; let authorization: string; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions({ name: 'anonymous1' }); - provider = useBasicCredentials - ? new AnonymousAuthenticationProvider(mockOptions, { - credentials: { username: 'user', password: 'pass' }, - }) - : new AnonymousAuthenticationProvider(mockOptions, { - credentials: { apiKey: 'some-apiKey' }, - }); - authorization = useBasicCredentials - ? new HTTPAuthorizationHeader( + let credentials; + switch (credentialsType) { + case CredentialsType.Basic: + credentials = { username: 'user', password: 'pass' }; + authorization = new HTTPAuthorizationHeader( 'Basic', new BasicHTTPAuthorizationHeaderCredentials('user', 'pass').toString() - ).toString() - : new HTTPAuthorizationHeader('ApiKey', 'some-apiKey').toString(); + ).toString(); + break; + case CredentialsType.ApiKey: + credentials = { apiKey: 'some-apiKey' }; + authorization = new HTTPAuthorizationHeader('ApiKey', 'some-apiKey').toString(); + break; + default: + credentials = 'elasticsearch_anonymous_user' as 'elasticsearch_anonymous_user'; + break; + } + + provider = new AnonymousAuthenticationProvider(mockOptions, { credentials }); }); describe('`login` method', () => { @@ -111,23 +127,29 @@ describe('AnonymousAuthenticationProvider', () => { }); it('does not handle authentication via `authorization` header.', async () => { - const request = httpServerMock.createKibanaRequest({ headers: { authorization } }); + const originalAuthorizationHeader = 'Basic credentials'; + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: originalAuthorizationHeader }, + }); await expect(provider.authenticate(request)).resolves.toEqual( AuthenticationResult.notHandled() ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(request.headers.authorization).toBe(authorization); + expect(request.headers.authorization).toBe(originalAuthorizationHeader); }); it('does not handle authentication via `authorization` header even if state exists.', async () => { - const request = httpServerMock.createKibanaRequest({ headers: { authorization } }); + const originalAuthorizationHeader = 'Basic credentials'; + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: originalAuthorizationHeader }, + }); await expect(provider.authenticate(request, {})).resolves.toEqual( AuthenticationResult.notHandled() ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(request.headers.authorization).toBe(authorization); + expect(request.headers.authorization).toBe(originalAuthorizationHeader); }); it('succeeds for non-AJAX requests if state is available.', async () => { @@ -191,7 +213,7 @@ describe('AnonymousAuthenticationProvider', () => { expect(request.headers).not.toHaveProperty('authorization'); }); - if (!useBasicCredentials) { + if (credentialsType === CredentialsType.ApiKey) { it('properly handles extended format for the ApiKey credentials', async () => { provider = new AnonymousAuthenticationProvider(mockOptions, { credentials: { apiKey: { id: 'some-id', key: 'some-key' } }, @@ -237,9 +259,19 @@ describe('AnonymousAuthenticationProvider', () => { }); it('`getHTTPAuthenticationScheme` method', () => { - expect(provider.getHTTPAuthenticationScheme()).toBe( - useBasicCredentials ? 'basic' : 'apikey' - ); + let expectedAuthenticationScheme; + switch (credentialsType) { + case CredentialsType.Basic: + expectedAuthenticationScheme = 'basic'; + break; + case CredentialsType.ApiKey: + expectedAuthenticationScheme = 'apikey'; + break; + default: + expectedAuthenticationScheme = null; + break; + } + expect(provider.getHTTPAuthenticationScheme()).toBe(expectedAuthenticationScheme); }); }); } diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.ts index 6f02cce371a41..6d62d3a909e55 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest } from '../../../../../../src/core/server'; +import { KibanaRequest, LegacyElasticsearchErrorHelpers } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -29,6 +29,11 @@ interface APIKeyCredentials { apiKey: { id: string; key: string } | string; } +/** + * Credentials that imply authentication based on the Elasticsearch native anonymous user. + */ +type ElasticsearchAnonymousUserCredentials = 'elasticsearch_anonymous_user'; + /** * Checks whether current request can initiate a new session. * @param request Request instance. @@ -44,7 +49,10 @@ function canStartNewSession(request: KibanaRequest) { * @param credentials */ function isAPIKeyCredentials( - credentials: UsernameAndPasswordCredentials | APIKeyCredentials + credentials: + | ElasticsearchAnonymousUserCredentials + | APIKeyCredentials + | UsernameAndPasswordCredentials ): credentials is APIKeyCredentials { return !!(credentials as APIKeyCredentials).apiKey; } @@ -59,14 +67,17 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider static readonly type = 'anonymous'; /** - * Defines HTTP authorization header that should be used to authenticate request. + * Defines HTTP authorization header that should be used to authenticate request. It isn't defined + * if provider should rely on Elasticsearch native anonymous access. */ - private readonly httpAuthorizationHeader: HTTPAuthorizationHeader; + private readonly httpAuthorizationHeader?: HTTPAuthorizationHeader; constructor( protected readonly options: Readonly<AuthenticationProviderOptions>, anonymousOptions?: Readonly<{ - credentials?: Readonly<UsernameAndPasswordCredentials | APIKeyCredentials>; + credentials?: Readonly< + ElasticsearchAnonymousUserCredentials | UsernameAndPasswordCredentials | APIKeyCredentials + >; }> ) { super(options); @@ -76,7 +87,11 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider throw new Error('Credentials must be specified'); } - if (isAPIKeyCredentials(credentials)) { + if (credentials === 'elasticsearch_anonymous_user') { + this.logger.debug( + 'Anonymous requests will be authenticated using Elasticsearch native anonymous user.' + ); + } else if (isAPIKeyCredentials(credentials)) { this.logger.debug('Anonymous requests will be authenticated via API key.'); this.httpAuthorizationHeader = new HTTPAuthorizationHeader( 'ApiKey', @@ -155,7 +170,7 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider * HTTP header that provider attaches to all successfully authenticated requests to Elasticsearch. */ public getHTTPAuthenticationScheme() { - return this.httpAuthorizationHeader.scheme.toLowerCase(); + return this.httpAuthorizationHeader?.scheme.toLowerCase() ?? null; } /** @@ -164,7 +179,9 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider * @param state State value previously stored by the provider. */ private async authenticateViaAuthorizationHeader(request: KibanaRequest, state?: unknown) { - const authHeaders = { authorization: this.httpAuthorizationHeader.toString() }; + const authHeaders = this.httpAuthorizationHeader + ? { authorization: this.httpAuthorizationHeader.toString() } + : ({} as Record<string, string>); try { const user = await this.getUser(request, authHeaders); this.logger.debug( @@ -173,7 +190,23 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider // Create session only if it doesn't exist yet, otherwise keep it unchanged. return AuthenticationResult.succeeded(user, { authHeaders, state: state ? undefined : {} }); } catch (err) { - this.logger.debug(`Failed to authenticate request : ${err.message}`); + if (LegacyElasticsearchErrorHelpers.isNotAuthorizedError(err)) { + if (!this.httpAuthorizationHeader) { + this.logger.error( + `Failed to authenticate anonymous request using Elasticsearch reserved anonymous user. Anonymous access may not be properly configured in Elasticsearch: ${err.message}` + ); + } else if (this.httpAuthorizationHeader.scheme.toLowerCase() === 'basic') { + this.logger.error( + `Failed to authenticate anonymous request using provided username/password credentials. The user with the provided username may not exist or the password is wrong: ${err.message}` + ); + } else { + this.logger.error( + `Failed to authenticate anonymous request using provided API key. The key may not exist or expired: ${err.message}` + ); + } + } else { + this.logger.error(`Failed to authenticate request : ${err.message}`); + } return AuthenticationResult.failed(err); } } diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index a306e701e4e8d..f41e721db33a2 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -902,8 +902,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.password]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: expected at least one defined value but got [undefined]" + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.password]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: expected at least one defined value but got [undefined]" `); expect(() => @@ -918,8 +919,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.username]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: expected at least one defined value but got [undefined]" + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.username]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: expected at least one defined value but got [undefined]" `); }); @@ -973,8 +975,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.username]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: types that failed validation: + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.username]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: types that failed validation: - [credentials.apiKey.0.key]: expected value of type [string] but got [undefined] - [credentials.apiKey.1]: expected value of type [string] but got [Object]" `); @@ -993,8 +996,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.username]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: types that failed validation: + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.username]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: types that failed validation: - [credentials.apiKey.0.id]: expected value of type [string] but got [undefined] - [credentials.apiKey.1]: expected value of type [string] but got [Object]" `); @@ -1073,6 +1077,40 @@ describe('config schema', () => { `); }); + it('can be successfully validated with `elasticsearch_anonymous_user` credentials', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + anonymous: { + anonymous1: { + order: 0, + credentials: 'elasticsearch_anonymous_user', + }, + }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "anonymous": Object { + "anonymous1": Object { + "credentials": "elasticsearch_anonymous_user", + "description": "Continue as Guest", + "enabled": true, + "hint": "For anonymous users", + "icon": "globe", + "order": 0, + "session": Object { + "idleTimeout": null, + }, + "showInSelector": true, + }, + }, + } + `); + }); + it('can be successfully validated with session config overrides', () => { expect( ConfigSchema.validate({ diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index b46c8dc2178a4..cf9ab3a78ab71 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -150,6 +150,7 @@ const providersConfigSchema = schema.object( }, { credentials: schema.oneOf([ + schema.literal('elasticsearch_anonymous_user'), schema.object({ username: schema.string(), password: schema.string(), diff --git a/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts b/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts new file mode 100644 index 0000000000000..3fc30c922b742 --- /dev/null +++ b/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts @@ -0,0 +1,40 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const anonymousAPITestsConfig = await readConfigFile(require.resolve('./anonymous.config.ts')); + return { + ...anonymousAPITestsConfig.getAll(), + + junit: { + reportName: 'X-Pack Security API Integration Tests (Anonymous with ES anonymous access)', + }, + + esTestCluster: { + ...anonymousAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...anonymousAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.anonymous.username=anonymous_user', + 'xpack.security.authc.anonymous.roles=anonymous_role', + ], + }, + + kbnTestServer: { + ...anonymousAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...anonymousAPITestsConfig + .get('kbnTestServer.serverArgs') + .filter((arg: string) => !arg.startsWith('--xpack.security.authc.providers')), + `--xpack.security.authc.providers=${JSON.stringify({ + anonymous: { anonymous1: { order: 0, credentials: 'elasticsearch_anonymous_user' } }, + basic: { basic1: { order: 1 } }, + })}`, + ], + }, + }; +} diff --git a/x-pack/test/security_api_integration/tests/anonymous/login.ts b/x-pack/test/security_api_integration/tests/anonymous/login.ts index e7c876f54ee5a..559dc7cc78036 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/login.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/login.ts @@ -31,18 +31,24 @@ export default function ({ getService }: FtrProviderContext) { expect(cookie.maxAge).to.be(0); } + const isElasticsearchAnonymousAccessEnabled = (config.get( + 'esTestCluster.serverArgs' + ) as string[]).some((setting) => setting.startsWith('xpack.security.authc.anonymous')); + describe('Anonymous authentication', () => { - before(async () => { - await security.user.create('anonymous_user', { - password: 'changeme', - roles: [], - full_name: 'Guest', + if (!isElasticsearchAnonymousAccessEnabled) { + before(async () => { + await security.user.create('anonymous_user', { + password: 'changeme', + roles: [], + full_name: 'Guest', + }); }); - }); - after(async () => { - await security.user.delete('anonymous_user'); - }); + after(async () => { + await security.user.delete('anonymous_user'); + }); + } it('should reject API requests if client is not authenticated', async () => { await supertest.get('/internal/security/me').set('kbn-xsrf', 'xxx').expect(401); @@ -97,7 +103,9 @@ export default function ({ getService }: FtrProviderContext) { expect(user.username).to.eql('anonymous_user'); expect(user.authentication_provider).to.eql({ type: 'anonymous', name: 'anonymous1' }); - expect(user.authentication_type).to.eql('realm'); + expect(user.authentication_type).to.eql( + isElasticsearchAnonymousAccessEnabled ? 'anonymous' : 'realm' + ); // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); From 44c436b8ad8bab89a9dccf53b496cf62a01d26b5 Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Wed, 2 Dec 2020 09:42:42 +0100 Subject: [PATCH 25/37] [Lens] Use index pattern through service instead of reading saved object (#84432) --- .../indexpattern_datasource/datapanel.tsx | 3 +- .../indexpattern_datasource/field_list.tsx | 7 -- x-pack/plugins/lens/server/plugin.tsx | 2 + .../server/routes/existing_fields.test.ts | 30 ++++---- .../lens/server/routes/existing_fields.ts | 71 ++++++++----------- .../plugins/lens/server/routes/field_stats.ts | 3 +- x-pack/plugins/lens/server/routes/index.ts | 3 +- .../plugins/lens/server/routes/telemetry.ts | 3 +- .../apis/lens/existing_fields.ts | 20 ++++++ 9 files changed, 73 insertions(+), 69 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index ad5509dd88bc9..5121714050c68 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -563,7 +563,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ '{availableFields} available {availableFields, plural, one {field} other {fields}}. {emptyFields} empty {emptyFields, plural, one {field} other {fields}}. {metaFields} meta {metaFields, plural, one {field} other {fields}}.', values: { availableFields: fieldGroups.AvailableFields.fields.length, - emptyFields: fieldGroups.EmptyFields.fields.length, + // empty fields can be undefined if there is no existence information to be fetched + emptyFields: fieldGroups.EmptyFields?.fields.length || 0, metaFields: fieldGroups.MetaFields.fields.length, }, })} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index eb7730677d52a..9e89468200e2c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -14,13 +14,6 @@ import { IndexPatternField } from './types'; import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; const PAGINATION_SIZE = 50; -export interface FieldsGroup { - specialFields: IndexPatternField[]; - availableFields: IndexPatternField[]; - emptyFields: IndexPatternField[]; - metaFields: IndexPatternField[]; -} - export type FieldGroups = Record< string, { diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index a8f9bef92349c..5a55b34cbcdff 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -7,6 +7,7 @@ import { Plugin, CoreSetup, CoreStart, PluginInitializerContext, Logger } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { Observable } from 'rxjs'; +import { PluginStart as DataPluginStart } from 'src/plugins/data/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { setupRoutes } from './routes'; import { @@ -23,6 +24,7 @@ export interface PluginSetupContract { export interface PluginStartContract { taskManager?: TaskManagerStartContract; + data: DataPluginStart; } export class LensServerPlugin implements Plugin<{}, {}, {}, {}> { diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index c877e69d7b0dd..0a3e669ba8538 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IndexPattern } from 'src/plugins/data/common'; import { existingFields, Field, buildFieldList } from './existing_fields'; describe('existingFields', () => { @@ -71,25 +72,20 @@ describe('existingFields', () => { describe('buildFieldList', () => { const indexPattern = { - id: '', - type: 'indexpattern', - attributes: { - title: 'testpattern', - type: 'type', - typeMeta: 'typemeta', - fields: JSON.stringify([ - { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, - { name: 'bar' }, - { name: '@bar' }, - { name: 'baz' }, - { name: '_mymeta' }, - ]), - }, - references: [], + title: 'testpattern', + type: 'type', + typeMeta: 'typemeta', + fields: [ + { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, + { name: 'bar' }, + { name: '@bar' }, + { name: 'baz' }, + { name: '_mymeta' }, + ], }; it('supports scripted fields', () => { - const fields = buildFieldList(indexPattern, []); + const fields = buildFieldList((indexPattern as unknown) as IndexPattern, []); expect(fields.find((f) => f.isScript)).toMatchObject({ isScript: true, name: 'foo', @@ -99,7 +95,7 @@ describe('buildFieldList', () => { }); it('supports meta fields', () => { - const fields = buildFieldList(indexPattern, ['_mymeta']); + const fields = buildFieldList((indexPattern as unknown) as IndexPattern, ['_mymeta']); expect(fields.find((f) => f.isMeta)).toMatchObject({ isScript: false, isMeta: true, diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 844c7b16e1eaa..aef8b1b3d7076 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -6,10 +6,12 @@ import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; -import { ILegacyScopedClusterClient, SavedObject, RequestHandlerContext } from 'src/core/server'; +import { ILegacyScopedClusterClient, RequestHandlerContext } from 'src/core/server'; import { CoreSetup, Logger } from 'src/core/server'; +import { IndexPattern, IndexPatternsService } from 'src/plugins/data/common'; import { BASE_API_URL } from '../../common'; -import { IndexPatternAttributes, UI_SETTINGS } from '../../../../../src/plugins/data/server'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/server'; +import { PluginStartContract } from '../plugin'; export function isBoomError(error: { isBoom?: boolean }): error is Boom { return error.isBoom === true; @@ -28,7 +30,7 @@ export interface Field { script?: string; } -export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { +export async function existingFieldsRoute(setup: CoreSetup<PluginStartContract>, logger: Logger) { const router = setup.http.createRouter(); router.post( @@ -47,11 +49,18 @@ export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { }, }, async (context, req, res) => { + const [{ savedObjects, elasticsearch }, { data }] = await setup.getStartServices(); + const savedObjectsClient = savedObjects.getScopedClient(req); + const esClient = elasticsearch.client.asScoped(req).asCurrentUser; try { return res.ok({ body: await fetchFieldExistence({ ...req.params, ...req.body, + indexPatternsService: await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + esClient + ), context, }), }); @@ -80,6 +89,7 @@ export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { async function fetchFieldExistence({ context, indexPatternId, + indexPatternsService, dslQuery = { match_all: {} }, fromDate, toDate, @@ -87,16 +97,14 @@ async function fetchFieldExistence({ }: { indexPatternId: string; context: RequestHandlerContext; + indexPatternsService: IndexPatternsService; dslQuery: object; fromDate?: string; toDate?: string; timeFieldName?: string; }) { const metaFields: string[] = await context.core.uiSettings.client.get(UI_SETTINGS.META_FIELDS); - const { indexPattern, indexPatternTitle } = await fetchIndexPatternDefinition( - indexPatternId, - context - ); + const indexPattern = await indexPatternsService.get(indexPatternId); const fields = buildFieldList(indexPattern, metaFields); const docs = await fetchIndexPatternStats({ @@ -104,51 +112,32 @@ async function fetchFieldExistence({ toDate, dslQuery, client: context.core.elasticsearch.legacy.client, - index: indexPatternTitle, - timeFieldName: timeFieldName || indexPattern.attributes.timeFieldName, + index: indexPattern.title, + timeFieldName: timeFieldName || indexPattern.timeFieldName, fields, }); return { - indexPatternTitle, + indexPatternTitle: indexPattern.title, existingFieldNames: existingFields(docs, fields), }; } -async function fetchIndexPatternDefinition(indexPatternId: string, context: RequestHandlerContext) { - const savedObjectsClient = context.core.savedObjects.client; - const indexPattern = await savedObjectsClient.get<IndexPatternAttributes>( - 'index-pattern', - indexPatternId - ); - const indexPatternTitle = indexPattern.attributes.title; - - return { - indexPattern, - indexPatternTitle, - }; -} - /** * Exported only for unit tests. */ -export function buildFieldList( - indexPattern: SavedObject<IndexPatternAttributes>, - metaFields: string[] -): Field[] { - return JSON.parse(indexPattern.attributes.fields).map( - (field: { name: string; lang: string; scripted?: boolean; script?: string }) => { - return { - name: field.name, - isScript: !!field.scripted, - lang: field.lang, - script: field.script, - // id is a special case - it doesn't show up in the meta field list, - // but as it's not part of source, it has to be handled separately. - isMeta: metaFields.includes(field.name) || field.name === '_id', - }; - } - ); +export function buildFieldList(indexPattern: IndexPattern, metaFields: string[]): Field[] { + return indexPattern.fields.map((field) => { + return { + name: field.name, + isScript: !!field.scripted, + lang: field.lang, + script: field.script, + // id is a special case - it doesn't show up in the meta field list, + // but as it's not part of source, it has to be handled separately. + isMeta: metaFields.includes(field.name) || field.name === '_id', + }; + }); } async function fetchIndexPatternStats({ diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 29e2416b74618..e0f1e05ed970d 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -11,10 +11,11 @@ import { CoreSetup } from 'src/core/server'; import { IFieldType } from 'src/plugins/data/common'; import { ESSearchResponse } from '../../../../typings/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; +import { PluginStartContract } from '../plugin'; const SHARD_SIZE = 5000; -export async function initFieldsRoute(setup: CoreSetup) { +export async function initFieldsRoute(setup: CoreSetup<PluginStartContract>) { const router = setup.http.createRouter(); router.post( { diff --git a/x-pack/plugins/lens/server/routes/index.ts b/x-pack/plugins/lens/server/routes/index.ts index 01018d8cd7fe5..b1d7e7ca8bc17 100644 --- a/x-pack/plugins/lens/server/routes/index.ts +++ b/x-pack/plugins/lens/server/routes/index.ts @@ -5,11 +5,12 @@ */ import { CoreSetup, Logger } from 'src/core/server'; +import { PluginStartContract } from '../plugin'; import { existingFieldsRoute } from './existing_fields'; import { initFieldsRoute } from './field_stats'; import { initLensUsageRoute } from './telemetry'; -export function setupRoutes(setup: CoreSetup, logger: Logger) { +export function setupRoutes(setup: CoreSetup<PluginStartContract>, logger: Logger) { existingFieldsRoute(setup, logger); initFieldsRoute(setup); initLensUsageRoute(setup); diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index 306c631cd78a7..820e32509923e 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -8,10 +8,11 @@ import Boom from '@hapi/boom'; import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { BASE_API_URL } from '../../common'; +import { PluginStartContract } from '../plugin'; // This route is responsible for taking a batch of click events from the browser // and writing them to saved objects -export async function initLensUsageRoute(setup: CoreSetup) { +export async function initLensUsageRoute(setup: CoreSetup<PluginStartContract>) { const router = setup.http.createRouter(); router.post( { diff --git a/x-pack/test/api_integration/apis/lens/existing_fields.ts b/x-pack/test/api_integration/apis/lens/existing_fields.ts index 08806df380f38..6eddaac50fda5 100644 --- a/x-pack/test/api_integration/apis/lens/existing_fields.ts +++ b/x-pack/test/api_integration/apis/lens/existing_fields.ts @@ -102,26 +102,46 @@ const metricBeatData = [ '_id', '_index', 'agent.ephemeral_id', + 'agent.ephemeral_id.keyword', 'agent.hostname', + 'agent.hostname.keyword', 'agent.id', + 'agent.id.keyword', 'agent.type', + 'agent.type.keyword', 'agent.version', + 'agent.version.keyword', 'ecs.version', + 'ecs.version.keyword', 'event.dataset', + 'event.dataset.keyword', 'event.duration', 'event.module', + 'event.module.keyword', 'host.architecture', + 'host.architecture.keyword', 'host.hostname', + 'host.hostname.keyword', 'host.id', + 'host.id.keyword', 'host.name', + 'host.name.keyword', 'host.os.build', + 'host.os.build.keyword', 'host.os.family', + 'host.os.family.keyword', 'host.os.kernel', + 'host.os.kernel.keyword', 'host.os.name', + 'host.os.name.keyword', 'host.os.platform', + 'host.os.platform.keyword', 'host.os.version', + 'host.os.version.keyword', 'metricset.name', + 'metricset.name.keyword', 'service.type', + 'service.type.keyword', 'system.cpu.cores', 'system.cpu.idle.pct', 'system.cpu.iowait.pct', From 30f8e41d45a88c9e1289af1548ac106625f7732a Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Wed, 2 Dec 2020 10:06:25 +0100 Subject: [PATCH 26/37] [Lens] Show color in flyout instead of auto (#84532) --- .../xy_visualization/color_assignment.ts | 51 +++++++++++- .../public/xy_visualization/visualization.tsx | 65 ++------------- .../xy_visualization/xy_config_panel.test.tsx | 83 +++++++++++++++++++ .../xy_visualization/xy_config_panel.tsx | 45 ++++++++-- 4 files changed, 180 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 68c47e11acfc0..e5764eaf0e8c0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -5,9 +5,11 @@ */ import { uniq, mapValues } from 'lodash'; -import { PaletteOutput } from 'src/plugins/charts/public'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { Datatable } from 'src/plugins/expressions'; -import { FormatFactory } from '../types'; +import { AccessorConfig, FormatFactory, FramePublicAPI } from '../types'; +import { getColumnToLabelMap } from './state_helpers'; +import { LayerConfig } from './types'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -87,3 +89,48 @@ export function getColorAssignments( }; }); } + +export function getAccessorColorConfig( + colorAssignments: ColorAssignments, + frame: FramePublicAPI, + layer: LayerConfig, + sortedAccessors: string[], + paletteService: PaletteRegistry +): AccessorConfig[] { + const layerContainsSplits = Boolean(layer.splitAccessor); + const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; + const totalSeriesCount = colorAssignments[currentPalette.name].totalSeriesCount; + return sortedAccessors.map((accessor) => { + const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + if (layerContainsSplits) { + return { + columnId: accessor as string, + triggerIcon: 'disabled', + }; + } + const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); + const rank = colorAssignments[currentPalette.name].getRank( + layer, + columnToLabel[accessor] || accessor, + accessor + ); + const customColor = + currentYConfig?.color || + paletteService.get(currentPalette.name).getColor( + [ + { + name: columnToLabel[accessor] || accessor, + rankAtDepth: rank, + totalSeriesAtDepth: totalSeriesCount, + }, + ], + { maxDepth: 1, totalSeries: totalSeriesCount }, + currentPalette.params + ); + return { + columnId: accessor as string, + triggerIcon: customColor ? 'color' : 'disabled', + color: customColor ? customColor : undefined, + }; + }); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 872eb179e6a5c..f0dcaf589b1c4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -10,24 +10,18 @@ import { render } from 'react-dom'; import { Position } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { PaletteRegistry } from 'src/plugins/charts/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { getSuggestions } from './xy_suggestions'; import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel'; -import { - Visualization, - OperationMetadata, - VisualizationType, - AccessorConfig, - FramePublicAPI, -} from '../types'; +import { Visualization, OperationMetadata, VisualizationType, AccessorConfig } from '../types'; import { State, SeriesType, visualizationTypes, LayerConfig } from './types'; -import { getColumnToLabelMap, isHorizontalChart } from './state_helpers'; +import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; import { LensIconChartMixedXy } from '../assets/chart_mixed_xy'; import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; -import { ColorAssignments, getColorAssignments } from './color_assignment'; +import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; const defaultIcon = LensIconChartBarStacked; const defaultSeriesType = 'bar_stacked'; @@ -328,7 +322,11 @@ export const getXyVisualization = ({ renderDimensionEditor(domElement, props) { render( <I18nProvider> - <DimensionEditor {...props} /> + <DimensionEditor + {...props} + formatFactory={data.fieldFormats.deserialize} + paletteService={paletteService} + /> </I18nProvider>, domElement ); @@ -375,51 +373,6 @@ export const getXyVisualization = ({ }, }); -function getAccessorColorConfig( - colorAssignments: ColorAssignments, - frame: FramePublicAPI, - layer: LayerConfig, - sortedAccessors: string[], - paletteService: PaletteRegistry -): AccessorConfig[] { - const layerContainsSplits = Boolean(layer.splitAccessor); - const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; - const totalSeriesCount = colorAssignments[currentPalette.name].totalSeriesCount; - return sortedAccessors.map((accessor) => { - const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); - if (layerContainsSplits) { - return { - columnId: accessor as string, - triggerIcon: 'disabled', - }; - } - const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); - const rank = colorAssignments[currentPalette.name].getRank( - layer, - columnToLabel[accessor] || accessor, - accessor - ); - const customColor = - currentYConfig?.color || - paletteService.get(currentPalette.name).getColor( - [ - { - name: columnToLabel[accessor] || accessor, - rankAtDepth: rank, - totalSeriesAtDepth: totalSeriesCount, - }, - ], - { maxDepth: 1, totalSeries: totalSeriesCount }, - currentPalette.params - ); - return { - columnId: accessor as string, - triggerIcon: customColor ? 'color' : 'disabled', - color: customColor ? customColor : undefined, - }; - }); -} - function validateLayersForDimension( dimension: string, layers: LayerConfig[], diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx index 99fbfa058a2de..7b84b990f963a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx @@ -14,6 +14,8 @@ import { FramePublicAPI } from '../types'; import { State } from './types'; import { Position } from '@elastic/charts'; import { createMockFramePublicAPI, createMockDatasource } from '../editor_frame_service/mocks'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { EuiColorPicker } from '@elastic/eui'; describe('XY Config panels', () => { let frame: FramePublicAPI; @@ -322,6 +324,8 @@ describe('XY Config panels', () => { accessor="bar" groupId="left" state={{ ...state, layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }] }} + formatFactory={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} /> ); @@ -343,6 +347,8 @@ describe('XY Config panels', () => { accessor="bar" groupId="left" state={state} + formatFactory={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} /> ); @@ -353,5 +359,82 @@ describe('XY Config panels', () => { expect(options!.map(({ label }) => label)).toEqual(['Auto', 'Left', 'Right']); }); + + test('sets the color of a dimension to the color from palette service if not set explicitly', () => { + const state = testState(); + const component = mount( + <DimensionEditor + layerId={state.layers[0].layerId} + frame={{ + ...frame, + activeData: { + first: { + type: 'datatable', + columns: [], + rows: [{ bar: 123 }], + }, + }, + }} + setState={jest.fn()} + accessor="bar" + groupId="left" + state={{ + ...state, + layers: [ + { + seriesType: 'bar', + layerId: 'first', + splitAccessor: undefined, + xAccessor: 'foo', + accessors: ['bar'], + }, + ], + }} + formatFactory={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} + /> + ); + + expect(component.find(EuiColorPicker).prop('color')).toEqual('black'); + }); + + test('uses the overwrite color if set', () => { + const state = testState(); + const component = mount( + <DimensionEditor + layerId={state.layers[0].layerId} + frame={{ + ...frame, + activeData: { + first: { + type: 'datatable', + columns: [], + rows: [{ bar: 123 }], + }, + }, + }} + setState={jest.fn()} + accessor="bar" + groupId="left" + state={{ + ...state, + layers: [ + { + seriesType: 'bar', + layerId: 'first', + splitAccessor: undefined, + xAccessor: 'foo', + accessors: ['bar'], + yConfig: [{ forAccessor: 'bar', color: 'red' }], + }, + ], + }} + formatFactory={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} + /> + ); + + expect(component.find(EuiColorPicker).prop('color')).toEqual('red'); + }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index a22530c5743b4..cd8a5993d3ecb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -5,7 +5,7 @@ */ import './xy_config_panel.scss'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { Position } from '@elastic/charts'; import { debounce } from 'lodash'; @@ -22,10 +22,12 @@ import { EuiToolTip, EuiIcon, } from '@elastic/eui'; +import { PaletteRegistry } from 'src/plugins/charts/public'; import { VisualizationLayerWidgetProps, VisualizationToolbarProps, VisualizationDimensionEditorProps, + FormatFactory, } from '../types'; import { State, @@ -48,6 +50,7 @@ import { AxisSettingsPopover } from './axis_settings_popover'; import { TooltipWrapper } from './tooltip_wrapper'; import { getAxesConfiguration } from './axes_configuration'; import { PalettePicker } from '../shared_components'; +import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; type UnwrapArray<T> = T extends Array<infer P> ? P : T; type AxesSettingsConfigKeys = keyof AxesSettingsConfig; @@ -445,7 +448,12 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) { } const idPrefix = htmlIdGenerator()(); -export function DimensionEditor(props: VisualizationDimensionEditorProps<State>) { +export function DimensionEditor( + props: VisualizationDimensionEditorProps<State> & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + } +) { const { state, setState, layerId, accessor } = props; const index = state.layers.findIndex((l) => l.layerId === layerId); const layer = state.layers[index]; @@ -556,12 +564,37 @@ const ColorPicker = ({ setState, layerId, accessor, -}: VisualizationDimensionEditorProps<State>) => { + frame, + formatFactory, + paletteService, +}: VisualizationDimensionEditorProps<State> & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; +}) => { const index = state.layers.findIndex((l) => l.layerId === layerId); const layer = state.layers[index]; const disabled = !!layer.splitAccessor; - const [color, setColor] = useState(getSeriesColor(layer, accessor)); + const overwriteColor = getSeriesColor(layer, accessor); + const currentColor = useMemo(() => { + if (overwriteColor || !frame.activeData) return overwriteColor; + + const colorAssignments = getColorAssignments( + state.layers, + { tables: frame.activeData }, + formatFactory + ); + const mappedAccessors = getAccessorColorConfig( + colorAssignments, + frame, + layer, + [accessor], + paletteService + ); + return mappedAccessors[0].color; + }, [overwriteColor, frame, paletteService, state.layers, accessor, formatFactory, layer]); + + const [color, setColor] = useState(currentColor); const handleColor: EuiColorPickerProps['onChange'] = (text, output) => { setColor(text); @@ -596,9 +629,9 @@ const ColorPicker = ({ <EuiColorPicker data-test-subj="indexPattern-dimension-colorPicker" compressed - isClearable + isClearable={Boolean(overwriteColor)} onChange={handleColor} - color={disabled ? '' : color} + color={disabled ? '' : color || currentColor} disabled={disabled} placeholder={i18n.translate('xpack.lens.xyChart.seriesColor.auto', { defaultMessage: 'Auto', From 59a405dc80e63abf118ab1a50a2a8bae2f5e4e14 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin <aleh.zasypkin@gmail.com> Date: Wed, 2 Dec 2020 11:32:22 +0100 Subject: [PATCH 27/37] Make all providers to preserve original URL when session expires. (#84229) --- x-pack/plugins/security/common/constants.ts | 3 + .../common/model/authenticated_user.ts | 3 +- .../model/authentication_provider.test.ts | 21 ++++ .../common/model/authentication_provider.ts | 21 ++++ x-pack/plugins/security/common/model/index.ts | 1 + x-pack/plugins/security/common/parse_next.ts | 12 ++- x-pack/plugins/security/common/types.ts | 8 +- .../logged_out/logged_out_page.test.tsx | 39 +++++++ .../logged_out/logged_out_page.tsx | 3 +- .../authentication/login/login_page.tsx | 7 +- .../public/session/session_expired.ts | 16 ++- .../server/audit/security_audit_logger.ts | 2 +- .../authentication/authenticator.test.ts | 101 +++++++++++++----- .../server/authentication/authenticator.ts | 71 ++++++++---- .../authentication/providers/anonymous.ts | 2 +- .../authentication/providers/base.mock.ts | 2 +- .../server/authentication/providers/base.ts | 2 +- .../authentication/providers/basic.test.ts | 27 ++--- .../server/authentication/providers/basic.ts | 10 +- .../authentication/providers/kerberos.test.ts | 4 +- .../authentication/providers/kerberos.ts | 2 +- .../authentication/providers/oidc.test.ts | 6 +- .../server/authentication/providers/oidc.ts | 17 +-- .../authentication/providers/pki.test.ts | 4 +- .../server/authentication/providers/pki.ts | 2 +- .../authentication/providers/saml.test.ts | 18 ++-- .../server/authentication/providers/saml.ts | 17 +-- .../authentication/providers/token.test.ts | 28 ++--- .../server/authentication/providers/token.ts | 10 +- x-pack/plugins/security/server/config.ts | 2 +- .../routes/views/access_agreement.test.ts | 2 +- .../security/server/routes/views/login.ts | 11 +- .../server/session_management/session.ts | 2 +- .../session_management/session_index.ts | 2 +- .../tests/anonymous/login.ts | 2 +- .../tests/kerberos/kerberos_login.ts | 2 +- .../login_selector/basic_functionality.ts | 2 +- .../tests/pki/pki_auth.ts | 2 +- .../tests/session_idle/cleanup.ts | 2 +- .../tests/session_lifespan/cleanup.ts | 2 +- 40 files changed, 322 insertions(+), 168 deletions(-) create mode 100644 x-pack/plugins/security/common/model/authentication_provider.test.ts create mode 100644 x-pack/plugins/security/common/model/authentication_provider.ts create mode 100644 x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 07e6ab6c72cb9..f53b5ca6d56ca 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -19,3 +19,6 @@ export const APPLICATION_PREFIX = 'kibana-'; export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*'; export const AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER = 'auth_provider_hint'; +export const LOGOUT_PROVIDER_QUERY_STRING_PARAMETER = 'provider'; +export const LOGOUT_REASON_QUERY_STRING_PARAMETER = 'msg'; +export const NEXT_URL_QUERY_STRING_PARAMETER = 'next'; diff --git a/x-pack/plugins/security/common/model/authenticated_user.ts b/x-pack/plugins/security/common/model/authenticated_user.ts index c22c5fc4ef0da..491ceb6845e28 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import type { AuthenticationProvider } from '../types'; -import { User } from './user'; +import type { AuthenticationProvider, User } from '.'; const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native']; diff --git a/x-pack/plugins/security/common/model/authentication_provider.test.ts b/x-pack/plugins/security/common/model/authentication_provider.test.ts new file mode 100644 index 0000000000000..fc32d3108be08 --- /dev/null +++ b/x-pack/plugins/security/common/model/authentication_provider.test.ts @@ -0,0 +1,21 @@ +/* + * 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 { shouldProviderUseLoginForm } from './authentication_provider'; + +describe('#shouldProviderUseLoginForm', () => { + ['basic', 'token'].forEach((providerType) => { + it(`returns "true" for "${providerType}" provider`, () => { + expect(shouldProviderUseLoginForm(providerType)).toEqual(true); + }); + }); + + ['anonymous', 'http', 'kerberos', 'oidc', 'pki', 'saml'].forEach((providerType) => { + it(`returns "false" for "${providerType}" provider`, () => { + expect(shouldProviderUseLoginForm(providerType)).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/security/common/model/authentication_provider.ts b/x-pack/plugins/security/common/model/authentication_provider.ts new file mode 100644 index 0000000000000..1b34fbc9da29a --- /dev/null +++ b/x-pack/plugins/security/common/model/authentication_provider.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Type and name tuple to identify provider used to authenticate user. + */ +export interface AuthenticationProvider { + type: string; + name: string; +} + +/** + * Checks whether authentication provider with the specified type uses Kibana's native login form. + * @param providerType Type of the authentication provider. + */ +export function shouldProviderUseLoginForm(providerType: string) { + return providerType === 'basic' || providerType === 'token'; +} diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 59d4908c67ffb..ee1dcffd4a794 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -7,6 +7,7 @@ export { ApiKey, ApiKeyToInvalidate } from './api_key'; export { User, EditUser, getUserDisplayName } from './user'; export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; +export { AuthenticationProvider, shouldProviderUseLoginForm } from './authentication_provider'; export { BuiltinESPrivileges } from './builtin_es_privileges'; export { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_privileges'; export { FeaturesPrivileges } from './features_privileges'; diff --git a/x-pack/plugins/security/common/parse_next.ts b/x-pack/plugins/security/common/parse_next.ts index 7ce0de05ad526..68903baeb05b8 100644 --- a/x-pack/plugins/security/common/parse_next.ts +++ b/x-pack/plugins/security/common/parse_next.ts @@ -5,19 +5,21 @@ */ import { parse } from 'url'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from './constants'; import { isInternalURL } from './is_internal_url'; export function parseNext(href: string, basePath = '') { const { query, hash } = parse(href, true); - if (!query.next) { + + let next = query[NEXT_URL_QUERY_STRING_PARAMETER]; + if (!next) { return `${basePath}/`; } - let next: string; - if (Array.isArray(query.next) && query.next.length > 0) { - next = query.next[0]; + if (Array.isArray(next) && next.length > 0) { + next = next[0]; } else { - next = query.next as string; + next = next as string; } // validate that `next` is not attempting a redirect to somewhere diff --git a/x-pack/plugins/security/common/types.ts b/x-pack/plugins/security/common/types.ts index c668c6ccf71d1..33e2875acefef 100644 --- a/x-pack/plugins/security/common/types.ts +++ b/x-pack/plugins/security/common/types.ts @@ -4,13 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -/** - * Type and name tuple to identify provider used to authenticate user. - */ -export interface AuthenticationProvider { - type: string; - name: string; -} +import type { AuthenticationProvider } from './model'; export interface SessionInfo { now: number; diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx new file mode 100644 index 0000000000000..89d622e086b38 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx @@ -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 React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test/jest'; +import { LoggedOutPage } from './logged_out_page'; + +import { coreMock } from '../../../../../../src/core/public/mocks'; + +describe('LoggedOutPage', () => { + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { href: 'https://some-host' }, + writable: true, + }); + }); + + it('points to a base path if `next` parameter is not provided', async () => { + const basePathMock = coreMock.createStart({ basePath: '/mock-base-path' }).http.basePath; + const wrapper = mountWithIntl(<LoggedOutPage basePath={basePathMock} />); + + expect(wrapper.find(EuiButton).prop('href')).toBe('/mock-base-path/'); + }); + + it('properly parses `next` parameter', async () => { + window.location.href = `https://host.com/mock-base-path/security/logged_out?next=${encodeURIComponent( + '/mock-base-path/app/home#/?_g=()' + )}`; + + const basePathMock = coreMock.createStart({ basePath: '/mock-base-path' }).http.basePath; + const wrapper = mountWithIntl(<LoggedOutPage basePath={basePathMock} />); + + expect(wrapper.find(EuiButton).prop('href')).toBe('/mock-base-path/app/home#/?_g=()'); + }); +}); diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx index a708931c3fa95..5498b8ef3644c 100644 --- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx +++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx @@ -9,6 +9,7 @@ import ReactDOM from 'react-dom'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, IBasePath } from 'src/core/public'; +import { parseNext } from '../../../common/parse_next'; import { AuthenticationStatePage } from '../components'; interface Props { @@ -25,7 +26,7 @@ export function LoggedOutPage({ basePath }: Props) { /> } > - <EuiButton href={basePath.prepend('/')}> + <EuiButton href={parseNext(window.location.href, basePath.serverBasePath)}> <FormattedMessage id="xpack.security.loggedOut.login" defaultMessage="Log in" /> </EuiButton> </AuthenticationStatePage> diff --git a/x-pack/plugins/security/public/authentication/login/login_page.tsx b/x-pack/plugins/security/public/authentication/login/login_page.tsx index 0646962684284..35703212762fd 100644 --- a/x-pack/plugins/security/public/authentication/login/login_page.tsx +++ b/x-pack/plugins/security/public/authentication/login/login_page.tsx @@ -15,7 +15,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiTitle } from '@elasti import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, FatalErrorsStart, HttpStart, NotificationsStart } from 'src/core/public'; -import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER } from '../../../common/constants'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + LOGOUT_REASON_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { LoginState } from '../../../common/login_state'; import { LoginForm, DisabledLoginForm } from './components'; @@ -219,7 +222,7 @@ export class LoginPage extends Component<Props, State> { http={this.props.http} notifications={this.props.notifications} selector={selector} - infoMessage={infoMessageMap.get(query.msg?.toString())} + infoMessage={infoMessageMap.get(query[LOGOUT_REASON_QUERY_STRING_PARAMETER]?.toString())} loginAssistanceMessage={this.props.loginAssistanceMessage} loginHelp={loginHelp} authProviderHint={query[AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER]?.toString()} diff --git a/x-pack/plugins/security/public/session/session_expired.ts b/x-pack/plugins/security/public/session/session_expired.ts index 5866526b8851e..52ba37c242d09 100644 --- a/x-pack/plugins/security/public/session/session_expired.ts +++ b/x-pack/plugins/security/public/session/session_expired.ts @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + LOGOUT_PROVIDER_QUERY_STRING_PARAMETER, + LOGOUT_REASON_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../common/constants'; + export interface ISessionExpired { logout(): void; } @@ -11,13 +17,15 @@ export interface ISessionExpired { const getNextParameter = () => { const { location } = window; const next = encodeURIComponent(`${location.pathname}${location.search}${location.hash}`); - return `&next=${next}`; + return `&${NEXT_URL_QUERY_STRING_PARAMETER}=${next}`; }; const getProviderParameter = (tenant: string) => { const key = `${tenant}/session_provider`; const providerName = sessionStorage.getItem(key); - return providerName ? `&provider=${encodeURIComponent(providerName)}` : ''; + return providerName + ? `&${LOGOUT_PROVIDER_QUERY_STRING_PARAMETER}=${encodeURIComponent(providerName)}` + : ''; }; export class SessionExpired { @@ -26,6 +34,8 @@ export class SessionExpired { logout() { const next = getNextParameter(); const provider = getProviderParameter(this.tenant); - window.location.assign(`${this.logoutUrl}?msg=SESSION_EXPIRED${next}${provider}`); + window.location.assign( + `${this.logoutUrl}?${LOGOUT_REASON_QUERY_STRING_PARAMETER}=SESSION_EXPIRED${next}${provider}` + ); } } diff --git a/x-pack/plugins/security/server/audit/security_audit_logger.ts b/x-pack/plugins/security/server/audit/security_audit_logger.ts index ee81f5f330f44..c0d431b3c2fa2 100644 --- a/x-pack/plugins/security/server/audit/security_audit_logger.ts +++ b/x-pack/plugins/security/server/audit/security_audit_logger.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AuthenticationProvider } from '../../common/types'; +import type { AuthenticationProvider } from '../../common/model'; import { LegacyAuditLogger } from './audit_service'; /** diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 16cb51cbfccf5..ed5d05dbcf619 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -111,31 +111,78 @@ describe('Authenticator', () => { ).toThrowError('Provider name "__http__" is reserved.'); }); - it('properly sets `loggedOut` URL.', () => { - const basicAuthenticationProviderMock = jest.requireMock('./providers/basic') - .BasicAuthenticationProvider; + describe('#options.urls.loggedOut', () => { + it('points to /login if provider requires login form', () => { + const authenticationProviderMock = jest.requireMock(`./providers/basic`) + .BasicAuthenticationProvider; + authenticationProviderMock.mockClear(); + new Authenticator(getMockOptions()); + const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; - basicAuthenticationProviderMock.mockClear(); - new Authenticator(getMockOptions()); - expect(basicAuthenticationProviderMock).toHaveBeenCalledWith( - expect.objectContaining({ - urls: { - loggedOut: '/mock-server-basepath/security/logged_out', - }, - }), - expect.anything() - ); + expect(getLoggedOutURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/login?msg=LOGGED_OUT' + ); - basicAuthenticationProviderMock.mockClear(); - new Authenticator(getMockOptions({ selector: { enabled: true } })); - expect(basicAuthenticationProviderMock).toHaveBeenCalledWith( - expect.objectContaining({ - urls: { - loggedOut: `/mock-server-basepath/login?msg=LOGGED_OUT`, - }, - }), - expect.anything() - ); + expect( + getLoggedOutURL( + httpServerMock.createKibanaRequest({ + query: { next: '/app/ml/encode me', msg: 'SESSION_EXPIRED' }, + }) + ) + ).toBe('/mock-server-basepath/login?next=%2Fapp%2Fml%2Fencode+me&msg=SESSION_EXPIRED'); + }); + + it('points to /login if login selector is enabled', () => { + const authenticationProviderMock = jest.requireMock(`./providers/saml`) + .SAMLAuthenticationProvider; + authenticationProviderMock.mockClear(); + new Authenticator( + getMockOptions({ + selector: { enabled: true }, + providers: { saml: { saml1: { order: 0, realm: 'realm' } } }, + }) + ); + const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; + + expect(getLoggedOutURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/login?msg=LOGGED_OUT' + ); + + expect( + getLoggedOutURL( + httpServerMock.createKibanaRequest({ + query: { next: '/app/ml/encode me', msg: 'SESSION_EXPIRED' }, + }) + ) + ).toBe('/mock-server-basepath/login?next=%2Fapp%2Fml%2Fencode+me&msg=SESSION_EXPIRED'); + }); + + it('points to /security/logged_out if login selector is NOT enabled', () => { + const authenticationProviderMock = jest.requireMock(`./providers/saml`) + .SAMLAuthenticationProvider; + authenticationProviderMock.mockClear(); + new Authenticator( + getMockOptions({ + selector: { enabled: false }, + providers: { saml: { saml1: { order: 0, realm: 'realm' } } }, + }) + ); + const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; + + expect(getLoggedOutURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/security/logged_out?msg=LOGGED_OUT' + ); + + expect( + getLoggedOutURL( + httpServerMock.createKibanaRequest({ + query: { next: '/app/ml/encode me', msg: 'SESSION_EXPIRED' }, + }) + ) + ).toBe( + '/mock-server-basepath/security/logged_out?next=%2Fapp%2Fml%2Fencode+me&msg=SESSION_EXPIRED' + ); + }); }); describe('HTTP authentication provider', () => { @@ -1769,7 +1816,9 @@ describe('Authenticator', () => { }); it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => { - const request = httpServerMock.createKibanaRequest({ query: { provider: 'basic1' } }); + const request = httpServerMock.createKibanaRequest({ + query: { provider: 'basic1' }, + }); mockOptions.session.get.mockResolvedValue(null); mockBasicAuthenticationProvider.logout.mockResolvedValue( @@ -1782,7 +1831,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request, null); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.clear).toHaveBeenCalled(); }); it('if session does not exist and provider name is not available, returns whatever authentication provider returns.', async () => { @@ -1811,7 +1860,7 @@ describe('Authenticator', () => { ); expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.clear).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 718415e485725..f175f47d30351 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -10,10 +10,15 @@ import { ILegacyClusterClient, IBasePath, } from '../../../../../src/core/server'; -import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER } from '../../common/constants'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + LOGOUT_PROVIDER_QUERY_STRING_PARAMETER, + LOGOUT_REASON_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../common/constants'; import type { SecurityLicense } from '../../common/licensing'; -import type { AuthenticatedUser } from '../../common/model'; -import type { AuthenticationProvider } from '../../common/types'; +import type { AuthenticatedUser, AuthenticationProvider } from '../../common/model'; +import { shouldProviderUseLoginForm } from '../../common/model'; import { SecurityAuditLogger, AuditServiceSetup, userLoginEvent } from '../audit'; import type { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; @@ -199,11 +204,6 @@ export class Authenticator { client: this.options.clusterClient, logger: this.options.loggers.get('tokens'), }), - urls: { - loggedOut: options.config.authc.selector.enabled - ? `${options.basePath.serverBasePath}/login?msg=LOGGED_OUT` - : `${options.basePath.serverBasePath}/security/logged_out`, - }, }; this.providers = new Map( @@ -218,6 +218,7 @@ export class Authenticator { ...providerCommonOptions, name, logger: options.loggers.get(type, name), + urls: { loggedOut: (request) => this.getLoggedOutURL(request, type) }, }), this.options.config.authc.providers[type]?.[name] ), @@ -232,6 +233,9 @@ export class Authenticator { ...providerCommonOptions, name: '__http__', logger: options.loggers.get(HTTPAuthenticationProvider.type), + urls: { + loggedOut: (request) => this.getLoggedOutURL(request, HTTPAuthenticationProvider.type), + }, }) ); } @@ -338,7 +342,9 @@ export class Authenticator { if (this.shouldRedirectToLoginSelector(request, existingSessionValue)) { this.logger.debug('Redirecting request to Login Selector.'); return AuthenticationResult.redirectTo( - `${this.options.basePath.serverBasePath}/login?next=${encodeURIComponent( + `${ + this.options.basePath.serverBasePath + }/login?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` )}${ suggestedProviderName && !existingSessionValue @@ -385,20 +391,17 @@ export class Authenticator { assertRequest(request); const sessionValue = await this.getSessionValue(request); - if (sessionValue) { + const suggestedProviderName = + sessionValue?.provider.name ?? + request.url.searchParams.get(LOGOUT_PROVIDER_QUERY_STRING_PARAMETER); + if (suggestedProviderName) { await this.session.clear(request); - return this.providers - .get(sessionValue.provider.name)! - .logout(request, sessionValue.state ?? null); - } - const queryStringProviderName = (request.query as Record<string, string>)?.provider; - if (queryStringProviderName) { - // provider name is passed in a query param and sourced from the browser's local storage; - // hence, we can't assume that this provider exists, so we have to check it - const provider = this.providers.get(queryStringProviderName); + // Provider name may be passed in a query param and sourced from the browser's local storage; + // hence, we can't assume that this provider exists, so we have to check it. + const provider = this.providers.get(suggestedProviderName); if (provider) { - return provider.logout(request, null); + return provider.logout(request, sessionValue?.state ?? null); } } else { // In case logout is called and we cannot figure out what provider is supposed to handle it, @@ -737,7 +740,7 @@ export class Authenticator { // redirect URL in the `next` parameter. Redirect URL provided in authentication result, if any, // always takes precedence over what is specified in `redirectURL` parameter. if (preAccessRedirectURL) { - preAccessRedirectURL = `${preAccessRedirectURL}?next=${encodeURIComponent( + preAccessRedirectURL = `${preAccessRedirectURL}?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( authenticationResult.redirectURL || redirectURL || `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` @@ -754,4 +757,30 @@ export class Authenticator { }) : authenticationResult; } + + /** + * Creates a logged out URL for the specified request and provider. + * @param request Request that initiated logout. + * @param providerType Type of the provider that handles logout. + */ + private getLoggedOutURL(request: KibanaRequest, providerType: string) { + // The app that handles logout needs to know the reason of the logout and the URL we may need to + // redirect user to once they log in again (e.g. when session expires). + const searchParams = new URLSearchParams(); + for (const [key, defaultValue] of [ + [NEXT_URL_QUERY_STRING_PARAMETER, null], + [LOGOUT_REASON_QUERY_STRING_PARAMETER, 'LOGGED_OUT'], + ] as Array<[string, string | null]>) { + const value = request.url.searchParams.get(key) || defaultValue; + if (value) { + searchParams.append(key, value); + } + } + + // Query string may contain the path where logout has been called or + // logout reason that login page may need to know. + return this.options.config.authc.selector.enabled || shouldProviderUseLoginForm(providerType) + ? `${this.options.basePath.serverBasePath}/login?${searchParams.toString()}` + : `${this.options.basePath.serverBasePath}/security/logged_out?${searchParams.toString()}`; + } } diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.ts index 6d62d3a909e55..4d1b5f4a74b2f 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.ts @@ -162,7 +162,7 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider return DeauthenticationResult.notHandled(); } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index 1b574e6e44c10..47d961bc8faf8 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -22,7 +22,7 @@ export function mockAuthenticationProviderOptions(options?: { name: string }) { tokens: { refresh: jest.fn(), invalidate: jest.fn() }, name: options?.name ?? 'basic1', urls: { - loggedOut: '/mock-server-basepath/security/logged_out', + loggedOut: jest.fn().mockReturnValue('/mock-server-basepath/security/logged_out'), }, }; } diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index a5a68f2a49315..f1845617c87a4 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -29,7 +29,7 @@ export interface AuthenticationProviderOptions { logger: Logger; tokens: PublicMethodsOf<Tokens>; urls: { - loggedOut: string; + loggedOut: (request: KibanaRequest) => string; }; } diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts index 87002ebed5672..4f93e2327da06 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts @@ -34,6 +34,8 @@ describe('BasicAuthenticationProvider', () => { let mockOptions: ReturnType<typeof mockAuthenticationProviderOptions>; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions(); + mockOptions.urls.loggedOut.mockReturnValue('/some-logged-out-page'); + provider = new BasicAuthenticationProvider(mockOptions); }); @@ -184,30 +186,13 @@ describe('BasicAuthenticationProvider', () => { ); }); - it('redirects to login view if state is `null`.', async () => { - await expect(provider.logout(httpServerMock.createKibanaRequest(), null)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') - ); - }); - - it('always redirects to the login page.', async () => { + it('redirects to the logged out URL.', async () => { await expect(provider.logout(httpServerMock.createKibanaRequest(), {})).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') + DeauthenticationResult.redirectTo('/some-logged-out-page') ); - }); - it('passes query string parameters to the login page.', async () => { - await expect( - provider.logout( - httpServerMock.createKibanaRequest({ - query: { next: '/app/ml', msg: 'SESSION_EXPIRED' }, - }), - {} - ) - ).resolves.toEqual( - DeauthenticationResult.redirectTo( - '/mock-server-basepath/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED' - ) + await expect(provider.logout(httpServerMock.createKibanaRequest(), null)).resolves.toEqual( + DeauthenticationResult.redirectTo('/some-logged-out-page') ); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/basic.ts b/x-pack/plugins/security/server/authentication/providers/basic.ts index 28b671346ee7f..6a5ae29dfd832 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.ts @@ -5,6 +5,7 @@ */ import { KibanaRequest } from '../../../../../../src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import { canRedirectRequest } from '../can_redirect_request'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -108,7 +109,7 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Redirecting request to Login page.'); const basePath = this.options.basePath.get(request); return AuthenticationResult.redirectTo( - `${basePath}/login?next=${encodeURIComponent( + `${basePath}/login?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( `${basePath}${request.url.pathname}${request.url.search}` )}` ); @@ -131,12 +132,7 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.notHandled(); } - // Query string may contain the path where logout has been called or - // logout reason that login page may need to know. - const queryString = request.url.search || `?msg=LOGGED_OUT`; - return DeauthenticationResult.redirectTo( - `${this.options.basePath.get(request)}/login${queryString}` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index eb4ac8f4dcbed..d368bf90cf360 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -470,7 +470,7 @@ describe('KerberosAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); @@ -501,7 +501,7 @@ describe('KerberosAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 2e15893d0845f..9bf419c7dacaa 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -124,7 +124,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 126306c885e53..9988ddd99c395 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -611,10 +611,10 @@ describe('OIDCAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); await expect(provider.logout(request, { nonce: 'x', realm: 'oidc1' })).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); @@ -647,7 +647,7 @@ describe('OIDCAuthenticationProvider', () => { await expect( provider.logout(request, { accessToken, refreshToken, realm: 'oidc1' }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', { diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index 250641d1cf174..c46ea37f144e9 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import type from 'type-detect'; import { KibanaRequest } from '../../../../../../src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; @@ -434,7 +435,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -450,14 +451,18 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. */ private captureRedirectURL(request: KibanaRequest) { + const searchParams = new URLSearchParams([ + [ + NEXT_URL_QUERY_STRING_PARAMETER, + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}`, + ], + ['providerType', this.type], + ['providerName', this.options.name], + ]); return AuthenticationResult.redirectTo( `${ this.options.basePath.serverBasePath - }/internal/security/capture-url?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` - )}&providerType=${encodeURIComponent(this.type)}&providerName=${encodeURIComponent( - this.options.name - )}`, + }/internal/security/capture-url?${searchParams.toString()}`, // Here we indicate that current session, if any, should be invalidated. It is a no-op for the // initial handshake, but is essential when both access and refresh tokens are expired. { state: null } diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index aa85b8a43af4d..763231f7fd0df 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -544,7 +544,7 @@ describe('PKIAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); @@ -572,7 +572,7 @@ describe('PKIAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, state)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 974a838127e1d..4bb0ddaa4ee65 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -128,7 +128,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 03c0b7404da39..5cba017e4916b 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -1022,10 +1022,10 @@ describe('SAMLAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); await expect(provider.logout(request, { somethingElse: 'x' } as any)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); @@ -1082,7 +1082,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1103,7 +1103,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1126,7 +1126,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1145,7 +1145,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken: 'x-saml-refresh-token', realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { @@ -1159,7 +1159,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); @@ -1174,7 +1174,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); @@ -1187,7 +1187,7 @@ describe('SAMLAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLResponse: 'xxx yyy' } }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 8f31968e5f639..34639a849d354 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; import { isInternalURL } from '../../../common/is_internal_url'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -282,7 +283,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -606,14 +607,18 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. */ private captureRedirectURL(request: KibanaRequest) { + const searchParams = new URLSearchParams([ + [ + NEXT_URL_QUERY_STRING_PARAMETER, + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}`, + ], + ['providerType', this.type], + ['providerName', this.options.name], + ]); return AuthenticationResult.redirectTo( `${ this.options.basePath.serverBasePath - }/internal/security/capture-url?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` - )}&providerType=${encodeURIComponent(this.type)}&providerName=${encodeURIComponent( - this.options.name - )}`, + }/internal/security/capture-url?${searchParams.toString()}`, // Here we indicate that current session, if any, should be invalidated. It is a no-op for the // initial handshake, but is essential when both access and refresh tokens are expired. { state: null } diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index e09400e9bb44a..5a600461ef467 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -37,6 +37,8 @@ describe('TokenAuthenticationProvider', () => { let mockOptions: MockAuthenticationProviderOptions; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions({ name: 'token' }); + mockOptions.urls.loggedOut.mockReturnValue('/some-logged-out-page'); + provider = new TokenAuthenticationProvider(mockOptions); }); @@ -347,11 +349,9 @@ describe('TokenAuthenticationProvider', () => { expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); }); - it('redirects to login view if state is `null`.', async () => { - const request = httpServerMock.createKibanaRequest(); - - await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') + it('redirects to the logged out URL if state is `null`.', async () => { + await expect(provider.logout(httpServerMock.createKibanaRequest(), null)).resolves.toEqual( + DeauthenticationResult.redirectTo('/some-logged-out-page') ); expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); @@ -372,28 +372,14 @@ describe('TokenAuthenticationProvider', () => { expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair); }); - it('redirects to /login if tokens are invalidated successfully', async () => { + it('redirects to the logged out URL if tokens are invalidated successfully.', async () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') - ); - - expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair); - }); - - it('redirects to /login with optional search parameters if tokens are invalidated successfully', async () => { - const request = httpServerMock.createKibanaRequest({ query: { yep: 'nope' } }); - const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - - mockOptions.tokens.invalidate.mockResolvedValue(undefined); - - await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?yep=nope') + DeauthenticationResult.redirectTo('/some-logged-out-page') ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 2032db4b0a8f2..67c2d244e75a2 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -6,6 +6,7 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { canRedirectRequest } from '../can_redirect_request'; @@ -145,10 +146,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { } } - const queryString = request.url.search || `?msg=LOGGED_OUT`; - return DeauthenticationResult.redirectTo( - `${this.options.basePath.get(request)}/login${queryString}` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -235,6 +233,8 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { const nextURL = encodeURIComponent( `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` ); - return `${this.options.basePath.get(request)}/login?next=${nextURL}`; + return `${this.options.basePath.get( + request + )}/login?${NEXT_URL_QUERY_STRING_PARAMETER}=${nextURL}`; } } diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index cf9ab3a78ab71..8d1415e1574d4 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -9,7 +9,7 @@ import type { Duration } from 'moment'; import { schema, Type, TypeOf } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { Logger, config as coreConfig } from '../../../../src/core/server'; -import type { AuthenticationProvider } from '../common/types'; +import type { AuthenticationProvider } from '../common/model'; export type ConfigType = ReturnType<typeof createConfig>; type RawConfigType = TypeOf<typeof ConfigSchema>; diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index b513230b3ba6f..dfe5faa95ae15 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -14,7 +14,7 @@ import { RequestHandlerContext, } from '../../../../../../src/core/server'; import { SecurityLicense, SecurityLicenseFeatures } from '../../../common/licensing'; -import { AuthenticationProvider } from '../../../common/types'; +import type { AuthenticationProvider } from '../../../common/model'; import { ConfigType } from '../../config'; import { Session } from '../../session_management'; import { defineAccessAgreementRoutes } from './access_agreement'; diff --git a/x-pack/plugins/security/server/routes/views/login.ts b/x-pack/plugins/security/server/routes/views/login.ts index 93d43d04a86ca..68becb48f596e 100644 --- a/x-pack/plugins/security/server/routes/views/login.ts +++ b/x-pack/plugins/security/server/routes/views/login.ts @@ -7,6 +7,11 @@ import { schema } from '@kbn/config-schema'; import { parseNext } from '../../../common/parse_next'; import { LoginState } from '../../../common/login_state'; +import { shouldProviderUseLoginForm } from '../../../common/model'; +import { + LOGOUT_REASON_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { RouteDefinitionParams } from '..'; /** @@ -26,8 +31,8 @@ export function defineLoginRoutes({ validate: { query: schema.object( { - next: schema.maybe(schema.string()), - msg: schema.maybe(schema.string()), + [NEXT_URL_QUERY_STRING_PARAMETER]: schema.maybe(schema.string()), + [LOGOUT_REASON_QUERY_STRING_PARAMETER]: schema.maybe(schema.string()), }, { unknowns: 'allow' } ), @@ -59,7 +64,7 @@ export function defineLoginRoutes({ // Since `config.authc.sortedProviders` is based on `config.authc.providers` config we can // be sure that config is present for every provider in `config.authc.sortedProviders`. const { showInSelector, description, hint, icon } = config.authc.providers[type]?.[name]!; - const usesLoginForm = type === 'basic' || type === 'token'; + const usesLoginForm = shouldProviderUseLoginForm(type); return { type, name, diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index 757b1aaeddcbc..4dc83a1abe4af 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -9,7 +9,7 @@ import { randomBytes, createHash } from 'crypto'; import nodeCrypto, { Crypto } from '@elastic/node-crypto'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { KibanaRequest, Logger } from '../../../../../src/core/server'; -import type { AuthenticationProvider } from '../../common/types'; +import type { AuthenticationProvider } from '../../common/model'; import type { ConfigType } from '../config'; import type { SessionIndex, SessionIndexValue } from './session_index'; import type { SessionCookie } from './session_cookie'; diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 96fff41d57503..45b2f4489c195 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -5,7 +5,7 @@ */ import type { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; -import type { AuthenticationProvider } from '../../common/types'; +import type { AuthenticationProvider } from '../../common/model'; import type { ConfigType } from '../config'; export interface SessionIndexOptions { diff --git a/x-pack/test/security_api_integration/tests/anonymous/login.ts b/x-pack/test/security_api_integration/tests/anonymous/login.ts index 559dc7cc78036..eaf999c509741 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/login.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/login.ts @@ -182,7 +182,7 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(logoutResponse.headers.location).to.be('/security/logged_out'); + expect(logoutResponse.headers.location).to.be('/security/logged_out?msg=LOGGED_OUT'); // Old cookie should be invalidated and not allow API access. const apiResponse = await supertest diff --git a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index e63f8cd2ebe32..26edc36563e1c 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -259,7 +259,7 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(logoutResponse.headers.location).to.be('/security/logged_out'); + expect(logoutResponse.headers.location).to.be('/security/logged_out?msg=LOGGED_OUT'); // Token that was stored in the previous cookie should be invalidated as well and old // session cookie should not allow API access. diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index edcc1b5744fe3..a9e83ff7dadce 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -10,7 +10,7 @@ import { resolve } from 'path'; import url from 'url'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getStateAndNonce } from '../../fixtures/oidc/oidc_tools'; import { getMutualAuthenticationResponseToken, diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 93eabe33dc687..0d630dab51cf7 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -307,7 +307,7 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(logoutResponse.headers.location).to.be('/security/logged_out'); + expect(logoutResponse.headers.location).to.be('/security/logged_out?msg=LOGGED_OUT'); }); it('should redirect to home page if session cookie is not provided', async () => { diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index c1e8bb9938986..8251ca3419ac8 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -7,7 +7,7 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 59e8c746a6d07..134c9e9b1ad82 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -7,7 +7,7 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; From 6a07349bb6f1c46d43df64c0ba6ef3720ed162e5 Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Wed, 2 Dec 2020 11:42:52 +0100 Subject: [PATCH 28/37] Add readme for new palette service (#84512) --- src/plugins/charts/README.md | 4 +++ .../charts/public/services/palettes/README.md | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/plugins/charts/public/services/palettes/README.md diff --git a/src/plugins/charts/README.md b/src/plugins/charts/README.md index 31727b7acb7a1..dae7b9695ed60 100644 --- a/src/plugins/charts/README.md +++ b/src/plugins/charts/README.md @@ -27,3 +27,7 @@ Truncated color mappings in `value`/`text` form ## Theme See Theme service [docs](public/services/theme/README.md) + +## Palettes + +See palette service [docs](public/services/palettes/README.md) diff --git a/src/plugins/charts/public/services/palettes/README.md b/src/plugins/charts/public/services/palettes/README.md new file mode 100644 index 0000000000000..3403d422682bd --- /dev/null +++ b/src/plugins/charts/public/services/palettes/README.md @@ -0,0 +1,33 @@ +# Palette Service + +The `palette` service offers a collection of palettes which implement a uniform interface for assigning colors to charts. The service provides methods for switching palettes +easily. It's used by the x-pack plugins `canvas` and `lens`. + +Each palette is allowed to store some state as well which has to be handled by the consumer. + +Palettes are integrated with the expression as well using the `system_palette` and `palette` functions. + +## Using the palette service + +To consume the palette service, use `charts.palettes.getPalettes` to lazily load the async bundle implementing existing palettes. This is recommended to be called in the renderer, not as part of the `setup` or `start` phases of a plugin. + +All palette definitions can be loaded using `paletteService.getAll()`. If the id of the palette is known, it can be fetched using `paleteService.get(id)`. + +One a palette is loaded, there are two ways to request colors - either by fetching a list of colors (`getColors`) or by specifying the chart object to be colored (`getColor`). If possible, using `getColor` is recommended because it allows the palette implementation to apply custom logic to coloring (e.g. lightening up colors or syncing colors) which has to be implemented by the consumer if `getColors` is used). + +### SeriesLayer + +If `getColor` is used, an array of `SeriesLayer` objects has to be passed in. These correspond with the current series in the chart a color has to be determined for. An array is necessary as some charts are constructed hierarchically (e.g. pie charts or treemaps). The array of objects represents the current series with all ancestors up to the corresponding root series. For each layer in the series hierarchy, the number of "sibling" series and the position of the current series has to be specified along with the name of the series. + +## Custom palette + +All palettes are stateless and define their own colors except for the `custom` palette which takes a state of the form +```ts +{ colors: string[]; gradient: boolean } +``` + +This state has to be passed into the `getColors` and `getColor` function to retrieve specific colors. + +## Registering new palettes + +Currently palettes can't be extended dynamically. From 5354008a425808a65f6879b9fcbb18264951b9b3 Mon Sep 17 00:00:00 2001 From: Anton Dosov <anton.dosov@elastic.co> Date: Wed, 2 Dec 2020 11:47:30 +0100 Subject: [PATCH 29/37] [Search] Disable "send to background" when auto-refresh is enabled (#84106) --- x-pack/plugins/data_enhanced/public/plugin.ts | 1 + .../background_session_indicator.stories.tsx | 9 ++++ .../background_session_indicator.test.tsx | 10 ++++ .../background_session_indicator.tsx | 5 +- ...cted_background_session_indicator.test.tsx | 47 +++++++++++++++++-- ...connected_background_session_indicator.tsx | 28 ++++++++++- 6 files changed, 93 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 393a28c289e82..a3b37e47287e5 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -69,6 +69,7 @@ export class DataEnhancedPlugin createConnectedBackgroundSessionIndicator({ sessionService: plugins.data.search.session, application: core.application, + timeFilter: plugins.data.query.timefilter.timefilter, }) ) ), diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx index c7195ea486e2f..4a6a852be755b 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx @@ -26,5 +26,14 @@ storiesOf('components/BackgroundSessionIndicator', module).add('default', () => <div> <BackgroundSessionIndicator state={SessionState.Restored} /> </div> + <div> + <BackgroundSessionIndicator + state={SessionState.Completed} + disabled={true} + disabledReasonText={ + 'Send to background capability is unavailable when auto-refresh is enabled' + } + /> + </div> </> )); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx index f401a460113c7..b7d342300f311 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx @@ -100,3 +100,13 @@ test('Canceled state', async () => { expect(onRefresh).toBeCalled(); }); + +test('Disabled state', async () => { + render( + <Container> + <BackgroundSessionIndicator state={SessionState.Loading} disabled={true} /> + </Container> + ); + + expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx index 674ce392aa2d0..ce77686c4f3c1 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx @@ -30,6 +30,8 @@ export interface BackgroundSessionIndicatorProps { viewBackgroundSessionsLink?: string; onSaveResults?: () => void; onRefresh?: () => void; + disabled?: boolean; + disabledReasonText?: string; } type ActionButtonProps = BackgroundSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; @@ -285,12 +287,13 @@ export const BackgroundSessionIndicator: React.FC<BackgroundSessionIndicatorProp data-test-subj={'backgroundSessionIndicator'} data-state={props.state} button={ - <EuiToolTip content={button.tooltipText}> + <EuiToolTip content={props.disabled ? props.disabledReasonText : button.tooltipText}> <EuiButtonIcon color={button.color} aria-label={button['aria-label']} iconType={button.iconType} onClick={onButtonClick} + disabled={props.disabled} /> </EuiToolTip> } diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx index 4c9fd50dc8c4c..e08773c6a8a76 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx @@ -5,21 +5,36 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, screen, act } from '@testing-library/react'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { createConnectedBackgroundSessionIndicator } from './connected_background_session_indicator'; import { BehaviorSubject } from 'rxjs'; -import { ISessionService, SessionState } from '../../../../../../../src/plugins/data/public'; +import { + ISessionService, + RefreshInterval, + SessionState, + TimefilterContract, +} from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; const coreStart = coreMock.createStart(); -const sessionService = dataPluginMock.createStartContract().search - .session as jest.Mocked<ISessionService>; +const dataStart = dataPluginMock.createStartContract(); +const sessionService = dataStart.search.session as jest.Mocked<ISessionService>; + +const refreshInterval$ = new BehaviorSubject<RefreshInterval>({ value: 0, pause: true }); +const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked<TimefilterContract>; +timeFilter.getRefreshIntervalUpdate$.mockImplementation(() => refreshInterval$); +timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue()); + +beforeEach(() => { + refreshInterval$.next({ value: 0, pause: true }); +}); test("shouldn't show indicator in case no active search session", async () => { const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService, application: coreStart.application, + timeFilter, }); const { getByTestId, container } = render(<BackgroundSessionIndicator />); @@ -35,8 +50,32 @@ test('should show indicator in case there is an active search session', async () const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, + timeFilter, }); const { getByTestId } = render(<BackgroundSessionIndicator />); await waitFor(() => getByTestId('backgroundSessionIndicator')); }); + +test('should be disabled during auto-refresh', async () => { + const state$ = new BehaviorSubject(SessionState.Loading); + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + sessionService: { ...sessionService, state$ }, + application: coreStart.application, + timeFilter, + }); + + render(<BackgroundSessionIndicator />); + + await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); + + expect( + screen.getByTestId('backgroundSessionIndicator').querySelector('button') + ).not.toBeDisabled(); + + act(() => { + refreshInterval$.next({ value: 0, pause: false }); + }); + + expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx index 1cc2fffcea8c5..b80295d87d202 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx @@ -5,24 +5,46 @@ */ import React from 'react'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; import useObservable from 'react-use/lib/useObservable'; +import { i18n } from '@kbn/i18n'; import { BackgroundSessionIndicator } from '../background_session_indicator'; -import { ISessionService } from '../../../../../../../src/plugins/data/public/'; +import { ISessionService, TimefilterContract } from '../../../../../../../src/plugins/data/public/'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { ApplicationStart } from '../../../../../../../src/core/public'; export interface BackgroundSessionIndicatorDeps { sessionService: ISessionService; + timeFilter: TimefilterContract; application: ApplicationStart; } export const createConnectedBackgroundSessionIndicator = ({ sessionService, application, + timeFilter, }: BackgroundSessionIndicatorDeps): React.FC => { + const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; + const isAutoRefreshEnabled$ = timeFilter + .getRefreshIntervalUpdate$() + .pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); + return () => { const state = useObservable(sessionService.state$.pipe(debounceTime(500))); + const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); + let disabled = false; + let disabledReasonText: string = ''; + + if (autoRefreshEnabled) { + disabled = true; + disabledReasonText = i18n.translate( + 'xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage', + { + defaultMessage: 'Send to background is not available when auto refresh is enabled.', + } + ); + } + if (!state) return null; return ( <RedirectAppLinks application={application}> @@ -40,6 +62,8 @@ export const createConnectedBackgroundSessionIndicator = ({ onCancel={() => { sessionService.cancel(); }} + disabled={disabled} + disabledReasonText={disabledReasonText} /> </RedirectAppLinks> ); From 126c99a175795645ade53ebd013c0fb6817f4b33 Mon Sep 17 00:00:00 2001 From: Bohdan Tsymbala <bohdan.tsymbala@elastic.co> Date: Wed, 2 Dec 2020 12:48:11 +0100 Subject: [PATCH 30/37] Added word break styles to the texts in the item details card. (#84654) * Added word break styles to the texts in the item details card. * Updated snapshots. --- .../__snapshots__/index.test.tsx.snap | 4 +- .../components/item_details_card/index.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 300 +++++++++--------- .../__snapshots__/index.test.tsx.snap | 10 +- 4 files changed, 159 insertions(+), 157 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap index d95e0300fe140..c7841f6d6bbcc 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap @@ -182,7 +182,9 @@ exports[`item_details_card ItemDetailsPropertySummary should render correctly 1` <Styled(EuiDescriptionListTitle)> name 1 </Styled(EuiDescriptionListTitle)> - <Styled(EuiDescriptionListDescription)> + <Styled(EuiDescriptionListDescription) + className="eui-textBreakWord" + > value 1 </Styled(EuiDescriptionListDescription)> </Fragment> diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx index 829d8db5a5a0f..8f32224f860e3 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx @@ -72,7 +72,7 @@ export const ItemDetailsPropertySummary = memo<ItemDetailsPropertySummaryProps>( ({ name, value }) => ( <> <DescriptionListTitle>{name}</DescriptionListTitle> - <DescriptionListDescription>{value}</DescriptionListDescription> + <DescriptionListDescription className="eui-textBreakWord">{value}</DescriptionListDescription> </> ) ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index 71b103949a80a..eb3a8f2132e7f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -138,7 +138,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -154,7 +154,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -170,7 +170,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -180,7 +180,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -196,7 +196,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -390,7 +390,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -406,7 +406,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -422,7 +422,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -432,7 +432,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -448,7 +448,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -642,7 +642,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -658,7 +658,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -674,7 +674,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -684,7 +684,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -700,7 +700,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -894,7 +894,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -910,7 +910,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -926,7 +926,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -936,7 +936,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -952,7 +952,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1146,7 +1146,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1162,7 +1162,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1178,7 +1178,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1188,7 +1188,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1204,7 +1204,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1398,7 +1398,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1414,7 +1414,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1430,7 +1430,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1440,7 +1440,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1456,7 +1456,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1650,7 +1650,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1666,7 +1666,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1682,7 +1682,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1692,7 +1692,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1708,7 +1708,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1902,7 +1902,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1918,7 +1918,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1934,7 +1934,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1944,7 +1944,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1960,7 +1960,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2154,7 +2154,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2170,7 +2170,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2186,7 +2186,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -2196,7 +2196,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2212,7 +2212,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2406,7 +2406,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2422,7 +2422,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2438,7 +2438,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -2448,7 +2448,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2464,7 +2464,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2948,7 +2948,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2964,7 +2964,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2980,7 +2980,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -2990,7 +2990,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3006,7 +3006,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3200,7 +3200,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3216,7 +3216,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3232,7 +3232,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3242,7 +3242,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3258,7 +3258,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3452,7 +3452,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3468,7 +3468,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3484,7 +3484,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3494,7 +3494,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3510,7 +3510,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3704,7 +3704,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3720,7 +3720,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3736,7 +3736,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3746,7 +3746,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3762,7 +3762,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3956,7 +3956,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3972,7 +3972,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3988,7 +3988,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3998,7 +3998,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4014,7 +4014,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4208,7 +4208,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4224,7 +4224,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4240,7 +4240,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -4250,7 +4250,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4266,7 +4266,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4460,7 +4460,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4476,7 +4476,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4492,7 +4492,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -4502,7 +4502,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4518,7 +4518,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4712,7 +4712,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4728,7 +4728,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4744,7 +4744,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -4754,7 +4754,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4770,7 +4770,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4964,7 +4964,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4980,7 +4980,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4996,7 +4996,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -5006,7 +5006,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5022,7 +5022,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5216,7 +5216,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5232,7 +5232,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5248,7 +5248,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -5258,7 +5258,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5274,7 +5274,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5716,7 +5716,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5732,7 +5732,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5748,7 +5748,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -5758,7 +5758,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5774,7 +5774,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5968,7 +5968,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5984,7 +5984,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6000,7 +6000,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6010,7 +6010,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6026,7 +6026,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6220,7 +6220,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6236,7 +6236,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6252,7 +6252,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6262,7 +6262,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6278,7 +6278,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6472,7 +6472,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6488,7 +6488,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6504,7 +6504,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6514,7 +6514,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6530,7 +6530,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6724,7 +6724,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6740,7 +6740,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6756,7 +6756,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6766,7 +6766,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6782,7 +6782,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6976,7 +6976,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6992,7 +6992,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7008,7 +7008,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7018,7 +7018,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7034,7 +7034,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7228,7 +7228,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7244,7 +7244,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7260,7 +7260,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7270,7 +7270,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7286,7 +7286,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7480,7 +7480,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7496,7 +7496,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7512,7 +7512,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7522,7 +7522,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7538,7 +7538,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7732,7 +7732,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7748,7 +7748,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7764,7 +7764,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7774,7 +7774,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7790,7 +7790,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7984,7 +7984,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -8000,7 +8000,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -8016,7 +8016,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -8026,7 +8026,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -8042,7 +8042,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap index 4a8f984b59a01..8f70c61ba4afc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap @@ -793,7 +793,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -809,7 +809,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -825,7 +825,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -835,7 +835,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -851,7 +851,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" From 452f841491d6ea2ff9ebf1b712a582aede6d1ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= <sorenlouv@gmail.com> Date: Wed, 2 Dec 2020 13:10:27 +0100 Subject: [PATCH 31/37] [APM] Refactor hooks and context (#84615) * Refactor hooks and context * Fix tsc issues * address feedback * Fix lint * type-only import --- .../anomaly_detection_setup_link.test.tsx | 2 +- .../anomaly_detection_setup_link.tsx | 10 ++--- .../public/application/action_menu/index.tsx | 2 +- .../public/application/application.test.tsx | 2 +- .../plugins/apm/public/application/csmApp.tsx | 4 +- .../plugins/apm/public/application/index.tsx | 6 +-- .../ErrorCountAlertTrigger/index.stories.tsx | 4 +- .../alerting/ErrorCountAlertTrigger/index.tsx | 10 +++-- .../index.stories.tsx | 6 +-- .../TransactionDurationAlertTrigger/index.tsx | 14 ++++--- .../index.tsx | 14 ++++--- .../index.tsx | 14 ++++--- .../app/Correlations/ErrorCorrelations.tsx | 4 +- .../app/Correlations/LatencyCorrelations.tsx | 4 +- .../Correlations/SignificantTermsTable.tsx | 2 +- .../components/app/Correlations/index.tsx | 4 +- .../ErrorGroupDetails/DetailView/index.tsx | 2 +- .../ErrorGroupDetails/Distribution/index.tsx | 2 +- .../app/ErrorGroupDetails/index.tsx | 27 ++++--------- .../List/__test__/List.test.tsx | 4 +- .../app/ErrorGroupOverview/List/index.tsx | 2 +- .../app/ErrorGroupOverview/index.tsx | 28 ++++--------- .../public/components/app/Home/Home.test.tsx | 2 +- .../app/Main/route_config/index.tsx | 2 +- .../route_handlers/agent_configuration.tsx | 2 +- .../RumDashboard/Charts/PageViewsChart.tsx | 2 +- .../app/RumDashboard/ClientMetrics/index.tsx | 2 +- .../ImpactfulMetrics/JSErrors.tsx | 4 +- .../PageLoadDistribution/index.tsx | 4 +- .../PageLoadDistribution/use_breakdowns.ts | 4 +- .../app/RumDashboard/PageViewsTrend/index.tsx | 4 +- .../app/RumDashboard/Panels/MainFilters.tsx | 4 +- .../app/RumDashboard/RumDashboard.tsx | 2 +- .../URLFilter/ServiceNameFilter/index.tsx | 2 +- .../__tests__/SelectableUrlList.test.tsx | 2 +- .../URLFilter/URLSearch/index.tsx | 4 +- .../app/RumDashboard/URLFilter/index.tsx | 4 +- .../RumDashboard/UXMetrics/KeyUXMetrics.tsx | 2 +- .../UXMetrics/__tests__/KeyUXMetrics.test.tsx | 2 +- .../app/RumDashboard/UXMetrics/index.tsx | 4 +- .../app/RumDashboard/UserPercentile/index.tsx | 2 +- .../RumDashboard/VisitorBreakdown/index.tsx | 4 +- .../VisitorBreakdownMap/EmbeddedMap.tsx | 2 +- .../VisitorBreakdownMap/useLayerList.ts | 2 +- .../VisitorBreakdownMap/useMapFilters.ts | 2 +- .../app/RumDashboard/hooks/useUxQuery.ts | 2 +- .../app/RumDashboard/utils/test_helper.tsx | 2 +- .../app/ServiceMap/Controls.test.tsx | 2 +- .../components/app/ServiceMap/Controls.tsx | 6 +-- .../components/app/ServiceMap/Cytoscape.tsx | 2 +- .../components/app/ServiceMap/EmptyBanner.tsx | 2 +- .../ServiceMap/Popover/AnomalyDetection.tsx | 2 +- .../app/ServiceMap/Popover/Buttons.test.tsx | 2 +- .../app/ServiceMap/Popover/Buttons.tsx | 4 +- .../ServiceMap/Popover/Popover.stories.tsx | 4 +- .../Popover/ServiceStatsFetcher.tsx | 4 +- .../app/ServiceMap/Popover/index.tsx | 2 +- .../app/ServiceMap/cytoscape_options.ts | 2 +- .../app/ServiceMap/empty_banner.test.tsx | 2 +- .../components/app/ServiceMap/index.test.tsx | 6 +-- .../components/app/ServiceMap/index.tsx | 10 ++--- .../app/ServiceNodeOverview/index.tsx | 4 +- .../ServicePage/ServicePage.tsx | 2 +- .../SettingsPage/SettingsPage.tsx | 4 +- .../index.stories.tsx | 4 +- .../AgentConfigurationCreateEdit/index.tsx | 2 +- .../List/ConfirmDeleteModal.tsx | 2 +- .../AgentConfigurations/List/index.tsx | 6 +-- .../Settings/AgentConfigurations/index.tsx | 4 +- .../app/Settings/ApmIndices/index.test.tsx | 4 +- .../app/Settings/ApmIndices/index.tsx | 4 +- .../DeleteButton.tsx | 2 +- .../CreateEditCustomLinkFlyout/index.tsx | 2 +- .../CustomizeUI/CustomLink/index.test.tsx | 6 +-- .../Settings/CustomizeUI/CustomLink/index.tsx | 6 +-- .../components/app/Settings/Settings.test.tsx | 2 +- .../anomaly_detection/add_environments.tsx | 4 +- .../app/Settings/anomaly_detection/index.tsx | 8 ++-- .../Settings/anomaly_detection/jobs_list.tsx | 2 +- .../anomaly_detection/legacy_jobs_callout.tsx | 2 +- .../public/components/app/Settings/index.tsx | 2 +- .../public/components/app/TraceLink/index.tsx | 4 +- .../app/TraceLink/trace_link.test.tsx | 8 ++-- .../components/app/TraceOverview/index.tsx | 4 +- .../TransactionDetails/Distribution/index.tsx | 4 +- .../WaterfallWithSummmary/TransactionTabs.tsx | 2 +- .../WaterfallContainer.stories.tsx | 2 +- .../WaterfallContainer/index.tsx | 2 +- .../waterfallContainer.stories.data.ts | 2 +- .../WaterfallWithSummmary/index.tsx | 2 +- .../app/TransactionDetails/index.tsx | 32 ++++++++------- .../use_waterfall_fetcher.ts} | 9 +++-- .../service_details/service_detail_tabs.tsx | 6 +-- .../ServiceList/HealthBadge.tsx | 2 +- .../ServiceList/service_list.test.tsx | 2 +- .../app/service_inventory/index.tsx | 35 +++++++++------- .../no_services_message.test.tsx | 4 +- .../service_inventory/no_services_message.tsx | 2 +- .../service_inventory.test.tsx | 28 +++++-------- .../use_anomaly_detection_jobs_fetcher.ts} | 13 +++--- .../components/app/service_metrics/index.tsx | 10 +++-- .../app/service_node_metrics/index.test.tsx | 2 +- .../app/service_node_metrics/index.tsx | 18 ++++----- .../components/app/service_overview/index.tsx | 2 +- .../service_overview.test.tsx | 20 +++++----- .../service_overview_errors_table/index.tsx | 4 +- .../service_overview_throughput_chart.tsx | 10 ++--- .../index.tsx | 4 +- .../TransactionList.stories.tsx | 2 +- .../app/transaction_overview/index.tsx | 26 ++++++------ .../transaction_overview.test.tsx | 28 +++++++------ .../use_transaction_list.ts} | 17 ++++---- .../user_experience_callout.tsx | 2 +- .../shared/ApmHeader/apm_header.stories.tsx | 4 +- .../components/shared/ApmHeader/index.tsx | 2 +- .../shared/DatePicker/date_picker.test.tsx | 6 +-- .../components/shared/DatePicker/index.tsx | 4 +- .../shared/EnvironmentFilter/index.tsx | 6 +-- .../shared/KueryBar/get_bool_filter.ts | 2 +- .../components/shared/KueryBar/index.tsx | 8 ++-- .../LicensePrompt/LicensePrompt.stories.tsx | 2 +- .../Links/DiscoverLinks/DiscoverLink.tsx | 2 +- .../shared/Links/ElasticDocsLink.tsx | 2 +- .../components/shared/Links/InfraLink.tsx | 2 +- .../components/shared/Links/KibanaLink.tsx | 2 +- .../Links/MachineLearningLinks/MLLink.tsx | 4 +- .../useTimeSeriesExplorerHref.ts | 4 +- .../shared/Links/SetupInstructionsLink.tsx | 2 +- .../components/shared/Links/apm/APMLink.tsx | 4 +- .../shared/Links/apm/ErrorOverviewLink.tsx | 2 +- .../shared/Links/apm/MetricOverviewLink.tsx | 2 +- .../apm/ServiceNodeMetricOverviewLink.tsx | 2 +- .../Links/apm/ServiceNodeOverviewLink.tsx | 2 +- .../shared/Links/apm/TraceOverviewLink.tsx | 2 +- .../Links/apm/TransactionDetailLink.tsx | 2 +- .../Links/apm/TransactionOverviewLink.tsx | 2 +- .../Links/apm/service_inventory_link.tsx | 2 +- .../TransactionTypeFilter/index.tsx | 2 +- .../components/shared/ManagedTable/index.tsx | 2 +- .../__test__/ErrorMetadata.test.tsx | 2 +- .../__test__/SpanMetadata.test.tsx | 2 +- .../__test__/TransactionMetadata.test.tsx | 2 +- .../__test__/MetadataTable.test.tsx | 2 +- .../components/shared/MetadataTable/index.tsx | 2 +- .../Summary/ErrorCountSummaryItemBadge.tsx | 2 +- .../CustomLinkToolbar.test.tsx | 2 +- .../CustomLinkMenuSection/index.test.tsx | 4 +- .../CustomLinkMenuSection/index.tsx | 2 +- .../TransactionActionMenu.tsx | 8 ++-- .../__test__/TransactionActionMenu.test.tsx | 6 +-- .../shared/TransactionActionMenu/sections.ts | 2 +- .../components/shared/charts/Legend/index.tsx | 2 +- .../charts/Timeline/Marker/AgentMarker.tsx | 2 +- .../Timeline/Marker/ErrorMarker.test.tsx | 2 +- .../charts/Timeline/Marker/ErrorMarker.tsx | 4 +- .../shared/charts/Timeline/TimelineAxis.tsx | 2 +- .../shared/charts/Timeline/VerticalLines.tsx | 2 +- .../shared/charts/chart_container.test.tsx | 2 +- .../shared/charts/chart_container.tsx | 2 +- .../shared/charts/metrics_chart/index.tsx | 2 +- .../spark_plot_with_value_label/index.tsx | 2 +- .../shared/charts/timeseries_chart.tsx | 14 +++---- .../transaction_breakdown_chart/index.tsx | 2 +- .../transaction_breakdown_chart_contents.tsx | 14 +++---- .../use_transaction_breakdown.ts | 8 ++-- .../charts/transaction_charts/index.tsx | 10 ++--- .../charts/transaction_charts/ml_header.tsx | 2 +- .../transaction_error_rate_chart/index.tsx | 6 +-- .../shared/table_fetch_wrapper/index.tsx | 2 +- .../{ => annotations}/annotations_context.tsx | 8 ++-- .../annotations/use_annotations_context.ts} | 4 +- .../apm_plugin_context.tsx} | 2 +- .../mock_apm_plugin_context.tsx} | 2 +- .../apm_plugin/use_apm_plugin_context.ts} | 2 +- .../apm_service_context.test.tsx | 0 .../{ => apm_service}/apm_service_context.tsx | 16 ++++---- .../apm_service/use_apm_service_context.ts} | 4 +- .../use_service_agent_name_fetcher.ts} | 6 +-- ...use_service_transaction_types_fetcher.tsx} | 6 +-- .../chart_pointer_event_context.tsx | 0 .../use_chart_pointer_event_context.tsx} | 4 +- .../Invalid_license_notification.tsx} | 0 .../index.tsx => license/license_context.tsx} | 4 +- .../license/use_license_context.ts} | 4 +- .../constants.ts | 0 .../helpers.ts | 0 .../mock_url_params_context_provider.tsx} | 2 +- .../resolve_url_params.ts} | 0 .../types.ts | 0 .../url_params_context.test.tsx | 2 +- .../url_params_context.tsx} | 2 +- .../url_params_context/use_url_params.tsx} | 2 +- x-pack/plugins/apm/public/hooks/useCallApi.ts | 2 +- .../plugins/apm/public/hooks/useKibanaUrl.ts | 2 +- .../apm/public/hooks/useLocalUIFilters.ts | 6 +-- .../apm/public/hooks/use_breadcrumbs.test.tsx | 4 +- .../apm/public/hooks/use_breadcrumbs.ts | 2 +- ...attern.ts => use_dynamic_index_pattern.ts} | 4 +- ...ments.tsx => use_environments_fetcher.tsx} | 4 +- .../use_error_group_distribution_fetcher.tsx | 40 +++++++++++++++++++ .../hooks/use_fetcher.integration.test.tsx | 4 +- ...eFetcher.test.tsx => use_fetcher.test.tsx} | 6 +-- .../hooks/{useFetcher.tsx => use_fetcher.tsx} | 2 +- ...s => use_service_metric_charts_fetcher.ts} | 19 ++++----- .../hooks/{useTheme.tsx => use_theme.tsx} | 0 ...s.ts => use_transaction_charts_fetcher.ts} | 12 +++--- ...> use_transaction_distribution_fetcher.ts} | 16 ++++---- .../apm/public/selectors/chart_selectors.ts | 2 +- .../plugins/apm/public/utils/testHelpers.tsx | 4 +- 209 files changed, 544 insertions(+), 512 deletions(-) rename x-pack/plugins/apm/public/{hooks/useWaterfall.ts => components/app/TransactionDetails/use_waterfall_fetcher.ts} (75%) rename x-pack/plugins/apm/public/{hooks/useAnomalyDetectionJobs.ts => components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts} (50%) rename x-pack/plugins/apm/public/{hooks/useTransactionList.ts => components/app/transaction_overview/use_transaction_list.ts} (75%) rename x-pack/plugins/apm/public/{hooks => components/shared/charts/transaction_breakdown_chart}/use_transaction_breakdown.ts (80%) rename x-pack/plugins/apm/public/context/{ => annotations}/annotations_context.tsx (83%) rename x-pack/plugins/apm/public/{hooks/use_annotations.ts => context/annotations/use_annotations_context.ts} (80%) rename x-pack/plugins/apm/public/context/{ApmPluginContext/index.tsx => apm_plugin/apm_plugin_context.tsx} (94%) rename x-pack/plugins/apm/public/context/{ApmPluginContext/MockApmPluginContext.tsx => apm_plugin/mock_apm_plugin_context.tsx} (97%) rename x-pack/plugins/apm/public/{hooks/useApmPluginContext.ts => context/apm_plugin/use_apm_plugin_context.ts} (84%) rename x-pack/plugins/apm/public/context/{ => apm_service}/apm_service_context.test.tsx (100%) rename x-pack/plugins/apm/public/context/{ => apm_service}/apm_service_context.tsx (75%) rename x-pack/plugins/apm/public/{hooks/use_apm_service.ts => context/apm_service/use_apm_service_context.ts} (75%) rename x-pack/plugins/apm/public/{hooks/use_service_agent_name.ts => context/apm_service/use_service_agent_name_fetcher.ts} (83%) rename x-pack/plugins/apm/public/{hooks/use_service_transaction_types.tsx => context/apm_service/use_service_transaction_types_fetcher.tsx} (83%) rename x-pack/plugins/apm/public/context/{ => chart_pointer_event}/chart_pointer_event_context.tsx (100%) rename x-pack/plugins/apm/public/{hooks/use_chart_pointer_event.tsx => context/chart_pointer_event/use_chart_pointer_event_context.tsx} (78%) rename x-pack/plugins/apm/public/context/{LicenseContext/InvalidLicenseNotification.tsx => license/Invalid_license_notification.tsx} (100%) rename x-pack/plugins/apm/public/context/{LicenseContext/index.tsx => license/license_context.tsx} (87%) rename x-pack/plugins/apm/public/{hooks/useLicense.ts => context/license/use_license_context.ts} (77%) rename x-pack/plugins/apm/public/context/{UrlParamsContext => url_params_context}/constants.ts (100%) rename x-pack/plugins/apm/public/context/{UrlParamsContext => url_params_context}/helpers.ts (100%) rename x-pack/plugins/apm/public/context/{UrlParamsContext/MockUrlParamsContextProvider.tsx => url_params_context/mock_url_params_context_provider.tsx} (93%) rename x-pack/plugins/apm/public/context/{UrlParamsContext/resolveUrlParams.ts => url_params_context/resolve_url_params.ts} (100%) rename x-pack/plugins/apm/public/context/{UrlParamsContext => url_params_context}/types.ts (100%) rename x-pack/plugins/apm/public/context/{UrlParamsContext => url_params_context}/url_params_context.test.tsx (98%) rename x-pack/plugins/apm/public/context/{UrlParamsContext/index.tsx => url_params_context/url_params_context.tsx} (98%) rename x-pack/plugins/apm/public/{hooks/useUrlParams.tsx => context/url_params_context/use_url_params.tsx} (84%) rename x-pack/plugins/apm/public/hooks/{useDynamicIndexPattern.ts => use_dynamic_index_pattern.ts} (89%) rename x-pack/plugins/apm/public/hooks/{useEnvironments.tsx => use_environments_fetcher.tsx} (94%) create mode 100644 x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx rename x-pack/plugins/apm/public/hooks/{useFetcher.test.tsx => use_fetcher.test.tsx} (96%) rename x-pack/plugins/apm/public/hooks/{useFetcher.tsx => use_fetcher.tsx} (98%) rename x-pack/plugins/apm/public/hooks/{useServiceMetricCharts.ts => use_service_metric_charts_fetcher.ts} (75%) rename x-pack/plugins/apm/public/hooks/{useTheme.tsx => use_theme.tsx} (100%) rename x-pack/plugins/apm/public/hooks/{useTransactionCharts.ts => use_transaction_charts_fetcher.ts} (82%) rename x-pack/plugins/apm/public/hooks/{useTransactionDistribution.ts => use_transaction_distribution_fetcher.ts} (89%) diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index b90f606d276eb..399a24a8bc165 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react'; import { MissingJobsAlert } from './anomaly_detection_setup_link'; -import * as hooks from '../../hooks/useFetcher'; +import * as hooks from '../../hooks/use_fetcher'; async function renderTooltipAnchor({ jobs, diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index a06d6f357c524..8a1d73c818944 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -16,10 +16,10 @@ import { getEnvironmentLabel, } from '../../../common/environment_filter_values'; import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; -import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher'; -import { useLicense } from '../../hooks/useLicense'; -import { useUrlParams } from '../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; +import { useLicenseContext } from '../../context/license/use_license_context'; +import { useUrlParams } from '../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../services/rest/createCallApmApi'; import { units } from '../../style/variables'; @@ -32,7 +32,7 @@ export function AnomalyDetectionSetupLink() { const environment = uiFilters.environment; const { core } = useApmPluginContext(); const canGetJobs = !!core.application.capabilities.ml?.canGetJobs; - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const { basePath } = core.http; diff --git a/x-pack/plugins/apm/public/application/action_menu/index.tsx b/x-pack/plugins/apm/public/application/action_menu/index.tsx index 1713ef61fac1e..438eb2bca7f24 100644 --- a/x-pack/plugins/apm/public/application/action_menu/index.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { getAlertingCapabilities } from '../../components/alerting/get_alert_capabilities'; import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link'; diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx index 75b7835c13151..c5091b1b554cc 100644 --- a/x-pack/plugins/apm/public/application/application.test.tsx +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Observable } from 'rxjs'; import { AppMountParameters, CoreStart, HttpSetup } from 'src/core/public'; -import { mockApmPluginContextValue } from '../context/ApmPluginContext/MockApmPluginContext'; +import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { renderApp } from './'; diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index 7fcbe7c518cd0..4d16643a83fe9 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -20,8 +20,8 @@ import { APMRouteDefinition } from '../application/routes'; import { renderAsRedirectTo } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; import { RumHome, UX_LABEL } from '../components/app/RumDashboard/RumHome'; -import { ApmPluginContext } from '../context/ApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { ApmPluginContext } from '../context/apm_plugin/apm_plugin_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ConfigSchema } from '../index'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 79c29867cb8e3..9c4413765a500 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -25,9 +25,9 @@ import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPat import { ApmPluginContext, ApmPluginContextValue, -} from '../context/ApmPluginContext'; -import { LicenseProvider } from '../context/LicenseContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +} from '../context/apm_plugin/apm_plugin_context'; +import { LicenseProvider } from '../context/license/license_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx index 1a565ab8708bc..f4f2be0a6e889 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorCountAlertTrigger } from '.'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; export default { title: 'app/ErrorCountAlertTrigger', diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx index a465b90e7bf05..efa792ff44273 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx @@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { EnvironmentField, ServiceField, IsAboveField } from '../fields'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; @@ -34,7 +34,11 @@ export function ErrorCountAlertTrigger(props: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); const defaults = { threshold: 25, diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx index d20aae29fb8ce..8b2d4e235ac25 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx @@ -8,12 +8,12 @@ import { cloneDeep, merge } from 'lodash'; import React, { ComponentType } from 'react'; import { MemoryRouter, Route } from 'react-router-dom'; import { TransactionDurationAlertTrigger } from '.'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; export default { title: 'app/TransactionDurationAlertTrigger', diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx index b7220de8079c9..3566850aa24c4 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx @@ -10,8 +10,8 @@ import { map } from 'lodash'; import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; @@ -21,7 +21,7 @@ import { TransactionTypeField, IsAboveField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -63,10 +63,14 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (!transactionTypes.length || !serviceName) { return null; diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx index e13ed6c1bcd6f..ff5939c601375 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ANOMALY_SEVERITY } from '../../../../../ml/common'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { @@ -23,7 +23,7 @@ import { ServiceField, TransactionTypeField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface Params { windowSize: number; @@ -47,10 +47,14 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx index 464409ed332e8..f723febde389d 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx @@ -7,8 +7,8 @@ import { useParams } from 'react-router-dom'; import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; @@ -18,7 +18,7 @@ import { EnvironmentField, IsAboveField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -38,10 +38,14 @@ interface Props { export function TransactionErrorRateAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx index 3ad71b52b6037..07ab89afd4108 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx @@ -17,8 +17,8 @@ import { import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx index 4364731501b89..30659cf3f9319 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx @@ -19,8 +19,8 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { getDurationFormatter } from '../../../../common/utils/formatters'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx b/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx index b74517902f89b..350f64367b766 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx @@ -10,7 +10,7 @@ import { useHistory } from 'react-router-dom'; import { EuiBasicTable } from '@elastic/eui'; import { asPercent, asInteger } from '../../../../common/utils/formatters'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { createHref } from '../../shared/Links/url_helpers'; type CorrelationsApiResponse = diff --git a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx index b0f6b83485e39..16a21e28fc08d 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx @@ -19,10 +19,10 @@ import { } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { enableCorrelations } from '../../../../common/ui_settings_keys'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { LatencyCorrelations } from './LatencyCorrelations'; import { ErrorCorrelations } from './ErrorCorrelations'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { createHref } from '../../shared/Links/url_helpers'; export function Correlations() { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 643064b2f3176..c0ce2ed388a12 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -22,7 +22,7 @@ import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { px, unit, units } from '../../../../style/variables'; import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; import { DiscoverErrorLink } from '../../../shared/Links/DiscoverLinks/DiscoverErrorLink'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 159f111bee04c..ab99c6ffa8da0 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -20,7 +20,7 @@ import d3 from 'd3'; import React from 'react'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; type ErrorDistributionAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/errors/distribution'>; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index dc97642dec357..95ebd5d4036de 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -21,14 +21,15 @@ import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { SearchBar } from '../../shared/search_bar'; import { DetailView } from './DetailView'; import { ErrorDistribution } from './Distribution'; +import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; const Titles = styled.div` margin-bottom: ${px(units.plus)}; @@ -88,24 +89,10 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { } }, [serviceName, start, end, groupId, uiFilters]); - const { data: errorDistributionData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', - params: { - path: { - serviceName, - }, - query: { - start, - end, - groupId, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, groupId, uiFilters]); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ + serviceName, + groupId, + }); useTrackPageview({ app: 'apm', path: 'error_group_details' }); useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 }); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx index 84b72b62248b0..4022caedadaab 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx @@ -6,8 +6,8 @@ import { mount } from 'enzyme'; import React from 'react'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../../context/url_params_context/mock_url_params_context_provider'; import { mockMoment, toJson } from '../../../../../utils/testHelpers'; import { ErrorGroupList } from '../index'; import props from './props.json'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index be1078ea860c3..200a5f467491b 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -19,7 +19,7 @@ import { truncate, unit, } from '../../../../style/variables'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { ManagedTable } from '../../../shared/ManagedTable'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index e2a02a2f3e7ae..71cb8e0e01602 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -16,13 +16,14 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { ErrorDistribution } from '../ErrorGroupDetails/Distribution'; import { ErrorGroupList } from './List'; +import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; interface ErrorGroupOverviewProps { serviceName: string; @@ -30,26 +31,11 @@ interface ErrorGroupOverviewProps { function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, sortField, sortDirection } = urlParams; - - const { data: errorDistributionData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', - params: { - path: { - serviceName, - }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters]); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ + serviceName, + groupId: undefined, + }); const { data: errorGroupListData } = useFetcher(() => { const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; diff --git a/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx b/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx index ab4ca1dfbb49d..148e0733b93ca 100644 --- a/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { Home } from '../Home'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; describe('Home component', () => { it('should render services', () => { diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index ce8f2b0ba611a..839c087305bd8 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { ApmServiceContextProvider } from '../../../../context/apm_service_context'; +import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context'; import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes'; import { APMRouteDefinition } from '../../../../application/routes'; diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx index ac1668a54ab95..10c8417223c77 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { toQuery } from '../../../../shared/Links/url_helpers'; import { Settings } from '../../../Settings'; import { AgentConfigurationCreateEdit } from '../../../Settings/AgentConfigurations/AgentConfigurationCreateEdit'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index 3787202f5dee4..6a56dbf40b33b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -27,7 +27,7 @@ import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 237d33a6a89a3..9fdb34935fee5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -14,7 +14,7 @@ import { EuiToolTip, EuiIconTip, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { useUxQuery } from '../hooks/useUxQuery'; import { formatToSec } from '../UXMetrics/KeyUXMetrics'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index 4c4f7110cafb9..2e6c5c8e23ee5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -16,8 +16,8 @@ import { } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { CsmSharedContext } from '../CsmSharedContext'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 4b94b98704da7..d7bc94e6564f1 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -6,8 +6,8 @@ import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageLoadDistChart } from '../Charts/PageLoadDistChart'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index c3f4ab44179fe..5c545a63d6d05 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { PercentileRange } from './index'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 84668f4b06d77..b339cc7774d75 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -6,8 +6,8 @@ import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageViewsChart } from '../Charts/PageViewsChart'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx index 6c7e2e22a9893..8d759d80352d7 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { EnvironmentFilter } from '../../../shared/EnvironmentFilter'; import { ServiceNameFilter } from '../URLFilter/ServiceNameFilter'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { UserPercentile } from '../UserPercentile'; export function MainFilters() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx index e4e9109f007e7..c810bd3e7c489 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -20,7 +20,7 @@ import { PageLoadAndViews } from './Panels/PageLoadAndViews'; import { VisitorBreakdownsPanel } from './Panels/VisitorBreakdowns'; import { useBreakPoints } from './hooks/useBreakPoints'; import { getPercentileLabel } from './UXMetrics/translations'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; export function RumDashboard() { const { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx index b70621b1e4cbc..756014004cc9b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx @@ -8,7 +8,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx index abafdf089748b..a492938deffab 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { createMemoryHistory } from 'history'; -import * as fetcherHook from '../../../../../../hooks/useFetcher'; +import * as fetcherHook from '../../../../../../hooks/use_fetcher'; import { SelectableUrlList } from '../SelectableUrlList'; import { render } from '../../../utils/test_helper'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx index 67692a9a8554b..61f75a430706c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -8,8 +8,8 @@ import useDebounce from 'react-use/lib/useDebounce'; import React, { useEffect, useState, FormEvent, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { I18LABELS } from '../../translations'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx index ef829ebf7f0cf..655cdaaca933b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx @@ -10,9 +10,9 @@ import { useHistory } from 'react-router-dom'; import { omit } from 'lodash'; import { URLSearch } from './URLSearch'; import { UrlList } from './UrlList'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; -import { removeUndefinedProps } from '../../../../context/UrlParamsContext/helpers'; +import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; import { LocalUIFilterName } from '../../../../../common/ui_filter'; export function URLFilter() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index 2ded35deb58f2..690595caa6c0e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -20,7 +20,7 @@ import { TBT_LABEL, TBT_TOOLTIP, } from './translations'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { UXMetrics } from '../../../../../../observability/public'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx index 3a6323a747a70..baa9cb7dd74f9 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import * as fetcherHook from '../../../../../hooks/useFetcher'; +import * as fetcherHook from '../../../../../hooks/use_fetcher'; import { KeyUXMetrics } from '../KeyUXMetrics'; describe('KeyUXMetrics', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 95a42ce3018f1..392b42cba12e5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -15,11 +15,11 @@ import { } from '@elastic/eui'; import { I18LABELS } from '../translations'; import { KeyUXMetrics } from './KeyUXMetrics'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { CoreVitals } from '../../../../../../observability/public'; import { CsmSharedContext } from '../CsmSharedContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getPercentileLabel } from './translations'; export function UXMetrics() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx index 04c7e3cc00287..260c775c7129b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useEffect } from 'react'; import { EuiSelect } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { I18LABELS } from '../translations'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index ce9485690b930..77d5697c31750 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { VisitorBreakdownChart } from '../Charts/VisitorBreakdownChart'; import { I18LABELS, VisitorBreakdownLabel } from '../translations'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function VisitorBreakdown() { const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx index 3a5c3d80ca7d1..eff03c58e9991 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx @@ -21,7 +21,7 @@ import { isErrorEmbeddable, } from '../../../../../../../../src/plugins/embeddable/public'; import { useLayerList } from './useLayerList'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { RenderTooltipContentParams } from '../../../../../../maps/public'; import { MapToolTip } from './MapToolTip'; import { useMapFilters } from './useMapFilters'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index a1cdf7bb646e5..54bfa81f26add 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -22,7 +22,7 @@ import { } from '../../../../../../maps/common/constants'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME, TRANSACTION_TYPE, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts index 774ac23d23196..c5cf081311f66 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FieldFilter as Filter } from '../../../../../../../../src/plugins/data/common'; import { CLIENT_GEO_COUNTRY_ISO_CODE, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts index 16396dc9fc15b..c8cd2c2c64da8 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useUxQuery() { const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx index 5522cad5690bc..d5b8cd83d437c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx @@ -13,7 +13,7 @@ import { Router } from 'react-router-dom'; import { MemoryHistory } from 'history'; import { EuiThemeProvider } from '../../../../../../observability/public'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; -import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; +import { UrlParamsProvider } from '../../../../context/url_params_context/url_params_context'; export const core = ({ http: { diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx index 1187b71dff825..659f9f63d0cfa 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx @@ -9,7 +9,7 @@ import { render } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; import { ThemeContext } from 'styled-components'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { Controls } from './Controls'; import { CytoscapeContext } from './Cytoscape'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx index b4408e20c04d2..a23fa72314aed 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -8,9 +8,9 @@ import { EuiButtonIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../shared/Links/url_helpers'; import { CytoscapeContext } from './Cytoscape'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 8a76c5f7bd8f1..1dea95d369966 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -16,7 +16,7 @@ import React, { useRef, useState, } from 'react'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; import { getCytoscapeOptions } from './cytoscape_options'; import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx index 63a9cf985959f..07e88294caadb 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { CytoscapeContext } from './Cytoscape'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; const EmptyBannerContainer = styled.div` margin: ${({ theme }) => theme.eui.gutterTypes.gutterSmall}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx index 788e5f25b6310..36c0c9f37f9c7 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx @@ -18,7 +18,7 @@ import { getServiceHealthStatus, getServiceHealthStatusColor, } from '../../../../../common/service_health_status'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { fontSize, px } from '../../../../style/variables'; import { asInteger, asDuration } from '../../../../../common/utils/formatters'; import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx index f98a7a1b33dd8..d9f33b8fca4cd 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx @@ -7,7 +7,7 @@ import React, { ReactNode } from 'react'; import { Buttons } from './Buttons'; import { render } from '@testing-library/react'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; function Wrapper({ children }: { children?: ReactNode }) { return <MockApmPluginContextWrapper>{children}</MockApmPluginContextWrapper>; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx index 8670cf623c253..56110a89ed888 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx @@ -9,8 +9,8 @@ import { EuiButton, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getAPMHref } from '../../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../../shared/Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx index 70eb5eaf8e576..313b262508c61 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx @@ -8,8 +8,8 @@ import cytoscape from 'cytoscape'; import { HttpSetup } from 'kibana/public'; import React, { ComponentType } from 'react'; import { EuiThemeProvider } from '../../../../../../observability/public'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../../services/rest/createCallApmApi'; import { CytoscapeContext } from '../Cytoscape'; import { Popover } from './'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx index be8c5cf8cd435..3b737c6fa4170 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx @@ -15,8 +15,8 @@ import { i18n } from '@kbn/i18n'; import { isNumber } from 'lodash'; import { ServiceNodeStats } from '../../../../../common/service_map'; import { ServiceStatsList } from './ServiceStatsList'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { AnomalyDetection } from './AnomalyDetection'; import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx index 7b7e3b46bb317..036d02531f794 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -15,7 +15,7 @@ import React, { } from 'react'; import { EuiPopover } from '@elastic/eui'; import cytoscape from 'cytoscape'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; import { CytoscapeContext } from '../Cytoscape'; import { getAnimationOptions } from '../cytoscape_options'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts index d8a8a3c8e9ab4..e2a54f6048682 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts @@ -15,7 +15,7 @@ import { getServiceHealthStatusColor, ServiceHealthStatus, } from '../../../../common/service_health_status'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { defaultIcon, iconForNode } from './icons'; export const popoverWidth = 280; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx index ae27d4d3baf75..a1fb7e7077add 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx @@ -7,7 +7,7 @@ import { act, waitFor } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { renderWithTheme } from '../../../utils/testHelpers'; import { CytoscapeContext } from './Cytoscape'; import { EmptyBanner } from './EmptyBanner'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index 2a5b4ce44ff46..97e507d7cc871 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -11,9 +11,9 @@ import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { License } from '../../../../../licensing/common/license'; import { EuiThemeProvider } from '../../../../../observability/public'; import { FETCH_STATUS } from '../../../../../observability/public/hooks/use_fetcher'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { LicenseContext } from '../../../context/LicenseContext'; -import * as useFetcherModule from '../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../context/license/license_context'; +import * as useFetcherModule from '../../../hooks/use_fetcher'; import { ServiceMap } from './'; const KibanaReactContext = createKibanaReactContext({ diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index 1731d3f9430d4..48a7f8f77ab84 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -13,10 +13,10 @@ import { isActivePlatinumLicense, SERVICE_MAP_TIMEOUT_ERROR, } from '../../../../common/service_map'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useLicense } from '../../../hooks/useLicense'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useLicenseContext } from '../../../context/license/use_license_context'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { DatePicker } from '../../shared/DatePicker'; import { LicensePrompt } from '../../shared/LicensePrompt'; @@ -70,7 +70,7 @@ export function ServiceMap({ serviceName, }: PropsWithChildren<ServiceMapProps>) { const theme = useTheme(); - const license = useLicense(); + const license = useLicenseContext(); const { urlParams } = useUrlParams(); const { data = { elements: [] }, status, error } = useFetcher(() => { diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx index 74e7b652d0ebe..c4c227deb6918 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx @@ -21,8 +21,8 @@ import { asInteger, asPercent, } from '../../../../common/utils/formatters'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { px, truncate, unit } from '../../../style/variables'; import { ServiceNodeMetricOverviewLink } from '../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx index 7c0869afe0cd1..18067a43861bd 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx @@ -21,7 +21,7 @@ import { omitAllOption, getOptionLabel, } from '../../../../../../../common/agent_configuration/all_option'; -import { useFetcher, FETCH_STATUS } from '../../../../../../hooks/useFetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; import { FormRowSelect } from './FormRowSelect'; import { APMLink } from '../../../../../shared/Links/apm/APMLink'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx index 54440559070ad..7e1146596dd87 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx @@ -32,8 +32,8 @@ import { validateSetting, } from '../../../../../../../common/agent_configuration/setting_definitions'; import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; -import { FETCH_STATUS } from '../../../../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; import { saveConfig } from './saveConfig'; import { SettingFormRow } from './SettingFormRow'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index db3f2c374a1ae..5ca643428e49c 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -14,13 +14,13 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { HttpSetup } from 'kibana/public'; import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types'; -import { FETCH_STATUS } from '../../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { createCallApmApi } from '../../../../../services/rest/createCallApmApi'; import { AgentConfigurationCreateEdit } from './index'; import { ApmPluginContext, ApmPluginContextValue, -} from '../../../../../context/ApmPluginContext'; +} from '../../../../../context/apm_plugin/apm_plugin_context'; import { EuiThemeProvider } from '../../../../../../../observability/public'; storiesOf( diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 4f94f255a4e4c..998175c895557 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -14,7 +14,7 @@ import { AgentConfiguration, AgentConfigurationIntake, } from '../../../../../../common/agent_configuration/configuration_types'; -import { FetcherResult } from '../../../../../hooks/useFetcher'; +import { FetcherResult } from '../../../../../hooks/use_fetcher'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; import { ServicePage } from './ServicePage/ServicePage'; import { SettingsPage } from './SettingsPage/SettingsPage'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx index adae50db85ada..958aafa8159df 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx @@ -13,7 +13,7 @@ import { APIReturnType, callApmApi, } from '../../../../../services/rest/createCallApmApi'; -import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0]; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx index 81079d78a148a..be4edbe2ea270 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx @@ -18,9 +18,9 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; import { getOptionLabel } from '../../../../../../common/agent_configuration/all_option'; -import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; -import { FETCH_STATUS } from '../../../../../hooks/useFetcher'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../../hooks/use_theme'; import { px, units } from '../../../../../style/variables'; import { createAgentConfigurationHref, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index 12c63f8702f25..c408d5e960cf3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -16,8 +16,8 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../../observability/public'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks'; import { AgentConfigurationList } from './List'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx index 53794ca9965ff..2adf85181886c 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx @@ -7,8 +7,8 @@ import { render } from '@testing-library/react'; import React from 'react'; import { ApmIndices } from '.'; -import * as hooks from '../../../../hooks/useFetcher'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import * as hooks from '../../../../hooks/use_fetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; describe('ApmIndices', () => { it('should not get stuck in infinite loop', () => { diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index a1ef9ddd87271..5a5d20cde9ade 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -19,10 +19,10 @@ import { EuiButton, EuiButtonEmpty, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { clearCache } from '../../../../services/rest/callApi'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; const APM_INDEX_LABELS = [ { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx index 5014584c3928a..ffcb85384642a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx @@ -10,7 +10,7 @@ import { NotificationsStart } from 'kibana/public'; import React, { useState } from 'react'; import { px, unit } from '../../../../../../style/variables'; import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; interface Props { onDelete: () => void; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx index c6566af3a8b61..f9c5aa17e411a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx @@ -15,7 +15,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { Filter } from '../../../../../../../common/custom_link/custom_link_types'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; import { LinkSection } from './LinkSection'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index 96a634828f669..1da7d415b5660 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -14,15 +14,15 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import * as apmApi from '../../../../../services/rest/createCallApmApi'; import { License } from '../../../../../../../licensing/common/license'; -import * as hooks from '../../../../../hooks/useFetcher'; -import { LicenseContext } from '../../../../../context/LicenseContext'; +import * as hooks from '../../../../../hooks/use_fetcher'; +import { LicenseContext } from '../../../../../context/license/license_context'; import { CustomLinkOverview } from '.'; import { expectTextsInDocument, expectTextsNotInDocument, } from '../../../../../utils/testHelpers'; import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; const data = [ { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index 771a8c6154dc0..6b5c7d583ee8a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -17,8 +17,8 @@ import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; import { INVALID_LICENSE } from '../../../../../../common/custom_link'; import { CustomLink } from '../../../../../../common/custom_link/custom_link_types'; -import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher'; -import { useLicense } from '../../../../../hooks/useLicense'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; +import { useLicenseContext } from '../../../../../context/license/use_license_context'; import { LicensePrompt } from '../../../../shared/LicensePrompt'; import { CreateCustomLinkButton } from './CreateCustomLinkButton'; import { CreateEditCustomLinkFlyout } from './CreateEditCustomLinkFlyout'; @@ -26,7 +26,7 @@ import { CustomLinkTable } from './CustomLinkTable'; import { EmptyPrompt } from './EmptyPrompt'; export function CustomLinkOverview() { - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('gold'); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); diff --git a/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx index 21da12477b024..cfef7ca937f66 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx @@ -5,7 +5,7 @@ */ import { render } from '@testing-library/react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import React, { ReactNode } from 'react'; import { Settings } from './'; import { createMemoryHistory } from 'history'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx index ccc1778e9fbde..e709c7e104472 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx @@ -21,8 +21,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ML_ERRORS } from '../../../../../common/anomaly_detection'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { createJobs } from './create_jobs'; import { getEnvironmentLabel } from '../../../../../common/environment_filter_values'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx index 9f43f4aa3df91..addfd64a9ef62 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx @@ -9,12 +9,12 @@ import { EuiTitle, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiEmptyPrompt } from '@elastic/eui'; import { ML_ERRORS } from '../../../../../common/anomaly_detection'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { JobsList } from './jobs_list'; import { AddEnvironments } from './add_environments'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { LicensePrompt } from '../../../shared/LicensePrompt'; -import { useLicense } from '../../../../hooks/useLicense'; +import { useLicenseContext } from '../../../../context/license/use_license_context'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; @@ -27,7 +27,7 @@ const DEFAULT_VALUE: AnomalyDetectionApiResponse = { export function AnomalyDetection() { const plugin = useApmPluginContext(); const canGetJobs = !!plugin.core.application.capabilities.ml?.canGetJobs; - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const [viewAddEnvironments, setViewAddEnvironments] = useState(false); diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx index 137dcfcdbb4f0..8d6a0740a8a08 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx index 1844e5754cfba..9e21eb2ffc870 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx @@ -7,7 +7,7 @@ import { EuiCallOut, EuiButton } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref } from '../../../../../../ml/public'; export function LegacyJobsCallout() { diff --git a/x-pack/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/index.tsx index c9c577285ee80..e974f05fbe994 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/index.tsx @@ -16,7 +16,7 @@ import React, { ReactNode } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { HeaderMenuPortal } from '../../../../../observability/public'; import { ActionMenu } from '../../../application/action_menu'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { HomeLink } from '../../shared/Links/apm/HomeLink'; diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index c1c9edb01eb8e..3f325f17af82d 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -8,8 +8,8 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getRedirectToTransactionDetailPageUrl } from './get_redirect_to_transaction_detail_page_url'; import { getRedirectToTracePageUrl } from './get_redirect_to_trace_page_url'; diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index e7c0400290dcb..c07e00ef387c9 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -8,13 +8,13 @@ import { shallow } from 'enzyme'; import React, { ReactNode } from 'react'; import { MemoryRouter, RouteComponentProps } from 'react-router-dom'; import { TraceLink } from './'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import * as hooks from '../../../hooks/useFetcher'; -import * as urlParamsHooks from '../../../hooks/useUrlParams'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import * as hooks from '../../../hooks/use_fetcher'; +import * as urlParamsHooks from '../../../context/url_params_context/use_url_params'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx index cbab2c44132f3..ab10d6b4f46a0 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel } from '@elastic/eui'; import React, { useMemo } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index 003f2ed05b09e..bebd5bdabbae3 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -27,8 +27,8 @@ import { ValuesType } from 'utility-types'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx index 48413d6207ee3..43732c23aea64 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx @@ -10,7 +10,7 @@ import { Location } from 'history'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { TransactionMetadata } from '../../../shared/MetadataTable/TransactionMetadata'; import { WaterfallContainer } from './WaterfallContainer'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx index e3ba02ce42c2e..5217c2abb11dc 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx @@ -9,7 +9,7 @@ import { MemoryRouter } from 'react-router-dom'; import { EuiThemeProvider } from '../../../../../../../observability/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TraceAPIResponse } from '../../../../../../server/lib/traces/get_trace'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { WaterfallContainer } from './index'; import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx index 501ca6d33d5af..806350679df55 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx @@ -6,7 +6,7 @@ import { Location } from 'history'; import React from 'react'; -import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../../../context/url_params_context/types'; import { ServiceLegends } from './ServiceLegends'; import { IWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { Waterfall } from './Waterfall'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts index f78fe39120d8d..0f6bebd9037cc 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts @@ -5,7 +5,7 @@ */ import { Location } from 'history'; -import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../../../context/url_params_context/types'; export const location = { pathname: '/services/opbeans-go/transactions/view', diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx index c9420dbb81cb9..d90fe393c94a4 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx @@ -18,7 +18,7 @@ import { Location } from 'history'; import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { TransactionSummary } from '../../../shared/Summary/TransactionSummary'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 8f335ddc71c72..c491b9f0e1eff 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -17,19 +17,19 @@ import React, { useMemo } from 'react'; import { isEmpty, flatten } from 'lodash'; import { useHistory } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom'; -import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; -import { useTransactionDistribution } from '../../../hooks/useTransactionDistribution'; -import { useWaterfall } from '../../../hooks/useWaterfall'; +import { useTransactionChartsFetcher } from '../../../hooks/use_transaction_charts_fetcher'; +import { useTransactionDistributionFetcher } from '../../../hooks/use_transaction_distribution_fetcher'; +import { useWaterfallFetcher } from './use_waterfall_fetcher'; import { ApmHeader } from '../../shared/ApmHeader'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { TransactionDistribution } from './Distribution'; import { WaterfallWithSummmary } from './WaterfallWithSummmary'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { HeightRetainer } from '../../shared/HeightRetainer'; import { Correlations } from '../Correlations'; @@ -50,18 +50,20 @@ export function TransactionDetails({ const { urlParams } = useUrlParams(); const history = useHistory(); const { - data: distributionData, - status: distributionStatus, - } = useTransactionDistribution(urlParams); + distributionData, + distributionStatus, + } = useTransactionDistributionFetcher(); const { - data: transactionChartsData, - status: transactionChartsStatus, - } = useTransactionCharts(); + transactionChartsData, + transactionChartsStatus, + } = useTransactionChartsFetcher(); - const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( - urlParams - ); + const { + waterfall, + exceedsMax, + status: waterfallStatus, + } = useWaterfallFetcher(); const { transactionName, transactionType } = urlParams; useTrackPageview({ app: 'apm', path: 'transaction_details' }); diff --git a/x-pack/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useWaterfall.ts rename to x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts index 6264ec45088a2..7458fa79bd1f3 100644 --- a/x-pack/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts @@ -5,9 +5,9 @@ */ import { useMemo } from 'react'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; -import { getWaterfall } from '../components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { getWaterfall } from './WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; const INITIAL_DATA = { root: undefined, @@ -15,7 +15,8 @@ const INITIAL_DATA = { errorsPerTransaction: {}, }; -export function useWaterfall(urlParams: IUrlParams) { +export function useWaterfallFetcher() { + const { urlParams } = useUrlParams(); const { traceId, start, end, transactionId } = urlParams; const { data = INITIAL_DATA, status, error } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx index 003bd6ba4c122..ae0dd85b6a8b5 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode } from 'react'; import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name'; import { enableServiceOverview } from '../../../../common/ui_settings_keys'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink'; import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink'; import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink'; @@ -23,7 +23,7 @@ import { ServiceMetrics } from '../service_metrics'; import { ServiceNodeOverview } from '../ServiceNodeOverview'; import { ServiceOverview } from '../service_overview'; import { TransactionOverview } from '../transaction_overview'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface Tab { key: string; @@ -44,7 +44,7 @@ interface Props { } export function ServiceDetailTabs({ serviceName, tab }: Props) { - const { agentName } = useApmService(); + const { agentName } = useApmServiceContext(); const { uiSettings } = useApmPluginContext().core; const overviewTab = { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx index e8ad3e65b1a47..3b97849b07790 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx @@ -10,7 +10,7 @@ import { getServiceHealthStatusLabel, ServiceHealthStatus, } from '../../../../../common/service_health_status'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; export function HealthBadge({ healthStatus, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx index 39cb73d2a0dd9..1c6fa9fe0447e 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx @@ -7,7 +7,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { mockMoment, renderWithTheme } from '../../../../utils/testHelpers'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { ServiceList, SERVICE_COLUMNS } from './'; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 3c84b3982642d..b1d725bba0ca9 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -17,17 +17,17 @@ import url from 'url'; import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { useAnomalyDetectionJobs } from '../../../hooks/useAnomalyDetectionJobs'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { Correlations } from '../Correlations'; import { NoServicesMessage } from './no_services_message'; import { ServiceList } from './ServiceList'; import { MLCallout } from './ServiceList/MLCallout'; +import { useAnomalyDetectionJobsFetcher } from './use_anomaly_detection_jobs_fetcher'; const initialData = { items: [], @@ -37,12 +37,10 @@ const initialData = { let hasDisplayedToast = false; -export function ServiceInventory() { +function useServicesFetcher() { + const { urlParams, uiFilters } = useUrlParams(); const { core } = useApmPluginContext(); - const { - urlParams: { start, end }, - uiFilters, - } = useUrlParams(); + const { start, end } = urlParams; const { data = initialData, status } = useFetcher( (callApmApi) => { if (start && end) { @@ -92,6 +90,13 @@ export function ServiceInventory() { } }, [data.hasLegacyData, core.http.basePath, core.notifications.toasts]); + return { servicesData: data, servicesStatus: status }; +} + +export function ServiceInventory() { + const { core } = useApmPluginContext(); + const { servicesData, servicesStatus } = useServicesFetcher(); + // The page is called "service inventory" to avoid confusion with the // "service overview", but this is tracked in some dashboards because it's the // initial landing page for APM, so it stays as "services_overview" (plural.) @@ -110,9 +115,9 @@ export function ServiceInventory() { ); const { - data: anomalyDetectionJobsData, - status: anomalyDetectionJobsStatus, - } = useAnomalyDetectionJobs(); + anomalyDetectionJobsData, + anomalyDetectionJobsStatus, + } = useAnomalyDetectionJobsFetcher(); const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage( 'apm.userHasDismissedServiceInventoryMlCallout', @@ -148,11 +153,11 @@ export function ServiceInventory() { <EuiFlexItem> <EuiPanel> <ServiceList - items={data.items} + items={servicesData.items} noItemsMessage={ <NoServicesMessage - historicalDataFound={data.hasHistoricalData} - status={status} + historicalDataFound={servicesData.hasHistoricalData} + status={servicesStatus} /> } /> diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx index 0fc2a2b4cdcef..cf1ccfbd36aaf 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx @@ -6,8 +6,8 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { NoServicesMessage } from './no_services_message'; function Wrapper({ children }: { children?: ReactNode }) { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx index d2763c6632c65..b20efc440312c 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { KibanaLink } from '../../shared/Links/KibanaLink'; import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../../shared/ErrorStatePrompt'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index de5e92664a769..1c838a01d05c7 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -13,17 +13,17 @@ import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { ServiceHealthStatus } from '../../../../common/service_health_status'; import { ServiceInventory } from '.'; import { EuiThemeProvider } from '../../../../../observability/public'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useAnomalyDetectionJobs from '../../../hooks/useAnomalyDetectionJobs'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import * as useLocalUIFilters from '../../../hooks/useLocalUIFilters'; -import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern'; +import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; import { SessionStorageMock } from '../../../services/__test__/SessionStorageMock'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import * as hook from './use_anomaly_detection_jobs_fetcher'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiStats: () => {} }, @@ -80,19 +80,13 @@ describe('ServiceInventory', () => { status: FETCH_STATUS.SUCCESS, }); - jest - .spyOn(useAnomalyDetectionJobs, 'useAnomalyDetectionJobs') - .mockReturnValue({ - status: FETCH_STATUS.SUCCESS, - data: { - jobs: [], - hasLegacyJobs: false, - }, - refetch: () => undefined, - }); + jest.spyOn(hook, 'useAnomalyDetectionJobsFetcher').mockReturnValue({ + anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, + anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, + }); jest - .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern') + .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPatternFetcher') .mockReturnValue({ indexPattern: undefined, status: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts similarity index 50% rename from x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts rename to x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts index bef016e5bedd1..901841ac4d593 100644 --- a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts +++ b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts @@ -3,16 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { useFetcher } from '../../../hooks/use_fetcher'; -import { useFetcher } from './useFetcher'; - -export function useAnomalyDetectionJobs() { - return useFetcher( +export function useAnomalyDetectionJobsFetcher() { + const { data, status } = useFetcher( (callApmApi) => - callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, - }), + callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }), [], { showToastOnError: false } ); + + return { anomalyDetectionJobsData: data, anomalyDetectionJobsStatus: status }; } diff --git a/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx index d0f8fc1e61332..bf99f5c87fa6a 100644 --- a/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx @@ -13,10 +13,10 @@ import { EuiFlexGroup, } from '@elastic/eui'; import React, { useMemo } from 'react'; -import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; +import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; import { MetricsChart } from '../../shared/charts/metrics_chart'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { Projection } from '../../../../common/projections'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; @@ -31,7 +31,9 @@ export function ServiceMetrics({ serviceName, }: ServiceMetricsProps) { const { urlParams } = useUrlParams(); - const { data, status } = useServiceMetricCharts(urlParams, agentName); + const { data, status } = useServiceMetricChartsFetcher({ + serviceNodeName: undefined, + }); const { start, end } = urlParams; const localFiltersConfig: React.ComponentProps< diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx index c6f7e68e4f4d0..0ba45fae15fef 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ServiceNodeMetrics } from '.'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { RouteComponentProps } from 'react-router-dom'; describe('ServiceNodeMetrics', () => { diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index 11de40b47ff86..aa1d9cccbdfa6 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -22,11 +22,11 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { px, truncate, unit } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { MetricsChart } from '../../shared/charts/metrics_chart'; @@ -58,12 +58,8 @@ type ServiceNodeMetricsProps = RouteComponentProps<{ export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) { const { urlParams, uiFilters } = useUrlParams(); const { serviceName, serviceNodeName } = match.params; - const { agentName } = useApmService(); - const { data } = useServiceMetricCharts( - urlParams, - agentName, - serviceNodeName - ); + const { agentName } = useApmServiceContext(); + const { data } = useServiceMetricChartsFetcher({ serviceNodeName }); const { start, end } = urlParams; const { data: { host, containerId } = INITIAL_DATA, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 15125128d9781..dcb407d27e690 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart'; import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index b364f027538a6..949f5cce0a64f 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -8,17 +8,17 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { CoreStart } from 'src/core/public'; import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; -import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern'; -import * as useFetcherHooks from '../../../hooks/useFetcher'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import * as useAnnotationsHooks from '../../../hooks/use_annotations'; -import * as useTransactionBreakdownHooks from '../../../hooks/use_transaction_breakdown'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; +import * as useFetcherHooks from '../../../hooks/use_fetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context'; +import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_breakdown_chart/use_transaction_breakdown'; import { renderWithTheme } from '../../../utils/testHelpers'; import { ServiceOverview } from './'; @@ -56,10 +56,10 @@ function Wrapper({ children }: { children?: ReactNode }) { describe('ServiceOverview', () => { it('renders', () => { jest - .spyOn(useAnnotationsHooks, 'useAnnotations') + .spyOn(useAnnotationsHooks, 'useAnnotationsContext') .mockReturnValue({ annotations: [] }); jest - .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern') + .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPatternFetcher') .mockReturnValue({ indexPattern: undefined, status: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index b4228878dd9f5..6e183924a80a7 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -14,8 +14,8 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import styled from 'styled-components'; import { asInteger } from '../../../../../common/utils/formatters'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { px, truncate, unit } from '../../../../style/variables'; import { SparkPlotWithValueLabel } from '../../../shared/charts/spark_plot/spark_plot_with_value_label'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index 94d92bfbe89dd..1662f44d1e421 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asTransactionRate } from '../../../../common/utils/formatters'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../../shared/charts/timeseries_chart'; @@ -24,7 +24,7 @@ export function ServiceOverviewThroughputChart({ const theme = useTheme(); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionType } = useApmService(); + const { transactionType } = useApmServiceContext(); const { start, end } = urlParams; const { data, status } = useFetcher(() => { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index e241bc2fed05a..6b02a44dcc2f4 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -21,8 +21,8 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { px, truncate, unit } from '../../../../style/variables'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx index 953397b9f3d5f..c14c31afe0445 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx @@ -7,7 +7,7 @@ import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { TransactionList } from './'; type TransactionGroup = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups'>['items'][0]; diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 28a27c034265a..9ff4ad916b174 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -23,10 +23,10 @@ import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; -import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; -import { useTransactionList } from '../../../hooks/useTransactionList'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import { useTransactionChartsFetcher } from '../../../hooks/use_transaction_charts_fetcher'; +import { useTransactionListFetcher } from './use_transaction_list'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; @@ -37,7 +37,7 @@ import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; import { UserExperienceCallout } from './user_experience_callout'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; function getRedirectLocation({ location, @@ -68,22 +68,22 @@ interface TransactionOverviewProps { export function TransactionOverview({ serviceName }: TransactionOverviewProps) { const location = useLocation(); const { urlParams } = useUrlParams(); - const { transactionType, transactionTypes } = useApmService(); + const { transactionType, transactionTypes } = useApmServiceContext(); // redirect to first transaction type useRedirect(getRedirectLocation({ location, transactionType, urlParams })); const { - data: transactionCharts, - status: transactionChartsStatus, - } = useTransactionCharts(); + transactionChartsData, + transactionChartsStatus, + } = useTransactionChartsFetcher(); useTrackPageview({ app: 'apm', path: 'transaction_overview' }); useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 }); const { - data: transactionListData, - status: transactionListStatus, - } = useTransactionList(urlParams); + transactionListData, + transactionListStatus, + } = useTransactionListFetcher(); const localFiltersConfig: React.ComponentProps< typeof LocalUIFilters @@ -134,7 +134,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} <TransactionCharts fetchStatus={transactionChartsStatus} - charts={transactionCharts} + charts={transactionChartsData} urlParams={urlParams} /> <EuiSpacer size="s" /> diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index d4a8b3a46991c..93d56ea19024e 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -10,13 +10,13 @@ import { CoreStart } from 'kibana/public'; import React from 'react'; import { Router } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { ApmServiceContextProvider } from '../../../context/apm_service_context'; -import { UrlParamsProvider } from '../../../context/UrlParamsContext'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; -import * as useFetcherHook from '../../../hooks/useFetcher'; -import * as useServiceTransactionTypesHook from '../../../hooks/use_service_transaction_types'; -import * as useServiceAgentNameHook from '../../../hooks/use_service_agent_name'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; +import { UrlParamsProvider } from '../../../context/url_params_context/url_params_context'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import * as useFetcherHook from '../../../hooks/use_fetcher'; +import * as useServiceTransactionTypesHook from '../../../context/apm_service/use_service_transaction_types_fetcher'; +import * as useServiceAgentNameHook from '../../../context/apm_service/use_service_agent_name_fetcher'; import { disableConsoleWarning, renderWithTheme, @@ -46,15 +46,17 @@ function setup({ // mock transaction types jest - .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypes') + .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') .mockReturnValue(serviceTransactionTypes); // mock agent - jest.spyOn(useServiceAgentNameHook, 'useServiceAgentName').mockReturnValue({ - agentName: 'nodejs', - error: undefined, - status: useFetcherHook.FETCH_STATUS.SUCCESS, - }); + jest + .spyOn(useServiceAgentNameHook, 'useServiceAgentNameFetcher') + .mockReturnValue({ + agentName: 'nodejs', + error: undefined, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useTransactionList.ts rename to x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts index 92b54beb715db..78883ec2cf0d3 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts @@ -5,10 +5,9 @@ */ import { useParams } from 'react-router-dom'; -import { useUiFilters } from '../context/UrlParamsContext'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { APIReturnType } from '../services/rest/createCallApmApi'; -import { useFetcher } from './useFetcher'; +import { APIReturnType } from '../../../services/rest/createCallApmApi'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; type TransactionsAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups'>; @@ -18,10 +17,10 @@ const DEFAULT_RESPONSE: Partial<TransactionsAPIResponse> = { bucketSize: 0, }; -export function useTransactionList(urlParams: IUrlParams) { +export function useTransactionListFetcher() { + const { urlParams, uiFilters } = useUrlParams(); const { serviceName } = useParams<{ serviceName?: string }>(); const { transactionType, start, end } = urlParams; - const uiFilters = useUiFilters(urlParams); const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { @@ -43,8 +42,8 @@ export function useTransactionList(urlParams: IUrlParams) { ); return { - data, - status, - error, + transactionListData: data, + transactionListStatus: status, + transactionListError: error, }; } diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx index 41e84d4acfba5..6e1154a458d6e 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiButton, EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; export function UserExperienceCallout() { const { core } = useApmPluginContext(); diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx index 56501d8c916f4..dd88b1ea7eb73 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx @@ -8,8 +8,8 @@ import { EuiTitle } from '@elastic/eui'; import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { HttpSetup } from '../../../../../../../src/core/public'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../services/rest/createCallApmApi'; import { ApmHeader } from './'; diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx index a806a3ea60154..04e03cda6a61e 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import styled from 'styled-components'; import { HeaderMenuPortal } from '../../../../../observability/public'; import { ActionMenu } from '../../../application/action_menu'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { EnvironmentFilter } from '../EnvironmentFilter'; const HeaderFlexGroup = styled(EuiFlexGroup)` diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx index 520cc2f423ddd..222c27cc7ed6d 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx @@ -10,12 +10,12 @@ import { mount } from 'enzyme'; import { createMemoryHistory } from 'history'; import React, { ReactNode } from 'react'; import { Router } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { UrlParamsContext, useUiFilters, -} from '../../../context/UrlParamsContext'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +} from '../../../context/url_params_context/url_params_context'; +import { IUrlParams } from '../../../context/url_params_context/types'; import { DatePicker } from './'; const history = createMemoryHistory(); diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx index f35cc06748911..f847ce0b6e96f 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx @@ -8,8 +8,8 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import React, { useEffect } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { UI_SETTINGS } from '../../../../../../../src/plugins/data/common'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { clearCache } from '../../../services/rest/callApi'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { TimePickerQuickRange, TimePickerTimeDefaults } from './typings'; diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index cace4c2770f37..4522cfa7195fd 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -13,8 +13,8 @@ import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../../../common/environment_filter_values'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; function updateEnvironmentUrl( @@ -67,7 +67,7 @@ export function EnvironmentFilter() { const { environment } = uiFilters; const { start, end } = urlParams; - const { environments, status = 'loading' } = useEnvironments({ + const { environments, status = 'loading' } = useEnvironmentsFetcher({ serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index e7dd03db6b63c..2276704edc342 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -13,7 +13,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { UIProcessorEvent } from '../../../../common/processor_event'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../context/url_params_context/types'; export function getBoolFilter({ groupId, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx index 2ef93fc32200e..5284e3f6aa011 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -14,9 +14,9 @@ import { IIndexPattern, QuerySuggestion, } from '../../../../../../../src/plugins/data/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useDynamicIndexPatternFetcher } from '../../../hooks/use_dynamic_index_pattern'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { getBoolFilter } from './get_bool_filter'; // @ts-expect-error @@ -65,7 +65,7 @@ export function KueryBar() { const example = examples[processorEvent || 'defaults']; - const { indexPattern } = useDynamicIndexPattern(processorEvent); + const { indexPattern } = useDynamicIndexPatternFetcher(processorEvent); const placeholder = i18n.translate('xpack.apm.kueryBar.placeholder', { defaultMessage: `Search {event, select, diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx index 1819e71a49753..bd68e7db77714 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx @@ -9,7 +9,7 @@ import { LicensePrompt } from '.'; import { ApmPluginContext, ApmPluginContextValue, -} from '../../../context/ApmPluginContext'; +} from '../../../context/apm_plugin/apm_plugin_context'; const contextMock = ({ core: { http: { basePath: { prepend: () => {} } } }, diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx index 93b5672aa54f9..70286655bba88 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx @@ -12,7 +12,7 @@ import { useLocation } from 'react-router-dom'; import rison, { RisonValue } from 'rison-node'; import url from 'url'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { getTimepickerRisonData } from '../rison_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx index 8c2829a515f83..e2447cc7a67a5 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx @@ -6,7 +6,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; // union type constisting of valid guide sections that we link to type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server' | '/kibana'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx index 630235e54c9fa..6d4bbbbfc2f80 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx @@ -9,7 +9,7 @@ import { IBasePath } from 'kibana/public'; import React from 'react'; import url from 'url'; import { InfraAppId } from '../../../../../infra/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { fromQuery } from './url_helpers'; interface InfraQueryParams { diff --git a/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx index 8aa0d4f5a3354..ab44374f48167 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx @@ -7,7 +7,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; import url from 'url'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; interface Props extends EuiLinkAnchorProps { path?: string; diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx index 5fbcd475cb47b..7bf017fb239e3 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx @@ -6,9 +6,9 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; interface MlRisonData { ml?: { diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts index 0f671fd363c75..eabef034bf3d9 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useTimeSeriesExplorerHref({ jobId, diff --git a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx index 0ff73d91d7c5b..68bee36dbe283 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx @@ -7,7 +7,7 @@ import { EuiButton, EuiButtonEmpty, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; const SETUP_INSTRUCTIONS_LABEL = i18n.translate( 'xpack.apm.setupInstructionsButtonLabel', diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx index 41c932bf9c9f5..98046193e3807 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx @@ -11,8 +11,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import url from 'url'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams, fromQuery, toQuery } from '../url_helpers'; interface Props extends EuiLinkAnchorProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx index 30b91fe2564f1..dcf21de7dca8d 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx index fbae80203f03b..de7130e878608 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx index 2553ec4353194..afdb177e467d8 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; interface Props extends APMLinkExtendProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx index 0a9553bcbfe6c..c107b436717c2 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { APMQueryParams } from '../url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx index 6aa362707800f..caa1498e6df87 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx index c9b26b557512c..ee798e0208c2b 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { APMLink, APMLinkExtendProps } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; interface Props extends APMLinkExtendProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx index 23e795b026d0c..92ff1b8a68ac0 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { APMQueryParams } from '../url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx index 039d9dcb1c0ed..318a1590be77c 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx index e6d266091ae52..43f7b089a2965 100644 --- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx @@ -13,7 +13,7 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../Links/url_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx index 9db563a0f6ba8..6f62fd24e71ea 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { orderBy } from 'lodash'; import React, { ReactNode, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; // TODO: this should really be imported from EUI diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx index e95122f54aff1..8f44d98cecdf7 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorMetadata } from '..'; import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 1f10d923e351e..c97e506187347 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { SpanMetadata } from '..'; import { Span } from '../../../../../../typings/es_schemas/ui/span'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index 8359716fc6966..4080a300ba17f 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { TransactionMetadata } from '..'; import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index 8e53aa4aa1089..8a4cd588c8260 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -8,7 +8,7 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataTable } from '..'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; import { SectionsWithRows } from '../helper'; diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx index 1d2ac4d18a2a7..283433fa37bf9 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx @@ -17,7 +17,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { HeightRetainer } from '../HeightRetainer'; import { fromQuery, toQuery } from '../Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx index ed33c59af36f4..83c2acb57e3c7 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { EuiBadge } from '@elastic/eui'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; import { px } from '../../../../public/style/variables'; import { units } from '../../../style/variables'; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx index 0241167aba1fb..777200099976e 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx @@ -7,7 +7,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx index db7a284f6adff..c4547595645a2 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx @@ -10,8 +10,8 @@ import { MemoryRouter } from 'react-router-dom'; import { CustomLinkMenuSection } from '.'; import { CustomLink as CustomLinkType } from '../../../../../common/custom_link/custom_link_types'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useFetcher from '../../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useFetcher from '../../../../hooks/use_fetcher'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx index 2825363b10197..0a67db0f15b32 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx @@ -22,7 +22,7 @@ import { import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { CustomLinkList } from './CustomLinkList'; import { CustomLinkToolbar } from './CustomLinkToolbar'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { LoadingStatePrompt } from '../../LoadingStatePrompt'; import { px } from '../../../../style/variables'; import { CreateEditCustomLinkFlyout } from '../../../app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout'; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 15a85113406e1..3f74b80bab064 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -18,9 +18,9 @@ import { SectionTitle, } from '../../../../../observability/public'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useLicense } from '../../../hooks/useLicense'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useLicenseContext } from '../../../context/license/use_license_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { CustomLinkMenuSection } from './CustomLinkMenuSection'; import { getSections } from './sections'; @@ -39,7 +39,7 @@ function ActionMenuButton({ onClick }: { onClick: () => void }) { } export function TransactionActionMenu({ transaction }: Props) { - const license = useLicense(); + const license = useLicenseContext(); const hasGoldLicense = license?.isActive && license?.hasAtLeast('gold'); const { core } = useApmPluginContext(); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 9b5f00f76eeb2..8cb863c8fc385 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { License } from '../../../../../../licensing/common/license'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import { LicenseContext } from '../../../../context/LicenseContext'; -import * as hooks from '../../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../../context/license/license_context'; +import * as hooks from '../../../../hooks/use_fetcher'; import * as apmApi from '../../../../services/rest/createCallApmApi'; import { expectTextsInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts index 4433865b44991..c77de875dc84f 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts @@ -10,7 +10,7 @@ import { isEmpty, pickBy } from 'lodash'; import moment from 'moment'; import url from 'url'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../context/url_params_context/types'; import { getDiscoverHref } from '../Links/DiscoverLinks/DiscoverLink'; import { getDiscoverQuery } from '../Links/DiscoverLinks/DiscoverTransactionLink'; import { getInfraHref } from '../Links/InfraLink'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx index 1a2a90c9fb3c3..eebb9e8d23d98 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import styled from 'styled-components'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { fontSizes, px, units } from '../../../../style/variables'; export enum Shape { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx index 37d3664e98acd..a6b46f4a64691 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiToolTip } from '@elastic/eui'; import styled from 'styled-components'; import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { px, units } from '../../../../../style/variables'; import { Legend } from '../../Legend'; import { AgentMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx index abe81185635b5..e69b23cf5f008 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx @@ -8,7 +8,7 @@ import { fireEvent } from '@testing-library/react'; import { act } from '@testing-library/react-hooks'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, renderWithTheme, diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx index de63e2323ddac..c6847bd5e674d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx @@ -8,12 +8,12 @@ import React, { useState } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { TRACE_ID, TRANSACTION_ID, } from '../../../../../../common/elasticsearch_fieldnames'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { px, unit, units } from '../../../../../style/variables'; import { ErrorMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; import { ErrorDetailLink } from '../../../Links/apm/ErrorDetailLink'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx index cb5a44432dcbc..dcdfee22e3cfc 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx @@ -9,7 +9,7 @@ import { inRange } from 'lodash'; import { Sticky } from 'react-sticky'; import { XAxis, XYPlot } from 'react-vis'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { px } from '../../../../style/variables'; import { Mark } from './'; import { LastTickValue } from './LastTickValue'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx index 5ea2e4cfedf18..ee1c899123994 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { VerticalGridLines, XYPlot } from 'react-vis'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { Mark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks'; import { PlotValues } from './plotUtils'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx index c0e8f869ce647..359eadfc55cff 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx @@ -5,7 +5,7 @@ */ import { render } from '@testing-library/react'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ChartContainer } from './chart_container'; describe('ChartContainer', () => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index b4486f1e9b94a..ef58430e1e31e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -7,7 +7,7 @@ import { EuiLoadingChart, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; interface Props { hasData: boolean; diff --git a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx index 9a561571df5a7..506c27051b511 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx @@ -15,7 +15,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { GenericMetricsChart } from '../../../../../server/lib/metrics/transform_metrics_chart'; import { Maybe } from '../../../../../typings/common'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; import { TimeseriesChart } from '../timeseries_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx index 3819ed30d104a..3bfcba63685b6 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { px, unit } from '../../../../../style/variables'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { SparkPlot } from '../'; type Color = diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index a857707ca0c75..689f80e01247e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -29,11 +29,11 @@ import { useHistory } from 'react-router-dom'; import { useChartTheme } from '../../../../../observability/public'; import { asAbsoluteDateTime } from '../../../../common/utils/formatters'; import { RectCoordinate, TimeSeries } from '../../../../typings/timeseries'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useAnnotations } from '../../../hooks/use_annotations'; -import { useChartPointerEvent } from '../../../hooks/use_chart_pointer_event'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useAnnotationsContext } from '../../../context/annotations/use_annotations_context'; +import { useChartPointerEventContext } from '../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { AnomalySeries } from '../../../selectors/chart_selectors'; import { unit } from '../../../style/variables'; import { ChartContainer } from './chart_container'; @@ -72,9 +72,9 @@ export function TimeseriesChart({ }: Props) { const history = useHistory(); const chartRef = React.createRef<Chart>(); - const { annotations } = useAnnotations(); + const { annotations } = useAnnotationsContext(); const chartTheme = useChartTheme(); - const { pointerEvent, setPointerEvent } = useChartPointerEvent(); + const { pointerEvent, setPointerEvent } = useChartPointerEventContext(); const { urlParams } = useUrlParams(); const theme = useTheme(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx index 4d9a1637bea76..38a980fbcd90a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useTransactionBreakdown } from '../../../../hooks/use_transaction_breakdown'; +import { useTransactionBreakdown } from './use_transaction_breakdown'; import { TransactionBreakdownChartContents } from './transaction_breakdown_chart_contents'; export function TransactionBreakdownChart({ diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx index 20056a6831adf..0eda922519f85 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx @@ -28,11 +28,11 @@ import { asPercent, } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useAnnotations } from '../../../../hooks/use_annotations'; -import { useChartPointerEvent } from '../../../../hooks/use_chart_pointer_event'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../hooks/use_theme'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useAnnotationsContext } from '../../../../context/annotations/use_annotations_context'; +import { useChartPointerEventContext } from '../../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../charts/chart_container'; import { onBrushEnd } from '../../charts/helper/helper'; @@ -52,9 +52,9 @@ export function TransactionBreakdownChartContents({ }: Props) { const history = useHistory(); const chartRef = React.createRef<Chart>(); - const { annotations } = useAnnotations(); + const { annotations } = useAnnotationsContext(); const chartTheme = useChartTheme(); - const { pointerEvent, setPointerEvent } = useChartPointerEvent(); + const { pointerEvent, setPointerEvent } = useChartPointerEventContext(); const { urlParams } = useUrlParams(); const theme = useTheme(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts similarity index 80% rename from x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts rename to x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index f1671ed7aa6d9..ff744d763ecae 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -5,15 +5,15 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; -import { useApmService } from './use_apm_service'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; export function useTransactionBreakdown() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { start, end, transactionName } = urlParams; - const { transactionType } = useApmService(); + const { transactionType } = useApmServiceContext(); const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx index ac117bbbd922a..bb7c0a9104fc7 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx @@ -20,11 +20,11 @@ import { TRANSACTION_ROUTE_CHANGE, } from '../../../../../common/transaction_types'; import { asTransactionRate } from '../../../../../common/utils/formatters'; -import { AnnotationsContextProvider } from '../../../../context/annotations_context'; -import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event_context'; -import { LicenseContext } from '../../../../context/LicenseContext'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { LicenseContext } from '../../../../context/license/license_context'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { ITransactionChartData } from '../../../../selectors/chart_selectors'; import { TimeseriesChart } from '../timeseries_chart'; import { TransactionBreakdownChart } from '../transaction_breakdown_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx index ee5cd8960d335..f0569ea1a0752 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 00472df95c4b1..06a5e7baef79b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -9,9 +9,9 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../hooks/use_theme'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../timeseries_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx b/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx index e8d62cd8bd85b..9538d46960fc9 100644 --- a/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode } from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../ErrorStatePrompt'; export function TableFetchWrapper({ diff --git a/x-pack/plugins/apm/public/context/annotations_context.tsx b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx similarity index 83% rename from x-pack/plugins/apm/public/context/annotations_context.tsx rename to x-pack/plugins/apm/public/context/annotations/annotations_context.tsx index 4e09a3d227b11..77285f976d850 100644 --- a/x-pack/plugins/apm/public/context/annotations_context.tsx +++ b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx @@ -6,10 +6,10 @@ import React, { createContext } from 'react'; import { useParams } from 'react-router-dom'; -import { Annotation } from '../../common/annotations'; -import { useFetcher } from '../hooks/useFetcher'; -import { useUrlParams } from '../hooks/useUrlParams'; -import { callApmApi } from '../services/rest/createCallApmApi'; +import { Annotation } from '../../../common/annotations'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; +import { callApmApi } from '../../services/rest/createCallApmApi'; export const AnnotationsContext = createContext({ annotations: [] } as { annotations: Annotation[]; diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts similarity index 80% rename from x-pack/plugins/apm/public/hooks/use_annotations.ts rename to x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts index 1cd9a7e65dda2..7fdc602b1916e 100644 --- a/x-pack/plugins/apm/public/hooks/use_annotations.ts +++ b/x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts @@ -5,9 +5,9 @@ */ import { useContext } from 'react'; -import { AnnotationsContext } from '../context/annotations_context'; +import { AnnotationsContext } from './annotations_context'; -export function useAnnotations() { +export function useAnnotationsContext() { const context = useContext(AnnotationsContext); if (!context) { diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx similarity index 94% rename from x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx rename to x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index 44952e64db59c..0e26db4820ea1 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -6,7 +6,7 @@ import { AppMountParameters, CoreStart } from 'kibana/public'; import { createContext } from 'react'; -import { ConfigSchema } from '../../'; +import { ConfigSchema } from '../..'; import { ApmPluginSetupDeps } from '../../plugin'; export interface ApmPluginContextValue { diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx similarity index 97% rename from x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx rename to x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 25e7f23a00125..7ab46c65c90d9 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode } from 'react'; import { Observable, of } from 'rxjs'; -import { ApmPluginContext, ApmPluginContextValue } from '.'; +import { ApmPluginContext, ApmPluginContextValue } from './apm_plugin_context'; import { ConfigSchema } from '../..'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { createCallApmApi } from '../../services/rest/createCallApmApi'; diff --git a/x-pack/plugins/apm/public/hooks/useApmPluginContext.ts b/x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts similarity index 84% rename from x-pack/plugins/apm/public/hooks/useApmPluginContext.ts rename to x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts index 80a04edbca858..7c480ea3da275 100644 --- a/x-pack/plugins/apm/public/hooks/useApmPluginContext.ts +++ b/x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { ApmPluginContext } from '../context/ApmPluginContext'; +import { ApmPluginContext } from './apm_plugin_context'; export function useApmPluginContext() { return useContext(ApmPluginContext); diff --git a/x-pack/plugins/apm/public/context/apm_service_context.test.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/apm_service_context.test.tsx rename to x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx diff --git a/x-pack/plugins/apm/public/context/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx similarity index 75% rename from x-pack/plugins/apm/public/context/apm_service_context.tsx rename to x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 2f1b33dea5aa6..b07763aed7b00 100644 --- a/x-pack/plugins/apm/public/context/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -5,15 +5,15 @@ */ import React, { createContext, ReactNode } from 'react'; -import { isRumAgentName } from '../../common/agent_name'; +import { isRumAgentName } from '../../../common/agent_name'; import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, -} from '../../common/transaction_types'; -import { useServiceTransactionTypes } from '../hooks/use_service_transaction_types'; -import { useUrlParams } from '../hooks/useUrlParams'; -import { useServiceAgentName } from '../hooks/use_service_agent_name'; -import { IUrlParams } from './UrlParamsContext/types'; +} from '../../../common/transaction_types'; +import { useServiceTransactionTypesFetcher } from './use_service_transaction_types_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; +import { useServiceAgentNameFetcher } from './use_service_agent_name_fetcher'; +import { IUrlParams } from '../url_params_context/types'; export const APMServiceContext = createContext<{ agentName?: string; @@ -27,8 +27,8 @@ export function ApmServiceContextProvider({ children: ReactNode; }) { const { urlParams } = useUrlParams(); - const { agentName } = useServiceAgentName(); - const transactionTypes = useServiceTransactionTypes(); + const { agentName } = useServiceAgentNameFetcher(); + const transactionTypes = useServiceTransactionTypesFetcher(); const transactionType = getTransactionType({ urlParams, transactionTypes, diff --git a/x-pack/plugins/apm/public/hooks/use_apm_service.ts b/x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/use_apm_service.ts rename to x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts index bc80c3771c39d..85c135f36719f 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_service.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts @@ -5,8 +5,8 @@ */ import { useContext } from 'react'; -import { APMServiceContext } from '../context/apm_service_context'; +import { APMServiceContext } from './apm_service_context'; -export function useApmService() { +export function useApmServiceContext() { return useContext(APMServiceContext); } diff --git a/x-pack/plugins/apm/public/hooks/use_service_agent_name.ts b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts similarity index 83% rename from x-pack/plugins/apm/public/hooks/use_service_agent_name.ts rename to x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts index 199f14532f7b4..9a1d969ec2c33 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_agent_name.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts @@ -5,10 +5,10 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; -export function useServiceAgentName() { +export function useServiceAgentNameFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx similarity index 83% rename from x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx rename to x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index 9d8892ac79b7d..85a10cc273bac 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -5,12 +5,12 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; const INITIAL_DATA = { transactionTypes: [] }; -export function useServiceTransactionTypes() { +export function useServiceTransactionTypesFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/context/chart_pointer_event_context.tsx b/x-pack/plugins/apm/public/context/chart_pointer_event/chart_pointer_event_context.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/chart_pointer_event_context.tsx rename to x-pack/plugins/apm/public/context/chart_pointer_event/chart_pointer_event_context.tsx diff --git a/x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx b/x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx similarity index 78% rename from x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx rename to x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx index 058ec594e2d22..bf53273104d60 100644 --- a/x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx +++ b/x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx @@ -5,9 +5,9 @@ */ import { useContext } from 'react'; -import { ChartPointerEventContext } from '../context/chart_pointer_event_context'; +import { ChartPointerEventContext } from './chart_pointer_event_context'; -export function useChartPointerEvent() { +export function useChartPointerEventContext() { const context = useContext(ChartPointerEventContext); if (!context) { diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/license/Invalid_license_notification.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx rename to x-pack/plugins/apm/public/context/license/Invalid_license_notification.tsx diff --git a/x-pack/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/plugins/apm/public/context/license/license_context.tsx similarity index 87% rename from x-pack/plugins/apm/public/context/LicenseContext/index.tsx rename to x-pack/plugins/apm/public/context/license/license_context.tsx index e6615a2fc98bf..557f135fa4c0e 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/plugins/apm/public/context/license/license_context.tsx @@ -7,8 +7,8 @@ import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { ILicense } from '../../../../licensing/public'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; -import { InvalidLicenseNotification } from './InvalidLicenseNotification'; +import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context'; +import { InvalidLicenseNotification } from './Invalid_license_notification'; export const LicenseContext = React.createContext<ILicense | undefined>( undefined diff --git a/x-pack/plugins/apm/public/hooks/useLicense.ts b/x-pack/plugins/apm/public/context/license/use_license_context.ts similarity index 77% rename from x-pack/plugins/apm/public/hooks/useLicense.ts rename to x-pack/plugins/apm/public/context/license/use_license_context.ts index ca828e49706a8..e86bb78d127ab 100644 --- a/x-pack/plugins/apm/public/hooks/useLicense.ts +++ b/x-pack/plugins/apm/public/context/license/use_license_context.ts @@ -5,8 +5,8 @@ */ import { useContext } from 'react'; -import { LicenseContext } from '../context/LicenseContext'; +import { LicenseContext } from './license_context'; -export function useLicense() { +export function useLicenseContext() { return useContext(LicenseContext); } diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/constants.ts b/x-pack/plugins/apm/public/context/url_params_context/constants.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/constants.ts rename to x-pack/plugins/apm/public/context/url_params_context/constants.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts rename to x-pack/plugins/apm/public/context/url_params_context/helpers.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx similarity index 93% rename from x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx rename to x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx index fd01e057ac3de..b593cbd57a9a9 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { IUrlParams } from './types'; -import { UrlParamsContext, useUiFilters } from '.'; +import { UrlParamsContext, useUiFilters } from './url_params_context'; const defaultUrlParams = { page: 0, diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts rename to x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/types.ts rename to x-pack/plugins/apm/public/context/url_params_context/types.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx similarity index 98% rename from x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx rename to x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx index 3a6ccce178cd4..6b57039678e0a 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react'; -import { UrlParamsContext, UrlParamsProvider } from './'; +import { UrlParamsContext, UrlParamsProvider } from './url_params_context'; import { mount } from 'enzyme'; import { Location, History } from 'history'; import { MemoryRouter, Router } from 'react-router-dom'; diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx similarity index 98% rename from x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx rename to x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 5682009019d7f..0a3f8459ff002 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -15,7 +15,7 @@ import { withRouter } from 'react-router-dom'; import { uniqueId, mapValues } from 'lodash'; import { IUrlParams } from './types'; import { getParsedDate } from './helpers'; -import { resolveUrlParams } from './resolveUrlParams'; +import { resolveUrlParams } from './resolve_url_params'; import { UIFilters } from '../../../typings/ui_filters'; import { localUIFilterNames, diff --git a/x-pack/plugins/apm/public/hooks/useUrlParams.tsx b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx similarity index 84% rename from x-pack/plugins/apm/public/hooks/useUrlParams.tsx rename to x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx index b9f47046812be..1bf071d9db35e 100644 --- a/x-pack/plugins/apm/public/hooks/useUrlParams.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { UrlParamsContext } from '../context/UrlParamsContext'; +import { UrlParamsContext } from './url_params_context'; export function useUrlParams() { return useContext(UrlParamsContext); diff --git a/x-pack/plugins/apm/public/hooks/useCallApi.ts b/x-pack/plugins/apm/public/hooks/useCallApi.ts index 3fec36e7fb24b..79e439c3f7e7a 100644 --- a/x-pack/plugins/apm/public/hooks/useCallApi.ts +++ b/x-pack/plugins/apm/public/hooks/useCallApi.ts @@ -6,7 +6,7 @@ import { useMemo } from 'react'; import { callApi } from '../services/rest/callApi'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; import { FetchOptions } from '../../common/fetch_options'; export function useCallApi() { diff --git a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts index b4a354c231633..66edb84378a45 100644 --- a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts +++ b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts @@ -5,7 +5,7 @@ */ import url from 'url'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; export function useKibanaUrl( /** The path to the plugin */ path: string, diff --git a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts index da1797fd712b1..551e92f8ba034 100644 --- a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts +++ b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts @@ -15,10 +15,10 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../server/lib/ui_filters/local_ui_filters/config'; import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; -import { removeUndefinedProps } from '../context/UrlParamsContext/helpers'; +import { removeUndefinedProps } from '../context/url_params_context/helpers'; import { useCallApi } from './useCallApi'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from './use_fetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; import { LocalUIFilterName } from '../../common/ui_filter'; const getInitialData = ( diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx index dcd6ed0ba4934..9127bd3adc69e 100644 --- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx @@ -9,11 +9,11 @@ import produce from 'immer'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { routes } from '../components/app/Main/route_config'; -import { ApmPluginContextValue } from '../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../context/ApmPluginContext/MockApmPluginContext'; +} from '../context/apm_plugin/mock_apm_plugin_context'; import { useBreadcrumbs } from './use_breadcrumbs'; function createWrapper(path: string) { diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts index 640170bf3bff2..089381cbe05b5 100644 --- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts @@ -16,7 +16,7 @@ import { } from 'react-router-dom'; import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes'; import { getAPMHref } from '../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; interface BreadcrumbWithoutLink extends ChromeBreadcrumb { match: Match<Record<string, string>>; diff --git a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts similarity index 89% rename from x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts rename to x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts index d0e12d8537846..becdf1f9ecc5e 100644 --- a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts +++ b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useFetcher } from './useFetcher'; +import { useFetcher } from './use_fetcher'; import { UIProcessorEvent } from '../../common/processor_event'; -export function useDynamicIndexPattern( +export function useDynamicIndexPatternFetcher( processorEvent: UIProcessorEvent | undefined ) { const { data, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx similarity index 94% rename from x-pack/plugins/apm/public/hooks/useEnvironments.tsx rename to x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx index 05ac780aefbde..1ad151b8c7e90 100644 --- a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx +++ b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useFetcher } from './useFetcher'; +import { useFetcher } from './use_fetcher'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, @@ -23,7 +23,7 @@ function getEnvironmentOptions(environments: string[]) { return [ENVIRONMENT_ALL, ...environmentOptions]; } -export function useEnvironments({ +export function useEnvironmentsFetcher({ serviceName, start, end, diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx new file mode 100644 index 0000000000000..1c17be884ebf5 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -0,0 +1,40 @@ +/* + * 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 { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useFetcher } from './use_fetcher'; + +export function useErrorGroupDistributionFetcher({ + serviceName, + groupId, +}: { + serviceName: string; + groupId: string | undefined; +}) { + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { data } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', + params: { + path: { serviceName }, + query: { + start, + end, + groupId, + uiFilters: JSON.stringify(uiFilters), + }, + }, + }); + } + }, + [serviceName, start, end, groupId, uiFilters] + ); + + return { errorDistributionData: data }; +} diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx index e837851828d94..e6f3b71af8a85 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx @@ -7,8 +7,8 @@ import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { delay } from '../utils/testHelpers'; -import { useFetcher } from './useFetcher'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; +import { useFetcher } from './use_fetcher'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; const wrapper = MockApmPluginContextWrapper; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/hooks/useFetcher.test.tsx rename to x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index 59dd9455c724c..9b4ad6bc9bb51 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -6,9 +6,9 @@ import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; import { delay } from '../utils/testHelpers'; -import { FetcherResult, useFetcher } from './useFetcher'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; -import { ApmPluginContextValue } from '../context/ApmPluginContext'; +import { FetcherResult, useFetcher } from './use_fetcher'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; +import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; // Wrap the hook with a provider so it can useApmPluginContext const wrapper = MockApmPluginContextWrapper; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx similarity index 98% rename from x-pack/plugins/apm/public/hooks/useFetcher.tsx rename to x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 6add0e8a2b480..a9a4871dc8707 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { APMClient, callApmApi } from '../services/rest/createCallApmApi'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; export enum FETCH_STATUS { LOADING = 'loading', diff --git a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts rename to x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 7a54c6ffc6dbe..c888c51589563 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -7,22 +7,23 @@ import { useParams } from 'react-router-dom'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetricsChartsByAgentAPIResponse } from '../../server/lib/metrics/get_metrics_chart_data_by_agent'; -import { useUiFilters } from '../context/UrlParamsContext'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; +import { useFetcher } from './use_fetcher'; const INITIAL_DATA: MetricsChartsByAgentAPIResponse = { charts: [], }; -export function useServiceMetricCharts( - urlParams: IUrlParams, - agentName?: string, - serviceNodeName?: string -) { +export function useServiceMetricChartsFetcher({ + serviceNodeName, +}: { + serviceNodeName: string | undefined; +}) { + const { urlParams, uiFilters } = useUrlParams(); + const { agentName } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end } = urlParams; - const uiFilters = useUiFilters(urlParams); const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && agentName) { diff --git a/x-pack/plugins/apm/public/hooks/useTheme.tsx b/x-pack/plugins/apm/public/hooks/use_theme.tsx similarity index 100% rename from x-pack/plugins/apm/public/hooks/useTheme.tsx rename to x-pack/plugins/apm/public/hooks/use_theme.tsx diff --git a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts b/x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts similarity index 82% rename from x-pack/plugins/apm/public/hooks/useTransactionCharts.ts rename to x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts index c790ac57edc3b..f5105e38b985e 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts @@ -7,10 +7,10 @@ import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { getTransactionCharts } from '../selectors/chart_selectors'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from './use_fetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; -export function useTransactionCharts() { +export function useTransactionChartsFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams: { transactionType, start, end, transactionName }, @@ -45,8 +45,8 @@ export function useTransactionCharts() { ); return { - data: memoizedData, - status, - error, + transactionChartsData: memoizedData, + transactionChartsStatus: status, + transactionChartsError: error, }; } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts similarity index 89% rename from x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts rename to x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 9cbfee37d1253..74222e8ffe038 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -6,12 +6,11 @@ import { flatten, omit, isEmpty } from 'lodash'; import { useHistory, useParams } from 'react-router-dom'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; -import { useUiFilters } from '../context/UrlParamsContext'; +import { useFetcher } from './use_fetcher'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; import { APIReturnType } from '../services/rest/createCallApmApi'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; type APIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups/distribution'>; @@ -21,8 +20,9 @@ const INITIAL_DATA = { bucketSize: 0, }; -export function useTransactionDistribution(urlParams: IUrlParams) { +export function useTransactionDistributionFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); const { start, end, @@ -31,10 +31,8 @@ export function useTransactionDistribution(urlParams: IUrlParams) { traceId, transactionName, } = urlParams; - const uiFilters = useUiFilters(urlParams); const history = useHistory(); - const { data = INITIAL_DATA, status, error } = useFetcher( async (callApmApi) => { if (serviceName && start && end && transactionType && transactionName) { @@ -96,5 +94,9 @@ export function useTransactionDistribution(urlParams: IUrlParams) { [serviceName, start, end, transactionType, transactionName, uiFilters] ); - return { data, status, error }; + return { + distributionData: data, + distributionStatus: status, + distributionError: error, + }; } diff --git a/x-pack/plugins/apm/public/selectors/chart_selectors.ts b/x-pack/plugins/apm/public/selectors/chart_selectors.ts index 2fdcaf9e4e675..37bd04e5d9980 100644 --- a/x-pack/plugins/apm/public/selectors/chart_selectors.ts +++ b/x-pack/plugins/apm/public/selectors/chart_selectors.ts @@ -17,7 +17,7 @@ import { RectCoordinate, TimeSeries, } from '../../typings/timeseries'; -import { IUrlParams } from '../context/UrlParamsContext/types'; +import { IUrlParams } from '../context/url_params_context/types'; import { getEmptySeries } from '../components/shared/charts/helper/get_empty_series'; import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor'; import { asDuration, asTransactionRate } from '../../common/utils/formatters'; diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index 87dfeb95b6326..21c87c18be363 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -24,8 +24,8 @@ import { PromiseReturnType } from '../../../observability/typings/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../server'; import { UIFilters } from '../../typings/ui_filters'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; const originalConsoleWarn = console.warn; // eslint-disable-line no-console /** From 2ad3d2bc53dc5a6f7d57c382f14aade9f191976a Mon Sep 17 00:00:00 2001 From: Chris Roberson <chrisronline@gmail.com> Date: Wed, 2 Dec 2020 07:23:50 -0500 Subject: [PATCH 32/37] Make alert status fetching more resilient (#84676) --- .../public/views/base_controller.js | 6 +-- .../lib/cluster/get_clusters_from_request.js | 40 ++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js index 0eb40c8dd5963..62c15f0913569 100644 --- a/x-pack/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_controller.js @@ -163,12 +163,12 @@ export class MonitoringViewBaseController { if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { promises.push(updateSetupModeData()); } - this.updateDataPromise = new PromiseWithCancel(Promise.all(promises)); + this.updateDataPromise = new PromiseWithCancel(Promise.allSettled(promises)); return this.updateDataPromise.promise().then(([pageData, alerts]) => { $scope.$apply(() => { this._isDataInitialized = true; // render will replace loading screen with the react component - $scope.pageData = this.data = pageData; // update the view's data with the fetch result - $scope.alerts = this.alerts = alerts; + $scope.pageData = this.data = pageData.value; // update the view's data with the fetch result + $scope.alerts = this.alerts = alerts.value || {}; }); }); }; 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 ddc33a4b93730..b676abd3de2dd 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 @@ -151,20 +151,32 @@ export async function getClustersFromRequest( 'production' ); if (prodLicenseInfo.clusterAlerts.enabled) { - cluster.alerts = { - list: await fetchStatus( - alertsClient, - req.server.plugins.monitoring.info, - undefined, - cluster.cluster_uuid, - start, - end, - [] - ), - alertsMeta: { - enabled: true, - }, - }; + try { + cluster.alerts = { + list: await fetchStatus( + alertsClient, + req.server.plugins.monitoring.info, + undefined, + cluster.cluster_uuid, + start, + end, + [] + ), + alertsMeta: { + enabled: true, + }, + }; + } catch (err) { + req.logger.warn( + `Unable to fetch alert status because '${err.message}'. Alerts may not properly show up in the UI.` + ); + cluster.alerts = { + list: {}, + alertsMeta: { + enabled: true, + }, + }; + } continue; } From 32200af4e93368a020549417e5ff2448275ec12b Mon Sep 17 00:00:00 2001 From: Bohdan Tsymbala <bohdan.tsymbala@elastic.co> Date: Wed, 2 Dec 2020 14:47:56 +0100 Subject: [PATCH 33/37] Changed the translation text for the description text in the antivirus registration form (#84626) * Changed the text for the description text in the antivirus registration form. Moved the form component to components folder and extracted translations into constants to make code more readable. * Extracted EventsForm to reduce duplication among events forms. --- .../antivirus_registration_form}/index.tsx | 49 +++-- .../view/components/events_form/index.tsx | 87 ++++++++ .../pages/policy/view/policy_details.tsx | 2 +- .../view/policy_forms/events/checkbox.tsx | 55 ----- .../policy/view/policy_forms/events/linux.tsx | 129 ++++------- .../policy/view/policy_forms/events/mac.tsx | 125 ++++------- .../view/policy_forms/events/translations.ts | 28 --- .../view/policy_forms/events/windows.tsx | 205 +++++++----------- 8 files changed, 281 insertions(+), 399 deletions(-) rename x-pack/plugins/security_solution/public/management/pages/policy/view/{policy_forms/antivirus_registration => components/antivirus_registration_form}/index.tsx (60%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/antivirus_registration/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx similarity index 60% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/antivirus_registration/index.tsx rename to x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx index 21fe14df81dd2..072f588663c5a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/antivirus_registration/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx @@ -14,6 +14,29 @@ import { isAntivirusRegistrationEnabled } from '../../../store/policy_details/se import { usePolicyDetailsSelector } from '../../policy_hooks'; import { ConfigForm } from '../../components/config_form'; +const TRANSLATIONS: Readonly<{ [K in 'title' | 'description' | 'label']: string }> = { + title: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type', + { + defaultMessage: 'Register as anti-virus', + } + ), + description: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation', + { + defaultMessage: + 'Toggle on to register Elastic as an official Anti-Virus solution for Windows OS. ' + + 'This will also disable Windows Defender.', + } + ), + label: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle', + { + defaultMessage: 'Register as anti-virus', + } + ), +}; + export const AntivirusRegistrationForm = memo(() => { const antivirusRegistrationEnabled = usePolicyDetailsSelector(isAntivirusRegistrationEnabled); const dispatch = useDispatch(); @@ -30,31 +53,11 @@ export const AntivirusRegistrationForm = memo(() => { ); return ( - <ConfigForm - type={i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type', - { - defaultMessage: 'Register as anti-virus', - } - )} - supportedOss={[OperatingSystem.WINDOWS]} - > - <EuiText size="s"> - {i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation', - { - defaultMessage: 'Switch the toggle to on to register Elastic anti-virus', - } - )} - </EuiText> + <ConfigForm type={TRANSLATIONS.title} supportedOss={[OperatingSystem.WINDOWS]}> + <EuiText size="s">{TRANSLATIONS.description}</EuiText> <EuiSpacer size="s" /> <EuiSwitch - label={i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle', - { - defaultMessage: 'Register as anti-virus', - } - )} + label={TRANSLATIONS.label} checked={antivirusRegistrationEnabled} onChange={handleSwitchChange} /> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx new file mode 100644 index 0000000000000..7cdf54316e4e2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx @@ -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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCheckbox, EuiSpacer, EuiText, htmlIdGenerator } from '@elastic/eui'; +import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; +import { OS } from '../../../types'; +import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; + +const OPERATING_SYSTEM_TO_TEST_SUBJ: { [K in OperatingSystem]: string } = { + [OperatingSystem.WINDOWS]: 'Windows', + [OperatingSystem.LINUX]: 'Linux', + [OperatingSystem.MAC]: 'Mac', +}; + +interface OperatingSystemToOsMap { + [OperatingSystem.WINDOWS]: OS.windows; + [OperatingSystem.LINUX]: OS.linux; + [OperatingSystem.MAC]: OS.mac; +} + +export type ProtectionField< + T extends OperatingSystem +> = keyof UIPolicyConfig[OperatingSystemToOsMap[T]]['events']; + +export type EventFormSelection<T extends OperatingSystem> = { [K in ProtectionField<T>]: boolean }; + +export interface EventFormOption<T extends OperatingSystem> { + name: string; + protectionField: ProtectionField<T>; +} + +export interface EventsFormProps<T extends OperatingSystem> { + os: T; + options: ReadonlyArray<EventFormOption<T>>; + selection: EventFormSelection<T>; + onValueSelection: (value: ProtectionField<T>, selected: boolean) => void; +} + +const countSelected = <T extends OperatingSystem>(selection: EventFormSelection<T>) => { + return Object.values(selection).filter((value) => value).length; +}; + +export const EventsForm = <T extends OperatingSystem>({ + os, + options, + selection, + onValueSelection, +}: EventsFormProps<T>) => ( + <ConfigForm + type={i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollection', { + defaultMessage: 'Event Collection', + })} + supportedOss={[os]} + rightCorner={ + <EuiText size="s" color="subdued"> + {i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled', { + defaultMessage: '{selected} / {total} event collections enabled', + values: { selected: countSelected(selection), total: options.length }, + })} + </EuiText> + } + > + <ConfigFormHeading> + {i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents', { + defaultMessage: 'Events', + })} + </ConfigFormHeading> + <EuiSpacer size="s" /> + {options.map(({ name, protectionField }) => ( + <EuiCheckbox + key={String(protectionField)} + id={htmlIdGenerator()()} + label={name} + data-test-subj={`policy${OPERATING_SYSTEM_TO_TEST_SUBJ[os]}Event_${protectionField}`} + checked={selection[protectionField]} + onChange={(event) => onValueSelection(protectionField, event.target.checked)} + /> + ))} + </ConfigForm> +); + +EventsForm.displayName = 'EventsForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 9c11bc6f5a4d1..1ce099c494cf0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -36,7 +36,7 @@ import { AgentsSummary } from './agents_summary'; import { VerticalDivider } from './vertical_divider'; import { WindowsEvents, MacEvents, LinuxEvents } from './policy_forms/events'; import { MalwareProtections } from './policy_forms/protections/malware'; -import { AntivirusRegistrationForm } from './policy_forms/antivirus_registration'; +import { AntivirusRegistrationForm } from './components/antivirus_registration_form'; import { useToasts } from '../../../../common/lib/kibana'; import { AppAction } from '../../../../common/store/actions'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx deleted file mode 100644 index 76077831c670b..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback, useMemo } from 'react'; -import { EuiCheckbox, EuiCheckboxProps, htmlIdGenerator } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; -import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { policyConfig } from '../../../store/policy_details/selectors'; -import { PolicyDetailsAction } from '../../../store/policy_details'; -import { UIPolicyConfig } from '../../../../../../../common/endpoint/types'; - -type EventsCheckboxProps = Omit<EuiCheckboxProps, 'id' | 'label' | 'checked' | 'onChange'> & { - name: string; - setter: (config: UIPolicyConfig, checked: boolean) => UIPolicyConfig; - getter: (config: UIPolicyConfig) => boolean; -}; - -export const EventsCheckbox = React.memo(function ({ - name, - setter, - getter, - ...otherProps -}: EventsCheckboxProps) { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const selected = getter(policyDetailsConfig); - const dispatch = useDispatch<(action: PolicyDetailsAction) => void>(); - const checkboxId = useMemo(() => htmlIdGenerator()(), []); - - const handleCheckboxChange = useCallback( - (event: React.ChangeEvent<HTMLInputElement>) => { - if (policyDetailsConfig) { - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: setter(policyDetailsConfig, event.target.checked) }, - }); - } - }, - [dispatch, policyDetailsConfig, setter] - ); - - return ( - <EuiCheckbox - id={checkboxId} - label={name} - checked={selected} - onChange={handleCheckboxChange} - {...otherProps} - /> - ); -}); - -EventsCheckbox.displayName = 'EventsCheckbox'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx index 999e3bac5653a..f9532eaecf701 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx @@ -4,96 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { EventsCheckbox } from './checkbox'; -import { OS } from '../../../types'; +import { useDispatch } from 'react-redux'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { setIn } from '../../../models/policy_details_config'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { selectedLinuxEvents, totalLinuxEvents } from '../../../store/policy_details/selectors'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { getIn, setIn } from '../../../models/policy_details_config'; -import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; -import { - COLLECTIONS_ENABLED_MESSAGE, - EVENTS_FORM_TYPE_LABEL, - EVENTS_HEADING, -} from './translations'; +import { EventFormOption, EventsForm } from '../../components/events_form'; -export const LinuxEvents = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedLinuxEvents); - const total = usePolicyDetailsSelector(totalLinuxEvents); - - const checkboxes = useMemo(() => { - const items: Array<{ - name: string; - os: 'linux'; - protectionField: keyof UIPolicyConfig['linux']['events']; - }> = [ - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file', - { - defaultMessage: 'File', - } - ), - os: OS.linux, - protectionField: 'file', - }, +const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.LINUX>> = [ + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file', { + defaultMessage: 'File', + }), + protectionField: 'file', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process', - { - defaultMessage: 'Process', - } - ), - os: OS.linux, - protectionField: 'process', - }, + defaultMessage: 'Process', + } + ), + protectionField: 'process', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network', - { - defaultMessage: 'Network', - } - ), - os: OS.linux, - protectionField: 'network', - }, - ]; - return ( - <> - <ConfigFormHeading>{EVENTS_HEADING}</ConfigFormHeading> - <EuiSpacer size="s" /> - {items.map((item, index) => { - return ( - <EventsCheckbox - name={item.name} - key={index} - data-test-subj={`policyLinuxEvent_${item.protectionField}`} - setter={(config, checked) => - setIn(config)(item.os)('events')(item.protectionField)(checked) - } - getter={(config) => getIn(config)(item.os)('events')(item.protectionField)} - /> - ); - })} - </> - ); - }, []); + defaultMessage: 'Network', + } + ), + protectionField: 'network', + }, +]; + +export const LinuxEvents = memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch(); return ( - <ConfigForm - type={EVENTS_FORM_TYPE_LABEL} - supportedOss={[OperatingSystem.LINUX]} - dataTestSubj="linuxEventingForm" - rightCorner={ - <EuiText size="s" color="subdued"> - {COLLECTIONS_ENABLED_MESSAGE(selected, total)} - </EuiText> + <EventsForm<OperatingSystem.LINUX> + os={OperatingSystem.LINUX} + selection={policyDetailsConfig.linux.events} + options={OPTIONS} + onValueSelection={(value, selected) => + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: setIn(policyDetailsConfig)('linux')('events')(value)(selected) }, + }) } - > - {checkboxes} - </ConfigForm> + /> ); }); + +LinuxEvents.displayName = 'LinuxEvents'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx index 6e15a3c4cd43b..ac6ae531ba172 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx @@ -4,96 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { EventsCheckbox } from './checkbox'; -import { OS } from '../../../types'; +import { useDispatch } from 'react-redux'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { setIn } from '../../../models/policy_details_config'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { selectedMacEvents, totalMacEvents } from '../../../store/policy_details/selectors'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { getIn, setIn } from '../../../models/policy_details_config'; -import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; -import { - COLLECTIONS_ENABLED_MESSAGE, - EVENTS_FORM_TYPE_LABEL, - EVENTS_HEADING, -} from './translations'; +import { EventFormOption, EventsForm } from '../../components/events_form'; -export const MacEvents = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedMacEvents); - const total = usePolicyDetailsSelector(totalMacEvents); +const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.MAC>> = [ + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file', { + defaultMessage: 'File', + }), + protectionField: 'file', + }, + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.process', { + defaultMessage: 'Process', + }), + protectionField: 'process', + }, + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network', { + defaultMessage: 'Network', + }), + protectionField: 'network', + }, +]; - const checkboxes = useMemo(() => { - const items: Array<{ - name: string; - os: 'mac'; - protectionField: keyof UIPolicyConfig['mac']['events']; - }> = [ - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file', - { - defaultMessage: 'File', - } - ), - os: OS.mac, - protectionField: 'file', - }, - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.process', - { - defaultMessage: 'Process', - } - ), - os: OS.mac, - protectionField: 'process', - }, - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network', - { - defaultMessage: 'Network', - } - ), - os: OS.mac, - protectionField: 'network', - }, - ]; - return ( - <> - <ConfigFormHeading>{EVENTS_HEADING}</ConfigFormHeading> - <EuiSpacer size="s" /> - {items.map((item, index) => { - return ( - <EventsCheckbox - name={item.name} - key={index} - data-test-subj={`policyMacEvent_${item.protectionField}`} - setter={(config, checked) => - setIn(config)(item.os)('events')(item.protectionField)(checked) - } - getter={(config) => getIn(config)(item.os)('events')(item.protectionField)} - /> - ); - })} - </> - ); - }, []); +export const MacEvents = memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch(); return ( - <ConfigForm - type={EVENTS_FORM_TYPE_LABEL} - supportedOss={[OperatingSystem.MAC]} - dataTestSubj="macEventingForm" - rightCorner={ - <EuiText size="s" color="subdued"> - {COLLECTIONS_ENABLED_MESSAGE(selected, total)} - </EuiText> + <EventsForm<OperatingSystem.MAC> + os={OperatingSystem.MAC} + selection={policyDetailsConfig.mac.events} + options={OPTIONS} + onValueSelection={(value, selected) => + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: setIn(policyDetailsConfig)('mac')('events')(value)(selected) }, + }) } - > - {checkboxes} - </ConfigForm> + /> ); }); + +MacEvents.displayName = 'MacEvents'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts deleted file mode 100644 index 3b48b7969a8ce..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const EVENTS_HEADING = i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents', - { - defaultMessage: 'Events', - } -); - -export const EVENTS_FORM_TYPE_LABEL = i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.eventCollection', - { - defaultMessage: 'Event Collection', - } -); - -export const COLLECTIONS_ENABLED_MESSAGE = (selected: number, total: number) => { - return i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled', { - defaultMessage: '{selected} / {total} event collections enabled', - values: { selected, total }, - }); -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx index c381249cf24b9..c99f2a6b72ac3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx @@ -4,142 +4,97 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { EventsCheckbox } from './checkbox'; -import { OS } from '../../../types'; +import { useDispatch } from 'react-redux'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { setIn } from '../../../models/policy_details_config'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { selectedWindowsEvents, totalWindowsEvents } from '../../../store/policy_details/selectors'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { getIn, setIn } from '../../../models/policy_details_config'; -import { - Immutable, - OperatingSystem, - UIPolicyConfig, -} from '../../../../../../../common/endpoint/types'; -import { - COLLECTIONS_ENABLED_MESSAGE, - EVENTS_FORM_TYPE_LABEL, - EVENTS_HEADING, -} from './translations'; +import { EventFormOption, EventsForm } from '../../components/events_form'; -export const WindowsEvents = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedWindowsEvents); - const total = usePolicyDetailsSelector(totalWindowsEvents); - - const checkboxes = useMemo(() => { - const items: Immutable< - Array<{ - name: string; - os: 'windows'; - protectionField: keyof UIPolicyConfig['windows']['events']; - }> - > = [ - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', - { - defaultMessage: 'DLL and Driver Load', - } - ), - os: OS.windows, - protectionField: 'dll_and_driver_load', - }, +const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.WINDOWS>> = [ + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dns', - { - defaultMessage: 'DNS', - } - ), - os: OS.windows, - protectionField: 'dns', - }, + defaultMessage: 'DLL and Driver Load', + } + ), + protectionField: 'dll_and_driver_load', + }, + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dns', { + defaultMessage: 'DNS', + }), + protectionField: 'dns', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.file', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.file', - { - defaultMessage: 'File', - } - ), - os: OS.windows, - protectionField: 'file', - }, + defaultMessage: 'File', + } + ), + protectionField: 'file', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.network', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.network', - { - defaultMessage: 'Network', - } - ), - os: OS.windows, - protectionField: 'network', - }, + defaultMessage: 'Network', + } + ), + protectionField: 'network', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.process', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.process', - { - defaultMessage: 'Process', - } - ), - os: OS.windows, - protectionField: 'process', - }, + defaultMessage: 'Process', + } + ), + protectionField: 'process', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.registry', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.registry', - { - defaultMessage: 'Registry', - } - ), - os: OS.windows, - protectionField: 'registry', - }, + defaultMessage: 'Registry', + } + ), + protectionField: 'registry', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.security', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.security', - { - defaultMessage: 'Security', - } - ), - os: OS.windows, - protectionField: 'security', - }, - ]; - return ( - <> - <ConfigFormHeading>{EVENTS_HEADING}</ConfigFormHeading> - <EuiSpacer size="s" /> - {items.map((item, index) => { - return ( - <EventsCheckbox - name={item.name} - key={index} - data-test-subj={`policyWindowsEvent_${item.protectionField}`} - setter={(config, checked) => - setIn(config)(item.os)('events')(item.protectionField)(checked) - } - getter={(config) => getIn(config)(item.os)('events')(item.protectionField)} - /> - ); - })} - </> - ); - }, []); + defaultMessage: 'Security', + } + ), + protectionField: 'security', + }, +]; + +export const WindowsEvents = memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch(); return ( - <ConfigForm - type={EVENTS_FORM_TYPE_LABEL} - supportedOss={[OperatingSystem.WINDOWS]} - dataTestSubj="windowsEventingForm" - rightCorner={ - <EuiText size="s" color="subdued"> - {COLLECTIONS_ENABLED_MESSAGE(selected, total)} - </EuiText> + <EventsForm<OperatingSystem.WINDOWS> + os={OperatingSystem.WINDOWS} + selection={policyDetailsConfig.windows.events} + options={OPTIONS} + onValueSelection={(value, selected) => + dispatch({ + type: 'userChangedPolicyConfig', + payload: { + policyConfig: setIn(policyDetailsConfig)('windows')('events')(value)(selected), + }, + }) } - > - {checkboxes} - </ConfigForm> + /> ); }); + +WindowsEvents.displayName = 'WindowsEvents'; From c1e7f69ca1bf3e9abe787fb1d2dfa5847a6ec73c Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin <aleh.zasypkin@gmail.com> Date: Wed, 2 Dec 2020 15:00:01 +0100 Subject: [PATCH 34/37] Migrate security routes to a new Elasticsearch client. (#84528) --- .../elasticsearch_client_plugin.ts | 254 ------------------ x-pack/plugins/security/server/plugin.ts | 1 - .../server/routes/api_keys/enabled.test.ts | 165 +++++------- .../server/routes/api_keys/get.test.ts | 27 +- .../security/server/routes/api_keys/get.ts | 10 +- .../server/routes/api_keys/invalidate.test.ts | 41 ++- .../server/routes/api_keys/invalidate.ts | 8 +- .../server/routes/api_keys/privileges.test.ts | 233 +++++----------- .../server/routes/api_keys/privileges.ts | 26 +- .../authorization/privileges/get_builtin.ts | 8 +- .../routes/authorization/roles/delete.test.ts | 30 +-- .../routes/authorization/roles/delete.ts | 4 +- .../routes/authorization/roles/get.test.ts | 30 +-- .../server/routes/authorization/roles/get.ts | 12 +- .../authorization/roles/get_all.test.ts | 27 +- .../routes/authorization/roles/get_all.ts | 10 +- .../routes/authorization/roles/put.test.ts | 182 ++++++------- .../server/routes/authorization/roles/put.ts | 21 +- .../security/server/routes/index.mock.ts | 2 - .../plugins/security/server/routes/index.ts | 9 +- .../server/routes/indices/get_fields.test.ts | 14 +- .../server/routes/indices/get_fields.ts | 16 +- .../server/routes/role_mapping/delete.test.ts | 52 ++-- .../server/routes/role_mapping/delete.ts | 14 +- .../routes/role_mapping/feature_check.test.ts | 92 +++---- .../routes/role_mapping/feature_check.ts | 46 ++-- .../server/routes/role_mapping/get.test.ts | 100 +++---- .../server/routes/role_mapping/get.ts | 12 +- .../server/routes/role_mapping/post.test.ts | 71 +++-- .../server/routes/role_mapping/post.ts | 15 +- .../routes/users/change_password.test.ts | 98 +++---- .../server/routes/users/change_password.ts | 45 ++-- .../server/routes/users/create_or_update.ts | 4 +- .../security/server/routes/users/delete.ts | 8 +- .../security/server/routes/users/get.ts | 12 +- .../security/server/routes/users/get_all.ts | 4 +- 36 files changed, 610 insertions(+), 1093 deletions(-) diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts index 529e8a8aa6e9c..0a43d8dd6973a 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts @@ -22,133 +22,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen }, }); - /** - * Perform a [shield.changePassword](Change the password of a user) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - The username of the user to change the password for - */ - shield.changePassword = ca({ - params: { - refresh: { - type: 'boolean', - }, - }, - urls: [ - { - fmt: '/_security/user/<%=username%>/_password', - req: { - username: { - type: 'string', - required: false, - }, - }, - }, - { - fmt: '/_security/user/_password', - }, - ], - needBody: true, - method: 'POST', - }); - - /** - * Perform a [shield.clearCachedRealms](Clears the internal user caches for specified realms) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.usernames - Comma-separated list of usernames to clear from the cache - * @param {String} params.realms - Comma-separated list of realms to clear - */ - shield.clearCachedRealms = ca({ - params: { - usernames: { - type: 'string', - required: false, - }, - }, - url: { - fmt: '/_security/realm/<%=realms%>/_clear_cache', - req: { - realms: { - type: 'string', - required: true, - }, - }, - }, - method: 'POST', - }); - - /** - * Perform a [shield.clearCachedRoles](Clears the internal caches for specified roles) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.name - Role name - */ - shield.clearCachedRoles = ca({ - params: {}, - url: { - fmt: '/_security/role/<%=name%>/_clear_cache', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - method: 'POST', - }); - - /** - * Perform a [shield.deleteRole](Remove a role from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.name - Role name - */ - shield.deleteRole = ca({ - params: { - refresh: { - type: 'boolean', - }, - }, - url: { - fmt: '/_security/role/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - /** - * Perform a [shield.deleteUser](Remove a user from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - username - */ - shield.deleteUser = ca({ - params: { - refresh: { - type: 'boolean', - }, - }, - url: { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - /** * Perform a [shield.getRole](Retrieve one or more roles from the native shield realm) request * @@ -173,30 +46,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen ], }); - /** - * Perform a [shield.getUser](Retrieve one or more users from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String, String[], Boolean} params.username - A comma-separated list of usernames - */ - shield.getUser = ca({ - params: {}, - urls: [ - { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'list', - required: false, - }, - }, - }, - { - fmt: '/_security/user', - }, - ], - }); - /** * Perform a [shield.putRole](Update or create a role for the native shield realm) request * @@ -249,19 +98,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen method: 'PUT', }); - /** - * Perform a [shield.getUserPrivileges](Retrieve a user's list of privileges) request - * - */ - shield.getUserPrivileges = ca({ - params: {}, - urls: [ - { - fmt: '/_security/user/_privileges', - }, - ], - }); - /** * Asks Elasticsearch to prepare SAML authentication request to be sent to * the 3rd-party SAML identity provider. @@ -489,36 +325,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen }, }); - shield.getBuiltinPrivileges = ca({ - params: {}, - urls: [ - { - fmt: '/_security/privilege/_builtin', - }, - ], - }); - - /** - * Gets API keys in Elasticsearch - * @param {boolean} owner A boolean flag that can be used to query API keys owned by the currently authenticated user. - * Defaults to false. The realm_name or username parameters cannot be specified when this parameter is set to true as - * they are assumed to be the currently authenticated ones. - */ - shield.getAPIKeys = ca({ - method: 'GET', - urls: [ - { - fmt: `/_security/api_key?owner=<%=owner%>`, - req: { - owner: { - type: 'boolean', - required: true, - }, - }, - }, - ], - }); - /** * Creates an API key in Elasticsearch for the current user. * @@ -591,64 +397,4 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen fmt: '/_security/delegate_pki', }, }); - - /** - * Retrieves all configured role mappings. - * - * @returns {{ [roleMappingName]: { enabled: boolean; roles: string[]; rules: Record<string, any>} }} - */ - shield.getRoleMappings = ca({ - method: 'GET', - urls: [ - { - fmt: '/_security/role_mapping', - }, - { - fmt: '/_security/role_mapping/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - ], - }); - - /** - * Saves the specified role mapping. - */ - shield.saveRoleMapping = ca({ - method: 'POST', - needBody: true, - urls: [ - { - fmt: '/_security/role_mapping/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - ], - }); - - /** - * Deletes the specified role mapping. - */ - shield.deleteRoleMapping = ca({ - method: 'DELETE', - urls: [ - { - fmt: '/_security/role_mapping/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - ], - }); } diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 17f2480026cc7..d6fe1356ce145 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -230,7 +230,6 @@ export class Plugin { basePath: core.http.basePath, httpResources: core.http.resources, logger: this.initializerContext.logger.get('routes'), - clusterClient, config, authc: this.authc, authz, diff --git a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts index 7968402cd2176..3a22b9fe003a1 100644 --- a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts @@ -4,115 +4,96 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LicenseCheck } from '../../../../licensing/server'; +import Boom from '@hapi/boom'; +import { + kibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from '../../../../../../src/core/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; -import Boom from '@hapi/boom'; -import { defineEnabledApiKeysRoutes } from './enabled'; -import { APIKeys } from '../../authentication/api_keys'; -interface TestOptions { - licenseCheckResult?: LicenseCheck; - apiResponse?: () => Promise<unknown>; - asserts: { statusCode: number; result?: Record<string, any> }; -} +import { defineEnabledApiKeysRoutes } from './enabled'; +import { Authentication } from '../../authentication'; describe('API keys enabled', () => { - const enabledApiKeysTest = ( - description: string, - { licenseCheckResult = { state: 'valid' }, apiResponse, asserts }: TestOptions - ) => { - test(description, async () => { - const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const apiKeys = new APIKeys({ - logger: mockRouteDefinitionParams.logger, - clusterClient: mockRouteDefinitionParams.clusterClient, - license: mockRouteDefinitionParams.license, - }); - - mockRouteDefinitionParams.authc.areAPIKeysEnabled.mockImplementation(() => - apiKeys.areAPIKeysEnabled() + function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } + ) { + return ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as RequestHandlerContext; + } + + let routeHandler: RequestHandler<any, any, any, any>; + let authc: jest.Mocked<Authentication>; + beforeEach(() => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + authc = mockRouteDefinitionParams.authc; + + defineEnabledApiKeysRoutes(mockRouteDefinitionParams); + + const [, apiKeyRouteHandler] = mockRouteDefinitionParams.router.get.mock.calls.find( + ([{ path }]) => path === '/internal/security/api_key/_enabled' + )!; + routeHandler = apiKeyRouteHandler; + }); + + describe('failure', () => { + test('returns result of license checker', async () => { + const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); + const response = await routeHandler( + mockContext, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory ); - if (apiResponse) { - mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementation(apiResponse); - } - - defineEnabledApiKeysRoutes(mockRouteDefinitionParams); - const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; - - const headers = { authorization: 'foo' }; - const mockRequest = httpServerMock.createKibanaRequest({ - method: 'get', - path: '/internal/security/api_key/_enabled', - headers, - }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; - - const response = await handler(mockContext, mockRequest, kibanaResponseFactory); - expect(response.status).toBe(asserts.statusCode); - expect(response.payload).toEqual(asserts.result); - - if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.invalidateAPIKey', - { - body: { - id: expect.any(String), - }, - } - ); - } else { - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); - } + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); - }; - describe('failure', () => { - enabledApiKeysTest('returns result of license checker', { - licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, - asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, - }); + test('returns error from cluster client', async () => { + const error = Boom.notAcceptable('test not acceptable message'); + authc.areAPIKeysEnabled.mockRejectedValue(error); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); - const error = Boom.notAcceptable('test not acceptable message'); - enabledApiKeysTest('returns error from cluster client', { - apiResponse: async () => { - throw error; - }, - asserts: { statusCode: 406, result: error }, + expect(response.status).toBe(406); + expect(response.payload).toEqual(error); }); }); describe('success', () => { - enabledApiKeysTest('returns true if API Keys are enabled', { - apiResponse: async () => ({}), - asserts: { - statusCode: 200, - result: { - apiKeysEnabled: true, - }, - }, + test('returns true if API Keys are enabled', async () => { + authc.areAPIKeysEnabled.mockResolvedValue(true); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ apiKeysEnabled: true }); }); - enabledApiKeysTest('returns false if API Keys are disabled', { - apiResponse: async () => { - const error = new Error(); - (error as any).body = { - error: { 'disabled.feature': 'api_keys' }, - }; - throw error; - }, - asserts: { - statusCode: 200, - result: { - apiKeysEnabled: false, - }, - }, + + test('returns false if API Keys are disabled', async () => { + authc.areAPIKeysEnabled.mockResolvedValue(false); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ apiKeysEnabled: false }); }); }); }); diff --git a/x-pack/plugins/security/server/routes/api_keys/get.test.ts b/x-pack/plugins/security/server/routes/api_keys/get.test.ts index cb991fb2f5aac..cc9e9a68e6e36 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.test.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; import { defineGetApiKeysRoutes } from './get'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { httpServerMock, coreMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; import Boom from '@hapi/boom'; @@ -26,11 +26,15 @@ describe('Get API keys', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.getApiKey.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineGetApiKeysRoutes(mockRouteDefinitionParams); @@ -43,22 +47,15 @@ describe('Get API keys', () => { query: { isAdmin: isAdmin.toString() }, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.getAPIKeys', - { owner: !isAdmin } - ); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getApiKey + ).toHaveBeenCalledWith({ owner: !isAdmin }); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); diff --git a/x-pack/plugins/security/server/routes/api_keys/get.ts b/x-pack/plugins/security/server/routes/api_keys/get.ts index 6e98b4b098405..b0c6ec090a0e5 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.ts @@ -10,7 +10,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetApiKeysRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/api_key', @@ -28,11 +28,11 @@ export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitio createLicensedRouteHandler(async (context, request, response) => { try { const isAdmin = request.query.isAdmin === 'true'; - const { api_keys: apiKeys } = (await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getAPIKeys', { owner: !isAdmin })) as { api_keys: ApiKey[] }; + const apiResponse = await context.core.elasticsearch.client.asCurrentUser.security.getApiKey<{ + api_keys: ApiKey[]; + }>({ owner: !isAdmin }); - const validKeys = apiKeys.filter(({ invalidated }) => !invalidated); + const validKeys = apiResponse.body.api_keys.filter(({ invalidated }) => !invalidated); return response.ok({ body: { apiKeys: validKeys } }); } catch (error) { diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts index 88e52f735395d..9ac41fdfa7483 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts @@ -6,18 +6,18 @@ import Boom from '@hapi/boom'; import { Type } from '@kbn/config-schema'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; import { defineInvalidateApiKeysRoutes } from './invalidate'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; interface TestOptions { licenseCheckResult?: LicenseCheck; apiResponses?: Array<() => Promise<unknown>>; payload?: Record<string, any>; - asserts: { statusCode: number; result?: Record<string, any>; apiArguments?: unknown[][] }; + asserts: { statusCode: number; result?: Record<string, any>; apiArguments?: unknown[] }; } describe('Invalidate API keys', () => { @@ -27,10 +27,15 @@ describe('Invalidate API keys', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; + for (const apiResponse of apiResponses) { - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey.mockImplementationOnce( + (async () => ({ body: await apiResponse() })) as any + ); } defineInvalidateApiKeysRoutes(mockRouteDefinitionParams); @@ -43,9 +48,6 @@ describe('Invalidate API keys', () => { body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); @@ -53,13 +55,10 @@ describe('Invalidate API keys', () => { if (Array.isArray(asserts.apiArguments)) { for (const apiArguments of asserts.apiArguments) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith( - mockRequest - ); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey + ).toHaveBeenCalledWith(apiArguments); } - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); @@ -128,7 +127,7 @@ describe('Invalidate API keys', () => { isAdmin: true, }, asserts: { - apiArguments: [['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }]], + apiArguments: [{ body: { id: 'si8If24B1bKsmSLTAhJV' } }], statusCode: 200, result: { itemsInvalidated: [], @@ -152,7 +151,7 @@ describe('Invalidate API keys', () => { isAdmin: true, }, asserts: { - apiArguments: [['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }]], + apiArguments: [{ body: { id: 'si8If24B1bKsmSLTAhJV' } }], statusCode: 200, result: { itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], @@ -168,9 +167,7 @@ describe('Invalidate API keys', () => { isAdmin: false, }, asserts: { - apiArguments: [ - ['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV', owner: true } }], - ], + apiArguments: [{ body: { id: 'si8If24B1bKsmSLTAhJV', owner: true } }], statusCode: 200, result: { itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], @@ -195,8 +192,8 @@ describe('Invalidate API keys', () => { }, asserts: { apiArguments: [ - ['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }], - ['shield.invalidateAPIKey', { body: { id: 'ab8If24B1bKsmSLTAhNC' } }], + { body: { id: 'si8If24B1bKsmSLTAhJV' } }, + { body: { id: 'ab8If24B1bKsmSLTAhNC' } }, ], statusCode: 200, result: { diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts index dd472c0b60cbc..3977954197007 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts @@ -15,7 +15,7 @@ interface ResponseType { errors: Array<Pick<ApiKey, 'id' | 'name'> & { error: Error }>; } -export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineInvalidateApiKeysRoutes({ router }: RouteDefinitionParams) { router.post( { path: '/internal/security/api_key/invalidate', @@ -28,8 +28,6 @@ export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDe }, createLicensedRouteHandler(async (context, request, response) => { try { - const scopedClusterClient = clusterClient.asScoped(request); - // Invalidate all API keys in parallel. const invalidationResult = ( await Promise.all( @@ -41,7 +39,9 @@ export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDe } // Send the request to invalidate the API key and return an error if it could not be deleted. - await scopedClusterClient.callAsCurrentUser('shield.invalidateAPIKey', { body }); + await context.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey({ + body, + }); return { key, error: undefined }; } catch (error) { return { key, error: wrapError(error) }; diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts index ecc3d32e20aec..b06d1329dc1db 100644 --- a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts @@ -6,23 +6,17 @@ import Boom from '@hapi/boom'; import { LicenseCheck } from '../../../../licensing/server'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; import { defineCheckPrivilegesRoutes } from './privileges'; -import { APIKeys } from '../../authentication/api_keys'; interface TestOptions { licenseCheckResult?: LicenseCheck; - callAsInternalUserResponses?: Array<() => Promise<unknown>>; - callAsCurrentUserResponses?: Array<() => Promise<unknown>>; - asserts: { - statusCode: number; - result?: Record<string, any>; - callAsInternalUserAPIArguments?: unknown[][]; - callAsCurrentUserAPIArguments?: unknown[][]; - }; + areAPIKeysEnabled?: boolean; + apiResponse?: () => Promise<unknown>; + asserts: { statusCode: number; result?: Record<string, any>; apiArguments?: unknown }; } describe('Check API keys privileges', () => { @@ -30,32 +24,23 @@ describe('Check API keys privileges', () => { description: string, { licenseCheckResult = { state: 'valid' }, - callAsInternalUserResponses = [], - callAsCurrentUserResponses = [], + areAPIKeysEnabled = true, + apiResponse, asserts, }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const apiKeys = new APIKeys({ - logger: mockRouteDefinitionParams.logger, - clusterClient: mockRouteDefinitionParams.clusterClient, - license: mockRouteDefinitionParams.license, - }); - - mockRouteDefinitionParams.authc.areAPIKeysEnabled.mockImplementation(() => - apiKeys.areAPIKeysEnabled() - ); + mockRouteDefinitionParams.authc.areAPIKeysEnabled.mockResolvedValue(areAPIKeysEnabled); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - for (const apiResponse of callAsCurrentUserResponses) { - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); - } - for (const apiResponse of callAsInternalUserResponses) { - mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementationOnce( - apiResponse + if (apiResponse) { + mockContext.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockImplementation( + (async () => ({ body: await apiResponse() })) as any ); } @@ -68,33 +53,15 @@ describe('Check API keys privileges', () => { path: '/internal/security/api_key/privileges', headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); - if (Array.isArray(asserts.callAsCurrentUserAPIArguments)) { - for (const apiArguments of asserts.callAsCurrentUserAPIArguments) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith( - mockRequest - ); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); - } - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); - } - - if (Array.isArray(asserts.callAsInternalUserAPIArguments)) { - for (const apiArguments of asserts.callAsInternalUserAPIArguments) { - expect(mockRouteDefinitionParams.clusterClient.callAsInternalUser).toHaveBeenCalledWith( - ...apiArguments - ); - } - } else { - expect(mockRouteDefinitionParams.clusterClient.callAsInternalUser).not.toHaveBeenCalled(); + if (asserts.apiArguments) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.hasPrivileges + ).toHaveBeenCalledWith(asserts.apiArguments); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); @@ -109,22 +76,13 @@ describe('Check API keys privileges', () => { const error = Boom.notAcceptable('test not acceptable message'); getPrivilegesTest('returns error from cluster client', { - callAsCurrentUserResponses: [ - async () => { - throw error; - }, - ], - callAsInternalUserResponses: [async () => {}], + apiResponse: async () => { + throw error; + }, asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 406, result: error, }, @@ -133,40 +91,17 @@ describe('Check API keys privileges', () => { describe('success', () => { getPrivilegesTest('returns areApiKeysEnabled and isAdmin', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: false }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [ - async () => ({ - api_keys: [ - { - id: 'si8If24B1bKsmSLTAhJV', - name: 'my-api-key', - creation: 1574089261632, - expiration: 1574175661632, - invalidated: false, - username: 'elastic', - realm: 'reserved', - }, - ], - }), - ], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: false }, + index: {}, + application: {}, + }), asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: true, isAdmin: true, canManage: true }, }, @@ -175,36 +110,18 @@ describe('Check API keys privileges', () => { getPrivilegesTest( 'returns areApiKeysEnabled=false when API Keys are disabled in Elasticsearch', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: true }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [ - async () => { - const error = new Error(); - (error as any).body = { - error: { - 'disabled.feature': 'api_keys', - }, - }; - throw error; - }, - ], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: true }, + index: {}, + application: {}, + }), + areAPIKeysEnabled: false, asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: false, isAdmin: true, canManage: true }, }, @@ -212,52 +129,34 @@ describe('Check API keys privileges', () => { ); getPrivilegesTest('returns isAdmin=false when user has insufficient privileges', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: false }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [async () => ({})], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: false }, + index: {}, + application: {}, + }), asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: true, isAdmin: false, canManage: false }, }, }); getPrivilegesTest('returns canManage=true when user can manage their own API Keys', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: true }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [async () => ({})], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: true }, + index: {}, + application: {}, + }), asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: true, isAdmin: false, canManage: true }, }, diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.ts index 9cccb96752772..dd5d81060c7e5 100644 --- a/x-pack/plugins/security/server/routes/api_keys/privileges.ts +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.ts @@ -8,11 +8,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineCheckPrivilegesRoutes({ - router, - clusterClient, - authc, -}: RouteDefinitionParams) { +export function defineCheckPrivilegesRoutes({ router, authc }: RouteDefinitionParams) { router.get( { path: '/internal/security/api_key/privileges', @@ -20,19 +16,25 @@ export function defineCheckPrivilegesRoutes({ }, createLicensedRouteHandler(async (context, request, response) => { try { - const scopedClusterClient = clusterClient.asScoped(request); - const [ { - cluster: { - manage_security: manageSecurity, - manage_api_key: manageApiKey, - manage_own_api_key: manageOwnApiKey, + body: { + cluster: { + manage_security: manageSecurity, + manage_api_key: manageApiKey, + manage_own_api_key: manageOwnApiKey, + }, }, }, areApiKeysEnabled, ] = await Promise.all([ - scopedClusterClient.callAsCurrentUser('shield.hasPrivileges', { + context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges<{ + cluster: { + manage_security: boolean; + manage_api_key: boolean; + manage_own_api_key: boolean; + }; + }>({ body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, }), authc.areAPIKeysEnabled(), diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts index 08cd3ba487b0b..39e6a4838d34d 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts @@ -7,13 +7,13 @@ import { BuiltinESPrivileges } from '../../../../common/model'; import { RouteDefinitionParams } from '../..'; -export function defineGetBuiltinPrivilegesRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetBuiltinPrivilegesRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/esPrivileges/builtin', validate: false }, async (context, request, response) => { - const privileges: BuiltinESPrivileges = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getBuiltinPrivileges'); + const { + body: privileges, + } = await context.core.elasticsearch.client.asCurrentUser.security.getBuiltinPrivileges<BuiltinESPrivileges>(); // Exclude the `none` privilege, as it doesn't make sense as an option within the Kibana UI privileges.cluster = privileges.cluster.filter((privilege) => privilege !== 'none'); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts index 9f5ec635f56cd..5143b727fcb4e 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts @@ -5,14 +5,11 @@ */ import Boom from '@hapi/boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineDeleteRolesRoutes } from './delete'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; interface TestOptions { @@ -29,11 +26,15 @@ describe('DELETE role', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRole.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineDeleteRolesRoutes(mockRouteDefinitionParams); @@ -46,22 +47,15 @@ describe('DELETE role', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.deleteRole', - { name } - ); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRole + ).toHaveBeenCalledWith({ name }); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts index eb56143288747..b877aaf6abd77 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts @@ -9,7 +9,7 @@ import { RouteDefinitionParams } from '../../index'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; -export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineDeleteRolesRoutes({ router }: RouteDefinitionParams) { router.delete( { path: '/api/security/role/{name}', @@ -19,7 +19,7 @@ export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefiniti }, createLicensedRouteHandler(async (context, request, response) => { try { - await clusterClient.asScoped(request).callAsCurrentUser('shield.deleteRole', { + await context.core.elasticsearch.client.asCurrentUser.security.deleteRole({ name: request.params.name, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index b25b13b9fc04a..a6090ee405329 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from '@hapi/boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetRolesRoutes } from './get'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; const application = 'kibana-.kibana'; @@ -32,11 +29,15 @@ describe('GET role', () => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); mockRouteDefinitionParams.authz.applicationName = application; + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineGetRolesRoutes(mockRouteDefinitionParams); @@ -49,22 +50,17 @@ describe('GET role', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.getRole', { - name, - }); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalledWith({ name }); } + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); }; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index bf1140e2e6fd1..ce4a622d30e61 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -8,9 +8,9 @@ import { schema } from '@kbn/config-schema'; import { RouteDefinitionParams } from '../..'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; -import { transformElasticsearchRoleToRole } from './model'; +import { ElasticsearchRole, transformElasticsearchRoleToRole } from './model'; -export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { +export function defineGetRolesRoutes({ router, authz }: RouteDefinitionParams) { router.get( { path: '/api/security/role/{name}', @@ -20,9 +20,11 @@ export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefi }, createLicensedRouteHandler(async (context, request, response) => { try { - const elasticsearchRoles = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRole', { name: request.params.name }); + const { + body: elasticsearchRoles, + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record<string, ElasticsearchRole> + >({ name: request.params.name }); const elasticsearchRole = elasticsearchRoles[request.params.name]; if (elasticsearchRole) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 30e0c52c4c443..b3a855b2e0ae7 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from '@hapi/boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetAllRolesRoutes } from './get_all'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; const application = 'kibana-.kibana'; @@ -32,11 +29,15 @@ describe('GET all roles', () => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); mockRouteDefinitionParams.authz.applicationName = application; + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineGetAllRolesRoutes(mockRouteDefinitionParams); @@ -48,19 +49,15 @@ describe('GET all roles', () => { path: '/api/security/role', headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.getRole'); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalled(); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index 24be6c60e4b12..21521dd6dbae3 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -9,14 +9,16 @@ import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { ElasticsearchRole, transformElasticsearchRoleToRole } from './model'; -export function defineGetAllRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { +export function defineGetAllRolesRoutes({ router, authz }: RouteDefinitionParams) { router.get( { path: '/api/security/role', validate: false }, createLicensedRouteHandler(async (context, request, response) => { try { - const elasticsearchRoles = (await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRole')) as Record<string, ElasticsearchRole>; + const { + body: elasticsearchRoles, + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record<string, ElasticsearchRole> + >(); // Transform elasticsearch roles into Kibana roles and return in a list sorted by the role name. return response.ok({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 811ea080b4316..779e1a7fab177 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -5,15 +5,12 @@ */ import { Type } from '@kbn/config-schema'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { GLOBAL_RESOURCE } from '../../../../common/constants'; import { definePutRolesRoutes } from './put'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; import { KibanaFeature } from '../../../../../features/server'; import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock'; @@ -47,35 +44,43 @@ const privilegeMap = { interface TestOptions { name: string; licenseCheckResult?: LicenseCheck; - apiResponses?: Array<() => Promise<unknown>>; + apiResponses?: { + get: () => Promise<unknown>; + put: () => Promise<unknown>; + }; payload?: Record<string, any>; asserts: { statusCode: number; result?: Record<string, any>; - apiArguments?: unknown[][]; + apiArguments?: { get: unknown[]; put: unknown[] }; recordSubFeaturePrivilegeUsage?: boolean; }; } const putRoleTest = ( description: string, - { - name, - payload, - licenseCheckResult = { state: 'valid' }, - apiResponses = [], - asserts, - }: TestOptions + { name, payload, licenseCheckResult = { state: 'valid' }, apiResponses, asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - for (const apiResponse of apiResponses) { - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; + + if (apiResponses?.get) { + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole.mockImplementationOnce( + (async () => ({ body: await apiResponses?.get() })) as any + ); + } + + if (apiResponses?.put) { + mockContext.core.elasticsearch.client.asCurrentUser.security.putRole.mockImplementationOnce( + (async () => ({ body: await apiResponses?.put() })) as any + ); } mockRouteDefinitionParams.getFeatureUsageService.mockReturnValue( @@ -131,21 +136,20 @@ const putRoleTest = ( body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); - if (Array.isArray(asserts.apiArguments)) { - for (const apiArguments of asserts.apiArguments) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); - } - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + if (asserts.apiArguments?.get) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalledWith(...asserts.apiArguments?.get); + } + if (asserts.apiArguments?.put) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.putRole + ).toHaveBeenCalledWith(...asserts.apiArguments?.put); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); @@ -208,12 +212,11 @@ describe('PUT role', () => { putRoleTest(`creates empty role`, { name: 'foo-role', payload: {}, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -224,7 +227,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -239,12 +242,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -261,7 +263,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -279,12 +281,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -301,7 +302,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -317,12 +318,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -339,7 +339,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -383,12 +383,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -426,7 +425,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -473,8 +472,8 @@ describe('PUT role', () => { }, ], }, - apiResponses: [ - async () => ({ + apiResponses: { + get: async () => ({ 'foo-role': { metadata: { bar: 'old-metadata', @@ -504,13 +503,12 @@ describe('PUT role', () => { ], }, }), - async () => {}, - ], + put: async () => {}, + }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -548,7 +546,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -577,8 +575,8 @@ describe('PUT role', () => { }, ], }, - apiResponses: [ - async () => ({ + apiResponses: { + get: async () => ({ 'foo-role': { metadata: { bar: 'old-metadata', @@ -613,13 +611,12 @@ describe('PUT role', () => { ], }, }), - async () => {}, - ], + put: async () => {}, + }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -652,7 +649,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -670,13 +667,12 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { recordSubFeaturePrivilegeUsage: true, - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -694,7 +690,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -712,13 +708,12 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { recordSubFeaturePrivilegeUsage: false, - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -736,7 +731,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -754,13 +749,12 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { recordSubFeaturePrivilegeUsage: false, - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -778,7 +772,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index cdedc9ac8a5eb..26c61b4ced15f 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -42,7 +42,6 @@ const roleGrantsSubFeaturePrivileges = ( export function definePutRolesRoutes({ router, authz, - clusterClient, getFeatures, getFeatureUsageService, }: RouteDefinitionParams) { @@ -64,12 +63,11 @@ export function definePutRolesRoutes({ const { name } = request.params; try { - const rawRoles: Record<string, ElasticsearchRole> = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRole', { - name: request.params.name, - ignore: [404], - }); + const { + body: rawRoles, + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record<string, ElasticsearchRole> + >({ name: request.params.name }, { ignore: [404] }); const body = transformPutPayloadToElasticsearchRole( request.body, @@ -77,11 +75,12 @@ export function definePutRolesRoutes({ rawRoles[name] ? rawRoles[name].applications : [] ); - const [features] = await Promise.all<KibanaFeature[]>([ + const [features] = await Promise.all([ getFeatures(), - clusterClient - .asScoped(request) - .callAsCurrentUser('shield.putRole', { name: request.params.name, body }), + context.core.elasticsearch.client.asCurrentUser.security.putRole({ + name: request.params.name, + body, + }), ]); if (roleGrantsSubFeaturePrivileges(features, request.body)) { diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index fab4a71df0cb0..1df499d981632 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -5,7 +5,6 @@ */ import { - elasticsearchServiceMock, httpServiceMock, loggingSystemMock, httpResourcesMock, @@ -25,7 +24,6 @@ export const routeDefinitionParamsMock = { basePath: httpServiceMock.createBasePath(), csp: httpServiceMock.createSetupContract().csp, logger: loggingSystemMock.create().get(), - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get(), { isTLSEnabled: false, }), diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 079c9e8ab9ce7..db71b04b3e6f0 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -5,13 +5,7 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { KibanaFeature } from '../../../features/server'; -import { - HttpResources, - IBasePath, - ILegacyClusterClient, - IRouter, - Logger, -} from '../../../../../src/core/server'; +import { HttpResources, IBasePath, IRouter, Logger } from '../../../../../src/core/server'; import { SecurityLicense } from '../../common/licensing'; import { Authentication } from '../authentication'; import { AuthorizationServiceSetup } from '../authorization'; @@ -36,7 +30,6 @@ export interface RouteDefinitionParams { basePath: IBasePath; httpResources: HttpResources; logger: Logger; - clusterClient: ILegacyClusterClient; config: ConfigType; authc: Authentication; authz: AuthorizationServiceSetup; diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.test.ts b/x-pack/plugins/security/server/routes/indices/get_fields.test.ts index 4c6182e99431d..6d3de11249a16 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.test.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { httpServerMock, elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; +import { httpServerMock, coreMock } from '../../../../../../src/core/server/mocks'; import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { routeDefinitionParamsMock } from '../index.mock'; @@ -36,10 +36,12 @@ const mockFieldMappingResponse = { describe('GET /internal/security/fields/{query}', () => { it('returns a list of deduplicated fields, omitting empty and runtime fields', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const scopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - scopedClient.callAsCurrentUser.mockResolvedValue(mockFieldMappingResponse); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(scopedClient); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + }; + mockContext.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping.mockImplementation( + (async () => ({ body: mockFieldMappingResponse })) as any + ); defineGetFieldsRoutes(mockRouteDefinitionParams); @@ -51,7 +53,7 @@ describe('GET /internal/security/fields/{query}', () => { path: `/internal/security/fields/foo`, headers, }); - const response = await handler({} as any, mockRequest, kibanaResponseFactory); + const response = await handler(mockContext as any, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); expect(response.payload).toEqual(['fooField', 'commonField', 'barField']); }); diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts index 44b8804ed8d6e..304e121f7fee1 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -22,7 +22,7 @@ interface FieldMappingResponse { }; } -export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/fields/{query}', @@ -30,14 +30,16 @@ export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinition }, async (context, request, response) => { try { - const indexMappings = (await clusterClient - .asScoped(request) - .callAsCurrentUser('indices.getFieldMapping', { + const { + body: indexMappings, + } = await context.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping<FieldMappingResponse>( + { index: request.params.query, fields: '*', - allowNoIndices: false, - includeDefaults: true, - })) as FieldMappingResponse; + allow_no_indices: false, + include_defaults: true, + } + ); // The flow is the following (see response format at https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html): // 1. Iterate over all matched indices. diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts index aec0310129f6e..33fd66f9e929d 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts @@ -5,24 +5,26 @@ */ import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { defineRoleMappingDeleteRoutes } from './delete'; describe('DELETE role mappings', () => { it('allows a role mapping to be deleted', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ acknowledged: true }); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) } } as any, + }; + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping.mockResolvedValue( + { body: { acknowledged: true } } as any + ); defineRoleMappingDeleteRoutes(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; const name = 'mapping1'; - const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ method: 'delete', @@ -30,31 +32,35 @@ describe('DELETE role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); expect(response.payload).toEqual({ acknowledged: true }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); expect( - mockScopedClusterClient.callAsCurrentUser - ).toHaveBeenCalledWith('shield.deleteRoleMapping', { name }); + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping + ).toHaveBeenCalledWith({ name }); }); describe('failure', () => { it('returns result of license check', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: 'invalid', + message: 'test forbidden message', + }), + }, + } as any, + }; defineRoleMappingDeleteRoutes(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; const name = 'mapping1'; - const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ method: 'delete', @@ -62,21 +68,13 @@ describe('DELETE role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { - check: jest.fn().mockReturnValue({ - state: 'invalid', - message: 'test forbidden message', - }), - }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual({ message: 'test forbidden message' }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping + ).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.ts index dc11bcd914b35..dbe9c5662a1f1 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/delete.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.ts @@ -8,9 +8,7 @@ import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapError } from '../../errors'; import { RouteDefinitionParams } from '..'; -export function defineRoleMappingDeleteRoutes(params: RouteDefinitionParams) { - const { clusterClient, router } = params; - +export function defineRoleMappingDeleteRoutes({ router }: RouteDefinitionParams) { router.delete( { path: '/internal/security/role_mapping/{name}', @@ -22,11 +20,11 @@ export function defineRoleMappingDeleteRoutes(params: RouteDefinitionParams) { }, createLicensedRouteHandler(async (context, request, response) => { try { - const deleteResponse = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.deleteRoleMapping', { - name: request.params.name, - }); + const { + body: deleteResponse, + } = await context.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping({ + name: request.params.name, + }); return response.ok({ body: deleteResponse }); } catch (error) { const wrappedError = wrapError(error); diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts index ee1d550bbe24d..8bd9f095b0f68 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts @@ -5,21 +5,16 @@ */ import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { - kibanaResponseFactory, - RequestHandlerContext, - ILegacyClusterClient, -} from '../../../../../../src/core/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; import { defineRoleMappingFeatureCheckRoute } from './feature_check'; interface TestOptions { licenseCheckResult?: LicenseCheck; canManageRoleMappings?: boolean; - nodeSettingsResponse?: Record<string, any>; - xpackUsageResponse?: Record<string, any>; - internalUserClusterClientImpl?: ILegacyClusterClient['callAsInternalUser']; + nodeSettingsResponse?: () => Record<string, any>; + xpackUsageResponse?: () => Record<string, any>; asserts: { statusCode: number; result?: Record<string, any> }; } @@ -38,57 +33,34 @@ const defaultXpackUsageResponse = { }, }; -const getDefaultInternalUserClusterClientImpl = ( - nodeSettingsResponse: TestOptions['nodeSettingsResponse'], - xpackUsageResponse: TestOptions['xpackUsageResponse'] -) => - ((async (endpoint: string, clientParams: Record<string, any>) => { - if (!clientParams) throw new TypeError('expected clientParams'); - - if (endpoint === 'nodes.info') { - return nodeSettingsResponse; - } - - if (endpoint === 'transport.request') { - if (clientParams.path === '/_xpack/usage') { - return xpackUsageResponse; - } - } - - throw new Error(`unexpected endpoint: ${endpoint}`); - }) as unknown) as TestOptions['internalUserClusterClientImpl']; - describe('GET role mappings feature check', () => { const getFeatureCheckTest = ( description: string, { licenseCheckResult = { state: 'valid' }, canManageRoleMappings = true, - nodeSettingsResponse = {}, - xpackUsageResponse = defaultXpackUsageResponse, - internalUserClusterClientImpl = getDefaultInternalUserClusterClientImpl( - nodeSettingsResponse, - xpackUsageResponse - ), + nodeSettingsResponse = async () => ({}), + xpackUsageResponse = async () => defaultXpackUsageResponse, asserts, }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementation( - internalUserClusterClientImpl + mockContext.core.elasticsearch.client.asInternalUser.nodes.info.mockImplementation( + (async () => ({ body: await nodeSettingsResponse() })) as any + ); + mockContext.core.elasticsearch.client.asInternalUser.transport.request.mockImplementation( + (async () => ({ body: await xpackUsageResponse() })) as any ); - mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method, payload) => { - if (method === 'shield.hasPrivileges') { - return { - has_all_requested: canManageRoleMappings, - }; - } - }); + mockContext.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ + body: { has_all_requested: canManageRoleMappings }, + } as any); defineRoleMappingFeatureCheckRoute(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; @@ -99,9 +71,6 @@ describe('GET role mappings feature check', () => { path: `/internal/security/_check_role_mapping_features`, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); @@ -124,7 +93,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('allows both script types when explicitly enabled', { - nodeSettingsResponse: { + nodeSettingsResponse: async () => ({ nodes: { someNodeId: { settings: { @@ -134,7 +103,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -147,7 +116,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('disallows stored scripts when disabled', { - nodeSettingsResponse: { + nodeSettingsResponse: async () => ({ nodes: { someNodeId: { settings: { @@ -157,7 +126,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -170,7 +139,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('disallows inline scripts when disabled', { - nodeSettingsResponse: { + nodeSettingsResponse: async () => ({ nodes: { someNodeId: { settings: { @@ -180,7 +149,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -193,7 +162,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('indicates incompatible realms when only native and file are enabled', { - xpackUsageResponse: { + xpackUsageResponse: async () => ({ security: { realms: { native: { @@ -206,7 +175,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -231,9 +200,12 @@ describe('GET role mappings feature check', () => { getFeatureCheckTest( 'falls back to allowing both script types if there is an error retrieving node settings', { - internalUserClusterClientImpl: (() => { - return Promise.reject(new Error('something bad happened')); - }) as TestOptions['internalUserClusterClientImpl'], + nodeSettingsResponse: async () => { + throw new Error('something bad happened'); + }, + xpackUsageResponse: async () => { + throw new Error('something bad happened'); + }, asserts: { statusCode: 200, result: { diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts index 88c7f193cea34..470039b8ae92b 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, ILegacyClusterClient } from 'src/core/server'; +import { ElasticsearchClient, Logger } from 'src/core/server'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; @@ -34,24 +34,18 @@ interface XPackUsageResponse { const INCOMPATIBLE_REALMS = ['file', 'native']; -export function defineRoleMappingFeatureCheckRoute({ - router, - clusterClient, - logger, -}: RouteDefinitionParams) { +export function defineRoleMappingFeatureCheckRoute({ router, logger }: RouteDefinitionParams) { router.get( { path: '/internal/security/_check_role_mapping_features', validate: false, }, createLicensedRouteHandler(async (context, request, response) => { - const { has_all_requested: canManageRoleMappings } = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.hasPrivileges', { - body: { - cluster: ['manage_security'], - }, - }); + const { + body: { has_all_requested: canManageRoleMappings }, + } = await context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges<{ + has_all_requested: boolean; + }>({ body: { cluster: ['manage_security'] } }); if (!canManageRoleMappings) { return response.ok({ @@ -61,7 +55,10 @@ export function defineRoleMappingFeatureCheckRoute({ }); } - const enabledFeatures = await getEnabledRoleMappingsFeatures(clusterClient, logger); + const enabledFeatures = await getEnabledRoleMappingsFeatures( + context.core.elasticsearch.client.asInternalUser, + logger + ); return response.ok({ body: { @@ -73,13 +70,12 @@ export function defineRoleMappingFeatureCheckRoute({ ); } -async function getEnabledRoleMappingsFeatures(clusterClient: ILegacyClusterClient, logger: Logger) { +async function getEnabledRoleMappingsFeatures(esClient: ElasticsearchClient, logger: Logger) { logger.debug(`Retrieving role mappings features`); - const nodeScriptSettingsPromise: Promise<NodeSettingsResponse> = clusterClient - .callAsInternalUser('nodes.info', { - filterPath: 'nodes.*.settings.script', - }) + const nodeScriptSettingsPromise = esClient.nodes + .info<NodeSettingsResponse>({ filter_path: 'nodes.*.settings.script' }) + .then(({ body }) => body) .catch((error) => { // fall back to assuming that node settings are unset/at their default values. // this will allow the role mappings UI to permit both role template script types, @@ -88,13 +84,11 @@ async function getEnabledRoleMappingsFeatures(clusterClient: ILegacyClusterClien return {}; }); - const xpackUsagePromise: Promise<XPackUsageResponse> = clusterClient - // `transport.request` is potentially unsafe when combined with untrusted user input. - // Do not augment with such input. - .callAsInternalUser('transport.request', { - method: 'GET', - path: '/_xpack/usage', - }) + // `transport.request` is potentially unsafe when combined with untrusted user input. + // Do not augment with such input. + const xpackUsagePromise = esClient.transport + .request({ method: 'GET', path: '/_xpack/usage' }) + .then(({ body }) => body as XPackUsageResponse) .catch((error) => { // fall back to no external realms configured. // this will cause a warning in the UI about no compatible realms being enabled, but will otherwise allow diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts index 2519034b386bf..625aae42a3907 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts @@ -6,9 +6,9 @@ import Boom from '@hapi/boom'; import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; import { defineRoleMappingGetRoutes } from './get'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; const mockRoleMappingResponse = { mapping1: { @@ -49,13 +49,22 @@ const mockRoleMappingResponse = { }, }; +function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } +) { + return { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; +} + describe('GET role mappings', () => { it('returns all role mappings', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(mockRoleMappingResponse); + const mockContext = getMockContext(); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping.mockResolvedValue({ + body: mockRoleMappingResponse, + } as any); defineRoleMappingGetRoutes(mockRouteDefinitionParams); @@ -67,11 +76,6 @@ describe('GET role mappings', () => { path: `/internal/security/role_mapping`, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); @@ -118,29 +122,27 @@ describe('GET role mappings', () => { }, ]); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.getRoleMappings', - { name: undefined } - ); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).toHaveBeenCalledWith({ name: undefined }); }); it('returns role mapping by name', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ - mapping1: { - enabled: true, - roles: ['foo', 'bar'], - rules: { - field: { - dn: 'CN=bob,OU=example,O=com', + const mockContext = getMockContext(); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping.mockResolvedValue({ + body: { + mapping1: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, }, }, }, - }); + } as any); defineRoleMappingGetRoutes(mockRouteDefinitionParams); @@ -155,11 +157,6 @@ describe('GET role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); @@ -175,16 +172,15 @@ describe('GET role mappings', () => { }, }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.getRoleMappings', - { name } - ); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).toHaveBeenCalledWith({ name }); }); describe('failure', () => { it('returns result of license check', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); defineRoleMappingGetRoutes(mockRouteDefinitionParams); @@ -196,29 +192,19 @@ describe('GET role mappings', () => { path: `/internal/security/role_mapping`, headers, }); - const mockContext = ({ - licensing: { - license: { - check: jest.fn().mockReturnValue({ - state: 'invalid', - message: 'test forbidden message', - }), - }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual({ message: 'test forbidden message' }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).not.toHaveBeenCalled(); }); it('returns a 404 when the role mapping is not found', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + const mockContext = getMockContext(); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping.mockRejectedValue( Boom.notFound('role mapping not found!') ); @@ -235,18 +221,12 @@ describe('GET role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(404); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); expect( - mockScopedClusterClient.callAsCurrentUser - ).toHaveBeenCalledWith('shield.getRoleMappings', { name }); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).toHaveBeenCalledWith({ name }); }); }); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index 63598584b5d1b..5ab9b1f6b4a24 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -14,7 +14,7 @@ interface RoleMappingsResponse { } export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { - const { clusterClient, logger, router } = params; + const { logger, router } = params; router.get( { @@ -29,13 +29,11 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { const expectSingleEntity = typeof request.params.name === 'string'; try { - const roleMappingsResponse: RoleMappingsResponse = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRoleMappings', { - name: request.params.name, - }); + const roleMappingsResponse = await context.core.elasticsearch.client.asCurrentUser.security.getRoleMapping<RoleMappingsResponse>( + { name: request.params.name } + ); - const mappings = Object.entries(roleMappingsResponse).map(([name, mapping]) => { + const mappings = Object.entries(roleMappingsResponse.body).map(([name, mapping]) => { return { name, ...mapping, diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts index 8f61d2a122f0c..5dc7a21a02c6f 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts @@ -5,17 +5,20 @@ */ import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { defineRoleMappingPostRoutes } from './post'; describe('POST role mappings', () => { it('allows a role mapping to be created', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ created: true }); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) } } as any, + }; + mockContext.core.elasticsearch.client.asCurrentUser.security.putRoleMapping.mockResolvedValue({ + body: { created: true }, + } as any); defineRoleMappingPostRoutes(mockRouteDefinitionParams); @@ -39,37 +42,41 @@ describe('POST role mappings', () => { }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); expect(response.payload).toEqual({ created: true }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.saveRoleMapping', - { - name, - body: { - enabled: true, - roles: ['foo', 'bar'], - rules: { - field: { - dn: 'CN=bob,OU=example,O=com', - }, + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.putRoleMapping + ).toHaveBeenCalledWith({ + name, + body: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', }, }, - } - ); + }, + }); }); describe('failure', () => { it('returns result of license check', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: 'invalid', + message: 'test forbidden message', + }), + }, + } as any, + }; defineRoleMappingPostRoutes(mockRouteDefinitionParams); @@ -81,22 +88,14 @@ describe('POST role mappings', () => { path: `/internal/security/role_mapping`, headers, }); - const mockContext = ({ - licensing: { - license: { - check: jest.fn().mockReturnValue({ - state: 'invalid', - message: 'test forbidden message', - }), - }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual({ message: 'test forbidden message' }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.putRoleMapping + ).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.ts b/x-pack/plugins/security/server/routes/role_mapping/post.ts index 11149f38069a7..6c1b19dacb601 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/post.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/post.ts @@ -8,9 +8,7 @@ import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapError } from '../../errors'; import { RouteDefinitionParams } from '..'; -export function defineRoleMappingPostRoutes(params: RouteDefinitionParams) { - const { clusterClient, router } = params; - +export function defineRoleMappingPostRoutes({ router }: RouteDefinitionParams) { router.post( { path: '/internal/security/role_mapping/{name}', @@ -43,13 +41,10 @@ export function defineRoleMappingPostRoutes(params: RouteDefinitionParams) { }, createLicensedRouteHandler(async (context, request, response) => { try { - const saveResponse = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.saveRoleMapping', { - name: request.params.name, - body: request.body, - }); - return response.ok({ body: saveResponse }); + const saveResponse = await context.core.elasticsearch.client.asCurrentUser.security.putRoleMapping( + { name: request.params.name, body: request.body } + ); + return response.ok({ body: saveResponse.body }); } catch (error) { const wrappedError = wrapError(error); return response.customError({ diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index c66b5f985cb33..d98c0acb7d86d 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -7,21 +7,20 @@ import { errors } from 'elasticsearch'; import { ObjectType } from '@kbn/config-schema'; import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { - ILegacyClusterClient, + Headers, IRouter, - ILegacyScopedClusterClient, kibanaResponseFactory, RequestHandler, RequestHandlerContext, RouteConfig, - ScopeableRequest, } from '../../../../../../src/core/server'; import { Authentication, AuthenticationResult } from '../../authentication'; import { Session } from '../../session_management'; import { defineChangeUserPasswordRoutes } from './change_password'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; import { sessionMock } from '../../session_management/session.mock'; import { routeDefinitionParamsMock } from '../index.mock'; @@ -30,19 +29,19 @@ describe('Change password', () => { let router: jest.Mocked<IRouter>; let authc: jest.Mocked<Authentication>; let session: jest.Mocked<PublicMethodsOf<Session>>; - let mockClusterClient: jest.Mocked<ILegacyClusterClient>; - let mockScopedClusterClient: jest.Mocked<ILegacyScopedClusterClient>; let routeHandler: RequestHandler<any, any, any>; let routeConfig: RouteConfig<any, any, any, any>; - let mockContext: RequestHandlerContext; - - function checkPasswordChangeAPICall(username: string, request: ScopeableRequest) { - expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); - expect(mockClusterClient.asScoped).toHaveBeenCalledWith(request); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.changePassword', - { username, body: { password: 'new-password' } } + let mockContext: DeeplyMockedKeys<RequestHandlerContext>; + + function checkPasswordChangeAPICall(username: string, headers?: Headers) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword + ).toHaveBeenCalledTimes(1); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword + ).toHaveBeenCalledWith( + { username, body: { password: 'new-password' } }, + headers && { headers } ); } @@ -56,15 +55,10 @@ describe('Change password', () => { authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockAuthenticatedUser())); session.get.mockResolvedValue(sessionMock.createValue()); - mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockClusterClient = routeParamsMock.clusterClient; - mockClusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - - mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; + mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) } }, + } as any; defineChangeUserPasswordRoutes(routeParamsMock); @@ -114,20 +108,18 @@ describe('Change password', () => { const changePasswordFailure = new (errors.AuthenticationException as any)('Unauthorized', { body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, }); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(changePasswordFailure); + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword.mockRejectedValue( + changePasswordFailure + ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual(changePasswordFailure); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); - expect(mockClusterClient.asScoped).toHaveBeenCalledWith({ - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + checkPasswordChangeAPICall(username, { + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); }); @@ -148,16 +140,16 @@ describe('Change password', () => { expect(response.payload).toEqual(loginFailureReason); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); }); it('returns 500 if password update request fails with non-401 error.', async () => { const failureReason = new Error('Request failed.'); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword.mockRejectedValue( + failureReason + ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); @@ -165,10 +157,8 @@ describe('Change password', () => { expect(response.payload).toEqual(failureReason); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); }); @@ -179,10 +169,8 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); expect(authc.login).toHaveBeenCalledTimes(1); @@ -209,10 +197,8 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); expect(authc.login).toHaveBeenCalledTimes(1); @@ -230,10 +216,8 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); expect(authc.login).not.toHaveBeenCalled(); @@ -249,7 +233,9 @@ describe('Change password', () => { it('returns 500 if password update request fails.', async () => { const failureReason = new Error('Request failed.'); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword.mockRejectedValue( + failureReason + ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); @@ -257,7 +243,7 @@ describe('Change password', () => { expect(response.payload).toEqual(failureReason); expect(authc.login).not.toHaveBeenCalled(); - checkPasswordChangeAPICall(username, mockRequest); + checkPasswordChangeAPICall(username); }); it('successfully changes user password.', async () => { @@ -267,7 +253,7 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); expect(authc.login).not.toHaveBeenCalled(); - checkPasswordChangeAPICall(username, mockRequest); + checkPasswordChangeAPICall(username); }); }); }); diff --git a/x-pack/plugins/security/server/routes/users/change_password.ts b/x-pack/plugins/security/server/routes/users/change_password.ts index be868f841eeeb..66d36b4294883 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.ts @@ -14,12 +14,7 @@ import { } from '../../authentication'; import { RouteDefinitionParams } from '..'; -export function defineChangeUserPasswordRoutes({ - authc, - session, - router, - clusterClient, -}: RouteDefinitionParams) { +export function defineChangeUserPasswordRoutes({ authc, session, router }: RouteDefinitionParams) { router.post( { path: '/internal/security/users/{username}/password', @@ -43,28 +38,26 @@ export function defineChangeUserPasswordRoutes({ // If user is changing their own password they should provide a proof of knowledge their // current password via sending it in `Authorization: Basic base64(username:current password)` // HTTP header no matter how they logged in to Kibana. - const scopedClusterClient = clusterClient.asScoped( - isUserChangingOwnPassword - ? { - headers: { - ...request.headers, - authorization: new HTTPAuthorizationHeader( - 'Basic', - new BasicHTTPAuthorizationHeaderCredentials( - username, - currentPassword || '' - ).toString() - ).toString(), - }, - } - : request - ); + const options = isUserChangingOwnPassword + ? { + headers: { + ...request.headers, + authorization: new HTTPAuthorizationHeader( + 'Basic', + new BasicHTTPAuthorizationHeaderCredentials( + username, + currentPassword || '' + ).toString() + ).toString(), + }, + } + : undefined; try { - await scopedClusterClient.callAsCurrentUser('shield.changePassword', { - username, - body: { password: newPassword }, - }); + await context.core.elasticsearch.client.asCurrentUser.security.changePassword( + { username, body: { password: newPassword } }, + options + ); } catch (error) { // This may happen only if user's credentials are rejected meaning that current password // isn't correct. diff --git a/x-pack/plugins/security/server/routes/users/create_or_update.ts b/x-pack/plugins/security/server/routes/users/create_or_update.ts index 5a3e50bb11d5c..a98848a583500 100644 --- a/x-pack/plugins/security/server/routes/users/create_or_update.ts +++ b/x-pack/plugins/security/server/routes/users/create_or_update.ts @@ -9,7 +9,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineCreateOrUpdateUserRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineCreateOrUpdateUserRoutes({ router }: RouteDefinitionParams) { router.post( { path: '/internal/security/users/{username}', @@ -28,7 +28,7 @@ export function defineCreateOrUpdateUserRoutes({ router, clusterClient }: RouteD }, createLicensedRouteHandler(async (context, request, response) => { try { - await clusterClient.asScoped(request).callAsCurrentUser('shield.putUser', { + await context.core.elasticsearch.client.asCurrentUser.security.putUser({ username: request.params.username, // Omit `username`, `enabled` and all fields with `null` value. body: Object.fromEntries( diff --git a/x-pack/plugins/security/server/routes/users/delete.ts b/x-pack/plugins/security/server/routes/users/delete.ts index 99a8d5c18ab3d..26a1765b4fbdf 100644 --- a/x-pack/plugins/security/server/routes/users/delete.ts +++ b/x-pack/plugins/security/server/routes/users/delete.ts @@ -9,7 +9,7 @@ import { RouteDefinitionParams } from '../index'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; -export function defineDeleteUserRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineDeleteUserRoutes({ router }: RouteDefinitionParams) { router.delete( { path: '/internal/security/users/{username}', @@ -19,9 +19,9 @@ export function defineDeleteUserRoutes({ router, clusterClient }: RouteDefinitio }, createLicensedRouteHandler(async (context, request, response) => { try { - await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.deleteUser', { username: request.params.username }); + await context.core.elasticsearch.client.asCurrentUser.security.deleteUser({ + username: request.params.username, + }); return response.noContent(); } catch (error) { diff --git a/x-pack/plugins/security/server/routes/users/get.ts b/x-pack/plugins/security/server/routes/users/get.ts index 0867910372546..aa6a4f6be8bad 100644 --- a/x-pack/plugins/security/server/routes/users/get.ts +++ b/x-pack/plugins/security/server/routes/users/get.ts @@ -9,7 +9,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineGetUserRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetUserRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/users/{username}', @@ -20,9 +20,13 @@ export function defineGetUserRoutes({ router, clusterClient }: RouteDefinitionPa createLicensedRouteHandler(async (context, request, response) => { try { const username = request.params.username; - const users = (await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getUser', { username })) as Record<string, {}>; + const { + body: users, + } = await context.core.elasticsearch.client.asCurrentUser.security.getUser< + Record<string, {}> + >({ + username, + }); if (!users[username]) { return response.notFound(); diff --git a/x-pack/plugins/security/server/routes/users/get_all.ts b/x-pack/plugins/security/server/routes/users/get_all.ts index 492ab27ab27ad..3c5ca184f747c 100644 --- a/x-pack/plugins/security/server/routes/users/get_all.ts +++ b/x-pack/plugins/security/server/routes/users/get_all.ts @@ -8,7 +8,7 @@ import { RouteDefinitionParams } from '../index'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; -export function defineGetAllUsersRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetAllUsersRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/users', validate: false }, createLicensedRouteHandler(async (context, request, response) => { @@ -16,7 +16,7 @@ export function defineGetAllUsersRoutes({ router, clusterClient }: RouteDefiniti return response.ok({ // Return only values since keys (user names) are already duplicated there. body: Object.values( - await clusterClient.asScoped(request).callAsCurrentUser('shield.getUser') + (await context.core.elasticsearch.client.asCurrentUser.security.getUser()).body ), }); } catch (error) { From 0c623b6c017fc0addbdf18afb7b03e6639538020 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher <k.gallagher.05@gmail.com> Date: Wed, 2 Dec 2020 14:13:20 +0000 Subject: [PATCH 35/37] [Synthetics] Waterfall chart (#84199) * Add a Waterfall component - Adds a generic Waterfall component - Adds consumer consumption code for Synthetics --- .../monitor/synthetics/executed_journey.tsx | 54 +- .../monitor/synthetics/waterfall/README.md | 123 ++++ .../waterfall/components/constants.ts | 12 + .../waterfall/components/legend.tsx | 28 + .../components/middle_truncated_text.test.tsx | 39 + .../components/middle_truncated_text.tsx | 61 ++ .../waterfall/components/sidebar.tsx | 43 ++ .../synthetics/waterfall/components/styles.ts | 89 +++ .../waterfall/components/waterfall_chart.tsx | 194 +++++ .../synthetics/data_formatting.test.ts | 687 ++++++++++++++++++ .../consumers/synthetics/data_formatting.ts | 336 +++++++++ .../waterfall/consumers/synthetics/types.ts | 197 +++++ .../synthetics/waterfall_chart_wrapper.tsx | 84 +++ .../waterfall/context/waterfall_chart.tsx | 44 ++ .../monitor/synthetics/waterfall/index.tsx | 10 + .../monitor/synthetics/waterfall/types.ts | 21 + 16 files changed, 1996 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/types.ts diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx index 9a3e045017f9a..0c47e4c73e976 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx @@ -57,29 +57,31 @@ interface ExecutedJourneyProps { journey: JourneyState; } -export const ExecutedJourney: FC<ExecutedJourneyProps> = ({ journey }) => ( - <div> - <EuiText> - <h3> - <FormattedMessage - id="xpack.uptime.synthetics.executedJourney.heading" - defaultMessage="Summary information" - /> - </h3> - <p> - {statusMessage( - journey.steps - .filter(isStepEnd) - .reduce(reduceStepStatus, { failed: 0, skipped: 0, succeeded: 0 }) - )} - </p> - </EuiText> - <EuiSpacer /> - <EuiFlexGroup direction="column"> - {journey.steps.filter(isStepEnd).map((step, index) => ( - <ExecutedStep key={index} index={index} step={step} /> - ))} - <EuiSpacer size="s" /> - </EuiFlexGroup> - </div> -); +export const ExecutedJourney: FC<ExecutedJourneyProps> = ({ journey }) => { + return ( + <div> + <EuiText> + <h3> + <FormattedMessage + id="xpack.uptime.synthetics.executedJourney.heading" + defaultMessage="Summary information" + /> + </h3> + <p> + {statusMessage( + journey.steps + .filter(isStepEnd) + .reduce(reduceStepStatus, { failed: 0, skipped: 0, succeeded: 0 }) + )} + </p> + </EuiText> + <EuiSpacer /> + <EuiFlexGroup direction="column"> + {journey.steps.filter(isStepEnd).map((step, index) => ( + <ExecutedStep key={index} index={index} step={step} /> + ))} + <EuiSpacer size="s" /> + </EuiFlexGroup> + </div> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md new file mode 100644 index 0000000000000..cf8d3b5345eaa --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md @@ -0,0 +1,123 @@ +# Waterfall chart + +## Introduction + +The waterfall chart component aims to be agnostic in it's approach, so that a variety of consumers / solutions can use it. Some of Elastic Chart's features are used in a non-standard way to facilitate this flexibility, this README aims to cover some of the things that might be less obvious, and also provides a high level overview of implementation. + +## Requirements for usage + +The waterfall chart component asssumes that the consumer is making use of `KibanaReactContext`, and as such things like `useKibana` can be called. + +Consumers are also expected to be using the `<EuiThemeProvider />` so that the waterfall chart can apply styled-component styles based on the EUI theme. + +These are the two hard requirements, but almost all plugins will be using these. + +## Rendering + +At it's core the watefall chart is a stacked bar chart that has been rotated through 90 degrees. As such it's important to understand that `x` is now represented as `y` and vice versa. + +## Flexibility + +This section aims to cover some things that are non-standard. + +### Tooltip + +By default the formatting of tooltip values is very basic, but for a waterfall chart there needs to be a great deal of flexibility to represent whatever breakdown you're trying to show. + +As such a custom tooltip component is used. This custom component would usually only have access to some basic props that pertain to the values of the hovered bar. The waterfall chart component extends this by making us of a waterfall chart context. + +The custom tooltip component can use the context to access the full set of chart data, find the relevant items (those with the same `x` value) and call a custom `renderTooltipItem` for each item, `renderTooltipItem` will be passed `item.config.tooltipProps`. Every consumer can choose what they use for their `tooltipProps`. + +Some consumers might need colours, some might need iconography and so on. The waterfall chart doesn't make assumptions, and will render out the React content returned by `renderTooltipItem`. + +IMPORTANT: `renderTooltipItem` is provided via context and not as a direct prop due to the fact the custom tooltip component would usually only have access to the props provided directly to it from Elastic Charts. + +### Colours + +The easiest way to facilitate specific colours for each stack (let's say your colours are mapped to a constraint like mime type) is to assign the colour directly on your datum `config` property, and then access this directly in the `barStyleAccessor` function, e.g. + +``` +barStyleAccessor={(datum) => { + return datum.datum.config.colour; +}) +``` + +### Config + +The notion of `config` has been mentioned already. But this is a place that consumers can store their solution specific properties. `renderTooltipItem` will make use of `config.tooltipProps`, and `barStyleAccessor` can make use of anything on `config`. + +### Sticky top axis + +By default there is no "sticky" axis functionality in Elastic Charts, therefore a second chart is rendered, this contains a replica of the top axis, and renders one empty data point (as a chart can't only have an axis). This second chart is then positioned in such a way that it covers the top of the real axis, and remains fixed. + +## Data + +The waterfall chart expects data in a relatively simple format, there are the usual plot properties (`x`, `y0`, and `y`) and then `config`. E.g. + +``` +const series = [ + {x: 0, y: 0, y: 100, config: { tooltipProps: { type: 'dns' }}}, + {x: 0, y0: 300, y: 500, config: { tooltipProps: { type: 'ssl' }}}, + {x: 1, y0: 250, y: 300, config: { tooltipProps: { propA: 'somethingBreakdownRelated' }}}, + {x: 1, y0: 500, y: 600, config: { tooltipProps: { propA: 'anotherBreakdown' }}}, +] +``` + +Gaps in bars are fine, and to be expected for certain solutions. + +## Sidebar items + +The waterfall chart component again doesn't make assumptions about consumer's sidebar items' content, but the waterfall chart does handle the rendering so the sidebar can be aligned and rendered properly alongside the chart itself. + +`sidebarItems` should be provided to the context, and a `renderSidebarItem` prop should be provided to the chart. + +A sidebar is optional. + +There is a great deal of flexibility here so that solutions can make use of this in the way they need. For example, if you'd like to add a toggle functionality, so that clicking an item shows / hides it's children, this would involve rendering your toggle in `renderSidebarItem` and then when clicked you can handle adjusting your data as necessary. + +IMPORTANT: It is important to understand that the chart itself makes use of a fixed height. The sidebar will create a space that has a matching height. Each item is assigned equal space vertically via Flexbox, so that the items align with the relevant bar to the right (these are two totally different rendering contexts, with the chart itself sitting within a `canvas` element). So it's important that whatever content you choose to render here doesn't exceed the available height available to each item. The chart's height is calculated as `numberOfBars * 32`, so content should be kept within that `32px` threshold. + +## Legend items + +Much the same as with the sidebar items, no assumptions are made here, solutions will have different aims. + +`legendItems` should be provided to the context, and a `renderLegendItem` prop should be provided to the chart. + +A legend is optional. + +## Overall usage + +Pulling all of this together, things look like this (for a specific solution): + +``` +const renderSidebarItem: RenderItem<SidebarItem> = (item, index) => { + return <MiddleTruncatedText text={`${index + 1}. ${item.url}`} />; +}; + +const renderLegendItem: RenderItem<LegendItem> = (item) => { + return <EuiHealth color={item.colour}>{item.name}</EuiHealth>; +}; + +<WaterfallProvider + data={series} + sidebarItems={sidebarItems} + legendItems={legendItems} + renderTooltipItem={(tooltipProps) => { + return <EuiHealth color={String(tooltipProps.colour)}>{tooltipProps.value}</EuiHealth>; + }} +> + <WaterfallChart + tickFormat={(d: number) => `${Number(d).toFixed(0)} ms`} + domain={{ min: domain.min, max: domain.max }} + barStyleAccessor={(datum) => { + return datum.datum.config.colour; + }} + renderSidebarItem={renderSidebarItem} + renderLegendItem={renderLegendItem} + /> +</WaterfallProvider> +``` + +A solution could easily forego a sidebar and legend for a more minimalistic view, e.g. maybe a mini waterfall within a table column. + + diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts new file mode 100644 index 0000000000000..ac650c5ef0ddd --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +// Pixel value +export const BAR_HEIGHT = 32; +// Flex grow value +export const MAIN_GROW_SIZE = 8; +// Flex grow value +export const SIDEBAR_GROW_SIZE = 2; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx new file mode 100644 index 0000000000000..85a205a7256f3 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx @@ -0,0 +1,28 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { IWaterfallContext } from '../context/waterfall_chart'; +import { WaterfallChartLegendContainer } from './styles'; +import { WaterfallChartProps } from './waterfall_chart'; + +interface LegendProps { + items: Required<IWaterfallContext>['legendItems']; + render: Required<WaterfallChartProps>['renderLegendItem']; +} + +export const Legend: React.FC<LegendProps> = ({ items, render }) => { + return ( + <WaterfallChartLegendContainer> + <EuiFlexGroup gutterSize="none"> + {items.map((item, index) => { + return <EuiFlexItem key={index}>{render(item, index)}</EuiFlexItem>; + })} + </EuiFlexGroup> + </WaterfallChartLegendContainer> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx new file mode 100644 index 0000000000000..4f54a347d22d2 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx @@ -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 { getChunks, MiddleTruncatedText } from './middle_truncated_text'; +import { shallowWithIntl } from '@kbn/test/jest'; +import React from 'react'; + +const longString = + 'this-is-a-really-really-really-really-really-really-really-really-long-string.madeup.extension'; + +describe('getChunks', () => { + it('Calculates chunks correctly', () => { + const result = getChunks(longString); + expect(result).toEqual({ + first: 'this-is-a-really-really-really-really-really-really-really-really-long-string.made', + last: 'up.extension', + }); + }); +}); + +describe('Component', () => { + it('Renders correctly', () => { + expect(shallowWithIntl(<MiddleTruncatedText text={longString} />)).toMatchInlineSnapshot(` + <styled.div> + <styled.div> + <styled.span> + this-is-a-really-really-really-really-really-really-really-really-long-string.made + </styled.span> + <styled.span> + up.extension + </styled.span> + </styled.div> + </styled.div> + `); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx new file mode 100644 index 0000000000000..519927d7db28b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx @@ -0,0 +1,61 @@ +/* + * 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, { useMemo } from 'react'; +import styled from 'styled-components'; + +const OuterContainer = styled.div` + width: 100%; + height: 100%; + position: relative; +`; + +const InnerContainer = styled.div` + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: hidden; + display: flex; + min-width: 0; +`; // NOTE: min-width: 0 ensures flexbox and no-wrap children can co-exist + +const FirstChunk = styled.span` + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +`; + +const LastChunk = styled.span` + flex-shrink: 0; +`; + +export const getChunks = (text: string) => { + const END_CHARS = 12; + const chars = text.split(''); + const splitPoint = chars.length - END_CHARS > 0 ? chars.length - END_CHARS : null; + const endChars = splitPoint ? chars.splice(splitPoint) : []; + return { first: chars.join(''), last: endChars.join('') }; +}; + +// Helper component for adding middle text truncation, e.g. +// really-really-really-long....ompressed.js +// Can be used to accomodate content in sidebar item rendering. +export const MiddleTruncatedText = ({ text }: { text: string }) => { + const chunks = useMemo(() => { + return getChunks(text); + }, [text]); + + return ( + <OuterContainer> + <InnerContainer> + <FirstChunk>{chunks.first}</FirstChunk> + <LastChunk>{chunks.last}</LastChunk> + </InnerContainer> + </OuterContainer> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx new file mode 100644 index 0000000000000..9ff544fc1946b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx @@ -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 React from 'react'; +import { EuiFlexItem } from '@elastic/eui'; +import { SIDEBAR_GROW_SIZE } from './constants'; +import { IWaterfallContext } from '../context/waterfall_chart'; +import { + WaterfallChartSidebarContainer, + WaterfallChartSidebarContainerInnerPanel, + WaterfallChartSidebarContainerFlexGroup, + WaterfallChartSidebarFlexItem, +} from './styles'; +import { WaterfallChartProps } from './waterfall_chart'; + +interface SidebarProps { + items: Required<IWaterfallContext>['sidebarItems']; + height: number; + render: Required<WaterfallChartProps>['renderSidebarItem']; +} + +export const Sidebar: React.FC<SidebarProps> = ({ items, height, render }) => { + return ( + <EuiFlexItem grow={SIDEBAR_GROW_SIZE}> + <WaterfallChartSidebarContainer height={height}> + <WaterfallChartSidebarContainerInnerPanel paddingSize="none"> + <WaterfallChartSidebarContainerFlexGroup direction="column" gutterSize="none"> + {items.map((item, index) => { + return ( + <WaterfallChartSidebarFlexItem key={index}> + {render(item, index)} + </WaterfallChartSidebarFlexItem> + ); + })} + </WaterfallChartSidebarContainerFlexGroup> + </WaterfallChartSidebarContainerInnerPanel> + </WaterfallChartSidebarContainer> + </EuiFlexItem> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts new file mode 100644 index 0000000000000..25f5e5f8f5cc9 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts @@ -0,0 +1,89 @@ +/* + * 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 { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { euiStyled } from '../../../../../../../observability/public'; + +// NOTE: This isn't a perfect solution - changes in font size etc within charts could change the ideal height here. +const FIXED_AXIS_HEIGHT = 33; + +interface WaterfallChartOuterContainerProps { + height?: number; +} + +export const WaterfallChartOuterContainer = euiStyled.div<WaterfallChartOuterContainerProps>` + height: ${(props) => (props.height ? `${props.height}px` : 'auto')}; + overflow-y: ${(props) => (props.height ? 'scroll' : 'visible')}; + overflow-x: hidden; +`; + +export const WaterfallChartFixedTopContainer = euiStyled.div` + position: sticky; + top: 0; + z-index: ${(props) => props.theme.eui.euiZLevel4}; +`; + +export const WaterfallChartFixedTopContainerSidebarCover = euiStyled(EuiPanel)` + height: 100%; + border-radius: 0 !important; + border: none; +`; // NOTE: border-radius !important is here as the "border" prop isn't working + +export const WaterfallChartFixedAxisContainer = euiStyled.div` + height: ${FIXED_AXIS_HEIGHT}px; +`; + +interface WaterfallChartSidebarContainer { + height: number; +} + +export const WaterfallChartSidebarContainer = euiStyled.div<WaterfallChartSidebarContainer>` + height: ${(props) => `${props.height - FIXED_AXIS_HEIGHT}px`}; + overflow-y: hidden; +`; + +export const WaterfallChartSidebarContainerInnerPanel = euiStyled(EuiPanel)` + border: 0; + height: 100%; +`; + +export const WaterfallChartSidebarContainerFlexGroup = euiStyled(EuiFlexGroup)` + height: 100%; +`; + +// Ensures flex items honour no-wrap of children, rather than trying to extend to the full width of children. +export const WaterfallChartSidebarFlexItem = euiStyled(EuiFlexItem)` + min-width: 0; + padding-left: ${(props) => props.theme.eui.paddingSizes.m}; + padding-right: ${(props) => props.theme.eui.paddingSizes.m}; +`; + +interface WaterfallChartChartContainer { + height: number; +} + +export const WaterfallChartChartContainer = euiStyled.div<WaterfallChartChartContainer>` + width: 100%; + height: ${(props) => `${props.height}px`}; + margin-top: -${FIXED_AXIS_HEIGHT}px; +`; + +export const WaterfallChartLegendContainer = euiStyled.div` + position: sticky; + bottom: 0; + z-index: ${(props) => props.theme.eui.euiZLevel4}; + background-color: ${(props) => props.theme.eui.euiColorLightestShade}; + padding: ${(props) => props.theme.eui.paddingSizes.xs}; + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + box-shadow: 0px -1px 4px 0px ${(props) => props.theme.eui.euiColorLightShade}; +`; // NOTE: EuiShadowColor is a little too dark to work with the background-color + +export const WaterfallChartTooltip = euiStyled.div` + background-color: ${(props) => props.theme.eui.euiColorDarkestShade}; + border-radius: ${(props) => props.theme.eui.euiBorderRadius}; + color: ${(props) => props.theme.eui.euiColorLightestShade}; + padding: ${(props) => props.theme.eui.paddingSizes.s}; +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx new file mode 100644 index 0000000000000..de4be0ea34b2c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx @@ -0,0 +1,194 @@ +/* + * 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, { useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + Axis, + BarSeries, + Chart, + Position, + ScaleType, + Settings, + TickFormatter, + DomainRange, + BarStyleAccessor, + TooltipInfo, + TooltipType, +} from '@elastic/charts'; +import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; +// NOTE: The WaterfallChart has a hard requirement that consumers / solutions are making use of KibanaReactContext, and useKibana etc +// can therefore be accessed. +import { useUiSetting$ } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { useWaterfallContext } from '../context/waterfall_chart'; +import { + WaterfallChartOuterContainer, + WaterfallChartFixedTopContainer, + WaterfallChartFixedTopContainerSidebarCover, + WaterfallChartFixedAxisContainer, + WaterfallChartChartContainer, + WaterfallChartTooltip, +} from './styles'; +import { WaterfallData } from '../types'; +import { BAR_HEIGHT, MAIN_GROW_SIZE, SIDEBAR_GROW_SIZE } from './constants'; +import { Sidebar } from './sidebar'; +import { Legend } from './legend'; + +const Tooltip = ({ header }: TooltipInfo) => { + const { data, renderTooltipItem } = useWaterfallContext(); + const relevantItems = data.filter((item) => { + return item.x === header?.value; + }); + return ( + <WaterfallChartTooltip> + <EuiFlexGroup direction="column" gutterSize="none"> + {relevantItems.map((item, index) => { + return ( + <EuiFlexItem key={index}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem> + ); + })} + </EuiFlexGroup> + </WaterfallChartTooltip> + ); +}; + +export type RenderItem<I = any> = (item: I, index: number) => JSX.Element; + +export interface WaterfallChartProps { + tickFormat: TickFormatter; + domain: DomainRange; + barStyleAccessor: BarStyleAccessor; + renderSidebarItem?: RenderItem; + renderLegendItem?: RenderItem; + maxHeight?: number; +} + +const getUniqueBars = (data: WaterfallData) => { + return data.reduce<Set<number>>((acc, item) => { + if (!acc.has(item.x)) { + acc.add(item.x); + return acc; + } else { + return acc; + } + }, new Set()); +}; + +const getChartHeight = (data: WaterfallData): number => getUniqueBars(data).size * BAR_HEIGHT; + +export const WaterfallChart = ({ + tickFormat, + domain, + barStyleAccessor, + renderSidebarItem, + renderLegendItem, + maxHeight = 600, +}: WaterfallChartProps) => { + const { data, sidebarItems, legendItems } = useWaterfallContext(); + + const generatedHeight = useMemo(() => { + return getChartHeight(data); + }, [data]); + + const [darkMode] = useUiSetting$<boolean>('theme:darkMode'); + + const theme = useMemo(() => { + return darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; + }, [darkMode]); + + const shouldRenderSidebar = + sidebarItems && sidebarItems.length > 0 && renderSidebarItem ? true : false; + const shouldRenderLegend = + legendItems && legendItems.length > 0 && renderLegendItem ? true : false; + + return ( + <WaterfallChartOuterContainer height={maxHeight}> + <> + <WaterfallChartFixedTopContainer> + <EuiFlexGroup gutterSize="none"> + {shouldRenderSidebar && ( + <EuiFlexItem grow={SIDEBAR_GROW_SIZE}> + <WaterfallChartFixedTopContainerSidebarCover paddingSize="none" /> + </EuiFlexItem> + )} + <EuiFlexItem grow={shouldRenderSidebar ? MAIN_GROW_SIZE : true}> + <WaterfallChartFixedAxisContainer> + <Chart className="axis-only-chart"> + <Settings + showLegend={false} + rotation={90} + tooltip={{ type: TooltipType.None }} + theme={theme} + /> + + <Axis + id="time" + position={Position.Top} + tickFormat={tickFormat} + domain={domain} + showGridLines={true} + /> + + <Axis id="values" position={Position.Left} tickFormat={() => ''} /> + + <BarSeries + id="waterfallItems" + xScaleType={ScaleType.Linear} + yScaleType={ScaleType.Linear} + xAccessor="x" + yAccessors={['y']} + y0Accessors={['y0']} + styleAccessor={barStyleAccessor} + data={[{ x: 0, y0: 0, y1: 0 }]} + /> + </Chart> + </WaterfallChartFixedAxisContainer> + </EuiFlexItem> + </EuiFlexGroup> + </WaterfallChartFixedTopContainer> + <EuiFlexGroup gutterSize="none"> + {shouldRenderSidebar && ( + <Sidebar items={sidebarItems!} height={generatedHeight} render={renderSidebarItem!} /> + )} + <EuiFlexItem grow={shouldRenderSidebar ? MAIN_GROW_SIZE : true}> + <WaterfallChartChartContainer height={generatedHeight}> + <Chart className="data-chart"> + <Settings + showLegend={false} + rotation={90} + tooltip={{ customTooltip: Tooltip }} + theme={theme} + /> + + <Axis + id="time" + position={Position.Top} + tickFormat={tickFormat} + domain={domain} + showGridLines={true} + /> + + <Axis id="values" position={Position.Left} tickFormat={() => ''} /> + + <BarSeries + id="waterfallItems" + xScaleType={ScaleType.Linear} + yScaleType={ScaleType.Linear} + xAccessor="x" + yAccessors={['y']} + y0Accessors={['y0']} + styleAccessor={barStyleAccessor} + data={data} + /> + </Chart> + </WaterfallChartChartContainer> + </EuiFlexItem> + </EuiFlexGroup> + {shouldRenderLegend && <Legend items={legendItems!} render={renderLegendItem!} />} + </> + </WaterfallChartOuterContainer> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts new file mode 100644 index 0000000000000..698e6b4be0c4c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts @@ -0,0 +1,687 @@ +/* + * 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 { colourPalette } from './data_formatting'; + +// const TEST_DATA = [ +// { +// '@timestamp': '2020-10-29T14:55:01.055Z', +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// type: 'heartbeat', +// version: '7.10.0', +// hostname: 'docker-desktop', +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// }, +// synthetics: { +// index: 7, +// payload: { +// request: { +// url: 'https://unpkg.com/director@1.2.8/build/director.js', +// method: 'GET', +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// mixed_content_type: 'none', +// initial_priority: 'High', +// referrer_policy: 'no-referrer-when-downgrade', +// }, +// status: 200, +// method: 'GET', +// end: 13902.944973, +// url: 'https://unpkg.com/director@1.2.8/build/director.js', +// type: 'Script', +// is_navigation_request: false, +// start: 13902.752946, +// response: { +// encoded_data_length: 179, +// protocol: 'h2', +// headers: { +// content_encoding: 'br', +// server: 'cloudflare', +// age: '94838', +// cf_cache_status: 'HIT', +// x_content_type_options: 'nosniff', +// last_modified: 'Wed, 04 Feb 2015 03:25:28 GMT', +// cf_ray: '5e9dbc2bdda2e5a7-MAN', +// content_type: 'application/javascript; charset=utf-8', +// x_cloud_trace_context: 'eec7acc7a6f96b5353ef0d648bf437ac', +// expect_ct: +// 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', +// access_control_allow_origin: '*', +// vary: 'Accept-Encoding', +// cache_control: 'public, max-age=31536000', +// date: 'Thu, 29 Oct 2020 14:55:00 GMT', +// cf_request_id: '061673ef6b0000e5a7cd07a000000001', +// etag: 'W/"4f70-NHpXdyWxnckEaeiXalAnXQ+oh4Q"', +// strict_transport_security: 'max-age=31536000; includeSubDomains; preload', +// }, +// remote_i_p_address: '104.16.125.175', +// connection_reused: true, +// timing: { +// dns_start: -1, +// push_end: 0, +// worker_fetch_start: -1, +// worker_respond_with_settled: -1, +// proxy_end: -1, +// worker_start: -1, +// worker_ready: -1, +// send_end: 158.391, +// connect_end: -1, +// connect_start: -1, +// send_start: 157.876, +// proxy_start: -1, +// push_start: 0, +// ssl_end: -1, +// receive_headers_end: 186.885, +// ssl_start: -1, +// request_time: 13902.757525, +// dns_end: -1, +// }, +// connection_id: 17, +// status_text: '', +// remote_port: 443, +// status: 200, +// security_details: { +// valid_to: 1627905600, +// certificate_id: 0, +// key_exchange_group: 'X25519', +// valid_from: 1596326400, +// protocol: 'TLS 1.3', +// issuer: 'Cloudflare Inc ECC CA-3', +// key_exchange: '', +// san_list: ['unpkg.com', '*.unpkg.com', 'sni.cloudflaressl.com'], +// signed_certificate_timestamp_list: [], +// certificate_transparency_compliance: 'unknown', +// cipher: 'AES_128_GCM', +// subject_name: 'sni.cloudflaressl.com', +// }, +// mime_type: 'application/javascript', +// url: 'https://unpkg.com/director@1.2.8/build/director.js', +// from_prefetch_cache: false, +// from_disk_cache: false, +// security_state: 'secure', +// response_time: 1.603983300513211e12, +// from_service_worker: false, +// }, +// }, +// journey: { +// name: 'check that title is present', +// id: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// }, +// monitor: { +// status: 'up', +// duration: { +// us: 24, +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// type: 'browser', +// timespan: { +// gte: '2020-10-29T14:55:01.055Z', +// lt: '2020-10-29T14:56:01.055Z', +// }, +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// }, +// event: { +// dataset: 'uptime', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.055Z', +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// version: '7.10.0', +// hostname: 'docker-desktop', +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// }, +// monitor: { +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// status: 'up', +// duration: { +// us: 13, +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// type: 'browser', +// timespan: { +// gte: '2020-10-29T14:55:01.055Z', +// lt: '2020-10-29T14:56:01.055Z', +// }, +// }, +// synthetics: { +// journey: { +// name: 'check that title is present', +// id: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// index: 9, +// payload: { +// start: 13902.76168, +// url: 'file:///opt/examples/todos/app/app.js', +// method: 'GET', +// is_navigation_request: false, +// end: 13902.770133, +// request: { +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// mixed_content_type: 'none', +// initial_priority: 'High', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'file:///opt/examples/todos/app/app.js', +// method: 'GET', +// }, +// status: 0, +// type: 'Script', +// response: { +// protocol: 'file', +// connection_reused: false, +// mime_type: 'text/javascript', +// security_state: 'secure', +// from_disk_cache: false, +// url: 'file:///opt/examples/todos/app/app.js', +// status_text: '', +// connection_id: 0, +// from_prefetch_cache: false, +// encoded_data_length: -1, +// headers: {}, +// status: 0, +// from_service_worker: false, +// }, +// }, +// }, +// event: { +// dataset: 'uptime', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.000Z', +// monitor: { +// timespan: { +// lt: '2020-10-29T14:56:01.000Z', +// gte: '2020-10-29T14:55:01.000Z', +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// status: 'up', +// duration: { +// us: 44365, +// }, +// type: 'browser', +// }, +// synthetics: { +// journey: { +// id: 'check that title is present', +// name: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// index: 5, +// payload: { +// status: 0, +// url: 'file:///opt/examples/todos/app/index.html', +// end: 13902.730261, +// request: { +// method: 'GET', +// headers: {}, +// mixed_content_type: 'none', +// initial_priority: 'VeryHigh', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'file:///opt/examples/todos/app/index.html', +// }, +// method: 'GET', +// response: { +// status: 0, +// connection_id: 0, +// from_disk_cache: false, +// headers: {}, +// encoded_data_length: -1, +// status_text: '', +// from_service_worker: false, +// connection_reused: false, +// url: 'file:///opt/examples/todos/app/index.html', +// remote_port: 0, +// security_state: 'secure', +// protocol: 'file', +// mime_type: 'text/html', +// remote_i_p_address: '', +// from_prefetch_cache: false, +// }, +// start: 13902.726626, +// type: 'Document', +// is_navigation_request: true, +// }, +// }, +// event: { +// dataset: 'uptime', +// }, +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// version: '7.10.0', +// hostname: 'docker-desktop', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.044Z', +// monitor: { +// type: 'browser', +// timespan: { +// lt: '2020-10-29T14:56:01.044Z', +// gte: '2020-10-29T14:55:01.044Z', +// }, +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// status: 'up', +// duration: { +// us: 10524, +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// }, +// synthetics: { +// package_version: '0.0.1', +// index: 6, +// payload: { +// status: 200, +// type: 'Stylesheet', +// url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css', +// method: 'GET', +// start: 13902.75266, +// is_navigation_request: false, +// end: 13902.943835, +// response: { +// remote_i_p_address: '104.16.125.175', +// response_time: 1.603983300511892e12, +// url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css', +// mime_type: 'text/css', +// protocol: 'h2', +// security_state: 'secure', +// encoded_data_length: 414, +// remote_port: 443, +// status_text: '', +// timing: { +// proxy_start: -1, +// worker_ready: -1, +// worker_fetch_start: -1, +// receive_headers_end: 189.169, +// worker_respond_with_settled: -1, +// connect_end: 160.311, +// worker_start: -1, +// send_start: 161.275, +// dns_start: 0.528, +// send_end: 161.924, +// ssl_end: 160.267, +// proxy_end: -1, +// ssl_start: 29.726, +// request_time: 13902.753988, +// dns_end: 5.212, +// push_end: 0, +// push_start: 0, +// connect_start: 5.212, +// }, +// connection_reused: false, +// from_service_worker: false, +// security_details: { +// san_list: ['unpkg.com', '*.unpkg.com', 'sni.cloudflaressl.com'], +// valid_from: 1596326400, +// cipher: 'AES_128_GCM', +// protocol: 'TLS 1.3', +// issuer: 'Cloudflare Inc ECC CA-3', +// valid_to: 1627905600, +// certificate_id: 0, +// key_exchange_group: 'X25519', +// certificate_transparency_compliance: 'unknown', +// key_exchange: '', +// subject_name: 'sni.cloudflaressl.com', +// signed_certificate_timestamp_list: [], +// }, +// connection_id: 17, +// status: 200, +// from_disk_cache: false, +// from_prefetch_cache: false, +// headers: { +// date: 'Thu, 29 Oct 2020 14:55:00 GMT', +// x_cloud_trace_context: '76a4f7b8be185f2ac9aa839de3d6f893', +// cache_control: 'public, max-age=31536000', +// expect_ct: +// 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', +// content_type: 'text/css; charset=utf-8', +// age: '627638', +// x_content_type_options: 'nosniff', +// last_modified: 'Sat, 09 Jan 2016 00:57:37 GMT', +// access_control_allow_origin: '*', +// cf_request_id: '061673ef6a0000e5a75a309000000001', +// vary: 'Accept-Encoding', +// strict_transport_security: 'max-age=31536000; includeSubDomains; preload', +// cf_ray: '5e9dbc2bdda1e5a7-MAN', +// content_encoding: 'br', +// etag: 'W/"1921-kYwbQVnRAA2V/L9Gr4SCtUE5LHQ"', +// server: 'cloudflare', +// cf_cache_status: 'HIT', +// }, +// }, +// request: { +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// mixed_content_type: 'none', +// initial_priority: 'VeryHigh', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css', +// method: 'GET', +// }, +// }, +// journey: { +// id: 'check that title is present', +// name: 'check that title is present', +// }, +// type: 'journey/network_info', +// }, +// event: { +// dataset: 'uptime', +// }, +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// version: '7.10.0', +// hostname: 'docker-desktop', +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.055Z', +// agent: { +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// version: '7.10.0', +// hostname: 'docker-desktop', +// }, +// synthetics: { +// index: 8, +// payload: { +// method: 'GET', +// type: 'Script', +// response: { +// url: 'file:///opt/examples/todos/app/vue.min.js', +// protocol: 'file', +// connection_id: 0, +// headers: {}, +// mime_type: 'text/javascript', +// from_service_worker: false, +// status_text: '', +// connection_reused: false, +// encoded_data_length: -1, +// from_disk_cache: false, +// security_state: 'secure', +// from_prefetch_cache: false, +// status: 0, +// }, +// is_navigation_request: false, +// request: { +// mixed_content_type: 'none', +// initial_priority: 'High', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'file:///opt/examples/todos/app/vue.min.js', +// method: 'GET', +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// }, +// end: 13902.772783, +// status: 0, +// start: 13902.760644, +// url: 'file:///opt/examples/todos/app/vue.min.js', +// }, +// journey: { +// name: 'check that title is present', +// id: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// }, +// monitor: { +// status: 'up', +// duration: { +// us: 82, +// }, +// name: 'check that title is present', +// type: 'browser', +// timespan: { +// gte: '2020-10-29T14:55:01.055Z', +// lt: '2020-10-29T14:56:01.055Z', +// }, +// id: 'check that title is present', +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// }, +// event: { +// dataset: 'uptime', +// }, +// ecs: { +// version: '1.6.0', +// }, +// }, +// ]; + +// const toMillis = (seconds: number) => seconds * 1000; + +// describe('getTimings', () => { +// it('Calculates timings for network events correctly', () => { +// // NOTE: Uses these timings as the file protocol events don't have timing information +// const eventOneTimings = getTimings( +// TEST_DATA[0].synthetics.payload.response.timing!, +// toMillis(TEST_DATA[0].synthetics.payload.start), +// toMillis(TEST_DATA[0].synthetics.payload.end) +// ); +// expect(eventOneTimings).toEqual({ +// blocked: 162.4549999999106, +// connect: -1, +// dns: -1, +// receive: 0.5629999989271255, +// send: 0.5149999999999864, +// ssl: undefined, +// wait: 28.494, +// }); + +// const eventFourTimings = getTimings( +// TEST_DATA[3].synthetics.payload.response.timing!, +// toMillis(TEST_DATA[3].synthetics.payload.start), +// toMillis(TEST_DATA[3].synthetics.payload.end) +// ); +// expect(eventFourTimings).toEqual({ +// blocked: 1.8559999997466803, +// connect: 25.52200000000002, +// dns: 4.683999999999999, +// receive: 0.6780000009983667, +// send: 0.6490000000000009, +// ssl: 130.541, +// wait: 27.245000000000005, +// }); +// }); +// }); + +// describe('getSeriesAndDomain', () => { +// let seriesAndDomain: any; +// let NetworkItems: any; + +// beforeAll(() => { +// NetworkItems = extractItems(TEST_DATA); +// seriesAndDomain = getSeriesAndDomain(NetworkItems); +// }); + +// it('Correctly calculates the domain', () => { +// expect(seriesAndDomain.domain).toEqual({ max: 218.34699999913573, min: 0 }); +// }); + +// it('Correctly calculates the series', () => { +// expect(seriesAndDomain.series).toEqual([ +// { +// config: { colour: '#f3b3a6', tooltipProps: { colour: '#f3b3a6', value: '3.635ms' } }, +// x: 0, +// y: 3.6349999997764826, +// y0: 0, +// }, +// { +// config: { +// colour: '#b9a888', +// tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 1.856ms' }, +// }, +// x: 1, +// y: 27.889999999731778, +// y0: 26.0339999999851, +// }, +// { +// config: { colour: '#54b399', tooltipProps: { colour: '#54b399', value: 'DNS: 4.684ms' } }, +// x: 1, +// y: 32.573999999731775, +// y0: 27.889999999731778, +// }, +// { +// config: { +// colour: '#da8b45', +// tooltipProps: { colour: '#da8b45', value: 'Connecting: 25.522ms' }, +// }, +// x: 1, +// y: 58.095999999731795, +// y0: 32.573999999731775, +// }, +// { +// config: { colour: '#edc5a2', tooltipProps: { colour: '#edc5a2', value: 'SSL: 130.541ms' } }, +// x: 1, +// y: 188.63699999973178, +// y0: 58.095999999731795, +// }, +// { +// config: { +// colour: '#d36086', +// tooltipProps: { colour: '#d36086', value: 'Sending request: 0.649ms' }, +// }, +// x: 1, +// y: 189.28599999973179, +// y0: 188.63699999973178, +// }, +// { +// config: { +// colour: '#b0c9e0', +// tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 27.245ms' }, +// }, +// x: 1, +// y: 216.5309999997318, +// y0: 189.28599999973179, +// }, +// { +// config: { +// colour: '#ca8eae', +// tooltipProps: { colour: '#ca8eae', value: 'Content downloading: 0.678ms' }, +// }, +// x: 1, +// y: 217.20900000073016, +// y0: 216.5309999997318, +// }, +// { +// config: { +// colour: '#b9a888', +// tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 162.455ms' }, +// }, +// x: 2, +// y: 188.77500000020862, +// y0: 26.320000000298023, +// }, +// { +// config: { +// colour: '#d36086', +// tooltipProps: { colour: '#d36086', value: 'Sending request: 0.515ms' }, +// }, +// x: 2, +// y: 189.2900000002086, +// y0: 188.77500000020862, +// }, +// { +// config: { +// colour: '#b0c9e0', +// tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 28.494ms' }, +// }, +// x: 2, +// y: 217.7840000002086, +// y0: 189.2900000002086, +// }, +// { +// config: { +// colour: '#9170b8', +// tooltipProps: { colour: '#9170b8', value: 'Content downloading: 0.563ms' }, +// }, +// x: 2, +// y: 218.34699999913573, +// y0: 217.7840000002086, +// }, +// { +// config: { colour: '#9170b8', tooltipProps: { colour: '#9170b8', value: '12.139ms' } }, +// x: 3, +// y: 46.15699999965727, +// y0: 34.01799999922514, +// }, +// { +// config: { colour: '#9170b8', tooltipProps: { colour: '#9170b8', value: '8.453ms' } }, +// x: 4, +// y: 43.506999999284744, +// y0: 35.053999999538064, +// }, +// ]); +// }); +// }); + +describe('Palettes', () => { + it('A colour palette comprising timing and mime type colours is correctly generated', () => { + expect(colourPalette).toEqual({ + blocked: '#b9a888', + connect: '#da8b45', + dns: '#54b399', + font: '#aa6556', + html: '#f3b3a6', + media: '#d6bf57', + other: '#e7664c', + receive: '#54b399', + script: '#9170b8', + send: '#d36086', + ssl: '#edc5a2', + stylesheet: '#ca8eae', + wait: '#b0c9e0', + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts new file mode 100644 index 0000000000000..9c66ea638c942 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts @@ -0,0 +1,336 @@ +/* + * 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 { euiPaletteColorBlind } from '@elastic/eui'; + +import { + PayloadTimings, + CalculatedTimings, + NetworkItems, + FriendlyTimingLabels, + FriendlyMimetypeLabels, + MimeType, + MimeTypesMap, + Timings, + TIMING_ORDER, + SidebarItems, + LegendItems, +} from './types'; +import { WaterfallData } from '../../../waterfall'; + +const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000); + +// The timing calculations here are based off several sources: +// https://github.com/ChromeDevTools/devtools-frontend/blob/2fe91adefb2921b4deb2b4b125370ef9ccdb8d1b/front_end/sdk/HARLog.js#L307 +// and +// https://chromium.googlesource.com/chromium/blink.git/+/master/Source/devtools/front_end/sdk/HAREntry.js#131 +// and +// https://github.com/cyrus-and/chrome-har-capturer/blob/master/lib/har.js#L195 +// Order of events: request_start = 0, [proxy], [dns], [connect [ssl]], [send], receive_headers_end + +export const getTimings = ( + timings: PayloadTimings, + requestSentTime: number, + responseReceivedTime: number +): CalculatedTimings => { + if (!timings) return { blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive: 0, ssl: -1 }; + + const getLeastNonNegative = (values: number[]) => + values.reduce<number>((best, value) => (value >= 0 && value < best ? value : best), Infinity); + const getOptionalTiming = (_timings: PayloadTimings, key: keyof PayloadTimings) => + _timings[key] >= 0 ? _timings[key] : -1; + + // NOTE: Request sent and request start can differ due to queue times + const requestStartTime = microToMillis(timings.request_time); + + // Queued + const queuedTime = requestSentTime < requestStartTime ? requestStartTime - requestSentTime : -1; + + // Blocked + // "blocked" represents both queued time + blocked/stalled time + proxy time (ie: anything before the request was actually started). + let blocked = queuedTime; + + const blockedStart = getLeastNonNegative([ + timings.dns_start, + timings.connect_start, + timings.send_start, + ]); + + if (blockedStart !== Infinity) { + blocked += blockedStart; + } + + // Proxy + // Proxy is part of blocked, but it can be quirky in that blocked can be -1 even though there are proxy timings. This can happen with + // protocols like Quic. + if (timings.proxy_end !== -1) { + const blockedProxy = timings.proxy_end - timings.proxy_start; + + if (blockedProxy && blockedProxy > blocked) { + blocked = blockedProxy; + } + } + + // DNS + const dnsStart = timings.dns_end >= 0 ? blockedStart : 0; + const dnsEnd = getOptionalTiming(timings, 'dns_end'); + const dns = dnsEnd - dnsStart; + + // SSL + const sslStart = getOptionalTiming(timings, 'ssl_start'); + const sslEnd = getOptionalTiming(timings, 'ssl_end'); + let ssl; + + if (sslStart >= 0 && sslEnd >= 0) { + ssl = timings.ssl_end - timings.ssl_start; + } + + // Connect + let connect = -1; + if (timings.connect_start >= 0) { + connect = timings.send_start - timings.connect_start; + } + + // Send + const send = timings.send_end - timings.send_start; + + // Wait + const wait = timings.receive_headers_end - timings.send_end; + + // Receive + const receive = responseReceivedTime - (requestStartTime + timings.receive_headers_end); + + // SSL connection is a part of the overall connection time + if (connect && ssl) { + connect = connect - ssl; + } + + return { blocked, dns, connect, send, wait, receive, ssl }; +}; + +// TODO: Switch to real API data, and type data as the payload response (if server response isn't preformatted) +export const extractItems = (data: any): NetworkItems => { + const items = data + .map((entry: any) => { + const requestSentTime = microToMillis(entry.synthetics.payload.start); + const responseReceivedTime = microToMillis(entry.synthetics.payload.end); + const requestStartTime = + entry.synthetics.payload.response && entry.synthetics.payload.response.timing + ? microToMillis(entry.synthetics.payload.response.timing.request_time) + : null; + + return { + timestamp: entry['@timestamp'], + method: entry.synthetics.payload.method, + url: entry.synthetics.payload.url, + status: entry.synthetics.payload.status, + mimeType: entry.synthetics.payload?.response?.mime_type, + requestSentTime, + responseReceivedTime, + earliestRequestTime: requestStartTime + ? Math.min(requestSentTime, requestStartTime) + : requestSentTime, + timings: + entry.synthetics.payload.response && entry.synthetics.payload.response.timing + ? getTimings( + entry.synthetics.payload.response.timing, + requestSentTime, + responseReceivedTime + ) + : null, + }; + }) + .sort((a: any, b: any) => { + return a.earliestRequestTime - b.earliestRequestTime; + }); + + return items; +}; + +const formatValueForDisplay = (value: number, points: number = 3) => { + return Number(value).toFixed(points); +}; + +const getColourForMimeType = (mimeType?: string) => { + const key = mimeType && MimeTypesMap[mimeType] ? MimeTypesMap[mimeType] : MimeType.Other; + return colourPalette[key]; +}; + +export const getSeriesAndDomain = (items: NetworkItems) => { + // The earliest point in time a request is sent or started. This will become our notion of "0". + const zeroOffset = items.reduce<number>((acc, item) => { + const { earliestRequestTime } = item; + return earliestRequestTime < acc ? earliestRequestTime : acc; + }, Infinity); + + const series = items.reduce<WaterfallData>((acc, item, index) => { + const { earliestRequestTime } = item; + + // Entries without timings should be handled differently: + // https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L140 + // If there are no concrete timings just plot one block via start and end + if (!item.timings || item.timings === null) { + const duration = item.responseReceivedTime - item.earliestRequestTime; + const colour = getColourForMimeType(item.mimeType); + return [ + ...acc, + { + x: index, + y0: item.earliestRequestTime - zeroOffset, + y: item.responseReceivedTime - zeroOffset, + config: { + colour, + tooltipProps: { + value: `${formatValueForDisplay(duration)}ms`, + colour, + }, + }, + }, + ]; + } + + let currentOffset = earliestRequestTime - zeroOffset; + + TIMING_ORDER.forEach((timing) => { + const value = item.timings![timing]; + const colour = + timing === Timings.Receive ? getColourForMimeType(item.mimeType) : colourPalette[timing]; + if (value && value >= 0) { + const y = currentOffset + value; + + acc.push({ + x: index, + y0: currentOffset, + y, + config: { + colour, + tooltipProps: { + value: `${FriendlyTimingLabels[timing]}: ${formatValueForDisplay( + y - currentOffset + )}ms`, + colour, + }, + }, + }); + currentOffset = y; + } + }); + return acc; + }, []); + + const yValues = series.map((serie) => serie.y); + const domain = { min: 0, max: Math.max(...yValues) }; + return { series, domain }; +}; + +export const getSidebarItems = (items: NetworkItems): SidebarItems => { + return items.map((item) => { + const { url, status, method } = item; + return { url, status, method }; + }); +}; + +export const getLegendItems = (): LegendItems => { + let timingItems: LegendItems = []; + Object.values(Timings).forEach((timing) => { + // The "receive" timing is mapped to a mime type colour, so we don't need to show this in the legend + if (timing === Timings.Receive) { + return; + } + timingItems = [ + ...timingItems, + { name: FriendlyTimingLabels[timing], colour: TIMING_PALETTE[timing] }, + ]; + }); + + let mimeTypeItems: LegendItems = []; + Object.values(MimeType).forEach((mimeType) => { + mimeTypeItems = [ + ...mimeTypeItems, + { name: FriendlyMimetypeLabels[mimeType], colour: MIME_TYPE_PALETTE[mimeType] }, + ]; + }); + return [...timingItems, ...mimeTypeItems]; +}; + +// Timing colour palette +type TimingColourPalette = { + [K in Timings]: string; +}; + +const SAFE_PALETTE = euiPaletteColorBlind({ rotations: 2 }); + +const buildTimingPalette = (): TimingColourPalette => { + const palette = Object.values(Timings).reduce<Partial<TimingColourPalette>>((acc, value) => { + switch (value) { + case Timings.Blocked: + acc[value] = SAFE_PALETTE[6]; + break; + case Timings.Dns: + acc[value] = SAFE_PALETTE[0]; + break; + case Timings.Connect: + acc[value] = SAFE_PALETTE[7]; + break; + case Timings.Ssl: + acc[value] = SAFE_PALETTE[17]; + break; + case Timings.Send: + acc[value] = SAFE_PALETTE[2]; + break; + case Timings.Wait: + acc[value] = SAFE_PALETTE[11]; + break; + case Timings.Receive: + acc[value] = SAFE_PALETTE[0]; + break; + } + return acc; + }, {}); + + return palette as TimingColourPalette; +}; + +const TIMING_PALETTE = buildTimingPalette(); + +// MimeType colour palette +type MimeTypeColourPalette = { + [K in MimeType]: string; +}; + +const buildMimeTypePalette = (): MimeTypeColourPalette => { + const palette = Object.values(MimeType).reduce<Partial<MimeTypeColourPalette>>((acc, value) => { + switch (value) { + case MimeType.Html: + acc[value] = SAFE_PALETTE[19]; + break; + case MimeType.Script: + acc[value] = SAFE_PALETTE[3]; + break; + case MimeType.Stylesheet: + acc[value] = SAFE_PALETTE[4]; + break; + case MimeType.Media: + acc[value] = SAFE_PALETTE[5]; + break; + case MimeType.Font: + acc[value] = SAFE_PALETTE[8]; + break; + case MimeType.Other: + acc[value] = SAFE_PALETTE[9]; + break; + } + return acc; + }, {}); + + return palette as MimeTypeColourPalette; +}; + +const MIME_TYPE_PALETTE = buildMimeTypePalette(); + +type ColourPalette = TimingColourPalette & MimeTypeColourPalette; + +export const colourPalette: ColourPalette = { ...TIMING_PALETTE, ...MIME_TYPE_PALETTE }; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts new file mode 100644 index 0000000000000..1dd58b4f86db3 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts @@ -0,0 +1,197 @@ +/* + * 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'; + +export enum Timings { + Blocked = 'blocked', + Dns = 'dns', + Connect = 'connect', + Ssl = 'ssl', + Send = 'send', + Wait = 'wait', + Receive = 'receive', +} + +export const FriendlyTimingLabels = { + [Timings.Blocked]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.timings.blocked', + { + defaultMessage: 'Queued / Blocked', + } + ), + [Timings.Dns]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.dns', { + defaultMessage: 'DNS', + }), + [Timings.Connect]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.timings.connect', + { + defaultMessage: 'Connecting', + } + ), + [Timings.Ssl]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.ssl', { + defaultMessage: 'SSL', + }), + [Timings.Send]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.send', { + defaultMessage: 'Sending request', + }), + [Timings.Wait]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.wait', { + defaultMessage: 'Waiting (TTFB)', + }), + [Timings.Receive]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.timings.receive', + { + defaultMessage: 'Content downloading', + } + ), +}; + +export const TIMING_ORDER = [ + Timings.Blocked, + Timings.Dns, + Timings.Connect, + Timings.Ssl, + Timings.Send, + Timings.Wait, + Timings.Receive, +] as const; + +export type CalculatedTimings = { + [K in Timings]?: number; +}; + +export enum MimeType { + Html = 'html', + Script = 'script', + Stylesheet = 'stylesheet', + Media = 'media', + Font = 'font', + Other = 'other', +} + +export const FriendlyMimetypeLabels = { + [MimeType.Html]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.html', { + defaultMessage: 'HTML', + }), + [MimeType.Script]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.script', + { + defaultMessage: 'JS', + } + ), + [MimeType.Stylesheet]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.stylesheet', + { + defaultMessage: 'CSS', + } + ), + [MimeType.Media]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.media', + { + defaultMessage: 'Media', + } + ), + [MimeType.Font]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.font', { + defaultMessage: 'Font', + }), + [MimeType.Other]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.other', + { + defaultMessage: 'Other', + } + ), +}; + +// NOTE: This list tries to cover the standard spec compliant mime types, +// and a few popular non-standard ones, but it isn't exhaustive. +export const MimeTypesMap: Record<string, MimeType> = { + 'text/html': MimeType.Html, + 'application/javascript': MimeType.Script, + 'text/javascript': MimeType.Script, + 'text/css': MimeType.Stylesheet, + // Images + 'image/apng': MimeType.Media, + 'image/bmp': MimeType.Media, + 'image/gif': MimeType.Media, + 'image/x-icon': MimeType.Media, + 'image/jpeg': MimeType.Media, + 'image/png': MimeType.Media, + 'image/svg+xml': MimeType.Media, + 'image/tiff': MimeType.Media, + 'image/webp': MimeType.Media, + // Common audio / video formats + 'audio/wave': MimeType.Media, + 'audio/wav': MimeType.Media, + 'audio/x-wav': MimeType.Media, + 'audio/x-pn-wav': MimeType.Media, + 'audio/webm': MimeType.Media, + 'video/webm': MimeType.Media, + 'audio/ogg': MimeType.Media, + 'video/ogg': MimeType.Media, + 'application/ogg': MimeType.Media, + // Fonts + 'font/otf': MimeType.Font, + 'font/ttf': MimeType.Font, + 'font/woff': MimeType.Font, + 'font/woff2': MimeType.Font, + 'application/x-font-opentype': MimeType.Font, + 'application/font-woff': MimeType.Font, + 'application/font-woff2': MimeType.Font, + 'application/vnd.ms-fontobject': MimeType.Font, + 'application/font-sfnt': MimeType.Font, +}; + +export interface NetworkItem { + timestamp: string; + method: string; + url: string; + status: number; + mimeType?: string; + // NOTE: This is the time the request was actually issued. timing.request_time might be later if the request was queued. + requestSentTime: number; + responseReceivedTime: number; + // NOTE: Denotes the earlier figure out of request sent time and request start time (part of timings). This can vary based on queue times, and + // also whether an entry actually has timings available. + // Ref: https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L154 + earliestRequestTime: number; + timings: CalculatedTimings | null; +} +export type NetworkItems = NetworkItem[]; + +// NOTE: A number will always be present if the property exists, but that number might be -1, which represents no value. +export interface PayloadTimings { + dns_start: number; + push_end: number; + worker_fetch_start: number; + worker_respond_with_settled: number; + proxy_end: number; + worker_start: number; + worker_ready: number; + send_end: number; + connect_end: number; + connect_start: number; + send_start: number; + proxy_start: number; + push_start: number; + ssl_end: number; + receive_headers_end: number; + ssl_start: number; + request_time: number; + dns_end: number; +} + +export interface ExtraSeriesConfig { + colour: string; +} + +export type SidebarItem = Pick<NetworkItem, 'url' | 'status' | 'method'>; +export type SidebarItems = SidebarItem[]; + +export interface LegendItem { + name: string; + colour: string; +} +export type LegendItems = LegendItem[]; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx new file mode 100644 index 0000000000000..434b44a94b79f --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.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, { useMemo, useState } from 'react'; +import { EuiHealth, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; +import { getSeriesAndDomain, getSidebarItems, getLegendItems } from './data_formatting'; +import { SidebarItem, LegendItem, NetworkItems } from './types'; +import { + WaterfallProvider, + WaterfallChart, + MiddleTruncatedText, + RenderItem, +} from '../../../waterfall'; + +const renderSidebarItem: RenderItem<SidebarItem> = (item, index) => { + const { status } = item; + + const isErrorStatusCode = (statusCode: number) => { + const is400 = statusCode >= 400 && statusCode <= 499; + const is500 = statusCode >= 500 && statusCode <= 599; + const isSpecific300 = statusCode === 301 || statusCode === 307 || statusCode === 308; + return is400 || is500 || isSpecific300; + }; + + return ( + <> + {!isErrorStatusCode(status) ? ( + <MiddleTruncatedText text={`${index + 1}. ${item.url}`} /> + ) : ( + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiFlexItem> + <MiddleTruncatedText text={`${index + 1}. ${item.url}`} /> + </EuiFlexItem> + <EuiFlexItem component="span" grow={false}> + <EuiBadge color="danger">{status}</EuiBadge> + </EuiFlexItem> + </EuiFlexGroup> + )} + </> + ); +}; + +const renderLegendItem: RenderItem<LegendItem> = (item) => { + return <EuiHealth color={item.colour}>{item.name}</EuiHealth>; +}; + +export const WaterfallChartWrapper = () => { + // TODO: Will be sourced via an API + const [networkData] = useState<NetworkItems>([]); + + const { series, domain } = useMemo(() => { + return getSeriesAndDomain(networkData); + }, [networkData]); + + const sidebarItems = useMemo(() => { + return getSidebarItems(networkData); + }, [networkData]); + + const legendItems = getLegendItems(); + + return ( + <WaterfallProvider + data={series} + sidebarItems={sidebarItems} + legendItems={legendItems} + renderTooltipItem={(tooltipProps) => { + return <EuiHealth color={String(tooltipProps.colour)}>{tooltipProps.value}</EuiHealth>; + }} + > + <WaterfallChart + tickFormat={(d: number) => `${Number(d).toFixed(0)} ms`} + domain={domain} + barStyleAccessor={(datum) => { + return datum.datum.config.colour; + }} + renderSidebarItem={renderSidebarItem} + renderLegendItem={renderLegendItem} + /> + </WaterfallProvider> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx new file mode 100644 index 0000000000000..ccee9d7994c80 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx @@ -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 React, { createContext, useContext, Context } from 'react'; +import { WaterfallData, WaterfallDataEntry } from '../types'; + +export interface IWaterfallContext { + data: WaterfallData; + sidebarItems?: unknown[]; + legendItems?: unknown[]; + renderTooltipItem: ( + item: WaterfallDataEntry['config']['tooltipProps'], + index?: number + ) => JSX.Element; +} + +export const WaterfallContext = createContext<Partial<IWaterfallContext>>({}); + +interface ProviderProps { + data: IWaterfallContext['data']; + sidebarItems?: IWaterfallContext['sidebarItems']; + legendItems?: IWaterfallContext['legendItems']; + renderTooltipItem: IWaterfallContext['renderTooltipItem']; +} + +export const WaterfallProvider: React.FC<ProviderProps> = ({ + children, + data, + sidebarItems, + legendItems, + renderTooltipItem, +}) => { + return ( + <WaterfallContext.Provider value={{ data, sidebarItems, legendItems, renderTooltipItem }}> + {children} + </WaterfallContext.Provider> + ); +}; + +export const useWaterfallContext = () => + useContext((WaterfallContext as unknown) as Context<IWaterfallContext>); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx new file mode 100644 index 0000000000000..c3ea39a9ace6e --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx @@ -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. + */ + +export { WaterfallChart, RenderItem, WaterfallChartProps } from './components/waterfall_chart'; +export { WaterfallProvider, useWaterfallContext } from './context/waterfall_chart'; +export { MiddleTruncatedText } from './components/middle_truncated_text'; +export { WaterfallData, WaterfallDataEntry } from './types'; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/types.ts new file mode 100644 index 0000000000000..d6901fb482599 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/types.ts @@ -0,0 +1,21 @@ +/* + * 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 PlotProperties { + x: number; + y: number; + y0: number; +} + +export interface WaterfallDataSeriesConfigProperties { + tooltipProps: Record<string, string | number>; +} + +export type WaterfallDataEntry = PlotProperties & { + config: WaterfallDataSeriesConfigProperties & Record<string, unknown>; +}; + +export type WaterfallData = WaterfallDataEntry[]; From 90a18cc15d71212363fa9081c545c9ce19f27c56 Mon Sep 17 00:00:00 2001 From: John Schulz <john.schulz@elastic.co> Date: Wed, 2 Dec 2020 09:49:21 -0500 Subject: [PATCH 36/37] [Fleet][EPM] Pass through valid manifest values from upload (#84703) * Add missing properties & improve type safety * Break up types for better readability --- .../server/services/epm/archive/validation.ts | 80 ++++++++++++++----- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts index dc7a91e08799c..b93a9119cd4df 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts @@ -5,7 +5,7 @@ */ import yaml from 'js-yaml'; -import { uniq } from 'lodash'; +import { pick, uniq } from 'lodash'; import { ArchivePackage, RegistryPolicyTemplate, @@ -21,6 +21,42 @@ import { pkgToPkgKey } from '../registry'; const MANIFESTS: Record<string, Buffer> = {}; const MANIFEST_NAME = 'manifest.yml'; +// not sure these are 100% correct but they do the job here +// keeping them local until others need them +type OptionalPropertyOf<T extends object> = Exclude< + { + [K in keyof T]: T extends Record<K, T[K]> ? never : K; + }[keyof T], + undefined +>; +type RequiredPropertyOf<T extends object> = Exclude<keyof T, OptionalPropertyOf<T>>; + +type RequiredPackageProp = RequiredPropertyOf<ArchivePackage>; +type OptionalPackageProp = OptionalPropertyOf<ArchivePackage>; +// pro: guarantee only supplying known values. these keys must be in ArchivePackage. no typos or new values +// pro: any values added to these lists will be passed through by default +// pro & con: values do need to be shadowed / repeated from ArchivePackage, but perhaps we want to limit values +const requiredArchivePackageProps: readonly RequiredPackageProp[] = [ + 'name', + 'version', + 'description', + 'type', + 'categories', + 'format_version', +] as const; + +const optionalArchivePackageProps: readonly OptionalPackageProp[] = [ + 'title', + 'release', + 'readme', + 'screenshots', + 'icons', + 'assets', + 'internal', + 'data_streams', + 'policy_templates', +] as const; + // TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the // package registry. At some point this should probably be replaced (or enhanced) with verification based on // https://github.com/elastic/package-spec/ @@ -58,43 +94,43 @@ function parseAndVerifyArchive(paths: string[]): ArchivePackage { } // ... which must be valid YAML - let manifest; + let manifest: ArchivePackage; try { manifest = yaml.load(manifestBuffer.toString()); } catch (error) { throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); } - // Package name and version from the manifest must match those from the toplevel directory - const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); - if (toplevelDir !== pkgKey) { + // must have mandatory fields + const reqGiven = pick(manifest, requiredArchivePackageProps); + const requiredKeysMatch = + Object.keys(reqGiven).toString() === requiredArchivePackageProps.toString(); + if (!requiredKeysMatch) { + const list = requiredArchivePackageProps.join(', '); throw new PackageInvalidArchiveError( - `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` + `Invalid top-level package manifest: one or more fields missing of ${list}` ); } - const { name, version, description, type, categories, format_version: formatVersion } = manifest; - // check for mandatory fields - if (!(name && version && description && type && categories && formatVersion)) { + // at least have all required properties + // get optional values and combine into one object for the remaining operations + const optGiven = pick(manifest, optionalArchivePackageProps); + const parsed: ArchivePackage = { ...reqGiven, ...optGiven }; + + // Package name and version from the manifest must match those from the toplevel directory + const pkgKey = pkgToPkgKey({ name: parsed.name, version: parsed.version }); + if (toplevelDir !== pkgKey) { throw new PackageInvalidArchiveError( - 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' + `Name ${parsed.name} and version ${parsed.version} do not match top-level directory ${toplevelDir}` ); } - const dataStreams = parseAndVerifyDataStreams(paths, name, version); - const policyTemplates = parseAndVerifyPolicyTemplates(manifest); + parsed.data_streams = parseAndVerifyDataStreams(paths, parsed.name, parsed.version); + parsed.policy_templates = parseAndVerifyPolicyTemplates(manifest); - return { - name, - version, - description, - type, - categories, - format_version: formatVersion, - data_streams: dataStreams, - policy_templates: policyTemplates, - }; + return parsed; } + function parseAndVerifyDataStreams( paths: string[], pkgName: string, From cc341b32354c71a3d0220328ce4f1787bba3caf0 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky <streamich@gmail.com> Date: Wed, 2 Dec 2020 16:06:18 +0100 Subject: [PATCH 37/37] Telemetry for Dyanmic Actions (Drilldowns) (#84580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 set up telemetry for UiActions * feat: 🎸 improve ui_actions_enhanced collector * feat: 🎸 namespace ui actions telemetry stats * refactor: 💡 improve dynamic actions collector setup * feat: 🎸 add tests for dynamicActionsCollector * feat: 🎸 collect dynamic action trigger statistics * refactor: 💡 standartize metric naming * feat: 🎸 aggregate action x trigger counts * test: 💍 add tests for factory stats * docs: ✏️ add ui actions enhanced telemetry docs * fix: 🐛 revert type change * refactor: 💡 make dynamic action stats global * refactor: 💡 use global telemetry stats in action factories --- x-pack/plugins/ui_actions_enhanced/README.md | 63 ++++ .../server/dynamic_action_enhancement.ts | 15 +- ...dynamic_action_factories_collector.test.ts | 124 ++++++++ .../dynamic_action_factories_collector.ts | 24 ++ .../dynamic_actions_collector.test.ts | 271 ++++++++++++++++++ .../telemetry/dynamic_actions_collector.ts | 36 +++ .../server/telemetry/get_metric_key.ts | 10 + 7 files changed, 536 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts create mode 100644 x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts create mode 100644 x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts create mode 100644 x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts create mode 100644 x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.ts diff --git a/x-pack/plugins/ui_actions_enhanced/README.md b/x-pack/plugins/ui_actions_enhanced/README.md index a4a37b559ff8d..cd2a34a2f7536 100644 --- a/x-pack/plugins/ui_actions_enhanced/README.md +++ b/x-pack/plugins/ui_actions_enhanced/README.md @@ -3,3 +3,66 @@ Registers commercially licensed generic actions like per panel time range and contains some code that supports drilldown work. - [__Dashboard drilldown user docs__](https://www.elastic.co/guide/en/kibana/master/drilldowns.html) + +## Dynamic Actions Telemetry + +Dynamic actions (drilldowns) report telemetry. Below is the summary of dynamic action metrics that are reported using telemetry. + +### Dynamic action count + +Total count of dynamic actions (drilldowns) on a saved object. + +``` +dynamicActions.count +``` + +### Count by factory ID + +Count of active dynamic actions (drilldowns) on a saved object by factory ID (drilldown type). + +``` +dynamicActions.actions.<factory_id>.count +``` + +For example: + +``` +dynamicActions.actions.DASHBOARD_TO_DASHBOARD_DRILLDOWN.count +dynamicActions.actions.URL_DRILLDOWN.count +``` + +### Count by trigger + +Count of active dynamic actions (drilldowns) on a saved object by a trigger to which they are attached. + +``` +dynamicActions.triggers.<trigger>.count +``` + +For example: + +``` +dynamicActions.triggers.VALUE_CLICK_TRIGGER.count +dynamicActions.triggers.RANGE_SELECT_TRIGGER.count +``` + +### Count by factory and trigger + +Count of active dynamic actions (drilldowns) on a saved object by a factory ID and trigger ID. + +``` +dynamicActions.action_triggers.<factory_id>_<trigger>.count +``` + +For example: + +``` +dynamicActions.action_triggers.DASHBOARD_TO_DASHBOARD_DRILLDOWN_VALUE_CLICK_TRIGGER.count +dynamicActions.action_triggers.DASHBOARD_TO_DASHBOARD_DRILLDOWN_RANGE_SELECT_TRIGGER.count +dynamicActions.action_triggers.URL_DRILLDOWN_VALUE_CLICK_TRIGGER.count +``` + +### Factory metrics + +Each dynamic action factory (drilldown type) can report its own stats, which is +done using the `.telemetry()` method on dynamic action factories. diff --git a/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts b/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts index 4cea7ddf4854a..16e7e7967838d 100644 --- a/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts +++ b/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts @@ -8,19 +8,20 @@ import { EnhancementRegistryDefinition } from '../../../../src/plugins/embeddabl import { SavedObjectReference } from '../../../../src/core/types'; import { ActionFactory, DynamicActionsState, SerializedEvent } from './types'; import { SerializableState } from '../../../../src/plugins/kibana_utils/common'; +import { dynamicActionsCollector } from './telemetry/dynamic_actions_collector'; +import { dynamicActionFactoriesCollector } from './telemetry/dynamic_action_factories_collector'; export const dynamicActionEnhancement = ( getActionFactory: (id: string) => undefined | ActionFactory ): EnhancementRegistryDefinition => { return { id: 'dynamicActions', - telemetry: (state: SerializableState, telemetry: Record<string, any>) => { - let telemetryData = telemetry; - (state as DynamicActionsState).events.forEach((event: SerializedEvent) => { - const factory = getActionFactory(event.action.factoryId); - if (factory) telemetryData = factory.telemetry(event, telemetryData); - }); - return telemetryData; + telemetry: (serializableState: SerializableState, stats: Record<string, any>) => { + const state = serializableState as DynamicActionsState; + stats = dynamicActionsCollector(state, stats); + stats = dynamicActionFactoriesCollector(getActionFactory, state, stats); + + return stats; }, extract: (state: SerializableState) => { const references: SavedObjectReference[] = []; diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts new file mode 100644 index 0000000000000..9d38fd9d302a4 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import { dynamicActionFactoriesCollector } from './dynamic_action_factories_collector'; +import { DynamicActionsState } from '../../common'; +import { ActionFactory } from '../types'; + +type GetActionFactory = (id: string) => undefined | ActionFactory; + +const factories: Record<string, ActionFactory> = { + FACTORY_ID_1: ({ + id: 'FACTORY_ID_1', + telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => { + stats.myStat_1 = 1; + stats.myStat_2 = 123; + return stats; + }), + } as unknown) as ActionFactory, + FACTORY_ID_2: ({ + id: 'FACTORY_ID_2', + telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => stats), + } as unknown) as ActionFactory, + FACTORY_ID_3: ({ + id: 'FACTORY_ID_3', + telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => { + stats.myStat_1 = 2; + stats.stringStat = 'abc'; + return stats; + }), + } as unknown) as ActionFactory, +}; + +const getActionFactory: GetActionFactory = (id: string) => factories[id]; + +const state: DynamicActionsState = { + events: [ + { + eventId: 'eventId-1', + triggers: ['TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_1', + name: 'Click me!', + config: {}, + }, + }, + { + eventId: 'eventId-2', + triggers: ['TRIGGER_2', 'TRIGGER_3'], + action: { + factoryId: 'FACTORY_ID_2', + name: 'Click me, too!', + config: { + doCleanup: true, + }, + }, + }, + { + eventId: 'eventId-3', + triggers: ['TRIGGER_4', 'TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_3', + name: 'Go to documentation', + config: { + url: 'http://google.com', + iamFeelingLucky: true, + }, + }, + }, + ], +}; + +beforeEach(() => { + Object.values(factories).forEach((factory) => { + ((factory.telemetry as unknown) as jest.SpyInstance).mockClear(); + }); +}); + +describe('dynamicActionFactoriesCollector', () => { + test('returns empty stats when there are not dynamic actions', () => { + const stats = dynamicActionFactoriesCollector( + getActionFactory, + { + events: [], + }, + {} + ); + + expect(stats).toEqual({}); + }); + + test('calls .telemetry() method of a supplied factory', () => { + const currentState = { + events: [state.events[0]], + }; + dynamicActionFactoriesCollector(getActionFactory, currentState, {}); + + const spy1 = (factories.FACTORY_ID_1.telemetry as unknown) as jest.SpyInstance; + const spy2 = (factories.FACTORY_ID_2.telemetry as unknown) as jest.SpyInstance; + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(0); + + expect(spy1.mock.calls[0][0]).toEqual(currentState.events[0]); + expect(typeof spy1.mock.calls[0][1]).toBe('object'); + expect(!!spy1.mock.calls[0][1]).toBe(true); + }); + + test('returns stats received from factory', () => { + const currentState = { + events: [state.events[0]], + }; + const stats = dynamicActionFactoriesCollector(getActionFactory, currentState, {}); + + expect(stats).toEqual({ + myStat_1: 1, + myStat_2: 123, + }); + }); +}); diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts new file mode 100644 index 0000000000000..2ece6102c27a4 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts @@ -0,0 +1,24 @@ +/* + * 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 { DynamicActionsState } from '../../common'; +import { ActionFactory } from '../types'; + +export const dynamicActionFactoriesCollector = ( + getActionFactory: (id: string) => undefined | ActionFactory, + state: DynamicActionsState, + stats: Record<string, any> +): Record<string, any> => { + for (const event of state.events) { + const factory = getActionFactory(event.action.factoryId); + + if (factory) { + stats = factory.telemetry(event, stats); + } + } + + return stats; +}; diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts new file mode 100644 index 0000000000000..99217cd98fa01 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts @@ -0,0 +1,271 @@ +/* + * 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/naming-convention */ + +import { dynamicActionsCollector } from './dynamic_actions_collector'; +import { DynamicActionsState } from '../../common'; + +const state: DynamicActionsState = { + events: [ + { + eventId: 'eventId-1', + triggers: ['TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_1', + name: 'Click me!', + config: {}, + }, + }, + { + eventId: 'eventId-2', + triggers: ['TRIGGER_2', 'TRIGGER_3'], + action: { + factoryId: 'FACTORY_ID_2', + name: 'Click me, too!', + config: { + doCleanup: true, + }, + }, + }, + { + eventId: 'eventId-3', + triggers: ['TRIGGER_4', 'TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_1', + name: 'Go to documentation', + config: { + url: 'http://google.com', + iamFeelingLucky: true, + }, + }, + }, + ], +}; + +describe('dynamicActionsCollector', () => { + describe('dynamic action count', () => { + test('equal to zero when there are no dynamic actions', () => { + const stats = dynamicActionsCollector( + { + events: [], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 0, + }); + }); + + test('does not update existing count if there are no dynamic actions', () => { + const stats = dynamicActionsCollector( + { + events: [], + }, + { + 'dynamicActions.count': 25, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 25, + }); + }); + + test('equal to one when there is one dynamic action', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 1, + }); + }); + + test('adds one to the current dynamic action count', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.count': 2, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 3, + }); + }); + + test('equal to three when there are three dynamic action', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[1], state.events[2]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 3, + }); + }); + }); + + describe('registered action counts', () => { + test('for single action sets count to one', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 1, + }); + }); + + test('adds count to existing action counts', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.actions.FACTORY_ID_1.count': 5, + 'dynamicActions.actions.FACTORY_ID_2.count': 1, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 6, + 'dynamicActions.actions.FACTORY_ID_2.count': 1, + }); + }); + + test('aggregates count factory count', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 2, + }); + }); + + test('returns counts for every factory type', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2], state.events[1]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 2, + 'dynamicActions.actions.FACTORY_ID_2.count': 1, + }); + }); + }); + + describe('action trigger counts', () => { + test('for single action sets count to one', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.triggers.TRIGGER_1.count': 1, + }); + }); + + test('adds count to existing stats', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.triggers.TRIGGER_1.count': 123, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.triggers.TRIGGER_1.count': 124, + }); + }); + + test('aggregates trigger counts from all dynamic actions', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2], state.events[1]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.triggers.TRIGGER_1.count': 2, + 'dynamicActions.triggers.TRIGGER_2.count': 1, + 'dynamicActions.triggers.TRIGGER_3.count': 1, + 'dynamicActions.triggers.TRIGGER_4.count': 1, + }); + }); + }); + + describe('action x trigger counts', () => { + test('returns single action (factoryId x trigger) stat', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 1, + }); + }); + + test('adds count to existing stats', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 3, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 4, + }); + }); + + test('aggregates actions x triggers counts for all events', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2], state.events[1]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 2, + 'dynamicActions.action_triggers.FACTORY_ID_2_TRIGGER_2.count': 1, + 'dynamicActions.action_triggers.FACTORY_ID_2_TRIGGER_3.count': 1, + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_4.count': 1, + }); + }); + }); +}); diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts new file mode 100644 index 0000000000000..ae595776fda58 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts @@ -0,0 +1,36 @@ +/* + * 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 { DynamicActionsState } from '../../common'; +import { getMetricKey } from './get_metric_key'; + +export const dynamicActionsCollector = ( + state: DynamicActionsState, + stats: Record<string, any> +): Record<string, any> => { + const countMetricKey = getMetricKey('count'); + + stats[countMetricKey] = state.events.length + (stats[countMetricKey] || 0); + + for (const event of state.events) { + const factoryId = event.action.factoryId; + const actionCountMetric = getMetricKey(`actions.${factoryId}.count`); + + stats[actionCountMetric] = 1 + (stats[actionCountMetric] || 0); + + for (const trigger of event.triggers) { + const triggerCountMetric = getMetricKey(`triggers.${trigger}.count`); + const actionXTriggerCountMetric = getMetricKey( + `action_triggers.${factoryId}_${trigger}.count` + ); + + stats[triggerCountMetric] = 1 + (stats[triggerCountMetric] || 0); + stats[actionXTriggerCountMetric] = 1 + (stats[actionXTriggerCountMetric] || 0); + } + } + + return stats; +}; diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.ts new file mode 100644 index 0000000000000..6d3ae370c5200 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.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. + */ + +const prefix = 'dynamicActions.'; + +/** Returns prefixed telemetry metric key for all dynamic action metrics. */ +export const getMetricKey = (path: string) => `${prefix}${path}`;