From d1134c551e63cf7efa28b966e68d2a3ce71ddf1b Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 15 Apr 2020 12:34:16 +0100 Subject: [PATCH 01/44] [Alerting] Handle when an Alerting Task fails due to its Alert object being deleted mid flight (#63093) Detects if a task run failed due to the task SO being deleted mid flight and if so writes debug logs instead of warnings. Detects if an Alerting task run failed due to the alert SO being deleted mid flight of the task and if so ensures the task doesn't reschedule itself (as it usually would with other types of tasks). Ensures that the operation of deleting or disabling an Alert won't fail if it fails to delete an already deleted task (a task might preemptively self delete if its underlying alert object was deleted, even if the overall delete operation wasn't deleted). --- .../plugins/alerting/server/alerts_client.ts | 7 ++- .../lib/delete_task_if_it_exists.test.ts | 44 +++++++++++++++++++ .../server/lib/delete_task_if_it_exists.ts | 17 +++++++ .../lib/is_alert_not_found_error.test.ts | 31 +++++++++++++ .../server/lib/is_alert_not_found_error.ts | 11 +++++ .../server/task_runner/task_runner.test.ts | 33 ++++++++++++++ .../server/task_runner/task_runner.ts | 30 ++++++++----- .../tasks/visualizations/task_runner.test.ts | 8 +--- .../lib/is_task_not_found_error.test.ts | 31 +++++++++++++ .../server/lib/is_task_not_found_error.ts | 11 +++++ .../task_manager/server/task_pool.test.ts | 25 +++++++++++ .../plugins/task_manager/server/task_pool.ts | 13 +++++- 12 files changed, 240 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts create mode 100644 x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts create mode 100644 x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts create mode 100644 x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts create mode 100644 x-pack/plugins/task_manager/server/lib/is_task_not_found_error.test.ts create mode 100644 x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 6f8478df58a53..3e4c26d3444c9 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -34,6 +34,7 @@ import { import { EncryptedSavedObjectsPluginStart } from '../../../plugins/encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; import { taskInstanceToAlertTaskInstance } from './task_runner/alert_task_instance'; +import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; type NormalizedAlertAction = Omit; export type CreateAPIKeyResult = @@ -268,7 +269,7 @@ export class AlertsClient { const removeResult = await this.savedObjectsClient.delete('alert', id); await Promise.all([ - taskIdToRemove ? this.taskManager.remove(taskIdToRemove) : null, + taskIdToRemove ? deleteTaskIfItExists(this.taskManager, taskIdToRemove) : null, apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, ]); @@ -510,7 +511,9 @@ export class AlertsClient { ); await Promise.all([ - attributes.scheduledTaskId ? this.taskManager.remove(attributes.scheduledTaskId) : null, + attributes.scheduledTaskId + ? deleteTaskIfItExists(this.taskManager, attributes.scheduledTaskId) + : null, apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, ]); } diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts new file mode 100644 index 0000000000000..84a1743387c9c --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; +import { taskManagerMock } from '../../../task_manager/server/mocks'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import { deleteTaskIfItExists } from './delete_task_if_it_exists'; + +describe('deleteTaskIfItExists', () => { + test('removes the task by its ID', async () => { + const tm = taskManagerMock.createStart(); + const id = uuid.v4(); + + expect(await deleteTaskIfItExists(tm, id)).toBe(undefined); + + expect(tm.remove).toHaveBeenCalledWith(id); + }); + + test('handles 404 errors caused by the task not existing', async () => { + const tm = taskManagerMock.createStart(); + const id = uuid.v4(); + + tm.remove.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)); + + expect(await deleteTaskIfItExists(tm, id)).toBe(undefined); + + expect(tm.remove).toHaveBeenCalledWith(id); + }); + + test('throws if any other errro is caused by task removal', async () => { + const tm = taskManagerMock.createStart(); + const id = uuid.v4(); + + const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); + tm.remove.mockRejectedValue(error); + + expect(deleteTaskIfItExists(tm, id)).rejects.toBe(error); + + expect(tm.remove).toHaveBeenCalledWith(id); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts new file mode 100644 index 0000000000000..53bb1b5cb5d53 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.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. + */ +import { TaskManagerStartContract } from '../../../task_manager/server'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; + +export async function deleteTaskIfItExists(taskManager: TaskManagerStartContract, taskId: string) { + try { + await taskManager.remove(taskId); + } catch (err) { + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + throw err; + } + } +} diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts new file mode 100644 index 0000000000000..46ceee3ce420b --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts @@ -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 { isAlertSavedObjectNotFoundError } from './is_alert_not_found_error'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import uuid from 'uuid'; + +describe('isAlertSavedObjectNotFoundError', () => { + test('identifies SavedObjects Not Found errors', () => { + const id = uuid.v4(); + // ensure the error created by SO parses as a string with the format we expect + expect( + `${SavedObjectsErrorHelpers.createGenericNotFoundError('alert', id)}`.includes(`alert/${id}`) + ).toBe(true); + + const errorBySavedObjectsHelper = SavedObjectsErrorHelpers.createGenericNotFoundError( + 'alert', + id + ); + + expect(isAlertSavedObjectNotFoundError(errorBySavedObjectsHelper, id)).toBe(true); + }); + + test('identifies generic errors', () => { + const id = uuid.v4(); + expect(isAlertSavedObjectNotFoundError(new Error(`not found`), id)).toBe(false); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts new file mode 100644 index 0000000000000..0aa83ad0e883c --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; + +export function isAlertSavedObjectNotFoundError(err: Error, alertId: string) { + return SavedObjectsErrorHelpers.isNotFoundError(err) && `${err}`.includes(alertId); +} diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 4e6d959f0ce60..31cc893f785cb 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -16,6 +16,7 @@ import { PluginStartContract as ActionsPluginStart } from '../../../actions/serv import { actionsMock } from '../../../actions/server/mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { IEventLogger } from '../../../event_log/server'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; const alertType = { id: 'test', @@ -665,4 +666,36 @@ describe('Task Runner', () => { } `); }); + + test('avoids rescheduling a failed Alert Task Runner when it throws due to failing to fetch the alert', async () => { + savedObjectsClient.get.mockImplementation(() => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', '1'); + }); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": undefined, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 190cdc184930c..1d4b12e96bc76 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -26,12 +26,13 @@ import { taskInstanceToAlertTaskInstance } from './alert_task_instance'; import { AlertInstances } from '../alert_instance/alert_instance'; import { EVENT_LOG_ACTIONS } from '../plugin'; import { IEvent, IEventLogger } from '../../../event_log/server'; +import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error'; const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' }; interface AlertTaskRunResult { state: AlertTaskState; - runAt: Date; + runAt: Date | undefined; } interface AlertTaskInstance extends ConcreteTaskInstance { @@ -328,22 +329,29 @@ export class TaskRunner { }; }, (err: Error) => { - this.logger.error(`Executing Alert "${alertId}" has resulted in Error: ${err.message}`); + const message = `Executing Alert "${alertId}" has resulted in Error: ${err.message}`; + if (isAlertSavedObjectNotFoundError(err, alertId)) { + this.logger.debug(message); + } else { + this.logger.error(message); + } return { ...originalState, previousStartedAt, }; } ), - runAt: resolveErr(runAt, () => - getNextRunAt( - new Date(), - // if we fail at this point we wish to recover but don't have access to the Alert's - // attributes, so we'll use a default interval to prevent the underlying task from - // falling into a failed state - FALLBACK_RETRY_INTERVAL - ) - ), + runAt: resolveErr(runAt, err => { + return isAlertSavedObjectNotFoundError(err, alertId) + ? undefined + : getNextRunAt( + new Date(), + // if we fail at this point we wish to recover but don't have access to the Alert's + // attributes, so we'll use a default interval to prevent the underlying task from + // falling into a failed state + FALLBACK_RETRY_INTERVAL + ); + }), }; } } diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts index dff5e24db3c6e..6a47983a6f4d9 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; import { getMockCallWithInternal, getMockConfig, @@ -13,6 +12,7 @@ import { } from '../../../test_utils'; import { visualizationsTaskRunner } from './task_runner'; import { TaskInstance } from '../../../../../task_manager/server'; +import { getNextMidnight } from '../../get_next_midnight'; describe('visualizationsTaskRunner', () => { let mockTaskInstance: TaskInstance; @@ -41,12 +41,6 @@ describe('visualizationsTaskRunner', () => { }); test('Summarizes visualization response data', async () => { - const getNextMidnight = () => - moment() - .add(1, 'days') - .startOf('day') - .toDate(); - const runner = visualizationsTaskRunner(mockTaskInstance, getMockConfig(), getMockEs()); const result = await runner(); diff --git a/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.test.ts b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.test.ts new file mode 100644 index 0000000000000..65922ea8e6de7 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.test.ts @@ -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 { isTaskSavedObjectNotFoundError } from './is_task_not_found_error'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import uuid from 'uuid'; + +describe('isTaskSavedObjectNotFoundError', () => { + test('identifies SavedObjects Not Found errors', () => { + const id = uuid.v4(); + // ensure the error created by SO parses as a string with the format we expect + expect( + `${SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)}`.includes(`task/${id}`) + ).toBe(true); + + const errorBySavedObjectsHelper = SavedObjectsErrorHelpers.createGenericNotFoundError( + 'task', + id + ); + + expect(isTaskSavedObjectNotFoundError(errorBySavedObjectsHelper, id)).toBe(true); + }); + + test('identifies generic errors', () => { + const id = uuid.v4(); + expect(isTaskSavedObjectNotFoundError(new Error(`not found`), id)).toBe(false); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts new file mode 100644 index 0000000000000..8cc1c08f2a967 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; + +export function isTaskSavedObjectNotFoundError(err: Error, taskId: string) { + return SavedObjectsErrorHelpers.isNotFoundError(err) && `${err}`.includes(taskId); +} diff --git a/x-pack/plugins/task_manager/server/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts index a827c1436c9bd..fb87b6290a3da 100644 --- a/x-pack/plugins/task_manager/server/task_pool.test.ts +++ b/x-pack/plugins/task_manager/server/task_pool.test.ts @@ -8,6 +8,7 @@ import sinon from 'sinon'; import { TaskPool, TaskPoolRunResult } from './task_pool'; import { mockLogger, resolvable, sleep } from './test_utils'; import { asOk } from './lib/result_type'; +import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; describe('TaskPool', () => { test('occupiedWorkers are a sum of running tasks', async () => { @@ -101,6 +102,30 @@ describe('TaskPool', () => { expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); }); + test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { + const logger = mockLogger(); + const pool = new TaskPool({ + maxWorkers: 3, + logger, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Task TaskType \\"shooooo\\" failed in attempt to run: Saved object [task/foo] not found", + ] + `); + expect(logger.warn).not.toHaveBeenCalled(); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + test('Running a task which fails still takes up capacity', async () => { const logger = mockLogger(); const pool = new TaskPool({ diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts index 90f1880a159da..8999fb48680ce 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool.ts @@ -11,6 +11,7 @@ import { performance } from 'perf_hooks'; import { Logger } from './types'; import { TaskRunner } from './task_runner'; +import { isTaskSavedObjectNotFoundError } from './lib/is_task_not_found_error'; interface Opts { maxWorkers: number; @@ -125,7 +126,17 @@ export class TaskPool { taskRunner .run() .catch(err => { - this.logger.warn(`Task ${taskRunner.toString()} failed in attempt to run: ${err.message}`); + // If a task Saved Object can't be found by an in flight task runner + // we asssume the underlying task has been deleted while it was running + // so we will log this as a debug, rather than a warn + const errorLogLine = `Task ${taskRunner.toString()} failed in attempt to run: ${ + err.message + }`; + if (isTaskSavedObjectNotFoundError(err, taskRunner.id)) { + this.logger.debug(errorLogLine); + } else { + this.logger.warn(errorLogLine); + } }) .then(() => this.running.delete(taskRunner)); } From c885ea53792456383fb145414b891c134f97593e Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Wed, 15 Apr 2020 06:43:03 -0500 Subject: [PATCH 02/44] Because onThreshold is changed even when it hasn't changed add a check (#63555) * Because onThreshold is changed even when it hasn't changed add a check * Fix join --- .../infra/public/components/alerting/metrics/expression.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 6cf4e5bed2e6f..7de52840777f9 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -399,7 +399,9 @@ export const ExpressionRow: React.FC = props => { const updateThreshold = useCallback( t => { - setAlertParams(expressionId, { ...expression, threshold: t }); + if (t.join() !== expression.threshold.join()) { + setAlertParams(expressionId, { ...expression, threshold: t }); + } }, [expressionId, expression, setAlertParams] ); From 5deb74e6c4ebf0c482c874c7c59902b6445a48b7 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Wed, 15 Apr 2020 14:34:29 +0200 Subject: [PATCH 03/44] [SIEM][Detection Engine] Increase UI unit tests coverage (#62230) --- .../siem/public/mock/test_providers.tsx | 27 ++++++ .../activity_monitor/index.test.tsx | 18 ++++ .../index.test.tsx | 18 ++++ .../no_api_integration_callout/index.test.tsx | 18 ++++ .../no_write_signals_callout/index.test.tsx | 18 ++++ .../components/signals/index.test.tsx | 43 +++++++++ .../components/signals/index.tsx | 2 +- .../signals_filter_group/index.test.tsx | 18 ++++ .../signals_utility_bar/batch_actions.tsx | 92 ------------------- .../signals_utility_bar/index.test.tsx | 33 +++++++ .../signals_histogram_panel/index.test.tsx | 29 ++++++ .../signals_histogram.test.tsx | 22 +++++ .../components/user_info/index.test.tsx | 50 ++++++++++ .../detection_engine.test.tsx | 43 +++++++++ .../detection_engine/detection_engine.tsx | 2 +- .../detection_engine_empty_page.test.tsx | 19 ++++ .../detection_engine_no_signal_index.test.tsx | 19 ++++ ...ction_engine_user_unauthenticated.test.tsx | 19 ++++ .../public/pages/detection_engine/helpers.ts | 18 ---- .../pages/detection_engine/index.test.tsx | 19 ++++ .../detection_engine/rules/all/index.test.tsx | 40 ++++++++ .../rules_table_filters.test.tsx | 20 ++++ .../tags_filter_popover.test.tsx | 25 +++++ .../components/accordion_title/index.test.tsx | 18 ++++ .../components/add_item_form/index.test.tsx | 32 +++++++ .../all_rules_tables/index.test.tsx | 46 ++++++++++ .../anomaly_threshold_slider/index.test.tsx | 24 +++++ .../anomaly_threshold_slider/index.tsx | 4 +- .../ml_job_description.test.tsx | 54 +++++++++++ .../description_step/ml_job_description.tsx | 12 ++- .../rules/components/mitre/helpers.test.tsx | 13 +++ .../rules/components/mitre/index.test.tsx | 31 +++++++ .../components/ml_job_select/index.test.tsx | 31 +++++++ .../optional_field_label/index.test.tsx | 17 ++++ .../components/pick_timeline/index.test.tsx | 31 +++++++ .../load_empty_prompt.test.tsx | 24 +++++ .../pre_packaged_rules/load_empty_prompt.tsx | 2 + .../update_callout.test.tsx | 36 ++++++++ .../rules/components/query_bar/index.test.tsx | 37 ++++++++ .../read_only_callout/index.test.tsx | 18 ++++ .../rule_actions_field/index.test.tsx | 33 +++++++ .../components/rule_status/helpers.test.tsx | 13 +++ .../components/rule_status/index.test.tsx | 18 ++++ .../schedule_item_form/index.test.tsx | 31 +++++++ .../select_rule_type/index.test.tsx | 25 +++++ .../components/severity_badge/index.test.tsx | 18 ++++ .../components/status_icon/index.test.tsx | 22 +++++ .../step_content_wrapper/index.test.tsx | 18 ++++ .../components/step_content_wrapper/index.tsx | 2 + .../step_define_rule/index.test.tsx | 20 ++++ .../components/step_panel/index.test.tsx | 22 +++++ .../rules/components/step_panel/index.tsx | 2 + .../step_rule_actions/index.test.tsx | 22 +++++ .../components/step_rule_actions/schema.tsx | 2 + .../step_schedule_rule/index.test.tsx | 27 ++++++ .../components/step_schedule_rule/schema.tsx | 2 + .../throttle_select_field/index.test.tsx | 24 +++++ .../rules/create/index.test.tsx | 23 +++++ .../rules/details/failure_history.test.tsx | 27 ++++++ .../rules/details/index.test.tsx | 47 ++++++++++ .../detection_engine/rules/details/index.tsx | 2 +- .../details/status_failed_callout.test.tsx | 18 ++++ .../rules/edit/index.test.tsx | 33 +++++++ .../detection_engine/rules/helpers.test.tsx | 18 +++- .../detection_engine/rules/index.test.tsx | 27 ++++++ .../detection_engine/rules/utils.test.ts | 24 +++++ 66 files changed, 1442 insertions(+), 120 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.test.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.ts diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index c7692755c1330..952f7f51b63f2 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -20,6 +20,7 @@ import { ThemeProvider } from 'styled-components'; import { createStore, State } from '../store'; import { mockGlobalState } from './global_state'; import { createKibanaContextProviderMock } from './kibana_react'; +import { FieldHook, useForm } from '../shared_imports'; jest.mock('ui/new_platform'); @@ -91,3 +92,29 @@ const TestProviderWithoutDragAndDropComponent: React.FC = ({ ); export const TestProviderWithoutDragAndDrop = React.memo(TestProviderWithoutDragAndDropComponent); + +export const useFormFieldMock = (options?: Partial): FieldHook => { + const { form } = useForm(); + + return { + path: 'path', + type: 'type', + value: [], + isPristine: false, + isValidating: false, + isValidated: false, + isChangingValue: false, + form, + errors: [], + isValid: true, + getErrorsMessages: jest.fn(), + onChange: jest.fn(), + setValue: jest.fn(), + setErrors: jest.fn(), + clearErrors: jest.fn(), + validate: jest.fn(), + reset: jest.fn(), + __serializeOutput: jest.fn(), + ...options, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.test.tsx new file mode 100644 index 0000000000000..c5a4057b64ea7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { ActivityMonitor } from './index'; + +describe('activity_monitor', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('[title="Activity monitor"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.test.tsx new file mode 100644 index 0000000000000..a2685017f86d6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineHeaderPage } from './index'; + +describe('detection_engine_header_page', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('[title="Title"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.test.tsx new file mode 100644 index 0000000000000..0e2589150e858 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { NoApiIntegrationKeyCallOut } from './index'; + +describe('no_api_integration_callout', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.test.tsx new file mode 100644 index 0000000000000..2e6890e60fc61 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { NoWriteSignalsCallOut } from './index'; + +describe('no_write_signals_callout', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.tsx new file mode 100644 index 0000000000000..b66a9fc881045 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.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 { shallow } from 'enzyme'; + +import { SignalsTableComponent } from './index'; + +describe('SignalsTableComponent', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[title="Signals"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index 6cdb2f326901e..ce8ae2054b2c7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -61,7 +61,7 @@ interface OwnProps { type SignalsTableComponentProps = OwnProps & PropsFromRedux; -const SignalsTableComponent: React.FC = ({ +export const SignalsTableComponent: React.FC = ({ canUserCRUD, clearEventsDeleted, clearEventsLoading, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.test.tsx new file mode 100644 index 0000000000000..dd30bb1b0a74d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { SignalsTableFilterGroup } from './index'; + +describe('SignalsTableFilterGroup', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiFilterButton')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx deleted file mode 100644 index bb45ff68cb01d..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx +++ /dev/null @@ -1,92 +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 { EuiContextMenuItem } from '@elastic/eui'; -import React from 'react'; -import * as i18n from './translations'; -import { TimelineNonEcsData } from '../../../../../graphql/types'; -import { SendSignalsToTimeline, UpdateSignalsStatus } from '../types'; -import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; - -interface GetBatchItems { - areEventsLoading: boolean; - allEventsSelected: boolean; - selectedEventIds: Readonly>; - updateSignalsStatus: UpdateSignalsStatus; - sendSignalsToTimeline: SendSignalsToTimeline; - closePopover: () => void; - isFilteredToOpen: boolean; -} -/** - * Returns ViewInTimeline / UpdateSignalStatus actions to be display within an EuiContextMenuPanel - * - * @param areEventsLoading are any events loading - * @param allEventsSelected are all events on all pages selected - * @param selectedEventIds - * @param updateSignalsStatus function for updating signal status - * @param sendSignalsToTimeline function for sending signals to timeline - * @param closePopover - * @param isFilteredToOpen currently selected filter options - */ -export const getBatchItems = ({ - areEventsLoading, - allEventsSelected, - selectedEventIds, - updateSignalsStatus, - sendSignalsToTimeline, - closePopover, - isFilteredToOpen, -}: GetBatchItems) => { - const allDisabled = areEventsLoading || Object.keys(selectedEventIds).length === 0; - const sendToTimelineDisabled = allEventsSelected || uniqueRuleCount(selectedEventIds) > 1; - const filterString = isFilteredToOpen - ? i18n.BATCH_ACTION_CLOSE_SELECTED - : i18n.BATCH_ACTION_OPEN_SELECTED; - - return [ - { - closePopover(); - sendSignalsToTimeline(); - }} - > - {i18n.BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE} - , - - { - closePopover(); - await updateSignalsStatus({ - signalIds: Object.keys(selectedEventIds), - status: isFilteredToOpen ? FILTER_CLOSED : FILTER_OPEN, - }); - }} - > - {filterString} - , - ]; -}; - -/** - * Returns the number of unique rules for a given list of signals - * - * @param signals - */ -export const uniqueRuleCount = ( - signals: Readonly> -): number => { - const ruleIds = Object.values(signals).flatMap( - data => data.find(d => d.field === 'signal.rule.id')?.value - ); - - return Array.from(new Set(ruleIds)).length; -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.test.tsx new file mode 100644 index 0000000000000..6cab43b5285b5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.test.tsx @@ -0,0 +1,33 @@ +/* + * 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 { SignalsUtilityBar } from './index'; + +jest.mock('../../../../../lib/kibana'); + +describe('SignalsUtilityBar', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[dataTestSubj="openCloseSignal"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx new file mode 100644 index 0000000000000..6921c49d8a8b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 { SignalsHistogramPanel } from './index'; + +jest.mock('../../../../lib/kibana'); +jest.mock('../../../../components/navigation/use_get_url_search'); + +describe('SignalsHistogramPanel', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[id="detections-histogram"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx new file mode 100644 index 0000000000000..5eb9beaaaf76a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx @@ -0,0 +1,22 @@ +/* + * 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 { SignalsHistogram } from './signals_histogram'; + +jest.mock('../../../../lib/kibana'); + +describe('SignalsHistogram', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('Chart')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx new file mode 100644 index 0000000000000..b3d710de5e94e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useUserInfo } from './index'; + +import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; +import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; +import { useKibana } from '../../../../lib/kibana'; +jest.mock('../../../../containers/detection_engine/signals/use_privilege_user'); +jest.mock('../../../../containers/detection_engine/signals/use_signal_index'); +jest.mock('../../../../lib/kibana'); + +describe('useUserInfo', () => { + beforeAll(() => { + (usePrivilegeUser as jest.Mock).mockReturnValue({}); + (useSignalIndex as jest.Mock).mockReturnValue({}); + (useKibana as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, + }, + }); + }); + it('returns default state', () => { + const { result } = renderHook(() => useUserInfo()); + + expect(result).toEqual({ + current: { + canUserCRUD: null, + hasEncryptionKey: null, + hasIndexManage: null, + hasIndexWrite: null, + isAuthenticated: null, + isSignalIndexExists: null, + loading: true, + signalIndexName: null, + }, + error: undefined, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.tsx new file mode 100644 index 0000000000000..779e9a4557f2a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.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 { shallow } from 'enzyme'; +import { useParams } from 'react-router-dom'; + +import '../../mock/match_media'; +import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; +import { DetectionEnginePageComponent } from './detection_engine'; +import { useUserInfo } from './components/user_info'; + +jest.mock('./components/user_info'); +jest.mock('../../lib/kibana'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +describe('DetectionEnginePageComponent', () => { + beforeAll(() => { + (useParams as jest.Mock).mockReturnValue({}); + (useUserInfo as jest.Mock).mockReturnValue({}); + }); + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('WithSource')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 1bd7ab2c4f1ae..a26d7f5672106 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -59,7 +59,7 @@ const detectionsTabs: Record = { }, }; -const DetectionEnginePageComponent: React.FC = ({ +export const DetectionEnginePageComponent: React.FC = ({ filters, query, setAbsoluteRangeDatePicker, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx new file mode 100644 index 0000000000000..f64526fd2f7c4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; +jest.mock('../../lib/kibana'); + +describe('DetectionEngineEmptyPage', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EmptyPage')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx new file mode 100644 index 0000000000000..e9f05f7aafe3c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; +jest.mock('../../lib/kibana'); + +describe('DetectionEngineNoIndex', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EmptyPage')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx new file mode 100644 index 0000000000000..e71f4de2b010b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; +jest.mock('../../lib/kibana'); + +describe('DetectionEngineUserUnauthenticated', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EmptyPage')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts deleted file mode 100644 index 1399df0fcf6d1..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts +++ /dev/null @@ -1,18 +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 sampleChartOptions = [ - { text: 'Risk scores', value: 'risk_scores' }, - { text: 'Severities', value: 'severities' }, - { text: 'Top destination IPs', value: 'destination_ips' }, - { text: 'Top event actions', value: 'event_actions' }, - { text: 'Top event categories', value: 'event_categories' }, - { text: 'Top host names', value: 'host_names' }, - { text: 'Top rule types', value: 'rule_types' }, - { text: 'Top rules', value: 'rules' }, - { text: 'Top source IPs', value: 'source_ips' }, - { text: 'Top users', value: 'users' }, -]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.test.tsx new file mode 100644 index 0000000000000..6c4980f1d1500 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import '../../mock/match_media'; +import { DetectionEngineContainer } from './index'; + +describe('DetectionEngineContainer', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('ManageUserInfo')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.tsx new file mode 100644 index 0000000000000..f4955c2a93b8d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.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 React from 'react'; +import { shallow } from 'enzyme'; + +import { AllRules } from './index'; + +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useHistory: jest.fn(), + }; +}); + +describe('AllRules', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[title="All rules"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx new file mode 100644 index 0000000000000..92f69d79110d2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -0,0 +1,20 @@ +/* + * 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 { RulesTableFilters } from './rules_table_filters'; + +describe('RulesTableFilters', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="show-elastic-rules-filter-button"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx new file mode 100644 index 0000000000000..e31b8394e07d6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.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 { TagsFilterPopover } from './tags_filter_popover'; + +describe('TagsFilterPopover', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('EuiPopover')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.test.tsx new file mode 100644 index 0000000000000..9202da3336565 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { AccordionTitle } from './index'; + +describe('AccordionTitle', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('h6').text()).toContain('title'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.tsx new file mode 100644 index 0000000000000..eafa89a33f596 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.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 from 'react'; +import { shallow } from 'enzyme'; + +import { AddItem } from './index'; +import { useFormFieldMock } from '../../../../../../public/mock/test_providers'; + +describe('AddItem', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[iconType="plusInCircle"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx new file mode 100644 index 0000000000000..3dab83bca2946 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx @@ -0,0 +1,46 @@ +/* + * 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, { useRef } from 'react'; +import { shallow } from 'enzyme'; + +import { AllRulesTables } from './index'; + +describe('AllRulesTables', () => { + it('renders correctly', () => { + const Component = () => { + const ref = useRef(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="rules-table"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx new file mode 100644 index 0000000000000..c0e957d94261f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { AnomalyThresholdSlider } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('AnomalyThresholdSlider', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('EuiRange')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx index 19d1c698cbd9b..01fddf98b97d8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx @@ -16,10 +16,10 @@ interface AnomalyThresholdSliderProps { type Event = React.ChangeEvent; type EventArg = Event | React.MouseEvent; -export const AnomalyThresholdSlider: React.FC = ({ +export const AnomalyThresholdSlider = ({ describedByIds = [], field, -}) => { +}: AnomalyThresholdSliderProps) => { const threshold = field.value as number; const onThresholdChange = useCallback( (event: EventArg) => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.tsx new file mode 100644 index 0000000000000..59231c31d15bb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.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 from 'react'; +import { shallow } from 'enzyme'; + +import { MlJobDescription, AuditIcon, JobStatusBadge } from './ml_job_description'; +jest.mock('../../../../../lib/kibana'); + +const job = { + moduleId: 'moduleId', + defaultIndexPattern: 'defaultIndexPattern', + isCompatible: true, + isInstalled: true, + isElasticJob: true, + datafeedId: 'datafeedId', + datafeedIndices: [], + datafeedState: 'datafeedState', + description: 'description', + groups: [], + hasDatafeed: true, + id: 'id', + isSingleMetricViewerJob: false, + jobState: 'jobState', + memory_status: 'memory_status', + processed_record_count: 0, +}; + +describe('MlJobDescription', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="machineLearningJobId"]')).toHaveLength(1); + }); +}); + +describe('AuditIcon', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiToolTip')).toHaveLength(0); + }); +}); + +describe('JobStatusBadge', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiBadge')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx index 5a9593f1a6de2..1664ea320bc1e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx @@ -20,7 +20,7 @@ enum MessageLevels { error = 'error', } -const AuditIcon: React.FC<{ +const AuditIconComponent: React.FC<{ message: SiemJob['auditMessage']; }> = ({ message }) => { if (!message) { @@ -45,7 +45,9 @@ const AuditIcon: React.FC<{ ); }; -export const JobStatusBadge: React.FC<{ job: SiemJob }> = ({ job }) => { +export const AuditIcon = React.memo(AuditIconComponent); + +const JobStatusBadgeComponent: React.FC<{ job: SiemJob }> = ({ job }) => { const isStarted = isJobStarted(job.jobState, job.datafeedState); const color = isStarted ? 'secondary' : 'danger'; const text = isStarted ? ML_JOB_STARTED : ML_JOB_STOPPED; @@ -57,6 +59,8 @@ export const JobStatusBadge: React.FC<{ job: SiemJob }> = ({ job }) => { ); }; +export const JobStatusBadge = React.memo(JobStatusBadgeComponent); + const JobLink = styled(EuiLink)` margin-right: ${({ theme }) => theme.eui.euiSizeS}; `; @@ -65,7 +69,7 @@ const Wrapper = styled.div` overflow: hidden; `; -export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { +const MlJobDescriptionComponent: React.FC<{ job: SiemJob }> = ({ job }) => { const jobUrl = useKibana().services.application.getUrlForApp( `ml#/jobs?mlManagement=(jobId:${encodeURI(job.id)})` ); @@ -83,6 +87,8 @@ export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { ); }; +export const MlJobDescription = React.memo(MlJobDescriptionComponent); + export const buildMlJobDescription = ( jobId: string, label: string, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx new file mode 100644 index 0000000000000..dc201eb21c911 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx @@ -0,0 +1,13 @@ +/* + * 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 { isMitreAttackInvalid } from './helpers'; + +describe('isMitreAttackInvalid', () => { + it('returns true if tacticName is empty', () => { + expect(isMitreAttackInvalid('', undefined)).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.tsx new file mode 100644 index 0000000000000..3e8d542682456 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.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 { shallow } from 'enzyme'; + +import { AddMitreThreat } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('AddMitreThreat', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="addMitre"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.tsx new file mode 100644 index 0000000000000..dea27d8d04536 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.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 { shallow } from 'enzyme'; + +import { MlJobSelect } from './index'; +import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../components/ml_popover/hooks/use_siem_jobs'); +jest.mock('../../../../../lib/kibana'); + +describe('MlJobSelect', () => { + beforeAll(() => { + (useSiemJobs as jest.Mock).mockReturnValue([false, []]); + }); + + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="mlJobSelect"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx new file mode 100644 index 0000000000000..789f12f290a34 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx @@ -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. + */ + +import { shallow } from 'enzyme'; + +import { OptionalFieldLabel } from './index'; + +describe('OptionalFieldLabel', () => { + it('renders correctly', () => { + const wrapper = shallow(OptionalFieldLabel); + + expect(wrapper.find('EuiTextColor')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.tsx new file mode 100644 index 0000000000000..fefc9697176c4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.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 { shallow } from 'enzyme'; + +import { PickTimeline } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('PickTimeline', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="pick-timeline"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx new file mode 100644 index 0000000000000..8ace42fc5c3f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { PrePackagedRulesPrompt } from './load_empty_prompt'; + +describe('PrePackagedRulesPrompt', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('EmptyPrompt')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx index 1cff4751e8188..5d136265ef1f2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -15,6 +15,8 @@ const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ `; +EmptyPrompt.displayName = 'EmptyPrompt'; + interface PrePackagedRulesPromptProps { createPrePackagedRules: () => void; loading: boolean; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx new file mode 100644 index 0000000000000..807da79fb7a1a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { UpdatePrePackagedRulesCallOut } from './update_callout'; +import { useKibana } from '../../../../../lib/kibana'; +jest.mock('../../../../../lib/kibana'); + +describe('UpdatePrePackagedRulesCallOut', () => { + beforeAll(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + docLinks: { + ELASTIC_WEBSITE_URL: '', + DOC_LINK_VERSION: '', + }, + }, + }); + }); + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx new file mode 100644 index 0000000000000..cdd06ad58bb4b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx @@ -0,0 +1,37 @@ +/* + * 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 { QueryBarDefineRule } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +jest.mock('../../../../../lib/kibana'); + +describe('QueryBarDefineRule', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="query-bar-define-rule"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.test.tsx new file mode 100644 index 0000000000000..e761cb3323b2c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { ReadOnlyCallOut } from './index'; + +describe('ReadOnlyCallOut', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx new file mode 100644 index 0000000000000..4cfad36b2933f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx @@ -0,0 +1,33 @@ +/* + * 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 { RuleActionsField } from './index'; +import { useKibana } from '../../../../../lib/kibana'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../lib/kibana'); + +describe('RuleActionsField', () => { + it('should not render ActionForm is no actions are supported', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + triggers_actions_ui: { + actionTypeRegistry: {}, + }, + }, + }); + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('ActionForm')).toHaveLength(0); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx new file mode 100644 index 0000000000000..aba30e4b7f3ca --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx @@ -0,0 +1,13 @@ +/* + * 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 { getStatusColor } from './helpers'; + +describe('rule_status helpers', () => { + it('getStatusColor returns subdued if null was provided', () => { + expect(getStatusColor(null)).toBe('subdued'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.test.tsx new file mode 100644 index 0000000000000..6e230de11c4f3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { RuleStatus } from './index'; + +describe('RuleStatus', () => { + it('renders loader correctly', () => { + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="rule-status-loader"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.tsx new file mode 100644 index 0000000000000..3829af02ca4f1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.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 { shallow } from 'enzyme'; + +import { ScheduleItem } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('ScheduleItem', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="schedule-item"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.test.tsx new file mode 100644 index 0000000000000..3d832d61abb28 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.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 { SelectRuleType } from './index'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../lib/kibana'); + +describe('SelectRuleType', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="selectRuleType"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.test.tsx new file mode 100644 index 0000000000000..a9dddfedc2bab --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { SeverityBadge } from './index'; + +describe('SeverityBadge', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiHealth')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx new file mode 100644 index 0000000000000..89b8a56e79054 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx @@ -0,0 +1,22 @@ +/* + * 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 { TestProviders } from '../../../../../mock'; +import { RuleStatusIcon } from './index'; +jest.mock('../../../../../lib/kibana'); + +describe('RuleStatusIcon', () => { + it('renders correctly', () => { + const wrapper = shallow(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('EuiAvatar')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.test.tsx new file mode 100644 index 0000000000000..af0547ea03261 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/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 React from 'react'; +import { shallow } from 'enzyme'; + +import { StepContentWrapper } from './index'; + +describe('StepContentWrapper', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('div')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx index b04a321dab05b..a7343a87a7ef8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx @@ -16,3 +16,5 @@ StyledDiv.defaultProps = { }; export const StepContentWrapper = React.memo(StyledDiv); + +StepContentWrapper.displayName = 'StepContentWrapper'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx new file mode 100644 index 0000000000000..ebef6348d477e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx @@ -0,0 +1,20 @@ +/* + * 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 { StepDefineRule } from './index'; + +jest.mock('../../../../../lib/kibana'); + +describe('StepDefineRule', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('Form[data-test-subj="stepDefineRule"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx new file mode 100644 index 0000000000000..ce01d6995a2d8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx @@ -0,0 +1,22 @@ +/* + * 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 { StepPanel } from './index'; + +describe('StepPanel', () => { + it('renders correctly', () => { + const wrapper = shallow( + +
+ + ); + + expect(wrapper.find('MyPanel')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx index 88cecadb8b137..1923ed09252dd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx @@ -20,6 +20,8 @@ const MyPanel = styled(EuiPanel)` position: relative; `; +MyPanel.displayName = 'MyPanel'; + const StepPanelComponent: React.FC = ({ children, loading, title }) => ( {loading && } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx new file mode 100644 index 0000000000000..69d118ba9f28e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx @@ -0,0 +1,22 @@ +/* + * 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 { StepRuleActions } from './index'; + +jest.mock('../../../../../lib/kibana'); + +describe('StepRuleActions', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="stepRuleActions"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index bc3b0dfe720bc..1b27d0e0fcc0e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* istanbul ignore file */ + import { i18n } from '@kbn/i18n'; import { FormSchema } from '../../../../../shared_imports'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.tsx new file mode 100644 index 0000000000000..98de933590d60 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.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 { shallow, mount } from 'enzyme'; + +import { TestProviders } from '../../../../../mock'; +import { StepScheduleRule } from './index'; + +describe('StepScheduleRule', () => { + it('renders correctly', () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('Form[data-test-subj="stepScheduleRule"]')).toHaveLength(1); + }); + + it('renders correctly if isReadOnlyView', () => { + const wrapper = shallow(); + + expect(wrapper.find('StepContentWrapper')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx index 8fbfdf5f25a51..e79aec2be6e15 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* istanbul ignore file */ + import { i18n } from '@kbn/i18n'; import { OptionalFieldLabel } from '../optional_field_label'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx new file mode 100644 index 0000000000000..0ab19b671494e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { ThrottleSelectField } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('ThrottleSelectField', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('SelectField')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx new file mode 100644 index 0000000000000..db32be652d0f7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 { TestProviders } from '../../../../mock'; +import { CreateRulePage } from './index'; +import { useUserInfo } from '../../components/user_info'; + +jest.mock('../../components/user_info'); + +describe('CreateRulePage', () => { + it('renders correctly', () => { + (useUserInfo as jest.Mock).mockReturnValue({}); + const wrapper = shallow(, { wrappingComponent: TestProviders }); + + expect(wrapper.find('[title="Create new rule"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.tsx new file mode 100644 index 0000000000000..a83ff4c54b076 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.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 { shallow } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { FailureHistory } from './failure_history'; +import { useRuleStatus } from '../../../../containers/detection_engine/rules'; +jest.mock('../../../../containers/detection_engine/rules'); + +describe('FailureHistory', () => { + beforeAll(() => { + (useRuleStatus as jest.Mock).mockReturnValue([false, null]); + }); + + it('renders correctly', () => { + const wrapper = shallow(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('EuiBasicTable')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx new file mode 100644 index 0000000000000..19c6f39a9bc7e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 '../../../../mock/match_media'; +import { TestProviders } from '../../../../mock'; +import { RuleDetailsPageComponent } from './index'; +import { setAbsoluteRangeDatePicker } from '../../../../store/inputs/actions'; +import { useUserInfo } from '../../components/user_info'; +import { useParams } from 'react-router-dom'; + +jest.mock('../../components/user_info'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +describe('RuleDetailsPageComponent', () => { + beforeAll(() => { + (useUserInfo as jest.Mock).mockReturnValue({}); + (useParams as jest.Mock).mockReturnValue({}); + }); + + it('renders correctly', () => { + const wrapper = shallow( + , + { + wrappingComponent: TestProviders, + } + ); + + expect(wrapper.find('WithSource')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 2b648a3b3f825..14e5f2b90882e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -88,7 +88,7 @@ const ruleDetailTabs = [ }, ]; -const RuleDetailsPageComponent: FC = ({ +export const RuleDetailsPageComponent: FC = ({ filters, query, setAbsoluteRangeDatePicker, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.test.tsx new file mode 100644 index 0000000000000..3394b0fc8c5c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.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 React from 'react'; +import { shallow } from 'enzyme'; + +import { RuleStatusFailedCallOut } from './status_failed_callout'; + +describe('RuleStatusFailedCallOut', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx new file mode 100644 index 0000000000000..d22bc12abf9fa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx @@ -0,0 +1,33 @@ +/* + * 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 { TestProviders } from '../../../../mock'; +import { EditRulePage } from './index'; +import { useUserInfo } from '../../components/user_info'; +import { useParams } from 'react-router-dom'; + +jest.mock('../../components/user_info'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +describe('EditRulePage', () => { + it('renders correctly', () => { + (useUserInfo as jest.Mock).mockReturnValue({}); + (useParams as jest.Mock).mockReturnValue({}); + const wrapper = shallow(, { wrappingComponent: TestProviders }); + + expect(wrapper.find('[title="Edit rule settings"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx index 443dbd2c93a35..1c01a19573cd6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx @@ -302,11 +302,25 @@ describe('rule helpers', () => { test('returns expected ActionsStepRule rule object', () => { const mockedRule = { ...mockRule('test-id'), - actions: [], + actions: [ + { + id: 'id', + group: 'group', + params: {}, + action_type_id: 'action_type_id', + }, + ], }; const result: ActionsStepRule = getActionsStepsData(mockedRule); const expected = { - actions: [], + actions: [ + { + id: 'id', + group: 'group', + params: {}, + actionTypeId: 'action_type_id', + }, + ], enabled: mockedRule.enabled, isNew: false, throttle: 'no_actions', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.tsx new file mode 100644 index 0000000000000..3fa81ca3ced08 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.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 { shallow } from 'enzyme'; + +import { RulesPage } from './index'; +import { useUserInfo } from '../components/user_info'; +import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; + +jest.mock('../components/user_info'); +jest.mock('../../../containers/detection_engine/rules'); + +describe('RulesPage', () => { + beforeAll(() => { + (useUserInfo as jest.Mock).mockReturnValue({}); + (usePrePackagedRules as jest.Mock).mockReturnValue({}); + }); + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('AllRules')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.ts new file mode 100644 index 0000000000000..34a521ed32b12 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.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 { getBreadcrumbs } from './utils'; + +describe('getBreadcrumbs', () => { + it('returns default value for incorrect params', () => { + expect( + getBreadcrumbs( + { + pageName: 'pageName', + detailName: 'detailName', + tabName: undefined, + search: '', + pathName: 'pathName', + }, + [] + ) + ).toEqual([{ href: '#/link-to/detections', text: 'Detections' }]); + }); +}); From 423a297f753e36dc98b107ecd48971d25ebefbb7 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 15 Apr 2020 09:23:57 -0400 Subject: [PATCH 04/44] [Ingest] Use agent.yml template when creating a datasource stream from a package (#62631) --- .../datasource_to_agent_datasource.test.ts | 34 ++--- .../datasource_to_agent_datasource.ts | 35 +---- .../common/types/models/datasource.ts | 1 + .../enrollment_token_list_page/index.tsx | 13 +- .../server/routes/datasource/handlers.ts | 37 ++++- .../ingest_manager/server/saved_objects.ts | 1 + .../server/services/datasource.test.ts | 137 ++++++++++++++++++ .../server/services/datasource.ts | 69 ++++++++- .../server/services/epm/agent/agent.test.ts | 46 +++--- .../server/services/epm/agent/agent.ts | 19 +-- .../epm/agent/tests/input.generated.yaml | 5 - .../server/services/epm/agent/tests/input.yml | 7 - .../services/epm/agent/tests/manifest.yml | 20 --- .../server/services/epm/packages/assets.ts | 10 ++ .../ingest_manager/server/services/setup.ts | 16 +- 15 files changed, 311 insertions(+), 139 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/server/services/datasource.test.ts delete mode 100644 x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml delete mode 100644 x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml delete mode 100644 x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts index b897c03e89f82..3496ea782ee99 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts @@ -38,6 +38,10 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { fooVar: { value: 'foo-value' }, fooVar2: { value: [1, 2] }, }, + pkg_stream: { + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, }, { id: 'test-logs-bar', @@ -95,7 +99,7 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }); }); - it('returns agent datasource config with flattened input and stream configs', () => { + it('returns agent datasource config with flattened input and package stream', () => { expect(storedDatasourceToAgentDatasource({ ...mockDatasource, inputs: [mockInput] })).toEqual({ id: 'mock-datasource', namespace: 'default', @@ -105,34 +109,18 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { { type: 'test-logs', enabled: true, - inputVar: 'input-value', - inputVar3: { - testField: 'test', - }, streams: [ { id: 'test-logs-foo', enabled: true, dataset: 'foo', - fooVar: 'foo-value', - fooVar2: [1, 2], + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], }, { id: 'test-logs-bar', enabled: true, dataset: 'bar', - barVar: 'bar-value', - barVar2: [1, 2], - barVar3: [ - { - namespace: 'mockNamespace', - anotherProp: 'test', - }, - { - namespace: 'mockNamespace2', - anotherProp: 'test2', - }, - ], }, ], }, @@ -160,17 +148,13 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { { type: 'test-logs', enabled: true, - inputVar: 'input-value', - inputVar3: { - testField: 'test', - }, streams: [ { id: 'test-logs-foo', enabled: true, dataset: 'foo', - fooVar: 'foo-value', - fooVar2: [1, 2], + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], }, ], }, diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts index 20bbbec8919d6..b509878b7f945 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts @@ -3,38 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { safeLoad } from 'js-yaml'; -import { - Datasource, - NewDatasource, - DatasourceConfigRecord, - DatasourceConfigRecordEntry, - FullAgentConfigDatasource, -} from '../types'; +import { Datasource, NewDatasource, FullAgentConfigDatasource } from '../types'; import { DEFAULT_OUTPUT } from '../constants'; -const configReducer = ( - configResult: DatasourceConfigRecord, - configEntry: [string, DatasourceConfigRecordEntry] -): DatasourceConfigRecord => { - const [configName, { type: configType, value: configValue }] = configEntry; - if (configValue !== undefined && configValue !== '') { - if (configType === 'yaml') { - try { - const yamlValue = safeLoad(configValue); - if (yamlValue) { - configResult[configName] = yamlValue; - } - } catch (e) { - // Silently swallow parsing error - } - } else { - configResult[configName] = configValue; - } - } - return configResult; -}; - export const storedDatasourceToAgentDatasource = ( datasource: Datasource | NewDatasource ): FullAgentConfigDatasource => { @@ -50,14 +21,14 @@ export const storedDatasourceToAgentDatasource = ( .map(input => { const fullInput = { ...input, - ...Object.entries(input.config || {}).reduce(configReducer, {}), streams: input.streams .filter(stream => stream.enabled) .map(stream => { const fullStream = { ...stream, - ...Object.entries(stream.config || {}).reduce(configReducer, {}), + ...stream.pkg_stream, }; + delete fullStream.pkg_stream; delete fullStream.config; return fullStream; }), diff --git a/x-pack/plugins/ingest_manager/common/types/models/datasource.ts b/x-pack/plugins/ingest_manager/common/types/models/datasource.ts index ee4d24ab11777..48243a12120f9 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/datasource.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/datasource.ts @@ -23,6 +23,7 @@ export interface DatasourceInputStream { dataset: string; processors?: string[]; config?: DatasourceConfigRecord; + pkg_stream?: any; } export interface DatasourceInput { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx index e4f7202aeee10..7520f88215efe 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx @@ -72,7 +72,7 @@ const ApiKeyField: React.FunctionComponent<{ apiKeyId: string }> = ({ apiKeyId } {key} ) : ( - •••••••••••••••••••••••••• + ••••••••••••••••••••• )} @@ -151,11 +151,10 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { defaultMessage: 'Name', }), truncateText: true, - width: '300px', textOnly: true, render: (name: string) => { return ( - + {name} ); @@ -166,7 +165,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.secretTitle', { defaultMessage: 'Secret', }), - width: '245px', + width: '215px', render: (apiKeyId: string) => { return ; }, @@ -186,7 +185,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.createdAtTitle', { defaultMessage: 'Created on', }), - width: '200px', + width: '150px', render: (createdAt: string) => { return createdAt ? ( @@ -198,7 +197,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.activeTitle', { defaultMessage: 'Active', }), - width: '80px', + width: '70px', render: (active: boolean) => { return ( @@ -212,7 +211,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.actionsTitle', { defaultMessage: 'Actions', }), - width: '100px', + width: '70px', render: (_: any, apiKey: EnrollmentAPIKey) => { return ( apiKey.active && ( diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts index 7ae562cf130ab..56d6053a1451b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; +import Boom from 'boom'; import { RequestHandler } from 'src/core/server'; import { appContextService, datasourceService } from '../../services'; import { ensureInstalledPackage } from '../../services/epm/packages'; @@ -75,6 +76,7 @@ export const createDatasourceHandler: RequestHandler< const soClient = context.core.savedObjects.client; const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + const newData = { ...request.body }; try { // Make sure the datasource package is installed if (request.body.package?.name) { @@ -83,10 +85,18 @@ export const createDatasourceHandler: RequestHandler< pkgName: request.body.package.name, callCluster, }); + + newData.inputs = (await datasourceService.assignPackageStream( + { + pkgName: request.body.package.name, + pkgVersion: request.body.package.version, + }, + request.body.inputs + )) as TypeOf['inputs']; } // Create datasource - const datasource = await datasourceService.create(soClient, request.body, { user }); + const datasource = await datasourceService.create(soClient, newData, { user }); const body: CreateDatasourceResponse = { item: datasource, success: true }; return response.ok({ body, @@ -107,14 +117,33 @@ export const updateDatasourceHandler: RequestHandler< const soClient = context.core.savedObjects.client; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; try { - const datasource = await datasourceService.update( + const datasource = await datasourceService.get(soClient, request.params.datasourceId); + + if (!datasource) { + throw Boom.notFound('Datasource not found'); + } + + const newData = { ...request.body }; + const pkg = newData.package || datasource.package; + const inputs = newData.inputs || datasource.inputs; + if (pkg && (newData.inputs || newData.package)) { + newData.inputs = (await datasourceService.assignPackageStream( + { + pkgName: pkg.name, + pkgVersion: pkg.version, + }, + inputs + )) as TypeOf['inputs']; + } + + const updatedDatasource = await datasourceService.update( soClient, request.params.datasourceId, - request.body, + newData, { user } ); return response.ok({ - body: { item: datasource, success: true }, + body: { item: updatedDatasource, success: true }, }); } catch (e) { return response.customError({ diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 6800cb4056700..1c36fda36847d 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -137,6 +137,7 @@ export const savedObjectMappings = { dataset: { type: 'keyword' }, processors: { type: 'keyword' }, config: { type: 'flattened' }, + pkg_stream: { type: 'flattened' }, }, }, }, diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.test.ts b/x-pack/plugins/ingest_manager/server/services/datasource.test.ts new file mode 100644 index 0000000000000..09c59998388d1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/datasource.test.ts @@ -0,0 +1,137 @@ +/* + * 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 { datasourceService } from './datasource'; + +async function mockedGetAssetsData(_a: any, _b: any, dataset: string) { + if (dataset === 'dataset1') { + return [ + { + buffer: Buffer.from(` +type: log +metricset: ["dataset1"] +paths: +{{#each paths}} +- {{this}} +{{/each}} +`), + }, + ]; + } + return []; +} + +jest.mock('./epm/packages/assets', () => { + return { + getAssetsDataForPackageKey: mockedGetAssetsData, + }; +}); + +describe('Datasource service', () => { + describe('assignPackageStream', () => { + it('should work with cofig variables from the stream', async () => { + const inputs = await datasourceService.assignPackageStream( + { + pkgName: 'package', + pkgVersion: '1.0.0', + }, + [ + { + type: 'log', + enabled: true, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + }, + ], + }, + ] + ); + + expect(inputs).toEqual([ + { + type: 'log', + enabled: true, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + pkg_stream: { + metricset: ['dataset1'], + paths: ['/var/log/set.log'], + type: 'log', + }, + }, + ], + }, + ]); + }); + + it('should work with config variables at the input level', async () => { + const inputs = await datasourceService.assignPackageStream( + { + pkgName: 'package', + pkgVersion: '1.0.0', + }, + [ + { + type: 'log', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + }, + ], + }, + ] + ); + + expect(inputs).toEqual([ + { + type: 'log', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + pkg_stream: { + metricset: ['dataset1'], + paths: ['/var/log/set.log'], + type: 'log', + }, + }, + ], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.ts b/x-pack/plugins/ingest_manager/server/services/datasource.ts index 1b8f2a690b94d..f27252aaa9a84 100644 --- a/x-pack/plugins/ingest_manager/server/services/datasource.ts +++ b/x-pack/plugins/ingest_manager/server/services/datasource.ts @@ -4,16 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsClientContract } from 'src/core/server'; +import { safeLoad } from 'js-yaml'; import { AuthenticatedUser } from '../../../security/server'; -import { DeleteDatasourcesResponse, packageToConfigDatasource } from '../../common'; +import { + DeleteDatasourcesResponse, + packageToConfigDatasource, + DatasourceInput, + DatasourceInputStream, +} from '../../common'; import { DATASOURCE_SAVED_OBJECT_TYPE } from '../constants'; import { NewDatasource, Datasource, ListWithKuery } from '../types'; import { agentConfigService } from './agent_config'; import { getPackageInfo, getInstallation } from './epm/packages'; import { outputService } from './output'; +import { getAssetsDataForPackageKey } from './epm/packages/assets'; +import { createStream } from './epm/agent/agent'; const SAVED_OBJECT_TYPE = DATASOURCE_SAVED_OBJECT_TYPE; +function getDataset(st: string) { + return st.split('.')[1]; +} + class DatasourceService { public async create( soClient: SavedObjectsClientContract, @@ -187,6 +199,61 @@ class DatasourceService { } } } + + public async assignPackageStream( + pkgInfo: { pkgName: string; pkgVersion: string }, + inputs: DatasourceInput[] + ): Promise { + const inputsPromises = inputs.map(input => _assignPackageStreamToInput(pkgInfo, input)); + return Promise.all(inputsPromises); + } +} + +const _isAgentStream = (p: string) => !!p.match(/agent\/stream\/stream\.yml/); + +async function _assignPackageStreamToInput( + pkgInfo: { pkgName: string; pkgVersion: string }, + input: DatasourceInput +) { + const streamsPromises = input.streams.map(stream => + _assignPackageStreamToStream(pkgInfo, input, stream) + ); + + const streams = await Promise.all(streamsPromises); + return { ...input, streams }; +} + +async function _assignPackageStreamToStream( + pkgInfo: { pkgName: string; pkgVersion: string }, + input: DatasourceInput, + stream: DatasourceInputStream +) { + if (!stream.enabled) { + return { ...stream, pkg_stream: undefined }; + } + const dataset = getDataset(stream.dataset); + const assetsData = await getAssetsDataForPackageKey(pkgInfo, _isAgentStream, dataset); + + const [pkgStream] = assetsData; + if (!pkgStream || !pkgStream.buffer) { + throw new Error(`Stream template not found for dataset ${dataset}`); + } + + // Populate template variables from input config and stream config + const data: { [k: string]: string | string[] } = {}; + if (input.config) { + for (const key of Object.keys(input.config)) { + data[key] = input.config[key].value; + } + } + if (stream.config) { + for (const key of Object.keys(stream.config)) { + data[key] = stream.config[key].value; + } + } + const yaml = safeLoad(createStream(data, pkgStream.buffer.toString())); + stream.pkg_stream = yaml; + return { ...stream }; } export const datasourceService = new DatasourceService(); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts index 4f75ba0332418..21de625532f03 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts @@ -4,29 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import fs from 'fs'; -import * as yaml from 'js-yaml'; -import path from 'path'; -import { createInput } from './agent'; +import { createStream } from './agent'; -test('test converting input and manifest into template', () => { - const manifest = yaml.safeLoad( - fs.readFileSync(path.join(__dirname, 'tests/manifest.yml'), 'utf8') - ); +test('Test creating a stream from template', () => { + const streamTemplate = ` +input: log +paths: +{{#each paths}} + - {{this}} +{{/each}} +exclude_files: [".gz$"] +processors: + - add_locale: ~ + `; + const vars = { + paths: ['/usr/local/var/log/nginx/access.log'], + }; - const inputTemplate = fs.readFileSync(path.join(__dirname, 'tests/input.yml'), 'utf8'); - const output = createInput(manifest.vars, inputTemplate); + const output = createStream(vars, streamTemplate); - // Golden file path - const generatedFile = path.join(__dirname, './tests/input.generated.yaml'); - - // Regenerate the file if `-generate` flag is used - if (process.argv.includes('-generate')) { - fs.writeFileSync(generatedFile, output); - } - - const outputData = fs.readFileSync(generatedFile, 'utf-8'); - - // Check that content file and generated file are equal - expect(outputData).toBe(output); + expect(output).toBe(` +input: log +paths: + - /usr/local/var/log/nginx/access.log +exclude_files: [".gz$"] +processors: + - add_locale: ~ + `); }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts index c7dd3dab38bc1..5d9a6d409aa1a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts @@ -5,19 +5,12 @@ */ import Handlebars from 'handlebars'; -import { RegistryVarsEntry } from '../../../types'; -/** - * This takes a dataset object as input and merges it with the input template. - * It returns the resolved template as a string. - */ -export function createInput(vars: RegistryVarsEntry[], inputTemplate: string): string { - const view: Record = {}; - - for (const v of vars) { - view[v.name] = v.default; - } +interface StreamVars { + [k: string]: string | string[]; +} - const template = Handlebars.compile(inputTemplate); - return template(view); +export function createStream(vars: StreamVars, streamTemplate: string) { + const template = Handlebars.compile(streamTemplate); + return template(vars); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml b/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml deleted file mode 100644 index 451ed554ce259..0000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml +++ /dev/null @@ -1,5 +0,0 @@ -type: log -paths: - - "/var/log/nginx/access.log*" - -tags: nginx diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml b/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml deleted file mode 100644 index 65a23fc2fa9ad..0000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml +++ /dev/null @@ -1,7 +0,0 @@ -type: log -paths: -{{#each paths}} - - "{{this}}" -{{/each}} - -tags: {{tags}} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml b/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml deleted file mode 100644 index 46a38179fe132..0000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml +++ /dev/null @@ -1,20 +0,0 @@ -title: Nginx Acess Logs -release: beta -type: logs -ingest_pipeline: default - -vars: - - name: paths - # Should we define this as array? How will the UI best make sense of it? - type: textarea - default: - - /var/log/nginx/access.log* - # I suggest to use ECS fields for this config options here: https://github.com/elastic/ecs/blob/master/schemas/os.yml - # This would need to be based on a predefined definition on what can be filtered on - os.darwin: - - /usr/local/var/log/nginx/access.log* - os.windows: - - c:/programdata/nginx/logs/*access.log* - - name: tags - default: [nginx] - type: text diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index 7026d9eae24c3..50d347c69cc24 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -71,3 +71,13 @@ export async function getAssetsData( return entries; } + +export async function getAssetsDataForPackageKey( + { pkgName, pkgVersion }: { pkgName: string; pkgVersion: string }, + filter = (path: string): boolean => true, + datasetName?: string +): Promise { + const registryPkgInfo = await Registry.fetchInfo(pkgName, pkgVersion); + + return getAssetsData(registryPkgInfo, filter, datasetName); +} diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index bbaf083fb8396..167a24481aba5 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -123,8 +123,18 @@ async function addPackageToConfig( pkgName: packageToInstall.name, pkgVersion: packageToInstall.version, }); - await datasourceService.create( - soClient, - packageToConfigDatasource(packageInfo, config.id, defaultOutput.id, undefined, config.namespace) + + const newDatasource = packageToConfigDatasource( + packageInfo, + config.id, + defaultOutput.id, + undefined, + config.namespace + ); + newDatasource.inputs = await datasourceService.assignPackageStream( + { pkgName: packageToInstall.name, pkgVersion: packageToInstall.version }, + newDatasource.inputs ); + + await datasourceService.create(soClient, newDatasource); } From 88d352392590ee08bc5ddbcfaf667d5d638a996b Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Wed, 15 Apr 2020 15:30:26 +0200 Subject: [PATCH 05/44] Add processors to devtools console autocomplete (#60553) This add completion support for the csv, circle, geoip, html strip, inference, set security user, urldecode and user agent processors to the dev tools console. --- .../server/lib/spec_definitions/js/ingest.ts | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/plugins/console/server/lib/spec_definitions/js/ingest.ts b/src/plugins/console/server/lib/spec_definitions/js/ingest.ts index 1182dc075f42f..20dbeda5e0b3d 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/ingest.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/ingest.ts @@ -57,6 +57,49 @@ const bytesProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-circle-processor.html +const circleProcessorDefinition = { + circle: { + __template: { + field: '', + error_distance: '', + shape_type: '', + }, + field: '', + target_field: '', + error_distance: '', + shape_type: { + __one_of: ['geo_shape', 'shape'], + }, + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/csv-processor.html +const csvProcessorDefinition = { + csv: { + __template: { + field: '', + target_fields: [''], + }, + field: '', + target_fields: [''], + separator: '', + quote: '', + empty_value: '', + trim: { + __one_of: [true, false], + }, + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/convert-processor.html const convertProcessorDefinition = { convert: { @@ -174,6 +217,25 @@ const foreachProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/geoip-processor.html +const geoipProcessorDefinition = { + geoip: { + __template: { + field: '', + }, + field: '', + target_field: '', + database_file: '', + properties: [''], + ignore_missing: { + __one_of: [false, true], + }, + first_only: { + __one_of: [false, true], + }, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/grok-processor.html const grokProcessorDefinition = { grok: { @@ -209,6 +271,37 @@ const gsubProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/htmlstrip-processor.html +const htmlStripProcessorDefinition = { + html_strip: { + __template: { + field: '', + }, + field: '', + target_field: '', + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html +const inferenceProcessorDefinition = { + inference: { + __template: { + model_id: '', + field_map: {}, + inference_config: {}, + }, + model_id: '', + field_map: {}, + inference_config: {}, + target_field: '', + ...commonPipelineParams, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/join-processor.html const joinProcessorDefinition = { join: { @@ -338,6 +431,18 @@ const setProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-node-set-security-user-processor.html +const setSecurityUserProcessorDefinition = { + set_security_user: { + __template: { + field: '', + }, + field: '', + properties: [''], + ...commonPipelineParams, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/split-processor.html const splitProcessorDefinition = { split: { @@ -394,10 +499,43 @@ const uppercaseProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/urldecode-processor.html +const urlDecodeProcessorDefinition = { + urldecode: { + __template: { + field: '', + }, + field: '', + target_field: '', + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/user-agent-processor.html +const userAgentProcessorDefinition = { + user_agent: { + __template: { + field: '', + }, + field: '', + target_field: '', + regex_file: '', + properties: [''], + ignore_missing: { + __one_of: [false, true], + }, + }, +}; + const processorDefinition = { __one_of: [ appendProcessorDefinition, bytesProcessorDefinition, + csvProcessorDefinition, + circleProcessorDefinition, convertProcessorDefinition, dateProcessorDefinition, dateIndexNameProcessorDefinition, @@ -406,8 +544,11 @@ const processorDefinition = { dropProcessorDefinition, failProcessorDefinition, foreachProcessorDefinition, + geoipProcessorDefinition, grokProcessorDefinition, gsubProcessorDefinition, + htmlStripProcessorDefinition, + inferenceProcessorDefinition, joinProcessorDefinition, jsonProcessorDefinition, kvProcessorDefinition, @@ -417,10 +558,13 @@ const processorDefinition = { renameProcessorDefinition, scriptProcessorDefinition, setProcessorDefinition, + setSecurityUserProcessorDefinition, splitProcessorDefinition, sortProcessorDefinition, trimProcessorDefinition, uppercaseProcessorDefinition, + urlDecodeProcessorDefinition, + userAgentProcessorDefinition, ], }; From 3b64a65ed6a103f86badcaa4d90cf3a992c9dbc0 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 15 Apr 2020 15:41:42 +0200 Subject: [PATCH 06/44] [Drilldowns] improve action.getHref() for drilldowns (#63228) getHref on Action interfaces in uiActions plugin is now async. getHref is now used only to support right click behaviour. execute() takes control on regular click. --- .../lib/actions/edit_panel_action.test.tsx | 2 +- .../public/lib/actions/edit_panel_action.ts | 11 ++++-- .../ui_actions/public/actions/action.ts | 6 ++-- .../public/actions/action_definition.ts | 2 +- .../public/actions/create_action.ts | 1 - .../build_eui_context_menu_panels.tsx | 36 ++++++++++++++----- .../public/triggers/trigger_internal.ts | 7 ---- .../public/sample_panel_link.ts | 6 ++-- 8 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index ce733bba6dda5..d07bf915845e9 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -56,7 +56,7 @@ test('getHref returns the edit urls', async () => { if (action.getHref) { const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true); expect( - action.getHref({ + await action.getHref({ embeddable, }) ).toBe(embeddable.getOutput().editUrl); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 9125dc0813f98..044e7b5d35ad8 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -62,11 +62,16 @@ export class EditPanelAction implements Action { return Boolean(canEditEmbeddable && inDashboardEditMode); } - public async execute() { - return; + public async execute(context: ActionContext) { + const href = await this.getHref(context); + if (href) { + // TODO: when apps start using browser router instead of hash router this has to be fixed + // https://github.com/elastic/kibana/issues/58217 + window.location.href = href; + } } - public getHref({ embeddable }: ActionContext): string { + public async getHref({ embeddable }: ActionContext): Promise { const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; return editUrl ? editUrl : ''; } diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index f532c2c8aa219..feaa1f6a60e2f 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -63,9 +63,11 @@ export interface Action { isCompatible(context: Context): Promise; /** - * If this returns something truthy, this is used in addition to the `execute` method when clicked. + * If this returns something truthy, this will be used as [href] attribute on a link if possible (e.g. in context menu item) + * to support right click -> open in a new tab behavior. + * For regular click navigation is prevented and `execute()` takes control. */ - getHref?(context: Context): string | undefined; + getHref?(context: Context): Promise; /** * Executes the action. diff --git a/src/plugins/ui_actions/public/actions/action_definition.ts b/src/plugins/ui_actions/public/actions/action_definition.ts index 3eaa13572a826..79fda78401abd 100644 --- a/src/plugins/ui_actions/public/actions/action_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_definition.ts @@ -63,7 +63,7 @@ export interface ActionDefinition { /** * If this returns something truthy, this is used in addition to the `execute` method when clicked. */ - getHref?(context: ActionContextMapping[T]): string | undefined; + getHref?(context: ActionContextMapping[T]): Promise; /** * Executes the action. diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 90a9415c0b497..cc66f221e4082 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -28,7 +28,6 @@ export function createAction(action: ActionDefinition): id: action.type, isCompatible: () => Promise.resolve(true), getDisplayName: () => '', - getHref: () => undefined, ...action, }; } diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index 3dce2c1f4c257..d26740ffdf033 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -71,7 +71,7 @@ async function buildEuiContextMenuPanelItems({ } items.push( - convertPanelActionToContextMenuItem({ + await convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, @@ -88,9 +88,9 @@ async function buildEuiContextMenuPanelItems({ * * @param {ContextMenuAction} action * @param {Embeddable} embeddable - * @return {EuiContextMenuPanelItemDescriptor} + * @return {Promise} */ -function convertPanelActionToContextMenuItem({ +async function convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, @@ -98,7 +98,7 @@ function convertPanelActionToContextMenuItem({ action: Action; actionContext: A; closeMenu: () => void; -}): EuiContextMenuPanelItemDescriptor { +}): Promise { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { name: action.MenuItem ? React.createElement(uiToReactComponent(action.MenuItem), { @@ -110,13 +110,33 @@ function convertPanelActionToContextMenuItem({ 'data-test-subj': `embeddablePanelAction-${action.id}`, }; - menuPanelItem.onClick = () => { - action.execute(actionContext); + menuPanelItem.onClick = event => { + if (event.currentTarget instanceof HTMLAnchorElement) { + // from react-router's + if ( + !event.defaultPrevented && // onClick prevented default + event.button === 0 && // ignore everything but left clicks + (!event.currentTarget.target || event.currentTarget.target === '_self') && // let browser handle "target=_blank" etc. + !(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) // ignore clicks with modifier keys + ) { + event.preventDefault(); + action.execute(actionContext); + } else { + // let browser handle navigation + } + } else { + // not a link + action.execute(actionContext); + } + closeMenu(); }; - if (action.getHref && action.getHref(actionContext)) { - menuPanelItem.href = action.getHref(actionContext); + if (action.getHref) { + const href = await action.getHref(actionContext); + if (href) { + menuPanelItem.href = href; + } } return menuPanelItem; diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index 5b670df354f78..1fc92d7c0cb1b 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -55,13 +55,6 @@ export class TriggerInternal { action: Action, context: TriggerContextMapping[T] ) { - const href = action.getHref && action.getHref(context); - - if (href) { - window.location.href = href; - return; - } - await action.execute(context); } diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts b/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts index b0f1219a815a3..faa774b8485b1 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts @@ -26,6 +26,8 @@ export const createSamplePanelLink = (): Action => createAction({ type: SAMPLE_PANEL_LINK, getDisplayName: () => 'Sample panel Link', - execute: async () => {}, - getHref: () => 'https://example.com/kibana/test', + execute: async () => { + window.location.href = 'https://example.com/kibana/test'; + }, + getHref: async () => 'https://example.com/kibana/test', }); From 6f1fe676f2c5fc053a0cb6e76d2355026dceae67 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 15 Apr 2020 06:43:17 -0700 Subject: [PATCH 07/44] Fixed inability to clear numeric field in a "Group over top docs" condition (#63543) --- .../public/common/expression_items/group_by_over.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index 6ad52a5416163..619d85d99719b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -146,16 +146,13 @@ export const GroupByExpression = ({ {groupByTypes[groupBy].sizeRequired ? ( - 0 && termSize !== undefined} - error={errors.termSize} - > + 0} error={errors.termSize}> 0 && termSize !== undefined} - value={termSize} + isInvalid={errors.termSize.length > 0} + value={termSize || ''} onChange={e => { const { value } = e.target; - const termSizeVal = value !== '' ? parseFloat(value) : MIN_TERM_SIZE; + const termSizeVal = value !== '' ? parseFloat(value) : undefined; onChangeSelectedTermSize(termSizeVal); }} min={MIN_TERM_SIZE} From d1d0a44d5d46673d2409cb1b44b8bf0c81107b35 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 15 Apr 2020 06:59:47 -0700 Subject: [PATCH 08/44] Closes #63113 by limiting service maps to only draw certain shapes in IE 11 without icons (#63558) --- .../app/ServiceMap/Popover/Contents.tsx | 36 +++++++++++++++---- .../app/ServiceMap/cytoscapeOptions.ts | 34 +++++++++++++----- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx index 52263878ca915..491ebdc5aad15 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx @@ -27,6 +27,30 @@ interface ContentsProps { selectedNodeServiceName: string; } +// IE 11 does not handle flex properties as expected. With browser detection, +// we can use regular div elements to render contents that are almost identical. +// +// This method of detecting IE is from a Stack Overflow answer: +// https://stackoverflow.com/a/21825207 +// +// @ts-ignore `documentMode` is not recognized as a valid property of `document`. +const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + +const FlexColumnGroup = (props: { + children: React.ReactNode; + style: React.CSSProperties; + direction: 'column'; + gutterSize: 's'; +}) => { + if (isIE11) { + const { direction, gutterSize, ...rest } = props; + return
; + } + return ; +}; +const FlexColumnItem = (props: { children: React.ReactNode }) => + isIE11 ?
: ; + export function Contents({ selectedNodeData, isService, @@ -36,18 +60,18 @@ export function Contents({ }: ContentsProps) { const frameworkName = selectedNodeData[SERVICE_FRAMEWORK_NAME]; return ( - - +

{label}

-
- + + {isService ? ( )} - + {isService && ( )} -
+ ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 92f66f698f044..bf0e052b951ae 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -12,6 +12,17 @@ import { } from '../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { defaultIcon, iconForNode } from './icons'; +// IE 11 does not properly load some SVGs or draw certain shapes. This causes +// a runtime error and the map fails work at all. We would prefer to do some +// kind of feature detection rather than browser detection, but some of these +// limitations are not well documented for older browsers. +// +// This method of detecting IE is from a Stack Overflow answer: +// https://stackoverflow.com/a/21825207 +// +// @ts-ignore `documentMode` is not recognized as a valid property of `document`. +const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + export const animationOptions: cytoscape.AnimationOptions = { duration: parseInt(theme.euiAnimSpeedNormal, 10), // @ts-ignore The cubic-bezier options here are not recognized by the cytoscape types @@ -37,8 +48,9 @@ const style: cytoscape.Stylesheet[] = [ // used here. // // @ts-ignore - 'background-image': (el: cytoscape.NodeSingular) => - iconForNode(el) ?? defaultIcon, + 'background-image': isIE11 + ? undefined + : (el: cytoscape.NodeSingular) => iconForNode(el) ?? defaultIcon, 'background-height': (el: cytoscape.NodeSingular) => isService(el) ? '60%' : '40%', 'background-width': (el: cytoscape.NodeSingular) => @@ -65,7 +77,7 @@ const style: cytoscape.Stylesheet[] = [ 'min-zoomed-font-size': parseInt(theme.euiSizeL, 10), 'overlay-opacity': 0, shape: (el: cytoscape.NodeSingular) => - isService(el) ? 'ellipse' : 'diamond', + isService(el) ? (isIE11 ? 'rectangle' : 'ellipse') : 'diamond', 'text-background-color': theme.euiColorLightestShade, 'text-background-opacity': 0, 'text-background-padding': theme.paddingSizes.xs, @@ -87,12 +99,12 @@ const style: cytoscape.Stylesheet[] = [ 'line-color': lineColor, 'overlay-opacity': 0, 'target-arrow-color': lineColor, - 'target-arrow-shape': 'triangle', + 'target-arrow-shape': isIE11 ? 'none' : 'triangle', // The DefinitelyTyped definitions don't specify this property since it's // fairly new. // // @ts-ignore - 'target-distance-from-node': theme.paddingSizes.xs, + 'target-distance-from-node': isIE11 ? undefined : theme.paddingSizes.xs, width: 1, 'source-arrow-shape': 'none', 'z-index': zIndexEdge @@ -101,12 +113,16 @@ const style: cytoscape.Stylesheet[] = [ { selector: 'edge[bidirectional]', style: { - 'source-arrow-shape': 'triangle', + 'source-arrow-shape': isIE11 ? 'none' : 'triangle', 'source-arrow-color': lineColor, - 'target-arrow-shape': 'triangle', + 'target-arrow-shape': isIE11 ? 'none' : 'triangle', // @ts-ignore - 'source-distance-from-node': parseInt(theme.paddingSizes.xs, 10), - 'target-distance-from-node': parseInt(theme.paddingSizes.xs, 10) + 'source-distance-from-node': isIE11 + ? undefined + : parseInt(theme.paddingSizes.xs, 10), + 'target-distance-from-node': isIE11 + ? undefined + : parseInt(theme.paddingSizes.xs, 10) } }, // @ts-ignore DefinitelyTyped says visibility is "none" but it's From 23a5734d07eb7dfb03039729a2b3d589384e4077 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Wed, 15 Apr 2020 17:03:20 +0300 Subject: [PATCH 09/44] [NP] TSVB (#63237) * Move TSVB into new platform * Get rid of isFunction checks * Remove extra import of styling constants * Move styles importing into plugin.ts Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 2 +- .../core_plugins/vis_type_timeseries/index.ts | 45 ------------------- .../vis_type_timeseries/package.json | 6 --- src/plugins/vis_type_timeseries/kibana.json | 2 + .../public/application}/_mixins.scss | 0 .../public/application}/_tvb_editor.scss | 0 .../public/application}/_variables.scss | 0 .../components/_annotations_editor.scss | 0 .../components/_color_picker.scss | 0 .../application}/components/_color_rules.scss | 0 .../components/_custom_color_picker.scss | 0 .../application}/components/_error.scss | 0 .../application}/components/_index.scss | 0 .../components/_markdown_editor.scss | 0 .../application}/components/_no_data.scss | 0 .../components/_series_editor.scss | 0 .../application}/components/_vis_editor.scss | 0 .../components/_vis_editor_visualization.scss | 0 .../application}/components/_vis_picker.scss | 0 .../components/_vis_with_splits.scss | 0 .../components/add_delete_buttons.js | 0 .../components/add_delete_buttons.test.js | 0 .../components/aggs/_agg_row.scss | 0 .../application}/components/aggs/_index.scss | 0 .../application}/components/aggs/agg.js | 0 .../application}/components/aggs/agg_row.js | 0 .../components/aggs/agg_select.js | 0 .../application}/components/aggs/aggs.js | 0 .../components/aggs/calculation.js | 0 .../components/aggs/cumulative_sum.js | 0 .../components/aggs/derivative.js | 0 .../components/aggs/field_select.js | 0 .../components/aggs/filter_ratio.js | 0 .../application}/components/aggs/math.js | 0 .../components/aggs/metric_select.js | 0 .../components/aggs/moving_average.js | 0 .../components/aggs/percentile.js | 0 .../components/aggs/percentile_rank/index.js | 0 .../aggs/percentile_rank/multi_value_row.js | 0 .../aggs/percentile_rank/percentile_rank.js | 0 .../percentile_rank/percentile_rank_values.js | 0 .../components/aggs/percentile_ui.js | 0 .../components/aggs/positive_only.js | 0 .../components/aggs/positive_rate.js | 0 .../components/aggs/serial_diff.js | 0 .../components/aggs/series_agg.js | 0 .../application}/components/aggs/static.js | 0 .../application}/components/aggs/std_agg.js | 0 .../components/aggs/std_deviation.js | 0 .../components/aggs/std_sibling.js | 0 .../aggs/temporary_unsupported_agg.js | 0 .../application}/components/aggs/top_hit.js | 0 .../components/aggs/unsupported_agg.js | 0 .../application}/components/aggs/vars.js | 0 .../components/annotations_editor.js | 0 .../application}/components/color_picker.js | 0 .../components/color_picker.test.js | 0 .../application}/components/color_rules.js | 0 .../components/color_rules.test.js | 0 .../components/custom_color_picker.js | 0 .../components/data_format_picker.js | 0 .../public/application}/components/error.js | 0 .../__snapshots__/icon_select.test.js.snap | 0 .../components/icon_select/icon_select.js | 0 .../icon_select/icon_select.test.js | 0 .../application}/components/index_pattern.js | 0 .../components/lib/agg_to_component.js | 0 .../components/lib/calculate_siblings.js | 0 .../components/lib/calculate_siblings.test.js | 0 .../application}/components/lib/charts.js | 0 .../components/lib/collection_actions.js | 14 ++---- .../components/lib/collection_actions.test.js | 0 .../components/lib/convert_series_to_vars.js | 0 .../lib/convert_series_to_vars.test.js | 0 .../components/lib/create_change_handler.js | 0 .../components/lib/create_number_handler.js | 4 +- .../lib/create_number_handler.test.js | 0 .../components/lib/create_select_handler.js | 8 ++-- .../lib/create_select_handler.test.js | 0 .../components/lib/create_text_handler.js | 4 +- .../lib/create_text_handler.test.js | 0 .../components/lib/create_xaxis_formatter.js | 0 .../application}/components/lib/detect_ie.js | 0 .../application}/components/lib/durations.js | 0 .../components/lib/durations.test.js | 0 .../components/lib/get_axis_label_string.js | 0 .../lib/get_axis_label_string.test.js | 0 .../lib/get_default_query_language.js | 2 +- .../components/lib/get_display_name.js | 0 .../components/lib/get_interval.js | 0 .../components/lib/new_metric_agg_fn.js | 0 .../components/lib/new_series_fn.js | 0 .../components/lib/re_id_series.js | 0 .../components/lib/re_id_series.test.js | 0 .../application}/components/lib/reorder.js | 0 .../components/lib/replace_vars.js | 0 .../components/lib/replace_vars.test.js | 0 .../components/lib/series_change_handler.js | 0 .../application}/components/lib/stacked.js | 0 .../components/lib/tick_formatter.js | 2 +- .../components/lib/tick_formatter.test.js | 2 +- .../components/markdown_editor.js | 0 .../public/application}/components/no_data.js | 0 .../application}/components/panel_config.js | 0 .../components/panel_config/_index.scss | 0 .../panel_config/_panel_config.scss | 0 .../components/panel_config/gauge.js | 0 .../components/panel_config/gauge.test.js | 0 .../components/panel_config/markdown.js | 0 .../components/panel_config/metric.js | 0 .../components/panel_config/table.js | 0 .../components/panel_config/timeseries.js | 0 .../components/panel_config/top_n.js | 0 .../components/query_bar_wrapper.js | 0 .../public/application}/components/series.js | 0 .../application}/components/series_config.js | 0 .../components/series_drag_handler.js | 0 .../application}/components/series_editor.js | 0 .../public/application}/components/split.js | 0 .../splits/__snapshots__/terms.test.js.snap | 0 .../components/splits/everything.js | 0 .../application}/components/splits/filter.js | 0 .../components/splits/filter_items.js | 0 .../application}/components/splits/filters.js | 0 .../components/splits/group_by_select.js | 0 .../application}/components/splits/terms.js | 0 .../components/splits/terms.test.js | 0 .../components/splits/unsupported_split.js | 0 .../application}/components/svg/bomb_icon.js | 0 .../application}/components/svg/fire_icon.js | 0 .../application}/components/vis_editor.js | 4 +- .../components/vis_editor_visualization.js | 0 .../application}/components/vis_picker.js | 0 .../components/vis_types/_index.scss | 0 .../components/vis_types/_vis_types.scss | 0 .../components/vis_types/gauge/series.js | 0 .../components/vis_types/gauge/series.test.js | 0 .../components/vis_types/gauge/vis.js | 0 .../vis_types/markdown/_markdown.scss | 0 .../components/vis_types/markdown/series.js | 0 .../components/vis_types/markdown/vis.js | 0 .../components/vis_types/metric/series.js | 0 .../vis_types/metric/series.test.js | 0 .../components/vis_types/metric/vis.js | 0 .../components/vis_types/table/config.js | 0 .../components/vis_types/table/is_sortable.js | 0 .../components/vis_types/table/series.js | 0 .../components/vis_types/table/vis.js | 2 +- .../components/vis_types/timeseries/config.js | 0 .../components/vis_types/timeseries/series.js | 0 .../components/vis_types/timeseries/vis.js | 2 +- .../components/vis_types/top_n/series.js | 0 .../components/vis_types/top_n/vis.js | 0 .../components/vis_with_splits.js | 0 .../application}/components/visualization.js | 0 .../public/application}/components/yes_no.js | 0 .../application}/components/yes_no.test.js | 0 .../contexts/form_validation_context.js | 0 .../contexts/query_input_bar_context.ts | 0 .../application}/contexts/vis_data_context.js | 0 .../public/application}/editor_controller.js | 10 ++--- .../public/application}/index.scss | 2 - .../public/application/index.ts} | 17 ++----- .../application}/lib/check_ui_restrictions.js | 0 .../application}/lib/create_brush_handler.js | 0 .../lib/create_brush_handler.test.js | 0 .../public/application}/lib/fetch_fields.js | 4 +- .../public/application}/lib/get_timezone.ts | 0 .../public/application/lib/index.ts | 22 +++++++++ .../application}/lib/set_is_reversed.js | 2 +- .../application}/lib/validate_interval.js | 0 .../visualizations/constants/chart.js | 0 .../visualizations/constants/icons.js | 0 .../visualizations/constants/index.js | 0 .../visualizations/lib/active_cursor.js | 0 .../visualizations/lib/calc_dimensions.js | 0 .../lib/calculate_coordinates.js | 0 .../visualizations/lib/get_value_by.js | 0 .../visualizations/views/_annotation.scss | 0 .../visualizations/views/_gauge.scss | 0 .../visualizations/views/_index.scss | 0 .../visualizations/views/_metric.scss | 0 .../visualizations/views/_top_n.scss | 0 .../visualizations/views/annotation.js | 0 .../visualizations/views/gauge.js | 0 .../visualizations/views/gauge_vis.js | 0 .../visualizations/views/metric.js | 0 .../timeseries/__mocks__/@elastic/charts.js | 0 .../__snapshots__/area_decorator.test.js.snap | 0 .../__snapshots__/bar_decorator.test.js.snap | 0 .../timeseries/decorators/area_decorator.js | 0 .../decorators/area_decorator.test.js | 0 .../timeseries/decorators/bar_decorator.js | 0 .../decorators/bar_decorator.test.js | 0 .../visualizations/views/timeseries/index.js | 2 +- .../model/__snapshots__/charts.test.js.snap | 0 .../views/timeseries/model/charts.js | 0 .../views/timeseries/model/charts.test.js | 0 .../__snapshots__/series_styles.test.js.snap | 0 .../views/timeseries/utils/series_styles.js | 0 .../timeseries/utils/series_styles.test.js | 0 .../views/timeseries/utils/stack_format.js | 0 .../timeseries/utils/stack_format.test.js | 0 .../views/timeseries/utils/theme.test.ts | 0 .../views/timeseries/utils/theme.ts | 0 .../visualizations/views/top_n.js | 0 .../vis_type_timeseries/public/index.ts | 2 +- .../vis_type_timeseries/public/metrics_fn.ts | 8 +--- .../public/metrics_type.ts | 12 ++--- .../vis_type_timeseries/public/plugin.ts | 9 ++-- .../public/request_handler.js | 3 +- .../vis_type_timeseries/public/services.ts | 4 +- 212 files changed, 66 insertions(+), 130 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timeseries/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_timeseries/package.json rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/_mixins.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/_tvb_editor.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/_variables.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_annotations_editor.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_color_picker.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_color_rules.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_custom_color_picker.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_error.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_index.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_markdown_editor.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_no_data.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_series_editor.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_vis_editor.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_vis_editor_visualization.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_vis_picker.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/_vis_with_splits.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/add_delete_buttons.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/add_delete_buttons.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/_agg_row.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/_index.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/agg.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/agg_row.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/agg_select.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/aggs.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/calculation.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/cumulative_sum.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/derivative.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/field_select.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/filter_ratio.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/math.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/metric_select.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/moving_average.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/percentile.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/percentile_rank/index.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/percentile_rank/multi_value_row.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/percentile_rank/percentile_rank.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/percentile_rank/percentile_rank_values.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/percentile_ui.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/positive_only.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/positive_rate.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/serial_diff.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/series_agg.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/static.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/std_agg.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/std_deviation.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/std_sibling.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/temporary_unsupported_agg.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/top_hit.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/unsupported_agg.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/aggs/vars.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/annotations_editor.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/color_picker.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/color_picker.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/color_rules.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/color_rules.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/custom_color_picker.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/data_format_picker.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/error.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/icon_select/__snapshots__/icon_select.test.js.snap (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/icon_select/icon_select.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/icon_select/icon_select.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/index_pattern.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/agg_to_component.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/calculate_siblings.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/calculate_siblings.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/charts.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/collection_actions.js (82%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/collection_actions.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/convert_series_to_vars.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/convert_series_to_vars.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_change_handler.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_number_handler.js (92%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_number_handler.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_select_handler.js (86%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_select_handler.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_text_handler.js (92%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_text_handler.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/create_xaxis_formatter.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/detect_ie.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/durations.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/durations.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/get_axis_label_string.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/get_axis_label_string.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/get_default_query_language.js (94%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/get_display_name.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/get_interval.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/new_metric_agg_fn.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/new_series_fn.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/re_id_series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/re_id_series.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/reorder.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/replace_vars.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/replace_vars.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/series_change_handler.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/stacked.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/tick_formatter.js (97%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/lib/tick_formatter.test.js (98%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/markdown_editor.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/no_data.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/_index.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/_panel_config.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/gauge.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/gauge.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/markdown.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/metric.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/table.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/timeseries.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/panel_config/top_n.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/query_bar_wrapper.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/series_config.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/series_drag_handler.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/series_editor.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/split.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/__snapshots__/terms.test.js.snap (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/everything.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/filter.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/filter_items.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/filters.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/group_by_select.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/terms.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/terms.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/splits/unsupported_split.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/svg/bomb_icon.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/svg/fire_icon.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_editor.js (99%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_editor_visualization.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_picker.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/_index.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/_vis_types.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/gauge/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/gauge/series.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/gauge/vis.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/markdown/_markdown.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/markdown/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/markdown/vis.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/metric/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/metric/series.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/metric/vis.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/table/config.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/table/is_sortable.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/table/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/table/vis.js (99%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/timeseries/config.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/timeseries/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/timeseries/vis.js (99%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/top_n/series.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_types/top_n/vis.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/vis_with_splits.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/visualization.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/yes_no.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/components/yes_no.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/contexts/form_validation_context.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/contexts/query_input_bar_context.ts (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/contexts/vis_data_context.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/editor_controller.js (94%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/index.scss (85%) rename src/{legacy/core_plugins/vis_type_timeseries/public/legacy.ts => plugins/vis_type_timeseries/public/application/index.ts} (58%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/check_ui_restrictions.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/create_brush_handler.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/create_brush_handler.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/fetch_fields.js (92%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/get_timezone.ts (100%) create mode 100644 src/plugins/vis_type_timeseries/public/application/lib/index.ts rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/set_is_reversed.js (97%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/lib/validate_interval.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/constants/chart.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/constants/icons.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/constants/index.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/lib/active_cursor.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/lib/calc_dimensions.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/lib/calculate_coordinates.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/lib/get_value_by.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/_annotation.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/_gauge.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/_index.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/_metric.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/_top_n.scss (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/annotation.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/gauge.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/gauge_vis.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/metric.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/__mocks__/@elastic/charts.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/decorators/area_decorator.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/decorators/area_decorator.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/decorators/bar_decorator.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/decorators/bar_decorator.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/index.js (99%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/model/charts.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/model/charts.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/series_styles.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/series_styles.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/stack_format.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/stack_format.test.js (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/theme.test.ts (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/timeseries/utils/theme.ts (100%) rename src/{legacy/core_plugins/vis_type_timeseries/public => plugins/vis_type_timeseries/public/application}/visualizations/views/top_n.js (100%) rename src/{legacy/core_plugins => plugins}/vis_type_timeseries/public/index.ts (93%) rename src/{legacy/core_plugins => plugins}/vis_type_timeseries/public/metrics_fn.ts (92%) rename src/{legacy/core_plugins => plugins}/vis_type_timeseries/public/metrics_type.ts (85%) rename src/{legacy/core_plugins => plugins}/vis_type_timeseries/public/plugin.ts (89%) rename src/{legacy/core_plugins => plugins}/vis_type_timeseries/public/request_handler.js (95%) rename src/{legacy/core_plugins => plugins}/vis_type_timeseries/public/services.ts (90%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 71d857c1c9041..5e40dc044b97a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,13 +12,13 @@ /src/legacy/core_plugins/kibana/public/visualize/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app -/src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app +/src/plugins/vis_type_timeseries/ @elastic/kibana-app # Core UI # Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon diff --git a/src/legacy/core_plugins/vis_type_timeseries/index.ts b/src/legacy/core_plugins/vis_type_timeseries/index.ts deleted file mode 100644 index 596fd5b581a71..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'metrics', - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars: server => ({}), - }, - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - chartResolution: Joi.number().default(150), - minimumBucketSize: Joi.number().default(10), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default metricsPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_timeseries/package.json b/src/legacy/core_plugins/vis_type_timeseries/package.json deleted file mode 100644 index 6b4874dfe6a68..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "author": "Chris Cowan", - "name": "metrics", - "version": "kibana" -} - diff --git a/src/plugins/vis_type_timeseries/kibana.json b/src/plugins/vis_type_timeseries/kibana.json index d77f4ac92da16..305e159ac2505 100644 --- a/src/plugins/vis_type_timeseries/kibana.json +++ b/src/plugins/vis_type_timeseries/kibana.json @@ -3,5 +3,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "server": true, + "ui": true, + "requiredPlugins": ["data", "expressions", "visualizations"], "optionalPlugins": ["usageCollection"] } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss b/src/plugins/vis_type_timeseries/public/application/_mixins.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss rename to src/plugins/vis_type_timeseries/public/application/_mixins.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_tvb_editor.scss b/src/plugins/vis_type_timeseries/public/application/_tvb_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/_tvb_editor.scss rename to src/plugins/vis_type_timeseries/public/application/_tvb_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_variables.scss b/src/plugins/vis_type_timeseries/public/application/_variables.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/_variables.scss rename to src/plugins/vis_type_timeseries/public/application/_variables.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_annotations_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_annotations_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_color_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_color_picker.scss rename to src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_color_rules.scss b/src/plugins/vis_type_timeseries/public/application/components/_color_rules.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_color_rules.scss rename to src/plugins/vis_type_timeseries/public/application/components/_color_rules.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_custom_color_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_custom_color_picker.scss rename to src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_error.scss b/src/plugins/vis_type_timeseries/public/application/components/_error.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_error.scss rename to src/plugins/vis_type_timeseries/public/application/components/_error.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_markdown_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_markdown_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_markdown_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_markdown_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_no_data.scss b/src/plugins/vis_type_timeseries/public/application/components/_no_data.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_no_data.scss rename to src/plugins/vis_type_timeseries/public/application/components/_no_data.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_series_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_series_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor_visualization.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_editor_visualization.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor_visualization.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_editor_visualization.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_picker.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_picker.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_picker.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_with_splits.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_with_splits.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_with_splits.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_with_splits.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.js b/src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.js rename to src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.test.js b/src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.test.js rename to src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_agg_row.scss b/src/plugins/vis_type_timeseries/public/application/components/aggs/_agg_row.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_agg_row.scss rename to src/plugins/vis_type_timeseries/public/application/components/aggs/_agg_row.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/aggs/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/aggs/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/aggs.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/aggs.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/cumulative_sum.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/cumulative_sum.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/derivative.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/derivative.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/field_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/field_select.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/math.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/metric_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/metric_select.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/moving_average.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/moving_average.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/index.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/index.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/index.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/index.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/multi_value_row.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/multi_value_row.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/multi_value_row.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/multi_value_row.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank_values.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank_values.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank_values.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank_values.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_ui.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_ui.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_only.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_only.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/serial_diff.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/serial_diff.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/series_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/series_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/static.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/static.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/static.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/static.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/std_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_sibling.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_sibling.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/temporary_unsupported_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/temporary_unsupported_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/unsupported_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/unsupported_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.js b/src/plugins/vis_type_timeseries/public/application/components/color_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/color_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.test.js b/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.test.js rename to src/plugins/vis_type_timeseries/public/application/components/color_picker.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js rename to src/plugins/vis_type_timeseries/public/application/components/color_rules.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.test.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.test.js rename to src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js b/src/plugins/vis_type_timeseries/public/application/components/custom_color_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/custom_color_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/data_format_picker.js b/src/plugins/vis_type_timeseries/public/application/components/data_format_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/data_format_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/data_format_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/error.js b/src/plugins/vis_type_timeseries/public/application/components/error.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/error.js rename to src/plugins/vis_type_timeseries/public/application/components/error.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/__snapshots__/icon_select.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/__snapshots__/icon_select.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.js b/src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.js rename to src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.test.js b/src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.test.js rename to src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js rename to src/plugins/vis_type_timeseries/public/application/components/index_pattern.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js b/src/plugins/vis_type_timeseries/public/application/components/lib/agg_to_component.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/agg_to_component.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.js b/src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/charts.js b/src/plugins/vis_type_timeseries/public/application/components/lib/charts.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/charts.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/charts.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.js b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js similarity index 82% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js index 79cbe98b3d3db..3eae18111bb4d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js @@ -18,7 +18,6 @@ */ import uuid from 'uuid'; -import _ from 'lodash'; const newFn = () => ({ id: uuid.v1() }); @@ -30,9 +29,7 @@ export function handleChange(props, doc) { if (row.id === doc.id) return doc; return row; }); - if (_.isFunction(props.onChange)) { - props.onChange(_.assign({}, model, part)); - } + props.onChange?.({ ...model, ...part }); } export function handleDelete(props, doc) { @@ -40,20 +37,15 @@ export function handleDelete(props, doc) { const collection = model[name] || []; const part = {}; part[name] = collection.filter(row => row.id !== doc.id); - if (_.isFunction(props.onChange)) { - props.onChange(_.assign({}, model, part)); - } + props.onChange?.({ ...model, ...part }); } export function handleAdd(props, fn = newFn) { - if (!_.isFunction(fn)) fn = newFn; const { model, name } = props; const collection = model[name] || []; const part = {}; part[name] = collection.concat([fn()]); - if (_.isFunction(props.onChange)) { - props.onChange(_.assign({}, model, part)); - } + props.onChange?.({ ...model, ...part }); } export const collectionActions = { handleAdd, handleDelete, handleChange }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_change_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_change_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_change_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_change_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js index fd432add43295..3bc8fd87f73dd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js @@ -25,8 +25,6 @@ export const createNumberHandler = handleChange => { if (!detectIE() || e.keyCode === 13) e.preventDefault(); const value = Number(_.get(e, 'target.value', defaultValue)); - if (_.isFunction(handleChange)) { - return handleChange({ [name]: value }); - } + return handleChange?.({ [name]: value }); }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.js similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.js index 30937b97d353f..cfc6a4dc57871 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.js @@ -21,10 +21,8 @@ import _ from 'lodash'; export const createSelectHandler = handleChange => { return name => selectedOptions => { - if (_.isFunction(handleChange)) { - return handleChange({ - [name]: _.get(selectedOptions, '[0].value', null), - }); - } + return handleChange?.({ + [name]: _.get(selectedOptions, '[0].value', null), + }); }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js index 8e2624488d954..82cc071b59d51 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js @@ -26,8 +26,6 @@ export const createTextHandler = handleChange => { if (!detectIE() || e.keyCode === 13) e.preventDefault(); const value = _.get(e, 'target.value', defaultValue); - if (_.isFunction(handleChange)) { - return handleChange({ [name]: value }); - } + return handleChange?.({ [name]: value }); }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_xaxis_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_xaxis_formatter.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/detect_ie.js b/src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/detect_ie.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.js b/src/plugins/vis_type_timeseries/public/application/components/lib/durations.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/durations.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/durations.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/durations.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js similarity index 94% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js index 26723da5ab5c9..972f937ad109d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js @@ -17,7 +17,7 @@ * under the License. */ -import { getUISettings } from '../../services'; +import { getUISettings } from '../../../services'; export function getDefaultQueryLanguage() { return getUISettings().get('search:queryLanguage'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_display_name.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_display_name.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_display_name.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_display_name.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_interval.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_interval.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_metric_agg_fn.js b/src/plugins/vis_type_timeseries/public/application/components/lib/new_metric_agg_fn.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_metric_agg_fn.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/new_metric_agg_fn.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_series_fn.js b/src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_series_fn.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.js b/src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/reorder.js b/src/plugins/vis_type_timeseries/public/application/components/lib/reorder.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/reorder.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/reorder.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/series_change_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/series_change_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/stacked.js b/src/plugins/vis_type_timeseries/public/application/components/lib/stacked.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/stacked.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/stacked.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js similarity index 97% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js index 3ab8e0f6b885e..fd316e66a16fb 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js @@ -20,7 +20,7 @@ import handlebars from 'handlebars/dist/handlebars'; import { isNumber } from 'lodash'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; -import { getFieldFormats } from '../../services'; +import { getFieldFormats } from '../../../services'; export const createTickFormatter = (format = '0,0.[00]', template, getConfig = null) => { const fieldFormats = getFieldFormats(); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js similarity index 98% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js index e87cba126bb46..ee10b254a9e15 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js @@ -19,7 +19,7 @@ import { createTickFormatter } from './tick_formatter'; import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats'; -import { setFieldFormats } from '../../services'; +import { setFieldFormats } from '../../../services'; const mockUiSettings = { get: item => { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js b/src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/no_data.js b/src/plugins/vis_type_timeseries/public/application/components/no_data.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/no_data.js rename to src/plugins/vis_type_timeseries/public/application/components/no_data.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/panel_config/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_panel_config.scss b/src/plugins/vis_type_timeseries/public/application/components/panel_config/_panel_config.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_panel_config.scss rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/_panel_config.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/markdown.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/markdown.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/timeseries.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/timeseries.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js b/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js rename to src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series.js b/src/plugins/vis_type_timeseries/public/application/components/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series.js rename to src/plugins/vis_type_timeseries/public/application/components/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series_config.js b/src/plugins/vis_type_timeseries/public/application/components/series_config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series_config.js rename to src/plugins/vis_type_timeseries/public/application/components/series_config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series_drag_handler.js b/src/plugins/vis_type_timeseries/public/application/components/series_drag_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series_drag_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/series_drag_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series_editor.js b/src/plugins/vis_type_timeseries/public/application/components/series_editor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/series_editor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js b/src/plugins/vis_type_timeseries/public/application/components/split.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/split.js rename to src/plugins/vis_type_timeseries/public/application/components/split.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/everything.js b/src/plugins/vis_type_timeseries/public/application/components/splits/everything.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/everything.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/everything.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filter.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/filter.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter_items.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter_items.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filters.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filters.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filters.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/filters.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/group_by_select.js b/src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/group_by_select.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/terms.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.test.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.test.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/terms.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/unsupported_split.js b/src/plugins/vis_type_timeseries/public/application/components/splits/unsupported_split.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/unsupported_split.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/unsupported_split.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js b/src/plugins/vis_type_timeseries/public/application/components/svg/bomb_icon.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js rename to src/plugins/vis_type_timeseries/public/application/components/svg/bomb_icon.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js b/src/plugins/vis_type_timeseries/public/application/components/svg/fire_icon.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js rename to src/plugins/vis_type_timeseries/public/application/components/svg/fire_icon.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index b4845696fc8c0..7075e86eb56bf 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js @@ -30,7 +30,7 @@ import { createBrushHandler } from '../lib/create_brush_handler'; import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns'; import { esKuery } from '../../../../../plugins/data/public'; -import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../services'; +import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../../services'; import { CoreStartContextProvider } from '../contexts/query_input_bar_context'; import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; @@ -96,7 +96,7 @@ export class VisEditor extends Component { return true; }; - handleChange = async partialModel => { + handleChange = partialModel => { if (isEmpty(partialModel)) { return; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_picker.js b/src/plugins/vis_type_timeseries/public/application/components/vis_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/_markdown.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/_markdown.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/_markdown.scss rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/_markdown.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/is_sortable.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/is_sortable.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/is_sortable.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/is_sortable.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index 1fe9358cbfea9..c6f1db149928c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -27,7 +27,7 @@ import { EuiToolTip, EuiIcon } from '@elastic/eui'; import { replaceVars } from '../../lib/replace_vars'; import { fieldFormats } from '../../../../../../../plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getFieldFormats } from '../../../services'; +import { getFieldFormats } from '../../../../services'; import { METRIC_TYPES } from '../../../../../../../plugins/vis_type_timeseries/common/metric_types'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/config.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index f559bc38b6c58..1004f7ee96a54 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -34,7 +34,7 @@ import { getInterval } from '../../lib/get_interval'; import { areFieldsDifferent } from '../../lib/charts'; import { createXaxisFormatter } from '../../lib/create_xaxis_formatter'; import { STACKED_OPTIONS } from '../../../visualizations/constants'; -import { getCoreStart, getUISettings } from '../../../services'; +import { getCoreStart, getUISettings } from '../../../../services'; export class TimeseriesVisualization extends Component { static propTypes = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_with_splits.js b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_with_splits.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/visualization.js b/src/plugins/vis_type_timeseries/public/application/components/visualization.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/visualization.js rename to src/plugins/vis_type_timeseries/public/application/components/visualization.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js b/src/plugins/vis_type_timeseries/public/application/components/yes_no.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js rename to src/plugins/vis_type_timeseries/public/application/components/yes_no.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.test.js b/src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.test.js rename to src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/form_validation_context.js b/src/plugins/vis_type_timeseries/public/application/contexts/form_validation_context.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/contexts/form_validation_context.js rename to src/plugins/vis_type_timeseries/public/application/contexts/form_validation_context.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts b/src/plugins/vis_type_timeseries/public/application/contexts/query_input_bar_context.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts rename to src/plugins/vis_type_timeseries/public/application/contexts/query_input_bar_context.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/vis_data_context.js b/src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/contexts/vis_data_context.js rename to src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js b/src/plugins/vis_type_timeseries/public/application/editor_controller.js similarity index 94% rename from src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js rename to src/plugins/vis_type_timeseries/public/application/editor_controller.js index 16a6348712065..af50d3a06d1fc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js +++ b/src/plugins/vis_type_timeseries/public/application/editor_controller.js @@ -20,7 +20,8 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { fetchIndexPatternFields } from './lib/fetch_fields'; -import { getSavedObjectsClient, getUISettings, getI18n } from './services'; +import { getSavedObjectsClient, getUISettings, getI18n } from '../services'; +import { VisEditor } from './components/vis_editor'; export class EditorController { constructor(el, vis, eventEmitter, embeddableHandler) { @@ -55,19 +56,14 @@ export class EditorController { this.state.isLoaded = true; }; - getComponent = () => { - return this.state.vis.type.editorConfig.component; - }; - async render(params) { - const Component = this.getComponent(); const I18nContext = getI18n().Context; !this.state.isLoaded && (await this.fetchDefaultParams()); render( - = { - expressions: npSetup.plugins.expressions, - visualizations: npSetup.plugins.visualizations, -}; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, plugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +// @ts-ignore +export { EditorController } from './editor_controller'; +export * from './lib'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/check_ui_restrictions.js b/src/plugins/vis_type_timeseries/public/application/lib/check_ui_restrictions.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/check_ui_restrictions.js rename to src/plugins/vis_type_timeseries/public/application/lib/check_ui_restrictions.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/create_brush_handler.js b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/create_brush_handler.js rename to src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/create_brush_handler.test.js b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/create_brush_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js rename to src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js index 9c64d0da2d88a..f986e300ac5a5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js @@ -17,8 +17,8 @@ * under the License. */ import { i18n } from '@kbn/i18n'; -import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns'; -import { getCoreStart } from '../services'; +import { extractIndexPatterns } from '../../../common/extract_index_patterns'; +import { getCoreStart } from '../../services'; export async function fetchFields(indexPatterns = ['*']) { const patterns = Array.isArray(indexPatterns) ? indexPatterns : [indexPatterns]; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/get_timezone.ts b/src/plugins/vis_type_timeseries/public/application/lib/get_timezone.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/get_timezone.ts rename to src/plugins/vis_type_timeseries/public/application/lib/get_timezone.ts diff --git a/src/plugins/vis_type_timeseries/public/application/lib/index.ts b/src/plugins/vis_type_timeseries/public/application/lib/index.ts new file mode 100644 index 0000000000000..2362762df152c --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/lib/index.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +// @ts-ignore +export { validateInterval } from './validate_interval'; +export { getTimezone } from './get_timezone'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/set_is_reversed.js b/src/plugins/vis_type_timeseries/public/application/lib/set_is_reversed.js similarity index 97% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/set_is_reversed.js rename to src/plugins/vis_type_timeseries/public/application/lib/set_is_reversed.js index 9f66bcd161916..070cf34c879d2 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/lib/set_is_reversed.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/set_is_reversed.js @@ -18,7 +18,7 @@ */ import color from 'color'; -import { getUISettings } from '../services'; +import { getUISettings } from '../../services'; const isDarkTheme = () => getUISettings().get('theme:darkMode'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js b/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js rename to src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js b/src/plugins/vis_type_timeseries/public/application/visualizations/constants/chart.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/constants/chart.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/icons.js b/src/plugins/vis_type_timeseries/public/application/visualizations/constants/icons.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/icons.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/constants/icons.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/constants/index.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/index.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/constants/index.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/active_cursor.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/active_cursor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/active_cursor.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/active_cursor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calc_dimensions.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/calc_dimensions.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calc_dimensions.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/calc_dimensions.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calculate_coordinates.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/calculate_coordinates.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calculate_coordinates.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/calculate_coordinates.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/get_value_by.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/get_value_by.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/get_value_by.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/get_value_by.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_annotation.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_annotation.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_annotation.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_annotation.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_gauge.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_gauge.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_gauge.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_gauge.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_index.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_index.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_metric.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_metric.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_metric.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_metric.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_top_n.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_top_n.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_top_n.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_top_n.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/annotation.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/annotation.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/annotation.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/annotation.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index 3ce3aae2649e1..b1c3c7ac6b67a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -33,7 +33,7 @@ import { import { EuiIcon } from '@elastic/eui'; import { getTimezone } from '../../../lib/get_timezone'; import { eventBus, ACTIVE_CURSOR } from '../../lib/active_cursor'; -import { getUISettings } from '../../../services'; +import { getUISettings } from '../../../../services'; import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constants'; import { AreaSeriesDecorator } from './decorators/area_decorator'; import { BarSeriesDecorator } from './decorators/bar_decorator'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/top_n.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/top_n.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/index.ts b/src/plugins/vis_type_timeseries/public/index.ts similarity index 93% rename from src/legacy/core_plugins/vis_type_timeseries/public/index.ts rename to src/plugins/vis_type_timeseries/public/index.ts index 16b099ba16ae9..fbf4a81b6ad1b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/index.ts +++ b/src/plugins/vis_type_timeseries/public/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../core/public'; +import { PluginInitializerContext } from '../../../core/public'; import { MetricsPlugin as Plugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/plugins/vis_type_timeseries/public/metrics_fn.ts similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts rename to src/plugins/vis_type_timeseries/public/metrics_fn.ts index 1f9cbecc2a354..008b13cce6565 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_fn.ts @@ -19,12 +19,8 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - ExpressionFunctionDefinition, - KibanaContext, - Render, -} from '../../../../plugins/expressions/public'; -import { PersistedState } from '../../../../plugins/visualizations/public'; +import { ExpressionFunctionDefinition, KibanaContext, Render } from '../../expressions/public'; +import { PersistedState } from '../../visualizations/public'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts similarity index 85% rename from src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts rename to src/plugins/vis_type_timeseries/public/metrics_type.ts index 1db35c406eb13..f83881a619a59 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -21,11 +21,10 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; +import { EditorController } from './application'; // @ts-ignore -import { EditorController } from './editor_controller'; -// @ts-ignore -import { PANEL_TYPES } from '../../../../plugins/vis_type_timeseries/common/panel_types'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; +import { PANEL_TYPES } from '../common/panel_types'; +import { defaultFeedbackMessage } from '../../kibana_utils/public'; export const metricsVisDefinition = { name: 'metrics', @@ -69,12 +68,9 @@ export const metricsVisDefinition = { show_legend: 1, show_grid: 1, }, - component: require('./components/vis_editor').VisEditor, + component: require('./application/components/vis_editor').VisEditor, }, editor: EditorController, - editorConfig: { - component: require('./components/vis_editor').VisEditor, - }, options: { showQueryBar: false, showFilterBar: false, diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts similarity index 89% rename from src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts rename to src/plugins/vis_type_timeseries/public/plugin.ts index 0310ecf6cfd87..e6c92c31450e7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -16,9 +16,12 @@ * specific language governing permissions and limitations * under the License. */ + +import './application/index.scss'; + import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricsFn } from './metrics_fn'; import { metricsVisDefinition } from './metrics_type'; @@ -30,7 +33,7 @@ import { setCoreStart, setDataStart, } from './services'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { DataPublicPluginStart } from '../../data/public'; /** @internal */ export interface MetricsPluginSetupDependencies { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js rename to src/plugins/vis_type_timeseries/public/request_handler.js index 2cac1567a6eb7..bd6c6d9553930 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js +++ b/src/plugins/vis_type_timeseries/public/request_handler.js @@ -17,8 +17,7 @@ * under the License. */ -import { validateInterval } from './lib/validate_interval'; -import { getTimezone } from './lib/get_timezone'; +import { getTimezone, validateInterval } from './application'; import { getUISettings, getDataStart, getCoreStart } from './services'; export const metricsRequestHandler = async ({ diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts b/src/plugins/vis_type_timeseries/public/services.ts similarity index 90% rename from src/legacy/core_plugins/vis_type_timeseries/public/services.ts rename to src/plugins/vis_type_timeseries/public/services.ts index 64ee897247b89..d93a376584eac 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts +++ b/src/plugins/vis_type_timeseries/public/services.ts @@ -18,8 +18,8 @@ */ import { I18nStart, SavedObjectsStart, IUiSettingsClient, CoreStart } from 'src/core/public'; -import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { createGetterSetter } from '../../kibana_utils/public'; +import { DataPublicPluginStart } from '../../data/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); From 3342f9e80e982dd0e4cc6d13c115f2de9ef2f3b7 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Wed, 15 Apr 2020 16:44:14 +0200 Subject: [PATCH 10/44] [Ingest] Add details to indexing strategy around allow chars (#63560) Add some recommendation around what chars should be used if a dataset or namespace contains a `-`. --- docs/ingest_manager/index.asciidoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ingest_manager/index.asciidoc b/docs/ingest_manager/index.asciidoc index 1254f412e14c5..22afa88c919e4 100644 --- a/docs/ingest_manager/index.asciidoc +++ b/docs/ingest_manager/index.asciidoc @@ -113,7 +113,7 @@ Ingest Management enforces an indexing strategy to allow the system to automatic {type}-{dataset}-{namespace} ``` -The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. +The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. If there is a dataset or a namespace with a `-` inside, it is recommended to replace it either by a `.` or a `_`. Note: More `{type}`s might be added in the future like `apm` and `endpoint`. @@ -126,6 +126,8 @@ This indexing strategy has a few advantages: * Having a global metrics and logs template, allows to create new indices on demand which still follow the convention. This is common in the case of k8s as an example. * Constant keywords allow to narrow down the indices we need to access for querying very efficiently. This is especially relevant in environments which a large number of indices or with indices on slower nodes. +Overall it creates smaller indices in size, makes querying more efficient and allows users to define their own naming parts in namespace and still benefiting from all features that can be built on top of the indexing startegy. + === Ingest Pipeline The ingest pipelines for a specific dataset will have the following naming scheme: From 6fc4840baef492bf2085ae55a8215d4906a53430 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 15 Apr 2020 15:48:04 +0100 Subject: [PATCH 11/44] [ML] Changing file data visualizer max upload setting to string (#63502) --- .../ml/common/constants/file_datavisualizer.ts | 6 ++++-- x-pack/plugins/ml/common/types/ml_config.ts | 4 ++-- .../file_based/components/utils/utils.ts | 12 +++++++++--- .../plugins/ml/server/routes/file_data_visualizer.ts | 6 +++--- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts b/x-pack/plugins/ml/common/constants/file_datavisualizer.ts index 675247af2db99..7e18b36fd7bab 100644 --- a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/constants/file_datavisualizer.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export const MAX_BYTES = 104857600; // 100MB -export const ABSOLUTE_MAX_BYTES = 1073741274; // 1GB +export const MAX_FILE_SIZE = '100MB'; +export const MAX_FILE_SIZE_BYTES = 104857600; // 100MB + +export const ABSOLUTE_MAX_FILE_SIZE_BYTES = 1073741274; // 1GB export const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b'; // Value to use in the Elasticsearch index mapping meta data to identify the diff --git a/x-pack/plugins/ml/common/types/ml_config.ts b/x-pack/plugins/ml/common/types/ml_config.ts index 8fd9fd22bad8a..f2ddadccb2170 100644 --- a/x-pack/plugins/ml/common/types/ml_config.ts +++ b/x-pack/plugins/ml/common/types/ml_config.ts @@ -5,11 +5,11 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { MAX_BYTES } from '../constants/file_datavisualizer'; +import { MAX_FILE_SIZE } from '../constants/file_datavisualizer'; export const configSchema = schema.object({ file_data_visualizer: schema.object({ - max_file_size_bytes: schema.number({ defaultValue: MAX_BYTES }), + max_file_size: schema.string({ defaultValue: MAX_FILE_SIZE }), }), }); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index ecef01aae0519..7b6464570e55c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -9,7 +9,8 @@ import numeral from '@elastic/numeral'; import { ml } from '../../../../services/ml_api_service'; import { AnalysisResult, InputOverrides } from '../../../../../../common/types/file_datavisualizer'; import { - ABSOLUTE_MAX_BYTES, + MAX_FILE_SIZE_BYTES, + ABSOLUTE_MAX_FILE_SIZE_BYTES, FILE_SIZE_DISPLAY_FORMAT, } from '../../../../../../common/constants/file_datavisualizer'; import { getMlConfig } from '../../../../util/dependency_cache'; @@ -61,8 +62,13 @@ export function readFile(file: File) { } export function getMaxBytes() { - const maxBytes = getMlConfig().file_data_visualizer.max_file_size_bytes; - return maxBytes < ABSOLUTE_MAX_BYTES ? maxBytes : ABSOLUTE_MAX_BYTES; + const maxFileSize = getMlConfig().file_data_visualizer.max_file_size; + // @ts-ignore + const maxBytes = numeral(maxFileSize.toUpperCase()).value(); + if (maxBytes < MAX_FILE_SIZE_BYTES) { + return MAX_FILE_SIZE_BYTES; + } + return maxBytes < ABSOLUTE_MAX_FILE_SIZE_BYTES ? maxBytes : ABSOLUTE_MAX_FILE_SIZE_BYTES; } export function getMaxBytesFormatted() { diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index b915d13aa9720..9f30847d9eb2e 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; import { RequestHandlerContext } from 'kibana/server'; -import { MAX_BYTES } from '../../common/constants/file_datavisualizer'; +import { MAX_FILE_SIZE_BYTES } from '../../common/constants/file_datavisualizer'; import { InputOverrides, Settings, @@ -79,7 +79,7 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat options: { body: { accepts: ['text/*', 'application/json'], - maxBytes: MAX_BYTES, + maxBytes: MAX_FILE_SIZE_BYTES, }, }, }, @@ -121,7 +121,7 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat options: { body: { accepts: ['application/json'], - maxBytes: MAX_BYTES, + maxBytes: MAX_FILE_SIZE_BYTES, }, }, }, From 02bcc8e78bbf954cc57d01dbcf7d7664b91c127b Mon Sep 17 00:00:00 2001 From: Brandon Morelli Date: Wed, 15 Apr 2020 07:54:26 -0700 Subject: [PATCH 12/44] [APM] docs: restructure APM documentation for 7.7 (#63280) --- docs/apm/advanced-queries.asciidoc | 30 ++++++++------ docs/apm/agent-configuration.asciidoc | 27 ++++++------ docs/apm/api.asciidoc | 6 ++- docs/apm/apm-alerts.asciidoc | 28 +++++++------ docs/apm/bottlenecks.asciidoc | 25 ----------- docs/apm/custom-links.asciidoc | 20 +++++---- docs/apm/deployment-annotations.asciidoc | 17 ++++++++ docs/apm/error-reports-watcher.asciidoc | 18 ++++++++ docs/apm/errors.asciidoc | 22 ++-------- docs/apm/filters.asciidoc | 11 +++-- docs/apm/getting-started.asciidoc | 51 ++++++++++++++++------- docs/apm/how-to-guides.asciidoc | 32 ++++++++++++++ docs/apm/images/dynamic-config.svg | 1 + docs/apm/index.asciidoc | 40 +++++++++++------- docs/apm/machine-learning.asciidoc | 27 ++++++++++++ docs/apm/metrics.asciidoc | 19 +-------- docs/apm/service-maps.asciidoc | 53 +++++++++++++++++++----- docs/apm/services.asciidoc | 6 +-- docs/apm/set-up.asciidoc | 35 ++++++++++++++++ docs/apm/settings.asciidoc | 11 +++-- docs/apm/spans.asciidoc | 5 ++- docs/apm/traces.asciidoc | 9 +++- docs/apm/transactions.asciidoc | 19 ++------- docs/apm/troubleshooting.asciidoc | 27 +++++++----- docs/apm/using-the-apm-ui.asciidoc | 51 ----------------------- docs/settings/apm-settings.asciidoc | 31 +++++++------- 26 files changed, 372 insertions(+), 249 deletions(-) delete mode 100644 docs/apm/bottlenecks.asciidoc create mode 100644 docs/apm/deployment-annotations.asciidoc create mode 100644 docs/apm/error-reports-watcher.asciidoc create mode 100644 docs/apm/how-to-guides.asciidoc create mode 100644 docs/apm/images/dynamic-config.svg create mode 100644 docs/apm/machine-learning.asciidoc create mode 100644 docs/apm/set-up.asciidoc delete mode 100644 docs/apm/using-the-apm-ui.asciidoc diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index add6f601489e1..f89f994e59e57 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -1,14 +1,11 @@ +[role="xpack"] [[advanced-queries]] -=== Advanced queries +=== Query your data -When querying in the APM app, you're simply searching and selecting data from fields in Elasticsearch documents. -Queries entered into the query bar are also added as parameters to the URL, -so it's easy to share a specific query or view with others. - -You can begin to see some of the transaction fields available for filtering: - -[role="screenshot"] -image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] +Querying your APM data is a powerful tool that can make finding bottlenecks in your code even easier. +Imagine you have a user that complains about a slow response time in a specific service. +With the query bar, you can easily filter the APM app to only display trace data for that user, +or, to only show transactions that are slower than a specified time threshold. [float] ==== Example APM app queries @@ -17,15 +14,24 @@ image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app i * Filter by response status code: `context.response.status_code >= 400` * Filter by single user ID: `context.user.id : 12` +When querying in the APM app, you're merely searching and selecting data from fields in Elasticsearch documents. +Queries entered into the query bar are also added as parameters to the URL, +so it's easy to share a specific query or view with others. + +When you type, you can begin to see some of the transaction fields available for filtering: + +[role="screenshot"] +image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] + TIP: Read the {kibana-ref}/kuery-query.html[Kibana Query Language Enhancements] documentation to learn more about the capabilities of the {kib} query language. [float] [[discover-advanced-queries]] === Querying in Discover -It may also be helpful to view your APM data in {kibana-ref}/discover.html[*Discover*]. +Alternatively, you can query your APM documents in {kibana-ref}/discover.html[*Discover*]. Querying documents in *Discover* works the same way as querying in the APM app, -and all of the example APM app queries can also be used in *Discover*. +and *Discover* supports all of the example APM app queries shown on this page. [float] ==== Example Discover query @@ -33,7 +39,7 @@ and all of the example APM app queries can also be used in *Discover*. One example where you may want to make use of *Discover*, is for viewing _all_ transactions for an endpoint, instead of just a sample. -TIP: Starting in v7.6, you can view 10 samples per bucket in the APM app, instead of just one. +TIP: Starting in v7.6, you can view ten samples per bucket in the APM app, instead of just one. Use the APM app to find a transaction name and time bucket that you're interested in learning more about. Then, switch to *Discover* and make a search: diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc index 0d2834c1a400e..d911c2154ea4c 100644 --- a/docs/apm/agent-configuration.asciidoc +++ b/docs/apm/agent-configuration.asciidoc @@ -1,12 +1,16 @@ [role="xpack"] [[agent-configuration]] -=== APM Agent configuration +=== APM Agent central configuration -APM Agent configuration allows you to fine-tune your agent configuration directly in Kibana. -Best of all, changes are automatically propagated to your APM agents so there's no need to redeploy. +++++ +Configure APM agents with central config +++++ -To get started, simply choose the services and environments you wish to configure. -The APM app will let you know when your configurations have been applied by your agents. +APM Agent configuration allows you to fine-tune your agent configuration from within the APM app. +Changes are automatically propagated to your APM agents, so there's no need to redeploy. + +To get started, choose the services and environments you wish to configure. +The APM app will let you know when your agents have applied your configurations. [role="screenshot"] image::apm/images/apm-agent-configuration.png[APM Agent configuration in Kibana] @@ -14,29 +18,28 @@ image::apm/images/apm-agent-configuration.png[APM Agent configuration in Kibana] [float] ==== Precedence -Configurations set with APM Agent configuration take precedence over configurations set locally in the Agent. +Configurations set from the APM app take precedence over configurations set locally in each Agent. However, if APM Server is slow to respond, is offline, reports an error, etc., APM agents will use local defaults until they're able to update the configuration. -For this reason, it is still important to set custom default configurations locally in each of your agents. +For this reason, it is still essential to set custom default configurations locally in each of your agents. [float] ==== APM Server setup This feature requires {apm-server-ref}/setup-kibana-endpoint.html[Kibana endpoint configuration] in APM Server. -Why is additional configuration needed in APM Server? -That's because APM Server acts as a proxy between the agents and Kibana. +APM Server acts as a proxy between the agents and Kibana. Kibana communicates any changed settings to APM Server so that your agents only need to poll APM Server to determine which settings have changed. [float] ==== Supported configurations -Each Agent has its own list of supported configurations. +Each Agent has a list of supported configurations. After selecting a Service name and environment in the APM app, -a list of all available configuration options, +a list of all supported configuration options, including descriptions and default values, will be displayed. -Supported configurations are also marked in each Agent's configuration documentation: +Supported configurations are also tagged with the image:./images/dynamic-config.svg[] badge in each Agent's configuration reference: [horizontal] Go Agent:: {apm-go-ref}/configuration.html[Configuration reference] diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index a8f4f4bf0baaa..93733f5990a46 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -1,6 +1,10 @@ [role="xpack"] [[apm-api]] -== API +== APM app API + +++++ +REST API +++++ Some APM app features are provided via a REST API: diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc index b8552c007b13d..75ce5f56c96c6 100644 --- a/docs/apm/apm-alerts.asciidoc +++ b/docs/apm/apm-alerts.asciidoc @@ -1,12 +1,16 @@ [role="xpack"] [[apm-alerts]] -=== Create an alert +=== Alerts + +++++ +Create an alert +++++ beta::[] -The APM app is integrated with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature. -It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use, -and allows all alerts to be centrally managed from <>. +The APM app integrates with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature. +It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use +and enables central management of all alerts from <>. [role="screenshot"] image::apm/images/apm-alert.png[Create an alert in the APM app] @@ -28,9 +32,9 @@ This guide creates an alert for the `opbeans-java` service based on the followin From the APM app, navigate to the `opbeans-java` service and select **Alerts** > **Create threshold alert** > **Transaction duration**. -The name of your alert will automatically be set as `Transaction duration | opbeans-java`, -and the alert will be tagged with `apm` and `service.name:opbeans-java`. -Feel free to edit either of these defaults. +`Transaction duration | opbeans-java` is automatically set as the name of the alert, +and `apm` and `service.name:opbeans-java` are added as tags. +It's fine to change the name of the alert, but do not edit the tags. Based on the alert criteria, define the following alert details: @@ -42,7 +46,7 @@ Based on the alert criteria, define the following alert details: * **FOR THE LAST** - `5 minutes` Select an action type. -Multiple action types can be selected, but in this example we want to post to a slack channel. +Multiple action types can be selected, but in this example, we want to post to a Slack channel. Select **Slack** > **Create a connector**. Enter a name for the connector, and paste the webhook URL. @@ -63,9 +67,9 @@ This guide creates an alert for the `opbeans-python` service based on the follow From the APM app, navigate to the `opbeans-python` service and select **Alerts** > **Create threshold alert** > **Error rate**. -The name of your alert will automatically be set as `Error rate | opbeans-python`, -and the alert will be tagged with `apm` and `service.name:opbeans-python`. -Feel free to edit either of these defaults. +`Error rate | opbeans-python` is automatically set as the name of the alert, +and `apm` and `service.name:opbeans-python` are added as tags. +It's fine to change the name of the alert, but do not edit the tags. Based on the alert criteria, define the following alert details: @@ -93,5 +97,5 @@ From this page, you can create, edit, disable, mute, and delete alerts, and crea See {kibana-ref}/alerting-getting-started.html[alerting and actions] for more information. NOTE: If you are using an **on-premise** Elastic Stack deployment with security, -TLS must be configured for communication between Elasticsearch and Kibana. +communication between Elasticsearch and Kibana must have TLS configured. More information is in the alerting {kibana-ref}/alerting-getting-started.html#alerting-setup-prerequisites[prerequisites]. \ No newline at end of file diff --git a/docs/apm/bottlenecks.asciidoc b/docs/apm/bottlenecks.asciidoc deleted file mode 100644 index fbde3e9ddcbd6..0000000000000 --- a/docs/apm/bottlenecks.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[role="xpack"] -[[apm-bottlenecks]] -== Visualizing Application Bottlenecks - -Elastic APM captures different types of information from within instrumented applications: - -* {apm-overview-ref-v}/transaction-spans.html[*Spans*] contain information about a specific code path that has been executed. -They measure from the start to end of an activity, -and they can have a parent/child relationship with other spans. -* {apm-overview-ref-v}/transactions.html[*Transactions*] are a special kind of span that have extra metadata associated with them. -You can think of transactions as the highest level of work you’re measuring within a service. -As an example, a transaction could be a request to your server, a batch job, or a custom transaction type. -* {apm-overview-ref-v}/errors.html[*Errors*] contain information about the original exception that occurred or about a log created when the exception occurred. - -Each of these information types have a specific page associated with them in the APM app. -These various pages display the captured data in curated charts and tables that allow you to easily compare and debug your applications. - -For example, you can see information about response times, requests per minute, and status codes per endpoint. -You can even dive into a specific request sample and get a complete waterfall view of what your application is spending its time on. -You might see that your bottlenecks are in database queries, cache calls, or external requests. -For each incoming request and each application error, -you can also see contextual information such as the request header, user information, -system values, or custom data that you manually attached to the request. - -Having access to application-level insights with just a few clicks can drastically decrease the time you spend debugging errors, slow response times, and crashes. diff --git a/docs/apm/custom-links.asciidoc b/docs/apm/custom-links.asciidoc index 75c1c9d0009a2..4fdf39b643f94 100644 --- a/docs/apm/custom-links.asciidoc +++ b/docs/apm/custom-links.asciidoc @@ -1,6 +1,11 @@ +[role="xpack"] [[custom-links]] === Custom links +++++ +Create custom links +++++ + Elastic's custom link feature allows you to easily create up to 500 dynamic links based on your specific APM data. Custom links can be filtered to only appear in the APM app for relevant services, @@ -12,7 +17,7 @@ Ready to dive in? Jump straight to the <>. [[custom-links-create]] === Create a link -Each custom link consists of a label, url, and optional filter. +Each custom link consists of a label, URL, and optional filter. The easiest way to create a custom link is from within the actions dropdown in the transaction detail page. This method will automatically apply filters, scoping the link to that specific service, environment, transaction type, and transaction name. @@ -25,8 +30,7 @@ and selecting **Create custom link**. ==== Label The name of your custom link. -This text will be shown in the actions context menu, -so keep it as short as possible. +The actions context menu displays this text, so keep it as short as possible. TIP: Custom links are displayed alphabetically in the actions menu. @@ -39,8 +43,8 @@ URLs support dynamic field name variables, encapsulated in double curly brackets These variables will be replaced with transaction metadata when the link is clicked. Because everyone's data is different, -you'll need to examine your own traces to see what metadata is available for use. -The easiest way to do this is to select a trace in the APM app, and click **Metadata** in the **Trace Sample** table. +you'll need to examine your traces to see what metadata is available for use. +To do this, select a trace in the APM app, and click **Metadata** in the **Trace Sample** table. [role="screenshot"] image::apm/images/example-metadata.png[Example metadata] @@ -49,7 +53,7 @@ image::apm/images/example-metadata.png[Example metadata] [[custom-links-filters]] ==== Filters -Filter each link to only appear so it only appears for specific services or transactions. +Filter each link to only appear for specific services or transactions. You can filter on the following fields: * `service.name` @@ -57,7 +61,7 @@ You can filter on the following fields: * `transaction.type` * `transaction.name` -Multiple values are allowed when comma separated. +Multiple values are allowed when comma-separated. [float] [[custom-links-examples]] @@ -68,7 +72,7 @@ Multiple values are allowed when comma separated. :github-query-params: https://help.github.com/en/github/managing-your-work-on-github/about-automation-for-issues-and-pull-requests-with-query-parameters Not sure where to start with custom links? -Take a look at the examples below, and customize them to your liking! +Take a look at the examples below and customize them to your liking! [float] [[custom-links-examples-email]] diff --git a/docs/apm/deployment-annotations.asciidoc b/docs/apm/deployment-annotations.asciidoc new file mode 100644 index 0000000000000..6feadf8463226 --- /dev/null +++ b/docs/apm/deployment-annotations.asciidoc @@ -0,0 +1,17 @@ +[role="xpack"] +[[transactions-annotations]] +=== Track deployments with annotations + +++++ +Track deployments +++++ + +For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. +This feature automatically tags new deployments, so you can easily see if your deploy has increased response times +for an end-user, or if the memory/CPU footprint of your application has changed. +Being able to identify bad deployments quickly enables you to rollback and fix issues without causing costly outages. + +Deployment annotations are automatically enabled, and appear when the `service.version` of your app changes. + +[role="screenshot"] +image::apm/images/apm-transaction-annotation.png[Example view of transactions annotation in the APM app in Kibana] diff --git a/docs/apm/error-reports-watcher.asciidoc b/docs/apm/error-reports-watcher.asciidoc new file mode 100644 index 0000000000000..f41597932b751 --- /dev/null +++ b/docs/apm/error-reports-watcher.asciidoc @@ -0,0 +1,18 @@ +[role="xpack"] +[[errors-alerts-with-watcher]] +=== Error reports with Watcher + +++++ +Enable error reports +++++ + +You can use the power of the alerting features with Watcher to get reports on error occurrences. +The Watcher assistant, which is available on the errors overview, can help you set up a watch per service. + +Configure the watch with an occurrences threshold, time interval, and the desired actions, such as email or Slack notifications. +With Watcher, your team can set up reports within minutes. + +Watches are managed separately in the dedicated Watcher UI available in Advanced Settings. + +[role="screenshot"] +image::apm/images/apm-errors-watcher-assistant.png[Example view of the Watcher assistant for errors in APM app in Kibana] diff --git a/docs/apm/errors.asciidoc b/docs/apm/errors.asciidoc index 689fa1fffa89e..49351ec255858 100644 --- a/docs/apm/errors.asciidoc +++ b/docs/apm/errors.asciidoc @@ -1,7 +1,8 @@ +[role="xpack"] [[errors]] === Errors overview -TIP: {apm-overview-ref-v}/errors.html[Errors] are defined as groups of exceptions with matching exception or log messages. +TIP: {apm-overview-ref-v}/errors.html[Errors] are groups of exceptions with a similar exception or log message. The *Errors* overview provides a high-level view of the error message and culprit, the number of occurrences, and the most recent occurrence. @@ -20,7 +21,7 @@ image::apm/images/apm-error-group.png[Example view of the error group page in th Here, you'll see the error message, culprit, and the number of occurrences over time. Further down, you'll see the Error occurrence table. -This is where you can see the details of a sampled error within this group. +This table shows the details of a sampled error within this group. The error shown is always the most recent to occur. Each error occurrence features a breakdown of the exception, including the stack trace from when the error occurred, @@ -28,19 +29,4 @@ and additional contextual information to help debug the issue. In some cases, you might also see a Transaction sample ID. This feature allows you to make a connection between the errors and transactions, by linking you to the specific transaction where the error occurred. -This allows you to see the whole trace, including which services the request went through. - -[float] -[[errors-alerts-with-watcher]] -==== Error reports with Watcher - -You can use the power of the alerting features with Watcher to get reports on error occurrences. -The Watcher assistant, which is available on the errors overview, can help you set up a watch per service. - -Configure the watch with an occurrences threshold, time interval, and the desired actions, such as email or Slack notifications. -With Watcher, your team can set up reports within minutes. - -Watches are managed separately in the dedicated Watcher UI available in Advanced Settings. - -[role="screenshot"] -image::apm/images/apm-errors-watcher-assistant.png[Example view of the Watcher assistant for errors in APM app in Kibana] \ No newline at end of file +This allows you to see the whole trace, including which services the request went through. diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc index 99ba827b0198d..d53adb439f0c8 100644 --- a/docs/apm/filters.asciidoc +++ b/docs/apm/filters.asciidoc @@ -1,6 +1,11 @@ +[role="xpack"] [[filters]] === Filters +++++ +Filter data +++++ + APM provides two different ways you can filter your data within the APM App: * <> @@ -42,7 +47,7 @@ It allows you to view only relevant data, and is especially useful for separatin By default, all environments are displayed. If there are no environment options, you'll see "not defined". Service environments are defined when configuring your APM agents. -It's very important to be consistent when naming environments in your agents. +It's vital to be consistent when naming environments in your agents. See the documentation for each agent you're using to learn how to configure service environments: * *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`] @@ -62,9 +67,9 @@ but only where they are applicable -- they are typically most useful in their or As an example, if you select a host on the Services overview, then select a transaction group, the host filter will still be applied. -These filters are very useful for quickly and easily removing noise from your data. +These filters are very useful for quickly and easily removing noise from your data. With just a click, you can filter your transactions by the transaction result, -host, container ID, and more. +host, container ID, and more. [role="screenshot"] image::apm/images/local-filter.png[Local filters available in the APM app in Kibana] \ No newline at end of file diff --git a/docs/apm/getting-started.asciidoc b/docs/apm/getting-started.asciidoc index 4a391f1a49672..89ce0be1499c5 100644 --- a/docs/apm/getting-started.asciidoc +++ b/docs/apm/getting-started.asciidoc @@ -1,22 +1,45 @@ [role="xpack"] [[apm-getting-started]] -== Getting Started +== Get started with the APM app -If you have not already installed and configured Elastic APM, -the *Setup Instructions* will get you started. +++++ +Get started +++++ -[role="screenshot"] -image::apm/images/apm-setup.png[Installation instructions on the APM page in Kibana] +Elastic APM captures different types of information from within instrumented applications: +* *Spans* contain information about the execution of a specific code path. +They measure from the start to end of an activity, +and they can have a parent/child relationship with other spans. +* *Transactions* are a special kind of span; +they are the first span for a particular service and have extra metadata associated with them. +As an example, a transaction could be a request to your server, a batch job, or a custom transaction type. +*Traces* link together related transactions to show an end-to-end performance of how a request was served and which services were part of it. +* *Errors* contain information about the original exception that occurred or about a log created when the exception occurred. -Index patterns tell Kibana which Elasticsearch indices you want to explore. -An APM index pattern is necessary for certain features in the APM app, like the query bar. -To set up the correct index pattern, -simply click *Load Kibana objects* at the bottom of the Setup Instructions. +Curated charts and tables display the different types of APM data, which allows you to compare and debug your applications easily. -After you install an Elastic APM agent library in your application, -the application automatically appears in the APM app in {kib}. -No further configuration is required. +* <> +* <> +* <> +* <> +* <> +* <> +* <> -[role="screenshot"] -image::apm/images/apm-index-pattern.png[Setup index pattern for APM in Kibana] +TIP: Want to learn more about the Elastic APM ecosystem? +See the {apm-get-started-ref}/overview.html[APM Overview]. + +include::services.asciidoc[] + +include::traces.asciidoc[] + +include::transactions.asciidoc[] + +include::spans.asciidoc[] + +include::errors.asciidoc[] + +include::metrics.asciidoc[] + +include::service-maps.asciidoc[] diff --git a/docs/apm/how-to-guides.asciidoc b/docs/apm/how-to-guides.asciidoc new file mode 100644 index 0000000000000..9b0efb4f7a359 --- /dev/null +++ b/docs/apm/how-to-guides.asciidoc @@ -0,0 +1,32 @@ +[role="xpack"] +[[apm-how-to]] +== How-to guides + +Learn how to perform common APM app tasks. + + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + +include::agent-configuration.asciidoc[] + +include::apm-alerts.asciidoc[] + +include::custom-links.asciidoc[] + +include::error-reports-watcher.asciidoc[] + +include::filters.asciidoc[] + +include::machine-learning.asciidoc[] + +include::advanced-queries.asciidoc[] + +include::deployment-annotations.asciidoc[] \ No newline at end of file diff --git a/docs/apm/images/dynamic-config.svg b/docs/apm/images/dynamic-config.svg new file mode 100644 index 0000000000000..df62a3c84f4b4 --- /dev/null +++ b/docs/apm/images/dynamic-config.svg @@ -0,0 +1 @@ + DynamicDynamic \ No newline at end of file diff --git a/docs/apm/index.asciidoc b/docs/apm/index.asciidoc index d3f0dc5b7f11f..79190efccdff2 100644 --- a/docs/apm/index.asciidoc +++ b/docs/apm/index.asciidoc @@ -4,25 +4,35 @@ [partintro] -- -Elastic Application Performance Monitoring (APM) automatically collects in-depth -performance metrics and errors from inside your applications. - -The **APM** app in {kib} is provided with the basic license. It -enables developers to drill down into the performance data for their applications -and quickly locate the performance bottlenecks. - -* <> -* <> -* <> - -NOTE: For more information about the components of Elastic APM, -see the {apm-get-started-ref}/overview.html[APM Overview]. +The APM app in {kib} is provided with the basic license. +It allows you to monitor your software services and applications in real-time; +visualize detailed performance information on your services, +identify and analyze errors, +and monitor host-level and agent-specific metrics like JVM and Go runtime metrics. + +[float] +[[apm-bottlenecks]] +== Visualizing application bottlenecks + +Having access to application-level insights with just a few clicks can drastically decrease the time you spend +debugging errors, slow response times, and crashes. + +For example, you can see information about response times, requests per minute, and status codes per endpoint. +You can even dive into a specific request sample and get a complete waterfall view of what your application is spending its time on. +You might see that your bottlenecks are in database queries, cache calls, or external requests. +For each incoming request and each application error, +you can also see contextual information such as the request header, user information, +system values, or custom data that you manually attached to the request. -- +include::set-up.asciidoc[] + include::getting-started.asciidoc[] -include::bottlenecks.asciidoc[] +include::how-to-guides.asciidoc[] -include::using-the-apm-ui.asciidoc[] +include::settings.asciidoc[] include::api.asciidoc[] + +include::troubleshooting.asciidoc[] diff --git a/docs/apm/machine-learning.asciidoc b/docs/apm/machine-learning.asciidoc new file mode 100644 index 0000000000000..9d347fc4f1111 --- /dev/null +++ b/docs/apm/machine-learning.asciidoc @@ -0,0 +1,27 @@ +[role="xpack"] +[[machine-learning-integration]] +=== Machine Learning integration + +++++ +Integrate with machine learning +++++ + +The Machine Learning integration will initiate a new job predefined to calculate anomaly scores on transaction response times. +The response time graph will show the expected bounds and add an annotation when the anomaly score is 75 or above. +Jobs can be created per transaction type, and based on the average response time. +Manage jobs in the *Machine Learning jobs management*. + +[role="screenshot"] +image::apm/images/apm-ml-integration.png[Example view of anomaly scores on response times in APM app in Kibana] + +[float] +[[create-ml-integration]] +=== Create a new machine learning job + +To enable machine learning anomaly detection, first choose a service to monitor. +Then, select **Integrations** > **Enable ML anomaly detection** and click **Create job**. +That's it! After a few minutes, the job will begin calculating results; +it might take additional time for results to appear on your graph. + +APM specific anomaly detection wizards are also available for certain Agents. +See the machine learning {ml-docs}/ootb-ml-jobs-apm.html[APM anomaly detection configurations] for more information. diff --git a/docs/apm/metrics.asciidoc b/docs/apm/metrics.asciidoc index ab394b785ef84..e82a4fbd5c291 100644 --- a/docs/apm/metrics.asciidoc +++ b/docs/apm/metrics.asciidoc @@ -1,3 +1,4 @@ +[role="xpack"] [[metrics]] === Metrics overview @@ -5,7 +6,7 @@ The *Metrics* overview provides agent-specific metrics, which lets you perform more in-depth root cause analysis investigations within the APM app. If you're experiencing a problem with your service, you can use this page to attempt to find the underlying cause. -For example, you might be able to correlate a high number of errors with a long transaction duration, high CPU usage, or a memory leak. +For example, you might be able to correlate a high number of errors with a long transaction duration, high CPU usage, or a memory leak. [role="screenshot"] image::apm/images/apm-metrics.png[Example view of the Metrics overview in APM app in Kibana] @@ -17,19 +18,3 @@ thread count, garbage collection rate, and garbage collection time spent per min [role="screenshot"] image::apm/images/jvm-metrics.png[Example view of the Metrics overview for the Java Agent] - -[[machine-learning-integration]] -=== Machine Learning integration - -The Machine Learning integration will initiate a new job predefined to calculate anomaly scores on transaction response times. -The response time graph will show the expected bounds and annotate the graph when the anomaly score is 75 or above. - -[role="screenshot"] -image::apm/images/apm-ml-integration.png[Example view of anomaly scores on response times in APM app in Kibana] - -Jobs can be created per transaction type and based on the average response time. -You can manage jobs in the *Machine Learning jobs management*. -It might take some time for results to appear on the graph. - -Machine learning is a platinum feature. For a comparison of the Elastic license levels, -see https://www.elastic.co/subscriptions[the subscription page]. \ No newline at end of file diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index e0d84f33b4dcb..be86b9d522ac5 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -1,37 +1,53 @@ +[role="xpack"] [[service-maps]] === Service maps beta::[] -A service map is a real-time diagram of the interactions occurring in your application’s architecture. -It allows you to easily visualize data flow and high-level statistics, like average transaction duration, -requests per minute, errors per minute, and metrics, allowing you to quickly assess the status of your services. +WARNING: Service map support for Internet Explorer 11 is extremely limited. +Please use Chrome or Firefox if available. -Our beta offering creates two types of service maps: +A service map is a real-time visual representation of the instrumented services in your application's architecture. +It shows you how these services are connected, along with high-level metrics like average transaction duration, +requests per minute, and errors per minute, that allow you to quickly assess the status of your services. -* Global: All services and connections are shown. -* Service-specific: Selecting a specific service will highlight it's connections. +We currently surface two types of service maps: + +* Global: All services instrumented with APM agents and the connections between them are shown. +* Service-specific: Highlight connections for a selected service. [role="screenshot"] image::apm/images/service-maps.png[Example view of service maps in the APM app in Kibana] +[float] +[[service-maps-how]] +=== How do service maps work? + +Service maps rely on distributed traces to draw connections between services. +As {apm-overview-ref-v}/distributed-tracing.html[distributed tracing] is enabled out-of-the-box for supported technologies, so are service maps. +However, if a service isn't instrumented, +or a `traceparent` header isn't being propagated to it, +distributed tracing will not work, and the connection will not be drawn on the map. + [float] [[visualize-your-architecture]] === Visualize your architecture Select the **Service Map** tab to get started. -By default, all services and connections are shown. -Whether your onboarding a new engineer, or just trying to grasp the big picture, +By default, all instrumented services and connections are shown. +Whether you're onboarding a new engineer, or just trying to grasp the big picture, click around, zoom in and out, and begin to visualize how your services are connected. If there's a specific service that interests you, select that service to highlight its connections. Clicking **Focus map** will refocus the map on that specific service and lock the connection highlighting. -From here, select **Service Details**, or click on the **Transaction** tab to jump to the Transaction overview. +From here, select **Service Details**, or click on the **Transaction** tab to jump to the Transaction overview +for the selected service. You can also use the tabs at the top of the page to easily jump to the **Errors** or **Metrics** overview. -While it's not possible to query in service maps, it is possible to filter by environment. +Filter out your maps by picking the environment from the environment drop-down filter. This can be useful if you have two or more services, in separate environments, but with the same name. -Use the environment drop down to only see the data you're interested in, like `dev` or `production`. +Use the environment drop-down to only see the data you're interested in, like `dev` or `production`. +Additional filters are not currently available for service maps. [role="screenshot"] image::apm/images/service-maps-java.png[Example view of service maps with Java highlighted in the APM app in Kibana] @@ -46,3 +62,18 @@ Nodes appear on the map in one of two shapes: * **Diamond**: Databases, external, and messaging. Interior icons represent the generic type, with specific icons for known entities, like Elasticsearch. Type and subtype are based on `span.type`, and `span.subtype`. + +[float] +[[service-maps-supported]] +=== Supported APM Agents + +Service maps are supported for the following Agent versions: + +[horizontal] +Go Agent:: >= v1.7.0 +Java Agent:: >= v1.13.0 +.NET Agent:: >= v1.3.0 +Node.js Agent:: >= v3.6.0 +Python Agent:: >= v5.5.0 +Ruby Agent:: >= v3.6.0 +Real User Monitoring (RUM) Agent:: >= v4.7.0 diff --git a/docs/apm/services.asciidoc b/docs/apm/services.asciidoc index 9af3e74562dab..395e23c379306 100644 --- a/docs/apm/services.asciidoc +++ b/docs/apm/services.asciidoc @@ -1,9 +1,9 @@ +[role="xpack"] [[services]] === Services overview -The *Services* overview gives you quick insights into the health and general performance of each service. - -You can add services by setting the `service.name` configuration in each of the {apm-agents-ref}[APM agents] you’re instrumenting. +The *Services* overview gives you quick insights into the health and general performance of all of your instrumented services. +Services are sorted by the `service.name` configured in each of the {apm-agents-ref}[APM agents] you’ve installed. [role="screenshot"] image::apm/images/apm-services-overview.png[Example view of services table the APM app in Kibana] \ No newline at end of file diff --git a/docs/apm/set-up.asciidoc b/docs/apm/set-up.asciidoc new file mode 100644 index 0000000000000..c5bf5e13b640b --- /dev/null +++ b/docs/apm/set-up.asciidoc @@ -0,0 +1,35 @@ +[role="xpack"] +[[apm-ui]] +== Set up the APM app + +++++ +Set up +++++ + +APM is available via the navigation sidebar in {Kib}. +If you have not already installed and configured Elastic APM, +the *Setup Instructions* in Kibana will get you started. + +[role="screenshot"] +image::apm/images/apm-setup.png[Installation instructions on the APM page in Kibana] + +[float] +[[apm-configure-index-pattern]] +=== Load the index pattern + +Index patterns tell Kibana which Elasticsearch indices you want to explore. +An APM index pattern is necessary for certain features in the APM app, like the query bar. +To set up the correct index pattern, +simply click *Load Kibana objects* at the bottom of the Setup Instructions. + +[role="screenshot"] +image::apm/images/apm-index-pattern.png[Setup index pattern for APM in Kibana] + +To use a custom index pattern, see <>. + +[float] +[[apm-getting-started-next]] +=== Next steps + +No further configuration in the APM app is required. +Install an APM Agent library in your service to begin visualizing and analyzing your data! diff --git a/docs/apm/settings.asciidoc b/docs/apm/settings.asciidoc index 37122fc9c635d..44da63143f63f 100644 --- a/docs/apm/settings.asciidoc +++ b/docs/apm/settings.asciidoc @@ -1,18 +1,23 @@ // Do not link directly to this page. // Link to the anchor in `/docs/settings/apm-settings.asciidoc` instead. +[role="xpack"] [[apm-settings-in-kibana]] -=== APM settings in Kibana +== APM app settings + +++++ +Settings +++++ You do not need to configure any settings to use the APM app. It is enabled by default. [float] [[apm-indices-settings]] -==== APM Indices +=== APM Indices include::./../settings/apm-settings.asciidoc[tag=apm-indices-settings] [float] [[general-apm-settings]] -==== General APM settings +=== General APM settings include::./../settings/apm-settings.asciidoc[tag=general-apm-settings] diff --git a/docs/apm/spans.asciidoc b/docs/apm/spans.asciidoc index ef21e1c5333e0..2eed339160fc4 100644 --- a/docs/apm/spans.asciidoc +++ b/docs/apm/spans.asciidoc @@ -1,7 +1,8 @@ +[role="xpack"] [[spans]] === Span timeline -TIP: A {apm-overview-ref-v}/transaction-spans.html[span] is defined as the duration of a single event. +TIP: A {apm-overview-ref-v}/transaction-spans.html[span] is the duration of a single event. Spans are automatically captured by APM agents, and you can also define custom spans. Each span has a type and is defined by a different color in the timeline/waterfall visualization. @@ -28,7 +29,7 @@ Services in a distributed trace are separated by color and listed in the order t [role="screenshot"] image::apm/images/apm-services-trace.png[Example of distributed trace colors in the APM app in Kibana] -Don't forget, a distributed trace includes more than one transaction. +Don't forget; a distributed trace includes more than one transaction. When viewing these distributed traces in the timeline waterfall, you'll see this image:apm/images/transaction-icon.png[APM icon] icon, which indicates the next transaction in the trace. These transactions can be expanded and viewed in detail by clicking on them. diff --git a/docs/apm/traces.asciidoc b/docs/apm/traces.asciidoc index 09d8f52b92840..8eef3d9bed4db 100644 --- a/docs/apm/traces.asciidoc +++ b/docs/apm/traces.asciidoc @@ -1,12 +1,17 @@ +[role="xpack"] [[traces]] === Traces overview +TIP: Traces link together related transactions to show an end-to-end performance of how a request was served +and which services were part of it. +In addition to the Traces overview, you can view your application traces in the <>. + The *Traces* overview displays the entry transaction for all traces in your application. If you're using <>, this view is key to finding the critical paths within your application. Transactions with the same name are grouped together and only shown once in this table. By default, transactions are sorted by _Impact_. -Impact helps show the most used and slowest endpoints in your service - in other words, +Impact helps show the most used and slowest endpoints in your service--in other words, it's the collective amount of pain a specific endpoint is causing your users. If there's a particular endpoint you're worried about, you can click on it to view the <>. @@ -33,4 +38,4 @@ You can use the <> to view a waterfall displa [role="screenshot"] image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM app in Kibana] -TIP: Distributed tracing is supported by all APM agents and there’s no additional configuration needed. \ No newline at end of file +TIP: Distributed tracing is supported by all APM agents, and there's no additional configuration needed. \ No newline at end of file diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 1eb037009efff..2e1022e6d684c 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -1,3 +1,4 @@ +[role="xpack"] [[transactions]] === Transaction overview @@ -56,20 +57,6 @@ For further details, including troubleshooting and custom implementation instruc refer to the documentation for each {apm-agents-ref}[APM Agent] you've implemented. ==== -[[transactions-annotations]] -==== Transaction annotations - -For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. -This feature automatically tags new deployments, so you can easily see if your deploy has increased response times -for an end-user, or if the memory/CPU footprint of your application has increased. -Being able to quickly identify bad deployments enables you to rollback and fix issues without causing costly outages. - -Deployment annotations are automatically enabled, and appear when the `service.version` of your app changes. - -[role="screenshot"] -image::apm/images/apm-transaction-annotation.png[Example view of transactions annotation in the APM app in Kibana] - - [[rum-transaction-overview]] ==== RUM Transaction overview @@ -82,7 +69,7 @@ image::apm/images/apm-geo-ui.jpg[average page load duration distribution] This data is available due to the geo-ip and user agent pipelines being enabled by default, which allows for the capture of geo-location and user agent data. These visualizations make it easy for you to visualize performance information about your -end users' experience based on their location. +end-users' experience based on their location. [[transaction-details]] ==== Transaction details @@ -103,7 +90,7 @@ The number of requests per bucket is displayed when hovering over the graph, and [role="screenshot"] image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph] -This graph shows a typical distribution, and indicates most of our requests were served quickly - awesome! +This graph shows a typical distribution, and indicates most of our requests were served quickly--awesome! It's the requests on the right, the ones taking longer than average, that we probably want to focus on. When you select one of these buckets, diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index c6174e1786c78..eb4fb790afd7f 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -1,19 +1,24 @@ -[[troubleshooting]] -=== Troubleshooting common problems +[[troubleshooting]] +== Troubleshoot common problems + +++++ +Troubleshooting +++++ If you have something to add to this section, please consider creating a pull request with your proposed changes at https://github.com/elastic/kibana. -Also check out the https://discuss.elastic.co/c/apm[APM discussion forum]. +Also, check out the https://discuss.elastic.co/c/apm[APM discussion forum]. +[float] [[no-apm-data-found]] -==== No APM data found +=== No APM data found This section can help with any of the following: * Data isn't displaying in the APM app -* You're seeing a message like "No Services Found", -* You're seeing errors like "Fielddata is disabled on text fields by default..." +* You see a message like "No Services Found", +* You see errors like "Fielddata is disabled on text fields by default..." There are a number of factors that could be at play here. One important thing to double-check first is your index template. @@ -52,12 +57,13 @@ you can customize the indices that the APM app uses to display data. Navigate to *APM* > *Settings* > *Indices*, and change all `apm_oss.*Pattern` values to include the new index pattern. For example: `customIndexName-*`. -==== Unknown route +[float] +=== Unknown route The {apm-app-ref}/transactions.html[transaction overview] will only display helpful information when the transactions in your services are named correctly. If you're seeing "GET unknown route" or "unknown route" in the APM app, -it could be a sign that something isn't working like it should. +it could be a sign that something isn't working as it should. Elastic APM Agents come with built-in support for popular frameworks out-of-the-box. This means, among other things, that the Agent will try to automatically name HTTP requests. @@ -71,7 +77,8 @@ To resolve this, you'll need to head over to the relevant {apm-agents-ref}[Agent Specifically, view the Agent's supported technologies page. You can also use the Agent's public API to manually set a name for the transaction. -==== Fields are not searchable +[float] +=== Fields are not searchable In Elasticsearch, index templates are used to define settings and mappings that determine how fields should be analyzed. The recommended index template file for APM Server is installed by the APM Server packages. @@ -92,7 +99,7 @@ Selecting the `apm-*` index pattern shows a listing of every field defined in th *Ensure a field is searchable* There are two things you can do to if you'd like to ensure a field is searchable: -1. Index your additional data as {apm-overview-ref}/metadata.html[labels] instead. +1. Index your additional data as {apm-overview-ref-v}/metadata.html[labels] instead. These are dynamic by default, which means they will be indexed and become searchable and aggregatable. 2. Use the {apm-server-ref}/configuration-template.html[`append_fields`] feature. As an example, diff --git a/docs/apm/using-the-apm-ui.asciidoc b/docs/apm/using-the-apm-ui.asciidoc deleted file mode 100644 index 904718999069d..0000000000000 --- a/docs/apm/using-the-apm-ui.asciidoc +++ /dev/null @@ -1,51 +0,0 @@ -[role="xpack"] -[[apm-ui]] -== Using APM - -APM is designed to be as intuitive as possible, -but you might come across certain terms or concepts that don’t feel native to you. -Not to worry, we've created this guide to help you get the most out of Elastic APM. - -APM is available via the navigation sidebar in {Kib}. - -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> - -include::filters.asciidoc[] - -include::services.asciidoc[] - -include::traces.asciidoc[] - -include::transactions.asciidoc[] - -include::spans.asciidoc[] - -include::service-maps.asciidoc[] - -include::errors.asciidoc[] - -include::metrics.asciidoc[] - -include::apm-alerts.asciidoc[] - -include::agent-configuration.asciidoc[] - -include::custom-links.asciidoc[] - -include::advanced-queries.asciidoc[] - -include::settings.asciidoc[] - -include::troubleshooting.asciidoc[] diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 91bbef5690fd5..fd53c3aeb3605 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -5,7 +5,10 @@ APM settings ++++ -You do not need to configure any settings to use the APM app. It is enabled by default. +These settings allow the APM app to function, and specify the data that it surfaces. +Unless you've customized your setup, +you do not need to configure any settings to use the APM app. +It is enabled by default. [float] [[apm-indices-settings-kb]] @@ -33,29 +36,29 @@ image::settings/images/apm-settings.png[APM app settings in Kibana] If you'd like to change any of the default values, copy and paste the relevant settings into your `kibana.yml` configuration file. +Changing these settings may disable features of the APM App. -xpack.apm.enabled:: Set to `false` to disabled the APM plugin {kib}. Defaults to -`true`. +xpack.apm.enabled:: Set to `false` to disable the APM app. Defaults to `true`. -xpack.apm.ui.enabled:: Set to `false` to hide the APM plugin {kib} from the menu. Defaults to -`true`. +xpack.apm.ui.enabled:: Set to `false` to hide the APM app from the menu. Defaults to `true`. -xpack.apm.ui.transactionGroupBucketSize:: Number of top transaction groups displayed in APM plugin in Kibana. Defaults to `100`. +xpack.apm.ui.transactionGroupBucketSize:: Number of top transaction groups displayed in the APM app. Defaults to `100`. -xpack.apm.ui.maxTraceItems:: Max number of child items displayed when viewing trace details. Defaults to `1000`. +xpack.apm.ui.maxTraceItems:: Maximum number of child items displayed when viewing trace details. Defaults to `1000`. -apm_oss.indexPattern:: Index pattern is used for integrations with Machine Learning and Kuery Bar. It must match all apm indices. Defaults to `apm-*`. +apm_oss.indexPattern:: The index pattern used for integrations with Machine Learning and Query Bar. +It must match all apm indices. Defaults to `apm-*`. -apm_oss.errorIndices:: Matcher for indices containing error documents. Defaults to `apm-*`. +apm_oss.errorIndices:: Matcher for all {apm-server-ref}/error-indices.html[error indices]. Defaults to `apm-*`. -apm_oss.onboardingIndices:: Matcher for indices containing onboarding documents. Defaults to `apm-*`. +apm_oss.onboardingIndices:: Matcher for all onboarding indices. Defaults to `apm-*`. -apm_oss.spanIndices:: Matcher for indices containing span documents. Defaults to `apm-*`. +apm_oss.spanIndices:: Matcher for all {apm-server-ref}/span-indices.html[span indices]. Defaults to `apm-*`. -apm_oss.transactionIndices:: Matcher for indices containing transaction documents. Defaults to `apm-*`. +apm_oss.transactionIndices:: Matcher for all {apm-server-ref}/transaction-indices.html[transaction indices]. Defaults to `apm-*`. -apm_oss.metricsIndices:: Matcher for indices containing metric documents. Defaults to `apm-*`. +apm_oss.metricsIndices:: Matcher for all {apm-server-ref}/metricset-indices.html[metrics indices]. Defaults to `apm-*`. -apm_oss.sourcemapIndices:: Matcher for indices containing sourcemap documents. Defaults to `apm-*`. +apm_oss.sourcemapIndices:: Matcher for all {apm-server-ref}/sourcemap-indices.html[source map indices]. Defaults to `apm-*`. // end::general-apm-settings[] From afb48bceca1aa64f9e70049d649f7e203fb1b613 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Wed, 15 Apr 2020 18:03:17 +0300 Subject: [PATCH 13/44] [NP] Move visTypeXy plugin (#63449) * Move vis_type_xy into NP * Substitute usage in vis_type_vislib * Disable plugin by default --- .github/CODEOWNERS | 2 +- .../vis_type_vislib/public/legacy.ts | 1 + .../vis_type_vislib/public/plugin.ts | 11 ++-- src/legacy/core_plugins/vis_type_xy/index.ts | 56 ------------------- .../core_plugins/vis_type_xy/package.json | 4 -- .../ui/public/new_platform/new_platform.ts | 2 + src/plugins/vis_type_xy/kibana.json | 7 +++ .../vis_type_xy/public/index.ts | 4 +- .../vis_type_xy/public/plugin.ts | 16 +++--- .../vis_type_xy/server/index.ts} | 25 +++------ 10 files changed, 34 insertions(+), 94 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_xy/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_xy/package.json create mode 100644 src/plugins/vis_type_xy/kibana.json rename src/{legacy/core_plugins => plugins}/vis_type_xy/public/index.ts (89%) rename src/{legacy/core_plugins => plugins}/vis_type_xy/public/plugin.ts (85%) rename src/{legacy/core_plugins/vis_type_xy/public/legacy.ts => plugins/vis_type_xy/server/index.ts} (50%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5e40dc044b97a..8a8a79e7c5f65 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,7 +13,7 @@ /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app -/src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app +/src/plugins/vis_type_xy/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts index aa11e0ef41fba..579caa1cb88f6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -30,6 +30,7 @@ const setupPlugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: npSetup.plugins.visualizations, charts: npSetup.plugins.charts, + visTypeXy: npSetup.plugins.visTypeXy, }; const startPlugins: Readonly = { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index 2731fb6f5fbe6..ef3f664252856 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -24,6 +24,7 @@ import { PluginInitializerContext, } from 'kibana/public'; +import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; @@ -39,7 +40,6 @@ import { createGoalVisTypeDefinition, } from './vis_type_vislib_vis_types'; import { ChartsPluginSetup } from '../../../../plugins/charts/public'; -import { ConfigSchema as VisTypeXyConfigSchema } from '../../vis_type_xy'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; import { setFormatService, setDataActions } from './services'; @@ -53,6 +53,7 @@ export interface VisTypeVislibPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + visTypeXy?: VisTypeXyPluginSetup; } /** @internal */ @@ -68,7 +69,7 @@ export class VisTypeVislibPlugin implements Plugin { public async setup( core: VisTypeVislibCoreSetup, - { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies + { expressions, visualizations, charts, visTypeXy }: VisTypeVislibPluginSetupDependencies ) { const visualizationDependencies: Readonly = { uiSettings: core.uiSettings, @@ -86,12 +87,8 @@ export class VisTypeVislibPlugin implements Plugin { ]; const vislibFns = [createVisTypeVislibVisFn(), createPieVisFn()]; - const visTypeXy = core.injectedMetadata.getInjectedVar('visTypeXy') as - | VisTypeXyConfigSchema['visTypeXy'] - | undefined; - // if visTypeXy plugin is disabled it's config will be undefined - if (!visTypeXy || !visTypeXy.enabled) { + if (!visTypeXy) { const convertedTypes: any[] = []; const convertedFns: any[] = []; diff --git a/src/legacy/core_plugins/vis_type_xy/index.ts b/src/legacy/core_plugins/vis_type_xy/index.ts deleted file mode 100644 index 58d2e425eef40..0000000000000 --- a/src/legacy/core_plugins/vis_type_xy/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../types'; - -export interface ConfigSchema { - visTypeXy: { - enabled: boolean; - }; -} - -const visTypeXyPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'visTypeXy', - require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars(server): ConfigSchema { - const config = server.config(); - - return { - visTypeXy: { - enabled: config.get('visTypeXy.enabled') as boolean, - }, - }; - }, - }, - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(false), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default visTypeXyPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_xy/package.json b/src/legacy/core_plugins/vis_type_xy/package.json deleted file mode 100644 index 920f7dcb44e87..0000000000000 --- a/src/legacy/core_plugins/vis_type_xy/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "visTypeXy", - "version": "kibana" -} diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 21b80e827e4c2..80fb837258d4c 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -22,6 +22,7 @@ import { IScope } from 'angular'; import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { createBrowserHistory } from 'history'; +import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; import { DashboardStart } from '../../../../plugins/dashboard/public'; import { setSetupServices, setStartServices } from './set_services'; import { @@ -93,6 +94,7 @@ export interface PluginsSetup { savedObjectsManagement: SavedObjectsManagementPluginSetup; mapsLegacy: MapsLegacyPluginSetup; indexPatternManagement: IndexPatternManagementSetup; + visTypeXy?: VisTypeXyPluginSetup; } export interface PluginsStart { diff --git a/src/plugins/vis_type_xy/kibana.json b/src/plugins/vis_type_xy/kibana.json new file mode 100644 index 0000000000000..ca02da45e9112 --- /dev/null +++ b/src/plugins/vis_type_xy/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "visTypeXy", + "version": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["charts", "expressions", "visualizations"] +} diff --git a/src/legacy/core_plugins/vis_type_xy/public/index.ts b/src/plugins/vis_type_xy/public/index.ts similarity index 89% rename from src/legacy/core_plugins/vis_type_xy/public/index.ts rename to src/plugins/vis_type_xy/public/index.ts index 218dc8aa8a683..9af75ce9059e9 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/index.ts +++ b/src/plugins/vis_type_xy/public/index.ts @@ -17,9 +17,11 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../core/public'; +import { PluginInitializerContext } from '../../../core/public'; import { VisTypeXyPlugin as Plugin } from './plugin'; +export { VisTypeXyPluginSetup } from './plugin'; + export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } diff --git a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts similarity index 85% rename from src/legacy/core_plugins/vis_type_xy/public/plugin.ts rename to src/plugins/vis_type_xy/public/plugin.ts index ab01b6b3153fb..667018c1e6e30 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -25,18 +25,18 @@ import { PluginInitializerContext, } from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { - VisualizationsSetup, - VisualizationsStart, -} from '../../../../plugins/visualizations/public'; -import { ChartsPluginSetup } from '../../../../plugins/charts/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { ChartsPluginSetup } from '../../charts/public'; export interface VisTypeXyDependencies { uiSettings: IUiSettingsClient; charts: ChartsPluginSetup; } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface VisTypeXyPluginSetup {} + /** @internal */ export interface VisTypeXyPluginSetupDependencies { expressions: ReturnType; @@ -53,7 +53,7 @@ export interface VisTypeXyPluginStartDependencies { type VisTypeXyCoreSetup = CoreSetup; /** @internal */ -export class VisTypeXyPlugin implements Plugin { +export class VisTypeXyPlugin implements Plugin { constructor(public initializerContext: PluginInitializerContext) {} public async setup( @@ -77,6 +77,8 @@ export class VisTypeXyPlugin implements Plugin { visTypeDefinitions.forEach((vis: any) => visualizations.createBaseVisualization(vis(visualizationDependencies)) ); + + return {}; } public start(core: CoreStart, deps: VisTypeXyPluginStartDependencies) { diff --git a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts b/src/plugins/vis_type_xy/server/index.ts similarity index 50% rename from src/legacy/core_plugins/vis_type_xy/public/legacy.ts rename to src/plugins/vis_type_xy/server/index.ts index 740ceeaac6a7d..afc879dc9c845 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts +++ b/src/plugins/vis_type_xy/server/index.ts @@ -17,24 +17,13 @@ * under the License. */ -import { npSetup, npStart } from 'ui/new_platform'; -import { PluginInitializerContext } from 'kibana/public'; +import { schema } from '@kbn/config-schema'; -import { plugin } from '.'; -import { VisTypeXyPluginSetupDependencies, VisTypeXyPluginStartDependencies } from './plugin'; - -const setupPlugins: Readonly = { - expressions: npSetup.plugins.expressions, - visualizations: npSetup.plugins.visualizations, - charts: npSetup.plugins.charts, -}; - -const startPlugins: Readonly = { - expressions: npStart.plugins.expressions, - visualizations: npStart.plugins.visualizations, +export const config = { + schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), }; -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, startPlugins); +export const plugin = () => ({ + setup() {}, + start() {}, +}); From 00a1144ae21cc0cda0980f25be153aba7b99298d Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Wed, 15 Apr 2020 17:07:57 +0200 Subject: [PATCH 14/44] Refactor Plugins to access elasticsearch from CoreStart (#59915) * x-pack/watcher: use Elasticsearch from CoreStart * x-pack/upgrade_assistant: use Elasticsearch from CoreStart * x-pack/actions: use Elasticsearch from CoreStart * x-pack/alerting: use Elasticsearch from CoreStart * x-pack/lens: use Elasticsearch from CoreStart * expressions: use Elasticsearch from CoreStart * x-pack/remote_clusters: remove unused Elasticsearch dependency on CoreSetup * x-pack/oss_telemetry: use Elasticsearch from CoreStart * Cleanup after #59886 * x-pack/watcher: create custom client only once * Revert "x-pack/watcher: create custom client only once" This reverts commit 78fc4d2e93c05b1fd014bd6fa31a608d6968ed43. * Revert "x-pack/watcher: use Elasticsearch from CoreStart" This reverts commit b621af93882ef7e64e35dcfeaf4dd95af5359f26. * x-pack/task_manager: use Elasticsearch from CoreStart * x-pack/event_log: use Elasticsearch from CoreStart * x-pack/alerting: use Elasticsearch from CoreStart * x-pack/apm: use Elasticsearch from CoreStart * x-pack/actions: use Elasticsearch from CoreStart * PR Feedback * APM review nits * Remove unused variable * Remove unused variable * x-pack/apm: better typesafety Co-authored-by: Elastic Machine --- .../elasticsearch/elasticsearch_service.ts | 17 +++-- src/core/server/server.ts | 2 +- src/plugins/expressions/server/legacy.ts | 23 +++--- x-pack/plugins/actions/server/plugin.test.ts | 3 + x-pack/plugins/actions/server/plugin.ts | 25 +++---- x-pack/plugins/actions/server/usage/task.ts | 8 ++- x-pack/plugins/alerting/server/plugin.ts | 12 ++-- x-pack/plugins/alerting/server/usage/task.ts | 9 ++- x-pack/plugins/apm/server/plugin.ts | 72 +++++++++++-------- .../server/es/cluster_client_adapter.test.ts | 2 +- .../server/es/cluster_client_adapter.ts | 9 +-- x-pack/plugins/event_log/server/es/context.ts | 4 +- x-pack/plugins/event_log/server/plugin.ts | 4 +- x-pack/plugins/lens/server/usage/task.ts | 5 +- .../oss_telemetry/server/lib/tasks/index.ts | 10 ++- .../lib/tasks/visualizations/task_runner.ts | 8 +-- x-pack/plugins/oss_telemetry/server/plugin.ts | 2 +- .../oss_telemetry/server/test_utils/index.ts | 12 ++-- .../plugins/remote_clusters/server/plugin.ts | 8 +-- .../plugins/remote_clusters/server/types.ts | 4 +- x-pack/plugins/task_manager/server/plugin.ts | 5 +- .../lib/telemetry/usage_collector.test.ts | 2 +- .../server/lib/telemetry/usage_collector.ts | 8 +-- .../upgrade_assistant/server/plugin.ts | 11 ++- .../routes/reindex_indices/reindex_indices.ts | 8 +-- 25 files changed, 148 insertions(+), 125 deletions(-) diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 684f6e15caff9..18725f04a05b5 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -33,7 +33,12 @@ import { CoreService } from '../../types'; import { merge } from '../../utils'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { ClusterClient, ScopeableRequest } from './cluster_client'; +import { + ClusterClient, + ScopeableRequest, + IClusterClient, + ICustomClusterClient, +} from './cluster_client'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; @@ -58,12 +63,14 @@ export class ElasticsearchService implements CoreService { private readonly log: Logger; private readonly config$: Observable; - private subscription: Subscription | undefined; + private subscription?: Subscription; private stop$ = new Subject(); private kibanaVersion: string; - createClient: InternalElasticsearchServiceSetup['createClient'] | undefined; - dataClient: InternalElasticsearchServiceSetup['dataClient'] | undefined; - adminClient: InternalElasticsearchServiceSetup['adminClient'] | undefined; + private createClient?: ( + type: string, + clientConfig?: Partial + ) => ICustomClusterClient; + private adminClient?: IClusterClient; constructor(private readonly coreContext: CoreContext) { this.kibanaVersion = coreContext.env.packageInfo.version; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 07ea431dd3a0d..684f50a5666e1 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -201,7 +201,7 @@ export class Server { uiSettings: uiSettingsStart, }; - const pluginsStart = await this.plugins.start(this.coreStart!); + const pluginsStart = await this.plugins.start(this.coreStart); await this.legacy.start({ core: { diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts index 17aa1c66a6835..1487f9f6734e9 100644 --- a/src/plugins/expressions/server/legacy.ts +++ b/src/plugins/expressions/server/legacy.ts @@ -26,7 +26,7 @@ import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common import Boom from 'boom'; import { schema } from '@kbn/config-schema'; -import { CoreSetup, Logger } from 'src/core/server'; +import { CoreSetup, Logger, APICaller } from 'src/core/server'; import { ExpressionsServerSetupDependencies } from './plugin'; import { typeSpecs, ExpressionType } from '../common'; import { serializeProvider } from '../common'; @@ -97,7 +97,10 @@ export const createLegacyServerEndpoints = ( * @param {*} handlers - The Canvas handlers * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` */ - async function runFunction(handlers: any, fnCall: any) { + async function runFunction( + handlers: { environment: string; elasticsearchClient: APICaller }, + fnCall: any + ) { const { functionName, args, context } = fnCall; const { deserialize } = serializeProvider(registries.types.toJS()); const fnDef = registries.serverFunctions.toJS()[functionName]; @@ -112,18 +115,14 @@ export const createLegacyServerEndpoints = ( * results back using ND-JSON. */ plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, request => { - const scopedClient = core.elasticsearch.dataClient.asScoped(request); - const handlers = { - environment: 'server', - elasticsearchClient: async ( - endpoint: string, - clientParams: Record = {}, - options?: any - ) => scopedClient.callAsCurrentUser(endpoint, clientParams, options), - }; - return { onBatchItem: async (fnCall: any) => { + const [coreStart] = await core.getStartServices(); + const handlers = { + environment: 'server', + elasticsearchClient: coreStart.elasticsearch.legacy.client.asScoped(request) + .callAsCurrentUser, + }; const result = await runFunction(handlers, fnCall); if (typeof result === 'undefined') { throw new Error(`Function ${fnCall.functionName} did not return anything.`); diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 6215b08df81d4..fa5b2f9399a4d 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -99,6 +99,9 @@ describe('Actions Plugin', () => { savedObjects: { client: {}, }, + elasticsearch: { + adminClient: jest.fn(), + }, }, } as any, httpServerMock.createKibanaRequest(), diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index ef3716070ab04..a8ab3bbb2fad2 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -11,13 +11,13 @@ import { Plugin, CoreSetup, CoreStart, - IClusterClient, KibanaRequest, Logger, SharedGlobalConfig, RequestHandler, IContextProvider, SavedObjectsServiceStart, + ElasticsearchServiceStart, } from '../../../../src/core/server'; import { @@ -89,7 +89,6 @@ export class ActionsPlugin implements Plugin, Plugi private readonly logger: Logger; private serverBasePath?: string; - private adminClient?: IClusterClient; private taskRunnerFactory?: TaskRunnerFactory; private actionTypeRegistry?: ActionTypeRegistry; private actionExecutor?: ActionExecutor; @@ -173,7 +172,6 @@ export class ActionsPlugin implements Plugin, Plugi this.actionTypeRegistry = actionTypeRegistry; this.serverBasePath = core.http.basePath.serverBasePath; this.actionExecutor = actionExecutor; - this.adminClient = core.elasticsearch.adminClient; this.spaces = plugins.spaces?.spacesService; registerBuiltInActionTypes({ @@ -233,7 +231,6 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry, taskRunnerFactory, kibanaIndex, - adminClient, isESOUsingEphemeralEncryptionKey, preconfiguredActions, } = this; @@ -242,7 +239,7 @@ export class ActionsPlugin implements Plugin, Plugi logger, eventLogger: this.eventLogger!, spaces: this.spaces, - getServices: this.getServicesFactory(core.savedObjects), + getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, actionTypeRegistry: actionTypeRegistry!, preconfiguredActions, @@ -282,7 +279,7 @@ export class ActionsPlugin implements Plugin, Plugi savedObjectsClient: core.savedObjects.getScopedClient(request), actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex: await kibanaIndex, - scopedClusterClient: adminClient!.asScoped(request), + scopedClusterClient: core.elasticsearch.legacy.client.asScoped(request), preconfiguredActions, }); }, @@ -291,11 +288,11 @@ export class ActionsPlugin implements Plugin, Plugi } private getServicesFactory( - savedObjects: SavedObjectsServiceStart + savedObjects: SavedObjectsServiceStart, + elasticsearch: ElasticsearchServiceStart ): (request: KibanaRequest) => Services { - const { adminClient } = this; return request => ({ - callCluster: adminClient!.asScoped(request).callAsCurrentUser, + callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser, savedObjectsClient: savedObjects.getScopedClient(request), }); } @@ -303,12 +300,8 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( defaultKibanaIndex: string ): IContextProvider, 'actions'> => { - const { - actionTypeRegistry, - adminClient, - isESOUsingEphemeralEncryptionKey, - preconfiguredActions, - } = this; + const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, preconfiguredActions } = this; + return async function actionsRouteHandlerContext(context, request) { return { getActionsClient: () => { @@ -321,7 +314,7 @@ export class ActionsPlugin implements Plugin, Plugi savedObjectsClient: context.core.savedObjects.client, actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, - scopedClusterClient: adminClient!.asScoped(request), + scopedClusterClient: context.core.elasticsearch.adminClient, preconfiguredActions, }); }, diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index a07a2aa8f1c70..ed0d876ed0208 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, CoreSetup } from 'kibana/server'; +import { Logger, CoreSetup, APICaller } from 'kibana/server'; import moment from 'moment'; import { RunContext, @@ -62,7 +62,11 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; - const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + const callCluster = (...args: Parameters) => { + return core.getStartServices().then(([{ elasticsearch: { legacy: { client } } }]) => + client.callAsInternalUser(...args) + ); + }; return { async run() { return Promise.all([ diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index fdca6c0a9b503..ad39d09bd6d3d 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -19,7 +19,6 @@ import { TaskRunnerFactory } from './task_runner'; import { AlertsClientFactory } from './alerts_client_factory'; import { LicenseState } from './lib/license_state'; import { - IClusterClient, KibanaRequest, Logger, PluginInitializerContext, @@ -29,6 +28,7 @@ import { IContextProvider, RequestHandler, SharedGlobalConfig, + ElasticsearchServiceStart, } from '../../../../src/core/server'; import { @@ -94,7 +94,6 @@ export class AlertingPlugin { private readonly logger: Logger; private alertTypeRegistry?: AlertTypeRegistry; private readonly taskRunnerFactory: TaskRunnerFactory; - private adminClient?: IClusterClient; private serverBasePath?: string; private licenseState: LicenseState | null = null; private isESOUsingEphemeralEncryptionKey?: boolean; @@ -119,7 +118,6 @@ export class AlertingPlugin { } public async setup(core: CoreSetup, plugins: AlertingPluginsSetup): Promise { - this.adminClient = core.elasticsearch.adminClient; this.licenseState = new LicenseState(plugins.licensing.license$); this.spaces = plugins.spaces?.spacesService; this.security = plugins.security; @@ -223,7 +221,7 @@ export class AlertingPlugin { taskRunnerFactory.initialize({ logger, - getServices: this.getServicesFactory(core.savedObjects), + getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), spaceIdToNamespace: this.spaceIdToNamespace, actionsPlugin: plugins.actions, encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, @@ -263,11 +261,11 @@ export class AlertingPlugin { }; private getServicesFactory( - savedObjects: SavedObjectsServiceStart + savedObjects: SavedObjectsServiceStart, + elasticsearch: ElasticsearchServiceStart ): (request: KibanaRequest) => Services { - const { adminClient } = this; return request => ({ - callCluster: adminClient!.asScoped(request).callAsCurrentUser, + callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser, savedObjectsClient: savedObjects.getScopedClient(request), }); } diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 3da60aef301e2..ab62d81d44f8a 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, CoreSetup } from 'kibana/server'; +import { Logger, CoreSetup, APICaller } from 'kibana/server'; import moment from 'moment'; import { RunContext, @@ -65,7 +65,12 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; - const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + const callCluster = (...args: Parameters) => { + return core.getStartServices().then(([{ elasticsearch: { legacy: { client } } }]) => + client.callAsInternalUser(...args) + ); + }; + return { async run() { return Promise.all([ diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index e18b6d33ca419..b434d41982f4c 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -3,7 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server'; +import { + PluginInitializerContext, + Plugin, + CoreSetup, + CoreStart, + Logger +} from 'src/core/server'; import { Observable, combineLatest, AsyncSubject } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { Server } from 'hapi'; @@ -37,6 +43,8 @@ export interface APMPluginContract { } export class APMPlugin implements Plugin { + private currentConfig?: APMConfig; + private logger?: Logger; legacySetup$: AsyncSubject; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; @@ -56,7 +64,7 @@ export class APMPlugin implements Plugin { actions?: ActionsPlugin['setup']; } ) { - const logger = this.initContext.logger.get(); + this.logger = this.initContext.logger.get(); const config$ = this.initContext.config.create(); const mergedConfig$ = combineLatest(plugins.apm_oss.config$, config$).pipe( map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig)) @@ -71,49 +79,40 @@ export class APMPlugin implements Plugin { } this.legacySetup$.subscribe(__LEGACY => { - createApmApi().init(core, { config$: mergedConfig$, logger, __LEGACY }); + createApmApi().init(core, { + config$: mergedConfig$, + logger: this.logger!, + __LEGACY + }); }); - const currentConfig = await mergedConfig$.pipe(take(1)).toPromise(); + this.currentConfig = await mergedConfig$.pipe(take(1)).toPromise(); if ( plugins.taskManager && plugins.usageCollection && - currentConfig['xpack.apm.telemetryCollectionEnabled'] + this.currentConfig['xpack.apm.telemetryCollectionEnabled'] ) { createApmTelemetry({ core, config$: mergedConfig$, usageCollector: plugins.usageCollection, taskManager: plugins.taskManager, - logger + logger: this.logger }); } - // create agent configuration index without blocking setup lifecycle - createApmAgentConfigurationIndex({ - esClient: core.elasticsearch.dataClient, - config: currentConfig, - logger - }); - // create custom action index without blocking setup lifecycle - createApmCustomLinkIndex({ - esClient: core.elasticsearch.dataClient, - config: currentConfig, - logger - }); - plugins.home.tutorials.registerTutorial( tutorialProvider({ - isEnabled: currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: currentConfig['apm_oss.indexPattern'], + isEnabled: this.currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], cloud: plugins.cloud, indices: { - errorIndices: currentConfig['apm_oss.errorIndices'], - metricsIndices: currentConfig['apm_oss.metricsIndices'], - onboardingIndices: currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: currentConfig['apm_oss.transactionIndices'] + errorIndices: this.currentConfig['apm_oss.errorIndices'], + metricsIndices: this.currentConfig['apm_oss.metricsIndices'], + onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: this.currentConfig['apm_oss.transactionIndices'] } }) ); @@ -127,12 +126,29 @@ export class APMPlugin implements Plugin { getApmIndices: async () => getApmIndices({ savedObjectsClient: await getInternalSavedObjectsClient(core), - config: currentConfig + config: await mergedConfig$.pipe(take(1)).toPromise() }) }; } - public async start() {} + public start(core: CoreStart) { + if (this.currentConfig == null || this.logger == null) { + throw new Error('APMPlugin needs to be setup before calling start()'); + } + + // create agent configuration index without blocking start lifecycle + createApmAgentConfigurationIndex({ + esClient: core.elasticsearch.legacy.client, + config: this.currentConfig, + logger: this.logger + }); + // create custom action index without blocking start lifecycle + createApmCustomLinkIndex({ + esClient: core.elasticsearch.legacy.client, + config: this.currentConfig, + logger: this.logger + }); + } public stop() {} } diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index ae26d7a7ece07..986486902c3fa 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -21,7 +21,7 @@ beforeEach(() => { clusterClient = elasticsearchServiceMock.createClusterClient(); clusterClientAdapter = new ClusterClientAdapter({ logger, - clusterClient, + clusterClientPromise: Promise.resolve(clusterClient), }); }); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 36bc94edfca4e..409bb2d00e161 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -14,7 +14,7 @@ export type IClusterClientAdapter = PublicMethodsOf; export interface ConstructorOpts { logger: Logger; - clusterClient: EsClusterClient; + clusterClientPromise: Promise; } export interface QueryEventsBySavedObjectResult { @@ -26,11 +26,11 @@ export interface QueryEventsBySavedObjectResult { export class ClusterClientAdapter { private readonly logger: Logger; - private readonly clusterClient: EsClusterClient; + private readonly clusterClientPromise: Promise; constructor(opts: ConstructorOpts) { this.logger = opts.logger; - this.clusterClient = opts.clusterClient; + this.clusterClientPromise = opts.clusterClientPromise; } public async indexDocument(doc: any): Promise { @@ -201,7 +201,8 @@ export class ClusterClientAdapter { private async callEs(operation: string, body?: any): Promise { try { this.debug(`callEs(${operation}) calls:`, body); - const result = await this.clusterClient.callAsInternalUser(operation, body); + const clusterClient = await this.clusterClientPromise; + const result = await clusterClient.callAsInternalUser(operation, body); this.debug(`callEs(${operation}) result:`, result); return result; } catch (err) { diff --git a/x-pack/plugins/event_log/server/es/context.ts b/x-pack/plugins/event_log/server/es/context.ts index 144f44ac8e5ea..b8e351367b695 100644 --- a/x-pack/plugins/event_log/server/es/context.ts +++ b/x-pack/plugins/event_log/server/es/context.ts @@ -32,7 +32,7 @@ export function createEsContext(params: EsContextCtorParams): EsContext { export interface EsContextCtorParams { logger: Logger; - clusterClient: EsClusterClient; + clusterClientPromise: Promise; indexNameRoot: string; } @@ -50,7 +50,7 @@ class EsContextImpl implements EsContext { this.initialized = false; this.esAdapter = new ClusterClientAdapter({ logger: params.logger, - clusterClient: params.clusterClient, + clusterClientPromise: params.clusterClientPromise, }); } diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index 2cc41354b4fbc..e5034f599f118 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -66,7 +66,9 @@ export class Plugin implements CorePlugin elasticsearch.legacy.client), }); this.eventLogService = new EventLogService({ diff --git a/x-pack/plugins/lens/server/usage/task.ts b/x-pack/plugins/lens/server/usage/task.ts index e2462be149745..469457f973b62 100644 --- a/x-pack/plugins/lens/server/usage/task.ts +++ b/x-pack/plugins/lens/server/usage/task.ts @@ -184,7 +184,10 @@ export function telemetryTaskRunner( ) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; - const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + const callCluster = async (...args: Parameters) => { + const [coreStart] = await core.getStartServices(); + return coreStart.elasticsearch.legacy.client.callAsInternalUser(...args); + }; return { async run() { diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts index be08338807dd0..9342c2574bedd 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts @@ -17,12 +17,12 @@ import { export function registerTasks({ taskManager, logger, - elasticsearch, + getStartServices, config, }: { taskManager?: TaskManagerSetupContract; logger: Logger; - elasticsearch: CoreSetup['elasticsearch']; + getStartServices: CoreSetup['getStartServices']; config: Observable<{ kibana: { index: string } }>; }) { if (!taskManager) { @@ -30,13 +30,17 @@ export function registerTasks({ return; } + const esClientPromise = getStartServices().then( + ([{ elasticsearch }]) => elasticsearch.legacy.client + ); + taskManager.registerTaskDefinitions({ [VIS_TELEMETRY_TASK]: { title: 'X-Pack telemetry calculator for Visualizations', type: VIS_TELEMETRY_TASK, createTaskRunner({ taskInstance }: { taskInstance: TaskInstance }) { return { - run: visualizationsTaskRunner(taskInstance, config, elasticsearch), + run: visualizationsTaskRunner(taskInstance, config, esClientPromise), }; }, }, diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index 556dc465e562d..f60c44e548f3f 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -6,7 +6,7 @@ import { Observable } from 'rxjs'; import _, { countBy, groupBy, mapValues } from 'lodash'; -import { APICaller, CoreSetup } from 'kibana/server'; +import { APICaller, IClusterClient } from 'src/core/server'; import { getNextMidnight } from '../../get_next_midnight'; import { TaskInstance } from '../../../../../task_manager/server'; import { ESSearchHit } from '../../../../../apm/typings/elasticsearch'; @@ -73,17 +73,15 @@ async function getStats(callCluster: APICaller, index: string) { export function visualizationsTaskRunner( taskInstance: TaskInstance, config: Observable<{ kibana: { index: string } }>, - es: CoreSetup['elasticsearch'] + esClientPromise: Promise ) { - const { callAsInternalUser: callCluster } = es.createClient('data'); - return async () => { let stats; let error; try { const index = (await config.toPromise()).kibana.index; - stats = await getStats(callCluster, index); + stats = await getStats((await esClientPromise).callAsInternalUser, index); } catch (err) { if (err.constructor === Error) { error = err.message; diff --git a/x-pack/plugins/oss_telemetry/server/plugin.ts b/x-pack/plugins/oss_telemetry/server/plugin.ts index 430fca2d39837..6a447da66952a 100644 --- a/x-pack/plugins/oss_telemetry/server/plugin.ts +++ b/x-pack/plugins/oss_telemetry/server/plugin.ts @@ -35,7 +35,7 @@ export class OssTelemetryPlugin implements Plugin { registerTasks({ taskManager: deps.taskManager, logger: this.logger, - elasticsearch: core.elasticsearch, + getStartServices: core.getStartServices, config: this.config, }); registerCollectors( diff --git a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts index fc8c98c164623..4d703db3dcc64 100644 --- a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts +++ b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { APICaller, CoreSetup } from 'kibana/server'; +import { APICaller } from 'kibana/server'; import { of } from 'rxjs'; +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { ConcreteTaskInstance, TaskStatus, @@ -43,10 +44,11 @@ const defaultMockSavedObjects = [ const defaultMockTaskDocs = [getMockTaskInstance()]; -export const getMockEs = (mockCallWithInternal: APICaller = getMockCallWithInternal()) => - (({ - createClient: () => ({ callAsInternalUser: mockCallWithInternal }), - } as unknown) as CoreSetup['elasticsearch']); +export const getMockEs = async (mockCallWithInternal: APICaller = getMockCallWithInternal()) => { + const client = elasticsearchServiceMock.createClusterClient(); + (client.callAsInternalUser as any) = mockCallWithInternal; + return client; +}; export const getMockCallWithInternal = (hits: unknown[] = defaultMockSavedObjects): APICaller => { return ((() => { diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts index b86f16228878a..fca4a5dbc5f94 100644 --- a/x-pack/plugins/remote_clusters/server/plugin.ts +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -29,15 +29,9 @@ export class RemoteClustersServerPlugin implements Plugin this.licenseStatus = { valid: false }; } - async setup( - { http, elasticsearch: elasticsearchService }: CoreSetup, - { licensing, cloud }: Dependencies - ) { - const elasticsearch = await elasticsearchService.adminClient; + async setup({ http }: CoreSetup, { licensing, cloud }: Dependencies) { const router = http.createRouter(); const routeDependencies: RouteDependencies = { - elasticsearch, - elasticsearchService, router, getLicenseStatus: () => this.licenseStatus, config: { diff --git a/x-pack/plugins/remote_clusters/server/types.ts b/x-pack/plugins/remote_clusters/server/types.ts index 85678cba92f19..23f4ed158c2d4 100644 --- a/x-pack/plugins/remote_clusters/server/types.ts +++ b/x-pack/plugins/remote_clusters/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'kibana/server'; +import { IRouter } from 'kibana/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { CloudSetup } from '../../cloud/server'; @@ -16,8 +16,6 @@ export interface Dependencies { export interface RouteDependencies { router: IRouter; getLicenseStatus: () => LicenseStatus; - elasticsearchService: ElasticsearchServiceSetup; - elasticsearch: IClusterClient; config: { isCloudEnabled: boolean; }; diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index fdfe0c068afcf..e837fcd9c0dec 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -38,17 +38,16 @@ export class TaskManagerPlugin public setup(core: CoreSetup, plugins: any): TaskManagerSetupContract { const logger = this.initContext.logger.get('taskManager'); const config$ = this.initContext.config.create(); - const elasticsearch = core.elasticsearch.adminClient; return { registerLegacyAPI: once((__LEGACY: PluginLegacyDependencies) => { config$.subscribe(async config => { - const [{ savedObjects }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }] = await core.getStartServices(); const savedObjectsRepository = savedObjects.createInternalRepository(['task']); this.legacyTaskManager$.next( createTaskManager(core, { logger, config, - elasticsearch, + elasticsearch: elasticsearch.legacy.client, savedObjectsRepository, savedObjectsSerializer: savedObjects.createSerializer(), }) diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index a4833d9a3d7fe..2af1a9ac38e44 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -58,7 +58,7 @@ describe('Upgrade Assistant Usage Collector', () => { }), }, elasticsearch: { - adminClient: clusterClient, + legacy: { client: clusterClient }, }, }; }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index 79d6e53c64ec0..9c2946db7f084 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -7,7 +7,7 @@ import { set } from 'lodash'; import { APICaller, - ElasticsearchServiceSetup, + ElasticsearchServiceStart, ISavedObjectsRepository, SavedObjectsServiceStart, } from 'src/core/server'; @@ -51,7 +51,7 @@ async function getDeprecationLoggingStatusValue(callAsCurrentUser: APICaller): P } export async function fetchUpgradeAssistantMetrics( - { adminClient }: ElasticsearchServiceSetup, + { legacy: { client: esClient } }: ElasticsearchServiceStart, savedObjects: SavedObjectsServiceStart ): Promise { const savedObjectsRepository = savedObjects.createInternalRepository(); @@ -60,7 +60,7 @@ export async function fetchUpgradeAssistantMetrics( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID ); - const callAsInternalUser = adminClient.callAsInternalUser.bind(adminClient); + const callAsInternalUser = esClient.callAsInternalUser.bind(esClient); const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue(callAsInternalUser); const getTelemetrySavedObject = ( @@ -107,7 +107,7 @@ export async function fetchUpgradeAssistantMetrics( } interface Dependencies { - elasticsearch: ElasticsearchServiceSetup; + elasticsearch: ElasticsearchServiceStart; savedObjects: SavedObjectsServiceStart; usageCollection: UsageCollectionSetup; } diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 6ccd073a9e020..bdca506cc7338 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -11,7 +11,6 @@ import { CoreStart, PluginInitializerContext, Logger, - ElasticsearchServiceSetup, SavedObjectsClient, SavedObjectsServiceStart, } from '../../../../src/core/server'; @@ -40,7 +39,6 @@ export class UpgradeAssistantServerPlugin implements Plugin { // Properties set at setup private licensing?: LicensingPluginSetup; - private elasticSearchService?: ElasticsearchServiceSetup; // Properties set at start private savedObjectsServiceStart?: SavedObjectsServiceStart; @@ -59,10 +57,9 @@ export class UpgradeAssistantServerPlugin implements Plugin { } setup( - { http, elasticsearch, getStartServices, capabilities }: CoreSetup, + { http, getStartServices, capabilities }: CoreSetup, { usageCollection, cloud, licensing }: PluginsSetup ) { - this.elasticSearchService = elasticsearch; this.licensing = licensing; const router = http.createRouter(); @@ -88,13 +85,13 @@ export class UpgradeAssistantServerPlugin implements Plugin { registerTelemetryRoutes(dependencies); if (usageCollection) { - getStartServices().then(([{ savedObjects }]) => { + getStartServices().then(([{ savedObjects, elasticsearch }]) => { registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, savedObjects }); }); } } - start({ savedObjects }: CoreStart) { + start({ savedObjects, elasticsearch }: CoreStart) { this.savedObjectsServiceStart = savedObjects; // The ReindexWorker uses a map of request headers that contain the authentication credentials @@ -107,7 +104,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { this.worker = createReindexWorker({ credentialStore: this.credentialStore, licensing: this.licensing!, - elasticsearchService: this.elasticSearchService!, + elasticsearchService: elasticsearch, logger: this.logger, savedObjects: new SavedObjectsClient( this.savedObjectsServiceStart.createInternalRepository() diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index 686f93b771e62..3d15916b17ff9 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; import { - ElasticsearchServiceSetup, + ElasticsearchServiceStart, kibanaResponseFactory, Logger, SavedObjectsClient, @@ -38,7 +38,7 @@ import { GetBatchQueueResponse, PostBatchResponse } from './types'; interface CreateReindexWorker { logger: Logger; - elasticsearchService: ElasticsearchServiceSetup; + elasticsearchService: ElasticsearchServiceStart; credentialStore: CredentialStore; savedObjects: SavedObjectsClient; licensing: LicensingPluginSetup; @@ -51,8 +51,8 @@ export function createReindexWorker({ savedObjects, licensing, }: CreateReindexWorker) { - const { adminClient } = elasticsearchService; - return new ReindexWorker(savedObjects, credentialStore, adminClient, logger, licensing); + const esClient = elasticsearchService.legacy.client; + return new ReindexWorker(savedObjects, credentialStore, esClient, logger, licensing); } const mapAnyErrorToKibanaHttpResponse = (e: any) => { From d0404487f614c260203c2221aa284c6cbc6365e8 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 15 Apr 2020 11:10:20 -0400 Subject: [PATCH 15/44] Add embeddable via saved object example (#61692) * Add embeddable via saved object example * give todoRefEmbed a different name from the by value one * fix types * fix order of unmounting Co-authored-by: Christos Nasikas --- examples/embeddable_examples/common/index.ts | 20 +++ .../common/todo_saved_object_attributes.ts | 26 +++ examples/embeddable_examples/kibana.json | 2 +- .../public/create_sample_data.ts | 36 +++++ examples/embeddable_examples/public/index.ts | 15 +- examples/embeddable_examples/public/plugin.ts | 37 ++++- .../embeddable_examples/public/todo/README.md | 43 +++++ .../public/todo/todo_ref_component.tsx | 86 ++++++++++ .../public/todo/todo_ref_embeddable.tsx | 153 ++++++++++++++++++ .../todo/todo_ref_embeddable_factory.tsx | 83 ++++++++++ examples/embeddable_examples/server/index.ts | 24 +++ examples/embeddable_examples/server/plugin.ts | 31 ++++ .../server/todo_saved_object.ts | 40 +++++ examples/embeddable_examples/tsconfig.json | 1 + .../embeddable_explorer/public/plugin.tsx | 3 + .../actions/replace_panel_flyout.tsx | 7 +- .../application/dashboard_app_controller.tsx | 3 +- .../embeddable/dashboard_container.tsx | 2 +- .../public/application/embeddable/types.ts | 6 +- ...embeddable_saved_object_converters.test.ts | 13 +- .../lib/embeddable_saved_object_converters.ts | 8 +- src/plugins/embeddable/public/index.ts | 2 + .../public/lib/containers/container.ts | 35 +--- .../public/lib/containers/i_container.ts | 15 +- .../public/lib/embeddables/i_embeddable.ts | 7 + .../public/lib/embeddables/index.ts | 1 + .../embeddables/saved_object_embeddable.ts | 30 ++++ .../add_panel/add_panel_flyout.tsx | 15 +- .../embeddable/public/tests/container.test.ts | 6 +- test/examples/embeddables/adding_children.ts | 11 ++ .../np_ready/public/app/dashboard_input.ts | 8 +- 31 files changed, 679 insertions(+), 90 deletions(-) create mode 100644 examples/embeddable_examples/common/index.ts create mode 100644 examples/embeddable_examples/common/todo_saved_object_attributes.ts create mode 100644 examples/embeddable_examples/public/create_sample_data.ts create mode 100644 examples/embeddable_examples/public/todo/README.md create mode 100644 examples/embeddable_examples/public/todo/todo_ref_component.tsx create mode 100644 examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx create mode 100644 examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx create mode 100644 examples/embeddable_examples/server/index.ts create mode 100644 examples/embeddable_examples/server/plugin.ts create mode 100644 examples/embeddable_examples/server/todo_saved_object.ts create mode 100644 src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts diff --git a/examples/embeddable_examples/common/index.ts b/examples/embeddable_examples/common/index.ts new file mode 100644 index 0000000000000..726420fb9bdc3 --- /dev/null +++ b/examples/embeddable_examples/common/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { TodoSavedObjectAttributes } from './todo_saved_object_attributes'; diff --git a/examples/embeddable_examples/common/todo_saved_object_attributes.ts b/examples/embeddable_examples/common/todo_saved_object_attributes.ts new file mode 100644 index 0000000000000..21b6df20fea90 --- /dev/null +++ b/examples/embeddable_examples/common/todo_saved_object_attributes.ts @@ -0,0 +1,26 @@ +/* + * 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 { SavedObjectAttributes } from '../../../src/core/types'; + +export interface TodoSavedObjectAttributes extends SavedObjectAttributes { + task: string; + icon?: string; + title?: string; +} diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json index c70bc7009ff51..f446e7f31ac8e 100644 --- a/examples/embeddable_examples/kibana.json +++ b/examples/embeddable_examples/kibana.json @@ -3,7 +3,7 @@ "version": "0.0.1", "kibanaVersion": "kibana", "configPath": ["embeddable_examples"], - "server": false, + "server": true, "ui": true, "requiredPlugins": ["embeddable"], "optionalPlugins": [] diff --git a/examples/embeddable_examples/public/create_sample_data.ts b/examples/embeddable_examples/public/create_sample_data.ts new file mode 100644 index 0000000000000..bd5ade18aa91e --- /dev/null +++ b/examples/embeddable_examples/public/create_sample_data.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 { SavedObjectsClientContract } from 'kibana/public'; +import { TodoSavedObjectAttributes } from '../common'; + +export async function createSampleData(client: SavedObjectsClientContract) { + await client.create( + 'todo', + { + task: 'Take the garbage out', + title: 'Garbage', + icon: 'trash', + }, + { + id: 'sample-todo-saved-object', + overwrite: true, + } + ); +} diff --git a/examples/embeddable_examples/public/index.ts b/examples/embeddable_examples/public/index.ts index 5fcd454b17a5c..4aac63fb52e2b 100644 --- a/examples/embeddable_examples/public/index.ts +++ b/examples/embeddable_examples/public/index.ts @@ -17,7 +17,6 @@ * under the License. */ -import { PluginInitializer } from 'kibana/public'; export { HELLO_WORLD_EMBEDDABLE, HelloWorldEmbeddable, @@ -26,18 +25,8 @@ export { export { ListContainer, LIST_CONTAINER } from './list_container'; export { TODO_EMBEDDABLE } from './todo'; -import { - EmbeddableExamplesPlugin, - EmbeddableExamplesSetupDependencies, - EmbeddableExamplesStartDependencies, -} from './plugin'; +import { EmbeddableExamplesPlugin } from './plugin'; export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container'; export { MULTI_TASK_TODO_EMBEDDABLE } from './multi_task_todo'; - -export const plugin: PluginInitializer< - void, - void, - EmbeddableExamplesSetupDependencies, - EmbeddableExamplesStartDependencies -> = () => new EmbeddableExamplesPlugin(); +export const plugin = () => new EmbeddableExamplesPlugin(); diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 31a3037332dda..75d34d2d6878f 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -21,12 +21,20 @@ import { EmbeddableSetup, EmbeddableStart } from '../../../src/plugins/embeddabl import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from './hello_world'; import { TODO_EMBEDDABLE, TodoEmbeddableFactory, TodoInput, TodoOutput } from './todo'; -import { MULTI_TASK_TODO_EMBEDDABLE, MultiTaskTodoEmbeddableFactory } from './multi_task_todo'; +import { + MULTI_TASK_TODO_EMBEDDABLE, + MultiTaskTodoEmbeddableFactory, + MultiTaskTodoInput, + MultiTaskTodoOutput, +} from './multi_task_todo'; import { SEARCHABLE_LIST_CONTAINER, SearchableListContainerFactory, } from './searchable_list_container'; import { LIST_CONTAINER, ListContainerFactory } from './list_container'; +import { createSampleData } from './create_sample_data'; +import { TodoRefInput, TodoRefOutput, TODO_REF_EMBEDDABLE } from './todo/todo_ref_embeddable'; +import { TodoRefEmbeddableFactory } from './todo/todo_ref_embeddable_factory'; export interface EmbeddableExamplesSetupDependencies { embeddable: EmbeddableSetup; @@ -36,9 +44,18 @@ export interface EmbeddableExamplesStartDependencies { embeddable: EmbeddableStart; } +export interface EmbeddableExamplesStart { + createSampleData: () => Promise; +} + export class EmbeddableExamplesPlugin implements - Plugin { + Plugin< + void, + EmbeddableExamplesStart, + EmbeddableExamplesSetupDependencies, + EmbeddableExamplesStartDependencies + > { public setup( core: CoreSetup, deps: EmbeddableExamplesSetupDependencies @@ -48,7 +65,7 @@ export class EmbeddableExamplesPlugin new HelloWorldEmbeddableFactory() ); - deps.embeddable.registerEmbeddableFactory( + deps.embeddable.registerEmbeddableFactory( MULTI_TASK_TODO_EMBEDDABLE, new MultiTaskTodoEmbeddableFactory() ); @@ -73,9 +90,21 @@ export class EmbeddableExamplesPlugin openModal: (await core.getStartServices())[0].overlays.openModal, })) ); + + deps.embeddable.registerEmbeddableFactory( + TODO_REF_EMBEDDABLE, + new TodoRefEmbeddableFactory(async () => ({ + savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, + getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + })) + ); } - public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) {} + public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) { + return { + createSampleData: () => createSampleData(core.savedObjects.client), + }; + } public stop() {} } diff --git a/examples/embeddable_examples/public/todo/README.md b/examples/embeddable_examples/public/todo/README.md new file mode 100644 index 0000000000000..e782511f093b3 --- /dev/null +++ b/examples/embeddable_examples/public/todo/README.md @@ -0,0 +1,43 @@ +There are two examples in here: + - TodoEmbeddable + - TodoRefEmbeddable + + # TodoEmbeddable + + The first example you should review is the HelloWorldEmbeddable. That is as basic an embeddable as you can get. + This embeddable is the next step up - an embeddable that renders dynamic input data. The data is simple: + - a required task string + - an optional title + - an optional icon string + - an optional search string + +It also has output data, which is `hasMatch` - whether or not the search string has matched any input data. + +`hasMatch` is a better fit for output data than input data, because it's state that is _derived_ from input data. + +For example, if it was input data, you could create a TodoEmbeddable with input like this: + +```ts + todoEmbeddableFactory.create({ task: 'take out the garabage', search: 'garbage', hasMatch: false }); +``` + +That's wrong because there is actually a match from the search string inside the task. + +The TodoEmbeddable component itself doesn't do anything with the `hasMatch` variable other than set it, but +if you check out `SearchableListContainer`, you can see an example where this output data is being used. + +## TodoRefEmbeddable + +This is an example of an embeddable based off of a saved object. The input is just the `savedObjectId` and +the `search` string. It has even more output parameters, and this time, it does read it's own output parameters in +order to calculate `hasMatch`. + +Output: +```ts +{ + hasMatch: boolean, + savedAttributes?: TodoSavedAttributes +} +``` + +`savedAttributes` is optional because it's possible a TodoSavedObject could not be found with the given savedObjectId. diff --git a/examples/embeddable_examples/public/todo/todo_ref_component.tsx b/examples/embeddable_examples/public/todo/todo_ref_component.tsx new file mode 100644 index 0000000000000..8e0a17be1ec72 --- /dev/null +++ b/examples/embeddable_examples/public/todo/todo_ref_component.tsx @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; + +import { EuiText } from '@elastic/eui'; +import { EuiAvatar } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { EuiFlexGrid } from '@elastic/eui'; +import { withEmbeddableSubscription } from '../../../../src/plugins/embeddable/public'; +import { TodoRefInput, TodoRefOutput, TodoRefEmbeddable } from './todo_ref_embeddable'; + +interface Props { + embeddable: TodoRefEmbeddable; + input: TodoRefInput; + output: TodoRefOutput; +} + +function wrapSearchTerms(task?: string, search?: string) { + if (!search) return task; + if (!task) return task; + const parts = task.split(new RegExp(`(${search})`, 'g')); + return parts.map((part, i) => + part === search ? ( + + {part} + + ) : ( + part + ) + ); +} + +export function TodoRefEmbeddableComponentInner({ + input: { search }, + output: { savedAttributes }, +}: Props) { + const icon = savedAttributes?.icon; + const title = savedAttributes?.title; + const task = savedAttributes?.task; + return ( + + + {icon ? ( + + ) : ( + + )} + + + + + +

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

+
+
+ + {wrapSearchTerms(task, search)} + +
+
+
+ ); +} + +export const TodoRefEmbeddableComponent = withEmbeddableSubscription< + TodoRefInput, + TodoRefOutput, + TodoRefEmbeddable +>(TodoRefEmbeddableComponentInner); diff --git a/examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx b/examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx new file mode 100644 index 0000000000000..da2dfb2c1a290 --- /dev/null +++ b/examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx @@ -0,0 +1,153 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Subscription } from 'rxjs'; +import { TodoSavedObjectAttributes } from 'examples/embeddable_examples/common'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { + Embeddable, + IContainer, + EmbeddableOutput, + SavedObjectEmbeddableInput, +} from '../../../../src/plugins/embeddable/public'; +import { TodoRefEmbeddableComponent } from './todo_ref_component'; + +// Notice this is not the same value as the 'todo' saved object type. Many of our +// cases in prod today use the same value, but this is unnecessary. +export const TODO_REF_EMBEDDABLE = 'TODO_REF_EMBEDDABLE'; + +export interface TodoRefInput extends SavedObjectEmbeddableInput { + /** + * Optional search string which will be used to highlight search terms as + * well as calculate `output.hasMatch`. + */ + search?: string; +} + +export interface TodoRefOutput extends EmbeddableOutput { + /** + * Should be true if input.search is defined and the task or title contain + * search as a substring. + */ + hasMatch: boolean; + /** + * Will contain the saved object attributes of the Todo Saved Object that matches + * `input.savedObjectId`. If the id is invalid, this may be undefined. + */ + savedAttributes?: TodoSavedObjectAttributes; +} + +/** + * Returns whether any attributes contain the search string. If search is empty, true is returned. If + * there are no savedAttributes, false is returned. + * @param search - the search string + * @param savedAttributes - the saved object attributes for the saved object with id `input.savedObjectId` + */ +function getHasMatch(search?: string, savedAttributes?: TodoSavedObjectAttributes): boolean { + if (!search) return true; + if (!savedAttributes) return false; + return Boolean( + (savedAttributes.task && savedAttributes.task.match(search)) || + (savedAttributes.title && savedAttributes.title.match(search)) + ); +} + +/** + * This is an example of an embeddable that is backed by a saved object. It's essentially the + * same as `TodoEmbeddable` but that is "by value", while this is "by reference". + */ +export class TodoRefEmbeddable extends Embeddable { + public readonly type = TODO_REF_EMBEDDABLE; + private subscription: Subscription; + private node?: HTMLElement; + private savedObjectsClient: SavedObjectsClientContract; + private savedObjectId?: string; + + constructor( + initialInput: TodoRefInput, + { + parent, + savedObjectsClient, + }: { + parent?: IContainer; + savedObjectsClient: SavedObjectsClientContract; + } + ) { + super(initialInput, { hasMatch: false }, parent); + this.savedObjectsClient = savedObjectsClient; + + this.subscription = this.getInput$().subscribe(async () => { + // There is a little more work today for this embeddable because it has + // more output it needs to update in response to input state changes. + let savedAttributes: TodoSavedObjectAttributes | undefined; + + // Since this is an expensive task, we save a local copy of the previous + // savedObjectId locally and only retrieve the new saved object if the id + // actually changed. + if (this.savedObjectId !== this.input.savedObjectId) { + this.savedObjectId = this.input.savedObjectId; + const todoSavedObject = await this.savedObjectsClient.get( + 'todo', + this.input.savedObjectId + ); + savedAttributes = todoSavedObject?.attributes; + } + + // The search string might have changed as well so we need to make sure we recalculate + // hasMatch. + this.updateOutput({ + hasMatch: getHasMatch(this.input.search, savedAttributes), + savedAttributes, + }); + }); + } + + public render(node: HTMLElement) { + if (this.node) { + ReactDOM.unmountComponentAtNode(this.node); + } + this.node = node; + ReactDOM.render(, node); + } + + /** + * Lets re-sync our saved object to make sure it's up to date! + */ + public async reload() { + this.savedObjectId = this.input.savedObjectId; + const todoSavedObject = await this.savedObjectsClient.get( + 'todo', + this.input.savedObjectId + ); + const savedAttributes = todoSavedObject?.attributes; + this.updateOutput({ + hasMatch: getHasMatch(this.input.search, savedAttributes), + savedAttributes, + }); + } + + public destroy() { + super.destroy(); + this.subscription.unsubscribe(); + if (this.node) { + ReactDOM.unmountComponentAtNode(this.node); + } + } +} diff --git a/examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx b/examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx new file mode 100644 index 0000000000000..e585ddd89674f --- /dev/null +++ b/examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx @@ -0,0 +1,83 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { TodoSavedObjectAttributes } from 'examples/embeddable_examples/common'; +import { + IContainer, + EmbeddableStart, + ErrorEmbeddable, + EmbeddableFactoryDefinition, +} from '../../../../src/plugins/embeddable/public'; +import { + TodoRefEmbeddable, + TODO_REF_EMBEDDABLE, + TodoRefInput, + TodoRefOutput, +} from './todo_ref_embeddable'; + +interface StartServices { + getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + savedObjectsClient: SavedObjectsClientContract; +} + +export class TodoRefEmbeddableFactory + implements + EmbeddableFactoryDefinition< + TodoRefInput, + TodoRefOutput, + TodoRefEmbeddable, + TodoSavedObjectAttributes + > { + public readonly type = TODO_REF_EMBEDDABLE; + public readonly savedObjectMetaData = { + name: 'Todo', + includeFields: ['task', 'icon', 'title'], + type: 'todo', + getIconForSavedObject: () => 'pencil', + }; + + constructor(private getStartServices: () => Promise) {} + + public async isEditable() { + return true; + } + + public createFromSavedObject = ( + savedObjectId: string, + input: Partial & { id: string }, + parent?: IContainer + ): Promise => { + return this.create({ ...input, savedObjectId }, parent); + }; + + public async create(input: TodoRefInput, parent?: IContainer) { + const { savedObjectsClient } = await this.getStartServices(); + return new TodoRefEmbeddable(input, { + parent, + savedObjectsClient, + }); + } + + public getDisplayName() { + return i18n.translate('embeddableExamples.todo.displayName', { + defaultMessage: 'Todo (by reference)', + }); + } +} diff --git a/examples/embeddable_examples/server/index.ts b/examples/embeddable_examples/server/index.ts new file mode 100644 index 0000000000000..9ddc3bc2cf715 --- /dev/null +++ b/examples/embeddable_examples/server/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { PluginInitializer } from 'kibana/server'; + +import { EmbeddableExamplesPlugin } from './plugin'; + +export const plugin: PluginInitializer = () => new EmbeddableExamplesPlugin(); diff --git a/examples/embeddable_examples/server/plugin.ts b/examples/embeddable_examples/server/plugin.ts new file mode 100644 index 0000000000000..d956b834d0d3c --- /dev/null +++ b/examples/embeddable_examples/server/plugin.ts @@ -0,0 +1,31 @@ +/* + * 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 { Plugin, CoreSetup, CoreStart } from 'kibana/server'; +import { todoSavedObject } from './todo_saved_object'; + +export class EmbeddableExamplesPlugin implements Plugin { + public setup(core: CoreSetup) { + core.savedObjects.registerType(todoSavedObject); + } + + public start(core: CoreStart) {} + + public stop() {} +} diff --git a/examples/embeddable_examples/server/todo_saved_object.ts b/examples/embeddable_examples/server/todo_saved_object.ts new file mode 100644 index 0000000000000..0f67c53cfa3e1 --- /dev/null +++ b/examples/embeddable_examples/server/todo_saved_object.ts @@ -0,0 +1,40 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; + +export const todoSavedObject: SavedObjectsType = { + name: 'todo', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + title: { + type: 'keyword', + }, + task: { + type: 'text', + }, + icon: { + type: 'keyword', + }, + }, + }, + migrations: {}, +}; diff --git a/examples/embeddable_examples/tsconfig.json b/examples/embeddable_examples/tsconfig.json index 091130487791b..7fa03739119b4 100644 --- a/examples/embeddable_examples/tsconfig.json +++ b/examples/embeddable_examples/tsconfig.json @@ -6,6 +6,7 @@ }, "include": [ "index.ts", + "common/**/*.ts", "public/**/*.ts", "public/**/*.tsx", "server/**/*.ts", diff --git a/examples/embeddable_explorer/public/plugin.tsx b/examples/embeddable_explorer/public/plugin.tsx index 7c75b108d9912..bba1b1748e207 100644 --- a/examples/embeddable_explorer/public/plugin.tsx +++ b/examples/embeddable_explorer/public/plugin.tsx @@ -18,6 +18,7 @@ */ import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; +import { EmbeddableExamplesStart } from 'examples/embeddable_examples/public/plugin'; import { UiActionsService } from '../../../src/plugins/ui_actions/public'; import { EmbeddableStart } from '../../../src/plugins/embeddable/public'; import { Start as InspectorStart } from '../../../src/plugins/inspector/public'; @@ -26,6 +27,7 @@ interface StartDeps { uiActions: UiActionsService; embeddable: EmbeddableStart; inspector: InspectorStart; + embeddableExamples: EmbeddableExamplesStart; } export class EmbeddableExplorerPlugin implements Plugin { @@ -36,6 +38,7 @@ export class EmbeddableExplorerPlugin implements Plugin { }); }; - public onReplacePanel = async (id: string, type: string, name: string) => { + public onReplacePanel = async (savedObjectId: string, type: string, name: string) => { const originalPanels = this.props.container.getInput().panels; const filteredPanels = { ...originalPanels }; @@ -76,7 +77,9 @@ export class ReplacePanelFlyout extends React.Component { const nny = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.y; // add the new view - const newObj = await this.props.container.addSavedObjectEmbeddable(type, id); + const newObj = await this.props.container.addNewEmbeddable(type, { + savedObjectId, + }); const finalPanels = _.cloneDeep(this.props.container.getInput().panels); (finalPanels[newObj.id] as DashboardPanelState).gridData.w = nnw; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index babd1fac274eb..283fe9f0a83a4 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -53,6 +53,7 @@ import { isErrorEmbeddable, openAddPanelFlyout, ViewMode, + SavedObjectEmbeddableInput, ContainerOutput, } from '../../../embeddable/public'; import { NavAction, SavedDashboardPanel } from '../types'; @@ -394,7 +395,7 @@ export class DashboardAppController { if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) { const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]; const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID]; - container.addSavedObjectEmbeddable(type, id); + container.addNewEmbeddable(type, { savedObjectId: id }); removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE); removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID); } diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 7f3a2913daac3..50089f1f061f4 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -55,7 +55,7 @@ export interface DashboardContainerInput extends ContainerInput { description?: string; isFullScreenMode: boolean; panels: { - [panelId: string]: DashboardPanelState; + [panelId: string]: DashboardPanelState; }; isEmptyState?: boolean; } diff --git a/src/plugins/dashboard/public/application/embeddable/types.ts b/src/plugins/dashboard/public/application/embeddable/types.ts index 3df305b0d7f1b..6d0221cb10e8b 100644 --- a/src/plugins/dashboard/public/application/embeddable/types.ts +++ b/src/plugins/dashboard/public/application/embeddable/types.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public'; import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; export type PanelId = string; export type SavedObjectId = string; @@ -28,7 +29,8 @@ export interface GridData { i: string; } -export interface DashboardPanelState - extends PanelState { +export interface DashboardPanelState< + TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput +> extends PanelState { readonly gridData: GridData; } diff --git a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts index 447563bbfbcfa..25ce203332422 100644 --- a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts +++ b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts @@ -23,11 +23,6 @@ import { } from './embeddable_saved_object_converters'; import { SavedDashboardPanel } from '../../types'; import { DashboardPanelState } from '../embeddable'; -import { EmbeddableInput } from 'src/plugins/embeddable/public'; - -interface CustomInput extends EmbeddableInput { - something: string; -} test('convertSavedDashboardPanelToPanelState', () => { const savedDashboardPanel: SavedDashboardPanel = { @@ -58,8 +53,8 @@ test('convertSavedDashboardPanelToPanelState', () => { explicitInput: { something: 'hi!', id: '123', + savedObjectId: 'savedObjectId', }, - savedObjectId: 'savedObjectId', type: 'search', }); }); @@ -86,7 +81,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', () }); test('convertPanelStateToSavedDashboardPanel', () => { - const dashboardPanel: DashboardPanelState = { + const dashboardPanel: DashboardPanelState = { gridData: { x: 0, y: 0, @@ -94,10 +89,10 @@ test('convertPanelStateToSavedDashboardPanel', () => { w: 15, i: '123', }, - savedObjectId: 'savedObjectId', explicitInput: { something: 'hi!', id: '123', + savedObjectId: 'savedObjectId', }, type: 'search', }; @@ -121,7 +116,7 @@ test('convertPanelStateToSavedDashboardPanel', () => { }); test('convertPanelStateToSavedDashboardPanel will not add an undefined id when not needed', () => { - const dashboardPanel: DashboardPanelState = { + const dashboardPanel: DashboardPanelState = { gridData: { x: 0, y: 0, diff --git a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts index 01cd55df0d8e9..b19ef31ccb9ac 100644 --- a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts +++ b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts @@ -19,6 +19,7 @@ import { omit } from 'lodash'; import { SavedDashboardPanel } from '../../types'; import { DashboardPanelState } from '../embeddable'; +import { SavedObjectEmbeddableInput } from '../../embeddable_plugin'; export function convertSavedDashboardPanelToPanelState( savedDashboardPanel: SavedDashboardPanel @@ -26,9 +27,9 @@ export function convertSavedDashboardPanelToPanelState( return { type: savedDashboardPanel.type, gridData: savedDashboardPanel.gridData, - ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), explicitInput: { id: savedDashboardPanel.panelIndex, + ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), ...(savedDashboardPanel.title !== undefined && { title: savedDashboardPanel.title }), ...savedDashboardPanel.embeddableConfig, }, @@ -42,13 +43,14 @@ export function convertPanelStateToSavedDashboardPanel( const customTitle: string | undefined = panelState.explicitInput.title ? (panelState.explicitInput.title as string) : undefined; + const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId; return { version, type: panelState.type, gridData: panelState.gridData, panelIndex: panelState.explicitInput.id, - embeddableConfig: omit(panelState.explicitInput, 'id'), + embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId']), ...(customTitle && { title: customTitle }), - ...(panelState.savedObjectId !== undefined && { id: panelState.savedObjectId }), + ...(savedObjectId !== undefined && { id: savedObjectId }), }; } diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 23275fbe8e8f0..bdb7bfbddc308 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -61,6 +61,8 @@ export { PropertySpec, ViewMode, withEmbeddableSubscription, + SavedObjectEmbeddableInput, + isSavedObjectEmbeddableInput, } from './lib'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index 4ab74e1883917..ffbe75f66b581 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -30,6 +30,7 @@ import { import { IContainer, ContainerInput, ContainerOutput, PanelState } from './i_container'; import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from '../errors'; import { EmbeddableStart } from '../../plugin'; +import { isSavedObjectEmbeddableInput } from '../embeddables/saved_object_embeddable'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -98,17 +99,6 @@ export abstract class Container< return this.createAndSaveEmbeddable(type, panelState); } - public async addSavedObjectEmbeddable< - TEmbeddableInput extends EmbeddableInput = EmbeddableInput, - TEmbeddable extends IEmbeddable = IEmbeddable - >(type: string, savedObjectId: string): Promise { - const factory = this.getFactory(type) as EmbeddableFactory; - const panelState = this.createNewPanelState(factory); - panelState.savedObjectId = savedObjectId; - - return this.createAndSaveEmbeddable(type, panelState); - } - public removeEmbeddable(embeddableId: string) { // Just a shortcut for removing the panel from input state, all internal state will get cleaned up naturally // by the listener. @@ -304,8 +294,10 @@ export abstract class Container< throw new EmbeddableFactoryNotFoundError(panel.type); } - embeddable = panel.savedObjectId - ? await factory.createFromSavedObject(panel.savedObjectId, inputForChild, this) + // TODO: lets get rid of this distinction with factories, I don't think it will be needed + // anymore after this change. + embeddable = isSavedObjectEmbeddableInput(inputForChild) + ? await factory.createFromSavedObject(inputForChild.savedObjectId, inputForChild, this) : await factory.create(inputForChild, this); } catch (e) { embeddable = new ErrorEmbeddable(e, { id: panel.explicitInput.id }, this); @@ -323,23 +315,6 @@ export abstract class Container< return; } - if (embeddable.getOutput().savedObjectId) { - this.updateInput({ - panels: { - ...this.input.panels, - [panel.explicitInput.id]: { - ...this.input.panels[panel.explicitInput.id], - ...(embeddable.getOutput().savedObjectId - ? { savedObjectId: embeddable.getOutput().savedObjectId } - : undefined), - explicitInput: { - ...this.input.panels[panel.explicitInput.id].explicitInput, - }, - }, - }, - } as Partial); - } - this.children[embeddable.id] = embeddable; this.updateOutput({ embeddableLoaded: { diff --git a/src/plugins/embeddable/public/lib/containers/i_container.ts b/src/plugins/embeddable/public/lib/containers/i_container.ts index 7da5f92ec92c1..31a7cd4f2e559 100644 --- a/src/plugins/embeddable/public/lib/containers/i_container.ts +++ b/src/plugins/embeddable/public/lib/containers/i_container.ts @@ -25,9 +25,7 @@ import { IEmbeddable, } from '../embeddables'; -export interface PanelState { - savedObjectId?: string; - +export interface PanelState { // The type of embeddable in this panel. Will be used to find the factory in which to // load the embeddable. type: string; @@ -89,17 +87,6 @@ export interface IContainer< */ removeEmbeddable(embeddableId: string): void; - /** - * Adds a new embeddable that is backed off of a saved object. - */ - addSavedObjectEmbeddable< - EEI extends EmbeddableInput = EmbeddableInput, - E extends Embeddable = Embeddable - >( - type: string, - savedObjectId: string - ): Promise; - /** * Adds a new embeddable to the container. `explicitInput` may partially specify the required embeddable input, * but the remainder must come from inherited container state. diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 6345c34b0dda2..c0fb98d2559db 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -26,6 +26,11 @@ import { TriggerContextMapping } from '../../../../ui_actions/public'; export interface EmbeddableInput { viewMode?: ViewMode; title?: string; + /** + * Note this is not a saved object id. It is used to uniquely identify this + * Embeddable instance from others (e.g. inside a container). It's possible to + * have two Embeddables where everything else is the same but the id. + */ id: string; lastReloadRequestTime?: number; hidePanelTitles?: boolean; @@ -44,6 +49,8 @@ export interface EmbeddableInput { * Whether this embeddable should not execute triggers. */ disableTriggers?: boolean; + + [key: string]: unknown; } export interface EmbeddableOutput { diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index 4d6ab37a50c05..ad8b9c35e60be 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -25,3 +25,4 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable'; export { withEmbeddableSubscription } from './with_subscription'; export { EmbeddableFactoryRenderer } from './embeddable_factory_renderer'; export { EmbeddableRoot } from './embeddable_root'; +export * from './saved_object_embeddable'; diff --git a/src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts new file mode 100644 index 0000000000000..6ca1800b16de4 --- /dev/null +++ b/src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts @@ -0,0 +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. + */ + +import { EmbeddableInput } from '..'; + +export interface SavedObjectEmbeddableInput extends EmbeddableInput { + savedObjectId: string; +} + +export function isSavedObjectEmbeddableInput( + input: EmbeddableInput | SavedObjectEmbeddableInput +): input is SavedObjectEmbeddableInput { + return (input as SavedObjectEmbeddableInput).savedObjectId !== undefined; +} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 50d8bcef8506c..5bf3f69a95c30 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -33,6 +33,7 @@ import { EmbeddableStart } from 'src/plugins/embeddable/public'; import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; import { SavedObjectFinderCreateNew } from './saved_object_finder_create_new'; +import { SavedObjectEmbeddableInput } from '../../../../embeddables'; interface Props { onClose: () => void; @@ -98,8 +99,18 @@ export class AddPanelFlyout extends React.Component { } }; - public onAddPanel = async (id: string, type: string, name: string) => { - this.props.container.addSavedObjectEmbeddable(type, id); + public onAddPanel = async (savedObjectId: string, savedObjectType: string, name: string) => { + const factoryForSavedObjectType = [...this.props.getAllFactories()].find( + factory => factory.savedObjectMetaData && factory.savedObjectMetaData.type === savedObjectType + ); + if (!factoryForSavedObjectType) { + throw new EmbeddableFactoryNotFoundError(savedObjectType); + } + + this.props.container.addNewEmbeddable( + factoryForSavedObjectType.type, + { savedObjectId } + ); this.showToast(name); }; diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index 87076399465d3..1aae43550ec6f 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -641,8 +641,7 @@ test('container stores ErrorEmbeddables when a saved object cannot be found', as panels: { '123': { type: 'vis', - explicitInput: { id: '123' }, - savedObjectId: '456', + explicitInput: { id: '123', savedObjectId: '456' }, }, }, viewMode: ViewMode.EDIT, @@ -663,8 +662,7 @@ test('ErrorEmbeddables get updated when parent does', async done => { panels: { '123': { type: 'vis', - explicitInput: { id: '123' }, - savedObjectId: '456', + explicitInput: { id: '123', savedObjectId: '456' }, }, }, viewMode: ViewMode.EDIT, diff --git a/test/examples/embeddables/adding_children.ts b/test/examples/embeddables/adding_children.ts index 110b8ce573332..5fe88b5dd33f0 100644 --- a/test/examples/embeddables/adding_children.ts +++ b/test/examples/embeddables/adding_children.ts @@ -23,6 +23,7 @@ import { PluginFunctionalProviderContext } from 'test/plugin_functional/services // eslint-disable-next-line import/no-default-export export default function({ getService }: PluginFunctionalProviderContext) { const testSubjects = getService('testSubjects'); + const flyout = getService('flyout'); describe('creating and adding children', () => { before(async () => { @@ -39,5 +40,15 @@ export default function({ getService }: PluginFunctionalProviderContext) { const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask'); expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task']); }); + + it('Can add a child backed off a saved object', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL'); + await testSubjects.click('savedObjectTitleGarbage'); + await testSubjects.moveMouseTo('euiFlyoutCloseButton'); + await flyout.ensureClosed('dashboardAddPanel'); + const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask'); + expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task', 'Take the garbage out']); + }); }); } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts index bb8951680be35..37ef8cad948cb 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts @@ -47,7 +47,7 @@ export const dashboardInput: DashboardContainerInput = { explicitInput: { id: '2', firstName: 'Sue', - } as any, + }, }, '822cd0f0-ce7c-419d-aeaa-1171cf452745': { gridData: { @@ -60,8 +60,8 @@ export const dashboardInput: DashboardContainerInput = { type: 'visualization', explicitInput: { id: '822cd0f0-ce7c-419d-aeaa-1171cf452745', + savedObjectId: '3fe22200-3dcb-11e8-8660-4d65aa086b3c', }, - savedObjectId: '3fe22200-3dcb-11e8-8660-4d65aa086b3c', }, '66f0a265-7b06-4974-accd-d05f74f7aa82': { gridData: { @@ -74,8 +74,8 @@ export const dashboardInput: DashboardContainerInput = { type: 'visualization', explicitInput: { id: '66f0a265-7b06-4974-accd-d05f74f7aa82', + savedObjectId: '4c0f47e0-3dcd-11e8-8660-4d65aa086b3c', }, - savedObjectId: '4c0f47e0-3dcd-11e8-8660-4d65aa086b3c', }, 'b2861741-40b9-4dc8-b82b-080c6e29a551': { gridData: { @@ -88,8 +88,8 @@ export const dashboardInput: DashboardContainerInput = { type: 'search', explicitInput: { id: 'b2861741-40b9-4dc8-b82b-080c6e29a551', + savedObjectId: 'be5accf0-3dca-11e8-8660-4d65aa086b3c', }, - savedObjectId: 'be5accf0-3dca-11e8-8660-4d65aa086b3c', }, }, isFullScreenMode: false, From 3a91e713aa3d403d7b440c5ba128d40e78f8449f Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Wed, 15 Apr 2020 11:13:28 -0400 Subject: [PATCH 16/44] Fixes Keyboard Shortcuts help extension (#63583) --- x-pack/legacy/plugins/canvas/public/application.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index 79b3918fef99b..f75b3b427c41b 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -104,8 +104,9 @@ export const initializeCanvas = async ( href: getDocumentationLinks().canvas, }, ], - content: domNode => () => { + content: domNode => { ReactDOM.render(, domNode); + return () => ReactDOM.unmountComponentAtNode(domNode); }, }); From bb0d03fe11b9ebba78f14a4a39a788fb0ce04abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Wed, 15 Apr 2020 11:16:14 -0400 Subject: [PATCH 17/44] Fix alerting UI flaky tests (#62817) * Use of testSubjects.setValue * Add waitForEditAlertFlyout * Fix some extra flakiness * Test half second sleep to confirm cause of flakiness * Revert 02417961f9ef0b0068cbeeaf17110844f133c189 * Try clearWithKeyboard * Fix test failures * Fix uptime tests * Revert uptime changes --- .../threshold/expression.tsx | 1 + .../action_connector_form/action_form.tsx | 1 + .../apps/triggers_actions_ui/alerts.ts | 64 ++++++++----------- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index 5bbec1221a3ac..7557b17f5b67d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -328,6 +328,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); setAddModalVisibility(true); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index c94e7116c5cea..bce8e08cd16d3 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const supertest = getService('supertest'); const find = getService('find'); + const retry = getService('retry'); async function createAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest @@ -38,8 +39,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdAlert; } - // FLAKY: https://github.com/elastic/kibana/issues/62472 - describe.skip('alerts', function() { + describe('alerts', function() { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('alertsTab'); @@ -48,10 +48,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should create an alert', async () => { const alertName = generateUniqueKey(); await pageObjects.triggersActionsUI.clickCreateAlertButton(); - const nameInput = await testSubjects.find('alertNameInput'); - await nameInput.click(); - await nameInput.clearValue(); - await nameInput.type(alertName); + await testSubjects.setValue('alertNameInput', alertName); await testSubjects.click('.index-threshold-SelectOption'); await testSubjects.click('selectIndexExpression'); const comboBox = await find.byCssSelector('#indexSelectSearchBox'); @@ -60,28 +57,28 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`); await filterSelectItem.click(); await testSubjects.click('thresholdAlertTimeFieldSelect'); - const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); - await fieldOptions[1].click(); + await retry.try(async () => { + const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); + expect(fieldOptions[1]).not.to.be(undefined); + await fieldOptions[1].click(); + }); + await testSubjects.click('closePopover'); // need this two out of popup clicks to close them + const nameInput = await testSubjects.find('alertNameInput'); await nameInput.click(); - // test for normal connector - await testSubjects.click('.webhook-ActionTypeSelectOption'); - const webhookBodyInput = await find.byCssSelector('.ace_text-input'); - await webhookBodyInput.focus(); - await webhookBodyInput.type('{\\"test\\":1}'); - - await testSubjects.click('addAlertActionButton'); - // pre-configured connector is loaded an displayed correctly await testSubjects.click('.slack-ActionTypeSelectOption'); - expect(await (await find.byCssSelector('#my-slack1')).isDisplayed()).to.be(true); - const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); - await loggingMessageInput.click(); - await loggingMessageInput.clearValue(); - await loggingMessageInput.type('test message'); + await testSubjects.click('createActionConnectorButton'); + const slackConnectorName = generateUniqueKey(); + await testSubjects.setValue('nameInput', slackConnectorName); + await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + const createdConnectorToastTitle = await pageObjects.common.closeToast(); + expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + await testSubjects.setValue('slackMessageTextArea', 'test message'); await testSubjects.click('messageAddVariableButton'); - const variableMenuButton = await testSubjects.find('variableMenuButton-0'); - await variableMenuButton.click(); + await testSubjects.click('variableMenuButton-0'); + await testSubjects.click('saveAlertButton'); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Saved '${alertName}'`); @@ -132,7 +129,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should edit an alert', async () => { const createdAlert = await createAlert({ alertTypeId: '.index-threshold', - name: 'new alert', + name: generateUniqueKey(), params: { aggType: 'count', termSize: 5, @@ -160,11 +157,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const editLink = await testSubjects.findAll('alertsTableCell-editLink'); await editLink[0].click(); - const updatedAlertName = 'Changed Alert Name'; - const nameInputToUpdate = await testSubjects.find('alertNameInput'); - await nameInputToUpdate.click(); - await nameInputToUpdate.clearValue(); - await nameInputToUpdate.type(updatedAlertName); + const updatedAlertName = `Changed Alert Name ${generateUniqueKey()}`; + await testSubjects.setValue('alertNameInput', updatedAlertName, { clearWithKeyboard: true }); await find.clickByCssSelector('[data-test-subj="saveEditedAlertButton"]:not(disabled)'); @@ -217,10 +211,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const editLink = await testSubjects.findAll('alertsTableCell-editLink'); await editLink[0].click(); - const throttleInputToSetInitialValue = await testSubjects.find('throttleInput'); - await throttleInputToSetInitialValue.click(); - await throttleInputToSetInitialValue.clearValue(); - await throttleInputToSetInitialValue.type('1'); + await testSubjects.setValue('throttleInput', '1', { clearWithKeyboard: true }); await find.clickByCssSelector('[data-test-subj="saveEditedAlertButton"]:not(disabled)'); @@ -308,11 +299,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const editLink = await testSubjects.findAll('alertsTableCell-editLink'); await editLink[0].click(); - const updatedAlertName = 'Changed Alert Name'; - const nameInputToUpdate = await testSubjects.find('alertNameInput'); - await nameInputToUpdate.click(); - await nameInputToUpdate.clearValue(); - await nameInputToUpdate.type(updatedAlertName); + const updatedAlertName = `Changed Alert Name ${generateUniqueKey()}`; + await testSubjects.setValue('alertNameInput', updatedAlertName); await testSubjects.click('cancelSaveEditedAlertButton'); await find.waitForDeletedByCssSelector('[data-test-subj="cancelSaveEditedAlertButton"]'); From 08aa21419046e52ab47a78b03e8076c9304e21bc Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Wed, 15 Apr 2020 11:19:46 -0400 Subject: [PATCH 18/44] Endpoint: Redux types (#63413) ## Summary Changes some types around so modifying redux state or actions will cause type errors * the state and action types aren't using `Immutable` directly. This means that you can instantiate them and mutate them * the `state` and `action` params received by all reducers will be automatically cast to `Immutable` * reducers can return either `Immutable` or regular versions of `state` * some code may be mutating redux state directly, this is being ignored (at least for now) --- .../plugins/endpoint/common/generate_data.ts | 9 +- .../models/policy_config.ts} | 6 +- x-pack/plugins/endpoint/common/types.ts | 125 ++++++++++++++++++ .../endpoint/models/index_pattern.ts | 16 +++ .../endpoint/models/policy_details_config.ts | 2 +- .../store/alerts/alert_details.test.ts | 5 +- .../endpoint/store/alerts/alert_list.test.ts | 6 +- .../alerts/alert_list_pagination.test.ts | 3 +- .../endpoint/store/alerts/reducer.ts | 8 +- .../endpoint/store/alerts/selectors.ts | 48 +++---- .../endpoint/store/hosts/action.ts | 8 +- .../endpoint/store/hosts/middleware.test.ts | 11 +- .../endpoint/store/hosts/reducer.ts | 5 +- .../endpoint/store/hosts/selectors.ts | 24 ++-- .../store/immutable_combine_reducers.ts | 13 ++ .../endpoint/store/policy_details/action.ts | 3 +- .../store/policy_details/index.test.ts | 4 +- .../store/policy_details/middleware.ts | 12 +- .../endpoint/store/policy_details/reducer.ts | 25 +++- .../store/policy_details/selectors.ts | 15 ++- .../endpoint/store/policy_list/action.ts | 3 +- .../endpoint/store/policy_list/index.test.ts | 10 +- .../endpoint/store/policy_list/reducer.ts | 19 +-- .../endpoint/store/policy_list/selectors.ts | 19 +-- .../store/policy_list/services/ingest.ts | 8 +- .../applications/endpoint/store/reducer.ts | 7 +- .../public/applications/endpoint/types.ts | 92 ++++--------- .../endpoint/view/alerts/index_search_bar.tsx | 11 +- .../endpoint/view/hosts/index.tsx | 6 +- .../view/policy/policy_forms/config_form.tsx | 46 ++++--- .../policy/policy_forms/events/checkbox.tsx | 2 +- .../view/policy/policy_forms/events/linux.tsx | 45 +++---- .../view/policy/policy_forms/events/mac.tsx | 43 +++--- .../policy/policy_forms/events/windows.tsx | 47 +++---- .../policy_forms/protections/malware.tsx | 33 ++--- .../endpoint/view/policy/policy_list.tsx | 10 +- 36 files changed, 444 insertions(+), 305 deletions(-) rename x-pack/plugins/endpoint/{public/applications/endpoint/models/policy.ts => common/models/policy_config.ts} (89%) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 3f783d90e577d..1978d780f54f5 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -6,11 +6,8 @@ import uuid from 'uuid'; import seedrandom from 'seedrandom'; -import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields } from './types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { PolicyData } from '../public/applications/endpoint/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { generatePolicy } from '../public/applications/endpoint/models/policy'; +import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields, PolicyData } from './types'; +import { factory as policyFactory } from './models/policy_config'; export type Event = AlertEvent | EndpointEvent; @@ -474,7 +471,7 @@ export class EndpointDocGenerator { streams: [], config: { policy: { - value: generatePolicy(), + value: policyFactory(), }, }, }, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/common/models/policy_config.ts similarity index 89% rename from x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts rename to x-pack/plugins/endpoint/common/models/policy_config.ts index 5269ee72f4039..199b8a91e4307 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts +++ b/x-pack/plugins/endpoint/common/models/policy_config.ts @@ -7,11 +7,9 @@ import { PolicyConfig, ProtectionModes } from '../types'; /** - * Generate a new Policy model. - * NOTE: in the near future, this will likely be removed and an API call to EPM will be used to retrieve - * the latest from the Endpoint package + * Return a new default `PolicyConfig`. */ -export const generatePolicy = (): PolicyConfig => { +export const factory = (): PolicyConfig => { return { windows: { events: { diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 403ca9832e191..7143f07d8c702 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -7,12 +7,15 @@ import { SearchResponse } from 'elasticsearch'; import { TypeOf } from '@kbn/config-schema'; import { alertingIndexGetQuerySchema } from './schema/alert_index'; +import { Datasource, NewDatasource } from '../../ingest_manager/common'; /** * A deep readonly type that will make all children of a given object readonly recursively */ export type Immutable = T extends undefined | null | boolean | string | number ? T + : unknown extends T + ? unknown : T extends Array ? ImmutableArray : T extends Map @@ -442,3 +445,125 @@ export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf< * Result of the validated query params when handling alert index requests. */ export type AlertingIndexGetQueryResult = TypeOf; + +/** + * Endpoint Policy configuration + */ +export interface PolicyConfig { + windows: { + events: { + dll_and_driver_load: boolean; + dns: boolean; + file: boolean; + network: boolean; + process: boolean; + registry: boolean; + security: boolean; + }; + malware: MalwareFields; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + mac: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + malware: MalwareFields; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + linux: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; +} + +/** + * Windows-specific policy configuration that is supported via the UI + */ +type WindowsPolicyConfig = Pick; + +/** + * Mac-specific policy configuration that is supported via the UI + */ +type MacPolicyConfig = Pick; + +/** + * Linux-specific policy configuration that is supported via the UI + */ +type LinuxPolicyConfig = Pick; + +/** + * The set of Policy configuration settings that are show/edited via the UI + */ +export interface UIPolicyConfig { + windows: WindowsPolicyConfig; + mac: MacPolicyConfig; + linux: LinuxPolicyConfig; +} + +interface PolicyConfigAdvancedOptions { + elasticsearch: { + indices: { + control: string; + event: string; + logging: string; + }; + kernel: { + connect: boolean; + process: boolean; + }; + }; +} + +/** Policy: Malware protection fields */ +export interface MalwareFields { + mode: ProtectionModes; +} + +/** Policy protection mode options */ +export enum ProtectionModes { + detect = 'detect', + prevent = 'prevent', + preventNotify = 'preventNotify', + off = 'off', +} + +/** + * Endpoint Policy data, which extends Ingest's `Datasource` type + */ +export type PolicyData = Datasource & NewPolicyData; + +/** + * New policy data. Used when updating the policy record via ingest APIs + */ +export type NewPolicyData = NewDatasource & { + inputs: [ + { + type: 'endpoint'; + enabled: boolean; + streams: []; + config: { + policy: { + value: PolicyConfig; + }; + }; + } + ]; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts new file mode 100644 index 0000000000000..0cae054432f96 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { all } from 'deepmerge'; +import { Immutable } from '../../../../common/types'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +/** + * Model for the `IIndexPattern` interface exported by the `data` plugin. + */ +export function clone(value: IIndexPattern | Immutable): IIndexPattern { + return all([value]) as IIndexPattern; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts index bf96942e83a91..3e56b1ff14d65 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UIPolicyConfig } from '../types'; +import { UIPolicyConfig } from '../../../../common/types'; /** * A typed Object.entries() function where the keys and values are typed based on the given object diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts index 79e9de9c67352..feac8944f476b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts @@ -14,16 +14,17 @@ import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../../../common/types'; describe('alert details tests', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. */ - let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + let selectorIsTrue: (selector: (state: Immutable) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); depsStart = depsStartMock(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts index b1cc2d46f614a..84281813312e0 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts @@ -12,20 +12,20 @@ import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../mocks'; -import { AlertResultList } from '../../../../../common/types'; +import { AlertResultList, Immutable } from '../../../../../common/types'; import { isOnAlertPage } from './selectors'; import { createBrowserHistory } from 'history'; import { mockAlertResultList } from './mock_alert_result_list'; describe('alert list tests', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. */ - let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + let selectorIsTrue: (selector: (state: Immutable) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); depsStart = depsStartMock(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts index bb5893f14287b..4cc86e9c0449c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts @@ -15,9 +15,10 @@ import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { uiQueryParams } from './selectors'; import { urlFromQueryParams } from '../../view/alerts/url_from_query_params'; +import { Immutable } from '../../../../../common/types'; describe('alert list pagination', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts index 4430a4d39cf4a..52b91dcae7d70 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { AlertListState } from '../../types'; +import { AlertListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; +import { Immutable } from '../../../../../common/types'; -const initialState = (): AlertListState => { +const initialState = (): Immutable => { return { alerts: [], alertDetails: undefined, @@ -22,7 +22,7 @@ const initialState = (): AlertListState => { }; }; -export const alertListReducer: Reducer = ( +export const alertListReducer: ImmutableReducer = ( state = initialState(), action ) => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index 5e9b08c09c2c7..cc362c3701956 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -19,23 +19,23 @@ const createStructuredSelector: CreateStructuredSelector = createStructuredSelec /** * Returns the Alert Data array from state */ -export const alertListData = (state: AlertListState) => state.alerts; +export const alertListData = (state: Immutable) => state.alerts; -export const selectedAlertDetailsData = (state: AlertListState) => state.alertDetails; +export const selectedAlertDetailsData = (state: Immutable) => state.alertDetails; /** * Returns the alert list pagination data from state */ export const alertListPagination = createStructuredSelector({ - pageIndex: (state: AlertListState) => state.pageIndex, - pageSize: (state: AlertListState) => state.pageSize, - total: (state: AlertListState) => state.total, + pageIndex: (state: Immutable) => state.pageIndex, + pageSize: (state: Immutable) => state.pageSize, + total: (state: Immutable) => state.total, }); /** * Returns a boolean based on whether or not the user is on the alerts page */ -export const isOnAlertPage = (state: AlertListState): boolean => { +export const isOnAlertPage = (state: Immutable): boolean => { return state.location ? state.location.pathname === '/alerts' : false; }; @@ -44,10 +44,10 @@ export const isOnAlertPage = (state: AlertListState): boolean => { * Used to calculate urls for links and such. */ export const uiQueryParams: ( - state: AlertListState + state: Immutable ) => Immutable = createSelector( - (state: AlertListState) => state.location, - (location: AlertListState['location']) => { + state => state.location, + (location: Immutable['location']) => { const data: AlertingIndexUIQueryParams = {}; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -82,7 +82,7 @@ export const uiQueryParams: ( * Parses the ui query params and returns a object that represents the query used by the SearchBar component. * If the query url param is undefined, a default is returned. */ -export const searchBarQuery: (state: AlertListState) => Query = createSelector( +export const searchBarQuery: (state: Immutable) => Query = createSelector( uiQueryParams, ({ query }) => { if (query !== undefined) { @@ -97,21 +97,20 @@ export const searchBarQuery: (state: AlertListState) => Query = createSelector( * Parses the ui query params and returns a rison encoded string that represents the search bar's date range. * A default is provided if 'date_range' is not present in the url params. */ -export const encodedSearchBarDateRange: (state: AlertListState) => string = createSelector( - uiQueryParams, - ({ date_range: dateRange }) => { - if (dateRange === undefined) { - return encode({ from: 'now-24h', to: 'now' }); - } else { - return dateRange; - } +export const encodedSearchBarDateRange: ( + state: Immutable +) => string = createSelector(uiQueryParams, ({ date_range: dateRange }) => { + if (dateRange === undefined) { + return encode({ from: 'now-24h', to: 'now' }); + } else { + return dateRange; } -); +}); /** * Parses the ui query params and returns a object that represents the dateRange used by the SearchBar component. */ -export const searchBarDateRange: (state: AlertListState) => TimeRange = createSelector( +export const searchBarDateRange: (state: Immutable) => TimeRange = createSelector( encodedSearchBarDateRange, encodedDateRange => { return (decode(encodedDateRange) as unknown) as TimeRange; @@ -122,7 +121,7 @@ export const searchBarDateRange: (state: AlertListState) => TimeRange = createSe * Parses the ui query params and returns an array of filters used by the SearchBar component. * If the 'filters' param is not present, a default is returned. */ -export const searchBarFilters: (state: AlertListState) => Filter[] = createSelector( +export const searchBarFilters: (state: Immutable) => Filter[] = createSelector( uiQueryParams, ({ filters }) => { if (filters !== undefined) { @@ -136,13 +135,14 @@ export const searchBarFilters: (state: AlertListState) => Filter[] = createSelec /** * Returns the indexPatterns used by the SearchBar component */ -export const searchBarIndexPatterns = (state: AlertListState) => state.searchBar.patterns; +export const searchBarIndexPatterns = (state: Immutable) => + state.searchBar.patterns; /** * query params to use when requesting alert data. */ export const apiQueryParams: ( - state: AlertListState + state: Immutable ) => Immutable = createSelector( uiQueryParams, encodedSearchBarDateRange, @@ -161,7 +161,7 @@ export const apiQueryParams: ( * True if the user has selected an alert to see details about. * Populated via the browsers query params. */ -export const hasSelectedAlert: (state: AlertListState) => boolean = createSelector( +export const hasSelectedAlert: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_alert: selectedAlert }) => selectedAlert !== undefined ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 4dafa68ddb647..21871ec8ca849 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -27,14 +27,8 @@ interface UserPaginatedHostList { payload: HostListPagination; } -// Why is FakeActionWithNoPayload here, see: https://github.com/elastic/endpoint-app-team/issues/273 -interface FakeActionWithNoPayload { - type: 'fakeActionWithNoPayLoad'; -} - export type HostAction = | ServerReturnedHostList | ServerReturnedHostDetails | ServerFailedToReturnHostDetails - | UserPaginatedHostList - | FakeActionWithNoPayload; + | UserPaginatedHostList; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 8c8578426aa29..8f39baddda00e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { CoreStart, HttpSetup } from 'kibana/public'; -import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { applyMiddleware, createStore, Store } from 'redux'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; import { hostListReducer, hostMiddlewareFactory } from './index'; -import { HostResultList } from '../../../../../common/types'; +import { HostResultList, Immutable } from '../../../../../common/types'; import { HostListState } from '../../types'; import { AppAction } from '../action'; import { listData } from './selectors'; @@ -20,9 +20,10 @@ describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; - let store: Store; - let getState: typeof store['getState']; - let dispatch: Dispatch; + type HostListStore = Store, Immutable>; + let store: HostListStore; + let getState: HostListStore['getState']; + let dispatch: HostListStore['dispatch']; let history: History; const getEndpointListApiResponse = (): HostResultList => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index ad6741dab7be7..298e819645dbe 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { HostListState } from '../../types'; +import { HostListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; const initialState = (): HostListState => { @@ -21,7 +20,7 @@ const initialState = (): HostListState => { }; }; -export const hostListReducer: Reducer = ( +export const hostListReducer: ImmutableReducer = ( state = initialState(), action ) => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index ebe310cb51190..35bf5d0616878 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -8,30 +8,30 @@ import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/types'; import { HostListState, HostIndexUIQueryParams } from '../../types'; -export const listData = (state: HostListState) => state.hosts; +export const listData = (state: Immutable) => state.hosts; -export const pageIndex = (state: HostListState) => state.pageIndex; +export const pageIndex = (state: Immutable) => state.pageIndex; -export const pageSize = (state: HostListState) => state.pageSize; +export const pageSize = (state: Immutable) => state.pageSize; -export const totalHits = (state: HostListState) => state.total; +export const totalHits = (state: Immutable) => state.total; -export const isLoading = (state: HostListState) => state.loading; +export const isLoading = (state: Immutable) => state.loading; -export const detailsError = (state: HostListState) => state.detailsError; +export const detailsError = (state: Immutable) => state.detailsError; -export const detailsData = (state: HostListState) => { +export const detailsData = (state: Immutable) => { return state.details; }; -export const isOnHostPage = (state: HostListState) => +export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; export const uiQueryParams: ( - state: HostListState + state: Immutable ) => Immutable = createSelector( - (state: HostListState) => state.location, - (location: HostListState['location']) => { + (state: Immutable) => state.location, + (location: Immutable['location']) => { const data: HostIndexUIQueryParams = {}; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -52,7 +52,7 @@ export const uiQueryParams: ( } ); -export const hasSelectedHost: (state: HostListState) => boolean = createSelector( +export const hasSelectedHost: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_host: selectedHost }) => { return selectedHost !== undefined; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts new file mode 100644 index 0000000000000..6895f0106fb5d --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts @@ -0,0 +1,13 @@ +/* + * 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 { combineReducers } from 'redux'; +import { ImmutableCombineReducers } from '../types'; + +/** + * Works the same as `combineReducers` from `redux`, but uses the `ImmutableCombineReducers` type. + */ +export const immutableCombineReducers: ImmutableCombineReducers = combineReducers; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts index 9905145048a8a..4de3dac02a8ec 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, PolicyDetailsState, ServerApiError, UIPolicyConfig } from '../../types'; +import { PolicyDetailsState, ServerApiError } from '../../types'; import { GetAgentStatusResponse } from '../../../../../../ingest_manager/common/types/rest_spec'; +import { PolicyData, UIPolicyConfig } from '../../../../../common/types'; interface ServerReturnedPolicyDetailsData { type: 'serverReturnedPolicyDetailsData'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts index f81852d6a074a..a24687ebbcbbc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts @@ -9,7 +9,7 @@ import { createStore, Dispatch, Store } from 'redux'; import { policyDetailsReducer, PolicyDetailsAction } from './index'; import { policyConfig } from './selectors'; import { clone } from '../../models/policy_details_config'; -import { generatePolicy } from '../../models/policy'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; describe('policy details: ', () => { let store: Store; @@ -38,7 +38,7 @@ describe('policy details: ', () => { streams: [], config: { policy: { - value: generatePolicy(), + value: policyConfigFactory(), }, }, }, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts index 2581ab37f5677..7a3fbe7f23b9b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts @@ -4,19 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - MiddlewareFactory, - PolicyData, - PolicyDetailsState, - UpdatePolicyResponse, -} from '../../types'; +import { MiddlewareFactory, PolicyDetailsState, UpdatePolicyResponse } from '../../types'; import { policyIdFromParams, isOnPolicyDetailsPage, policyDetails } from './selectors'; -import { generatePolicy } from '../../models/policy'; import { sendGetDatasource, sendGetFleetAgentStatusForConfig, sendPutDatasource, } from '../policy_list/services/ingest'; +import { PolicyData } from '../../../../../common/types'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; export const policyDetailsMiddlewareFactory: MiddlewareFactory = coreStart => { const http = coreStart.http; @@ -49,7 +45,7 @@ export const policyDetailsMiddlewareFactory: MiddlewareFactory { return { @@ -23,7 +23,7 @@ const initialPolicyDetailsState = (): PolicyDetailsState => { }; }; -export const policyDetailsReducer: Reducer = ( +export const policyDetailsReducer: ImmutableReducer = ( state = initialPolicyDetailsState(), action ) => { @@ -70,7 +70,7 @@ export const policyDetailsReducer: Reducer = ( } if (action.type === 'userChangedUrl') { - const newState = { + const newState: Immutable = { ...state, location: action.payload, }; @@ -79,8 +79,10 @@ export const policyDetailsReducer: Reducer = ( // Did user just enter the Detail page? if so, then set the loading indicator and return new state if (isCurrentlyOnDetailsPage && !wasPreviouslyOnDetailsPage) { - newState.isLoading = true; - return newState; + return { + ...newState, + isLoading: true, + }; } return { ...initialPolicyDetailsState(), @@ -93,10 +95,19 @@ export const policyDetailsReducer: Reducer = ( return state; } const newState = { ...state, policyItem: { ...state.policyItem } }; - const newPolicy: any = { ...fullPolicy(state) }; + const newPolicy: PolicyConfig = { ...fullPolicy(state) }; + + /** + * This is directly changing redux state because `policyItem.inputs` was copied over and not cloned. + */ + // @ts-ignore newState.policyItem.inputs[0].config.policy.value = newPolicy; Object.entries(action.payload.policyConfig).forEach(([section, newSettings]) => { + /** + * this is not safe because `action.payload.policyConfig` may have excess keys + */ + // @ts-ignore newPolicy[section as keyof UIPolicyConfig] = { ...newPolicy[section as keyof UIPolicyConfig], ...newSettings, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts index a37a06bafcf05..98e129132e1cb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts @@ -5,14 +5,15 @@ */ import { createSelector } from 'reselect'; -import { PolicyConfig, PolicyDetailsState, UIPolicyConfig } from '../../types'; -import { generatePolicy } from '../../models/policy'; +import { PolicyDetailsState } from '../../types'; +import { Immutable, PolicyConfig, UIPolicyConfig } from '../../../../../common/types'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; /** Returns the policy details */ -export const policyDetails = (state: PolicyDetailsState) => state.policyItem; +export const policyDetails = (state: Immutable) => state.policyItem; /** Returns a boolean of whether the user is on the policy details page or not */ -export const isOnPolicyDetailsPage = (state: PolicyDetailsState) => { +export const isOnPolicyDetailsPage = (state: Immutable) => { if (state.location) { const pathnameParts = state.location.pathname.split('/'); return pathnameParts[1] === 'policy' && pathnameParts[2]; @@ -32,14 +33,16 @@ export const policyIdFromParams: (state: PolicyDetailsState) => string = createS } ); +const defaultFullPolicy: Immutable = policyConfigFactory(); + /** * Returns the full Endpoint Policy, which will include private settings not shown on the UI. * Note: this will return a default full policy if the `policyItem` is `undefined` */ -export const fullPolicy: (s: PolicyDetailsState) => PolicyConfig = createSelector( +export const fullPolicy: (s: Immutable) => PolicyConfig = createSelector( policyDetails, policyData => { - return policyData?.inputs[0]?.config?.policy?.value ?? generatePolicy(); + return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy; } ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts index 3db224f049c05..4c379b7426461 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, ServerApiError } from '../../types'; +import { ServerApiError } from '../../types'; +import { PolicyData } from '../../../../../common/types'; interface ServerReturnedPolicyListData { type: 'serverReturnedPolicyListData'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts index 4d153b5e03cd2..97a2b65fb65f8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts @@ -5,7 +5,7 @@ */ import { EndpointAppLocation, PolicyListState } from '../../types'; -import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { applyMiddleware, createStore, Store } from 'redux'; import { AppAction } from '../action'; import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; @@ -18,13 +18,15 @@ import { setPolicyListApiMockImplementation, } from './test_mock_utils'; import { INGEST_API_DATASOURCES } from './services/ingest'; +import { Immutable } from '../../../../../common/types'; describe('policy list store concerns', () => { let fakeCoreStart: ReturnType; let depsStart: DepsStartMock; - let store: Store; - let getState: typeof store['getState']; - let dispatch: Dispatch; + type PolicyListStore = Store, Immutable>; + let store: PolicyListStore; + let getState: PolicyListStore['getState']; + let dispatch: PolicyListStore['dispatch']; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; beforeEach(() => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts index 30c1deac7f5e1..ccd3f84dd060c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { PolicyListState } from '../../types'; +import { PolicyListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; import { isOnPolicyListPage } from './selectors'; +import { Immutable } from '../../../../../common/types'; const initialPolicyListState = (): PolicyListState => { return { @@ -21,7 +21,7 @@ const initialPolicyListState = (): PolicyListState => { }; }; -export const policyListReducer: Reducer = ( +export const policyListReducer: ImmutableReducer = ( state = initialPolicyListState(), action ) => { @@ -42,7 +42,7 @@ export const policyListReducer: Reducer = ( } if (action.type === 'userChangedUrl') { - const newState = { + const newState: Immutable = { ...state, location: action.payload, }; @@ -53,14 +53,15 @@ export const policyListReducer: Reducer = ( // Also adjust some state if user is just entering the policy list view if (isCurrentlyOnListPage) { if (!wasPreviouslyOnListPage) { - newState.apiError = undefined; - newState.isLoading = true; + return { + ...newState, + apiError: undefined, + isLoading: true, + }; } return newState; } - return { - ...initialPolicyListState(), - }; + return initialPolicyListState(); } return state; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts index ce13d89b2b8c2..6d2e952fa07bb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts @@ -7,32 +7,33 @@ import { createSelector } from 'reselect'; import { parse } from 'query-string'; import { PolicyListState, PolicyListUrlSearchParams } from '../../types'; +import { Immutable } from '../../../../../common/types'; const PAGE_SIZES = Object.freeze([10, 20, 50]); -export const selectPolicyItems = (state: PolicyListState) => state.policyItems; +export const selectPolicyItems = (state: Immutable) => state.policyItems; -export const selectPageIndex = (state: PolicyListState) => state.pageIndex; +export const selectPageIndex = (state: Immutable) => state.pageIndex; -export const selectPageSize = (state: PolicyListState) => state.pageSize; +export const selectPageSize = (state: Immutable) => state.pageSize; -export const selectTotal = (state: PolicyListState) => state.total; +export const selectTotal = (state: Immutable) => state.total; -export const selectIsLoading = (state: PolicyListState) => state.isLoading; +export const selectIsLoading = (state: Immutable) => state.isLoading; -export const selectApiError = (state: PolicyListState) => state.apiError; +export const selectApiError = (state: Immutable) => state.apiError; -export const isOnPolicyListPage = (state: PolicyListState) => { +export const isOnPolicyListPage = (state: Immutable) => { return state.location?.pathname === '/policy'; }; -const routeLocation = (state: PolicyListState) => state.location; +const routeLocation = (state: Immutable) => state.location; /** * Returns the supported URL search params, populated with defaults if none where present in the URL */ export const urlSearchParams: ( - state: PolicyListState + state: Immutable ) => PolicyListUrlSearchParams = createSelector(routeLocation, location => { const searchParams = { page_index: 0, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts index 5fccb01d1ad35..4356517e43c2c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts @@ -9,12 +9,8 @@ import { GetDatasourcesRequest, GetAgentStatusResponse, } from '../../../../../../../ingest_manager/common'; -import { - NewPolicyData, - GetPolicyListResponse, - GetPolicyResponse, - UpdatePolicyResponse, -} from '../../../types'; +import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; +import { NewPolicyData } from '../../../../../../common/types'; const INGEST_API_ROOT = `/api/ingest_manager`; export const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts index c8b2d08676724..2f77c380d9387 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts @@ -3,15 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers, Reducer } from 'redux'; + import { hostListReducer } from './hosts'; import { AppAction } from './action'; import { alertListReducer } from './alerts'; -import { GlobalState } from '../types'; +import { GlobalState, ImmutableReducer } from '../types'; import { policyListReducer } from './policy_list'; import { policyDetailsReducer } from './policy_details'; +import { immutableCombineReducers } from './immutable_combine_reducers'; -export const appReducer: Reducer = combineReducers({ +export const appReducer: ImmutableReducer = immutableCombineReducers({ hostList: hostListReducer, alertList: alertListReducer, policyList: policyListReducer, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 015468f84e740..7aca94d3e9c7c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Dispatch, MiddlewareAPI } from 'redux'; +import { Dispatch, MiddlewareAPI, Action as ReduxAction, AnyAction as ReduxAnyAction } from 'redux'; import { IIndexPattern } from 'src/plugins/data/public'; import { HostMetadata, @@ -13,13 +13,14 @@ import { Immutable, ImmutableArray, AlertDetails, + MalwareFields, + UIPolicyConfig, + PolicyData, } from '../../../common/types'; import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; import { CoreStart } from '../../../../../../src/core/public'; import { - Datasource, - NewDatasource, GetAgentStatusResponse, GetDatasourcesResponse, GetOneDatasourceResponse, @@ -59,29 +60,6 @@ export interface ServerApiError { message: string; } -/** - * New policy data. Used when updating the policy record via ingest APIs - */ -export type NewPolicyData = NewDatasource & { - inputs: [ - { - type: 'endpoint'; - enabled: boolean; - streams: []; - config: { - policy: { - value: PolicyConfig; - }; - }; - } - ]; -}; - -/** - * Endpoint Policy data, which extends Ingest's `Datasource` type - */ -export type PolicyData = Datasource & NewPolicyData; - /** * Policy list store state */ @@ -192,30 +170,6 @@ interface PolicyConfigAdvancedOptions { }; } -/** - * Windows-specific policy configuration that is supported via the UI - */ -type WindowsPolicyConfig = Pick; - -/** - * Mac-specific policy configuration that is supported via the UI - */ -type MacPolicyConfig = Pick; - -/** - * Linux-specific policy configuration that is supported via the UI - */ -type LinuxPolicyConfig = Pick; - -/** - * The set of Policy configuration settings that are show/edited via the UI - */ -export interface UIPolicyConfig { - windows: WindowsPolicyConfig; - mac: MacPolicyConfig; - linux: LinuxPolicyConfig; -} - /** OS used in Policy */ export enum OS { windows = 'windows', @@ -246,20 +200,7 @@ export type KeysByValueCriteria = { }[keyof O]; /** Returns an array of the policy OSes that have a malware protection field */ - export type MalwareProtectionOSes = KeysByValueCriteria; -/** Policy: Malware protection fields */ -export interface MalwareFields { - mode: ProtectionModes; -} - -/** Policy protection mode options */ -export enum ProtectionModes { - detect = 'detect', - prevent = 'prevent', - preventNotify = 'preventNotify', - off = 'off', -} export interface GlobalState { readonly hostList: HostListState; @@ -349,3 +290,28 @@ export interface GetPolicyResponse extends GetOneDatasourceResponse { export interface UpdatePolicyResponse extends UpdateDatasourceResponse { item: PolicyData; } + +/** + * Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`. + * Use this type for all Reducers in order to help enforce our pattern of immutable state. + */ +export type ImmutableReducer = ( + state: Immutable | undefined, + action: Immutable +) => State | Immutable; + +/** + * A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation, + * but will enforce that `Immutable` versions of `state` and `action` are received. + */ +export type ImmutableCombineReducers = ( + reducers: ImmutableReducersMapObject +) => ImmutableReducer; + +/** + * Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that + * the `state` and `action` received are `Immutable` versions. + */ +type ImmutableReducersMapObject = { + [K in keyof S]: ImmutableReducer; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx index 5b872962a5dc0..1ede06c086517 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { memo, useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { encode, RisonValue } from 'rison-node'; @@ -14,11 +14,18 @@ import { urlFromQueryParams } from './url_from_query_params'; import { useAlertListSelector } from './hooks/use_alerts_selector'; import * as selectors from '../../store/alerts/selectors'; import { EndpointPluginServices } from '../../../../plugin'; +import { clone } from '../../models/index_pattern'; export const AlertIndexSearchBar = memo(() => { const history = useHistory(); const queryParams = useAlertListSelector(selectors.uiQueryParams); const searchBarIndexPatterns = useAlertListSelector(selectors.searchBarIndexPatterns); + + // Deeply clone the search bar index patterns as the receiving component may mutate them + const clonedSearchBarIndexPatterns = useMemo( + () => searchBarIndexPatterns.map(pattern => clone(pattern)), + [searchBarIndexPatterns] + ); const searchBarQuery = useAlertListSelector(selectors.searchBarQuery); const searchBarDateRange = useAlertListSelector(selectors.searchBarDateRange); const searchBarFilters = useAlertListSelector(selectors.searchBarFilters); @@ -68,7 +75,7 @@ export const AlertIndexSearchBar = memo(() => { dataTestSubj="alertsSearchBar" appName="endpoint" isLoading={false} - indexPatterns={searchBarIndexPatterns} + indexPatterns={clonedSearchBarIndexPatterns} query={searchBarQuery} dateRangeFrom={searchBarDateRange.from} dateRangeTo={searchBarDateRange.to} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 94625b8c66191..1d81d6e8a16db 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -23,12 +23,14 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; +import { EuiBasicTableColumn } from '@elastic/eui'; import { HostDetailsFlyout } from './details'; import * as selectors from '../../store/hosts/selectors'; import { HostAction } from '../../store/hosts/action'; import { useHostListSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; +import { HostMetadata, Immutable } from '../../../../../common/types'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { @@ -65,7 +67,7 @@ export const HostList = () => { [dispatch] ); - const columns = useMemo(() => { + const columns: Array>> = useMemo(() => { return [ { field: '', @@ -174,7 +176,7 @@ export const HostList = () => { [...listData], [listData])} columns={columns} loading={isLoading} pagination={paginationSetup} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx index 8b6c32c3277ed..341086c7cf75c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiCard, EuiFlexGroup, @@ -25,14 +25,27 @@ const PolicyDetailCard = styled.div` } `; export const ConfigForm: React.FC<{ + /** + * A subtitle for this component. + **/ type: string; - supportedOss: string[]; + /** + * Types of supported operating systems. + */ + supportedOss: React.ReactNode; children: React.ReactNode; - id: string; - /** Takes a react component to be put on the right corner of the card */ + /** + * A description for the component. + */ + description: string; + /** + * The `data-test-subj` attribute to append to a certain child element. + */ + dataTestSubj: string; + /** React Node to be put on the right corner of the card */ rightCorner: React.ReactNode; -}> = React.memo(({ type, supportedOss, children, id, rightCorner }) => { - const typeTitle = () => { +}> = React.memo(({ type, supportedOss, children, dataTestSubj, rightCorner, description }) => { + const typeTitle = useMemo(() => { return ( @@ -59,28 +72,25 @@ export const ConfigForm: React.FC<{ - {supportedOss.join(', ')} + {supportedOss} {rightCorner} ); - }; + }, [rightCorner, supportedOss, type]); return ( - - {children} - - } - /> + title={typeTitle} + > + + {children} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx index bec6b33b85c7f..74322ac8b993b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx @@ -11,7 +11,7 @@ import { htmlIdGenerator } from '@elastic/eui'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { policyConfig } from '../../../../store/policy_details/selectors'; import { PolicyDetailsAction } from '../../../../store/policy_details'; -import { UIPolicyConfig } from '../../../../types'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const EventsCheckbox = React.memo(function({ name, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx index ca2215d3d0a59..c3d6bdba7c852 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx @@ -8,24 +8,24 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { ImmutableArray } from '../../../../../../../common/types'; -import { getIn, setIn } from '../../../../models/policy_details_config'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedLinuxEvents, totalLinuxEvents } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { getIn, setIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const LinuxEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedLinuxEvents); const total = usePolicyDetailsSelector(totalLinuxEvents); - const checkboxes: ImmutableArray<{ - name: string; - os: 'linux'; - protectionField: keyof UIPolicyConfig['linux']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: Array<{ + name: string; + os: 'linux'; + protectionField: keyof UIPolicyConfig['linux']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.linux.events.file', { defaultMessage: 'File', @@ -47,11 +47,7 @@ export const LinuxEvents = React.memo(() => { os: OS.linux, protectionField: 'network', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + ]; return ( <> @@ -63,7 +59,7 @@ export const LinuxEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -96,13 +92,16 @@ export const LinuxEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [i18n.translate('xpack.endpoint.policy.details.linux', { defaultMessage: 'Linux' })], - [] - )} - id="linuxEventsForm" + description={i18n.translate('xpack.endpoint.policy.details.eventCollectionLabel', { + defaultMessage: 'Event Collection', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.linux', { + defaultMessage: 'Linux', + })} + dataTestSubj="linuxEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx index 5024d02603d77..40b80b9af0f65 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx @@ -8,24 +8,24 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { ImmutableArray } from '../../../../../../../common/types'; -import { getIn, setIn } from '../../../../models/policy_details_config'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedMacEvents, totalMacEvents } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { getIn, setIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const MacEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedMacEvents); const total = usePolicyDetailsSelector(totalMacEvents); - const checkboxes: ImmutableArray<{ - name: string; - os: 'mac'; - protectionField: keyof UIPolicyConfig['mac']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: Array<{ + name: string; + os: 'mac'; + protectionField: keyof UIPolicyConfig['mac']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.mac.events.file', { defaultMessage: 'File', @@ -47,11 +47,7 @@ export const MacEvents = React.memo(() => { os: OS.mac, protectionField: 'network', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + ]; return ( <> @@ -63,7 +59,7 @@ export const MacEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -96,13 +92,14 @@ export const MacEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' })], - [] - )} - id="macEventsForm" + description={i18n.translate('xpack.endpoint.policy.details.eventCollectionLabel', { + defaultMessage: 'Event Collection', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' })} + dataTestSubj="macEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx index 5b347ec387f48..7f946de9614ca 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx @@ -8,27 +8,27 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { ImmutableArray } from '../../../../../../../common/types'; -import { setIn, getIn } from '../../../../models/policy_details_config'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedWindowsEvents, totalWindowsEvents, } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { setIn, getIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig, ImmutableArray } from '../../../../../../../common/types'; export const WindowsEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedWindowsEvents); const total = usePolicyDetailsSelector(totalWindowsEvents); - const checkboxes: ImmutableArray<{ - name: string; - os: 'windows'; - protectionField: keyof UIPolicyConfig['windows']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: ImmutableArray<{ + name: string; + os: 'windows'; + protectionField: keyof UIPolicyConfig['windows']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', { defaultMessage: 'DLL and Driver Load', @@ -78,11 +78,7 @@ export const WindowsEvents = React.memo(() => { os: OS.windows, protectionField: 'security', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + ]; return ( <> @@ -94,7 +90,7 @@ export const WindowsEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -127,15 +123,16 @@ export const WindowsEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [ - i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }), - ], - [] - )} - id="windowsEventsForm" + description={i18n.translate('xpack.endpoint.policy.details.windowsLabel', { + defaultMessage: 'Windows', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.windows', { + defaultMessage: 'Windows', + })} + dataTestSubj="windowsEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx index 66b22178607b9..14871c71ec038 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx @@ -11,8 +11,8 @@ import { EuiRadio, EuiSwitch, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { htmlIdGenerator } from '@elastic/eui'; -import { Immutable } from '../../../../../../../common/types'; -import { OS, ProtectionModes, MalwareProtectionOSes } from '../../../../types'; +import { Immutable, ProtectionModes, ImmutableArray } from '../../../../../../../common/types'; +import { OS, MalwareProtectionOSes } from '../../../../types'; import { ConfigForm } from '../config_form'; import { policyConfig } from '../../../../store/policy_details/selectors'; import { usePolicyDetailsSelector } from '../../policy_hooks'; @@ -73,7 +73,7 @@ export const MalwareProtections = React.memo(() => { // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; - const radios: Array<{ + const radios: ImmutableArray<{ id: ProtectionModes; label: string; protection: 'malware'; @@ -123,7 +123,7 @@ export const MalwareProtections = React.memo(() => { [dispatch, policyDetailsConfig] ); - const RadioButtons = () => { + const radioButtons = useMemo(() => { return ( <> @@ -148,9 +148,9 @@ export const MalwareProtections = React.memo(() => { ); - }; + }, [radios]); - const ProtectionSwitch = () => { + const protectionSwitch = useMemo(() => { return ( { onChange={handleSwitchChange} /> ); - }; + }, [handleSwitchChange, selected]); return ( + supportedOss={i18n.translate('xpack.endpoint.policy.details.windowsAndMac', { + defaultMessage: 'Windows, Mac', + })} + dataTestSubj="malwareProtectionsForm" + description={i18n.translate('xpack.endpoint.policy.details.malwareLabel', { + defaultMessage: 'Malware', + })} + rightCorner={protectionSwitch} + > + {radioButtons} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index 295312fff01dd..062c7afb6706d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -20,10 +20,10 @@ import { } from '../../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; import { PolicyListAction } from '../../store/policy_list'; -import { PolicyData } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { PageView } from '../components/page_view'; import { LinkToApp } from '../components/link_to_app'; +import { Immutable, PolicyData } from '../../../../../common/types'; interface TableChangeCallbackArguments { page: { index: number; size: number }; @@ -44,8 +44,8 @@ const PolicyLink: React.FC<{ name: string; route: string }> = ({ name, route }) ); }; -const renderPolicyNameLink = (value: string, _item: PolicyData) => { - return ; +const renderPolicyNameLink = (value: string, item: Immutable) => { + return ; }; export const PolicyList = React.memo(() => { @@ -88,7 +88,7 @@ export const PolicyList = React.memo(() => { [history, location.pathname] ); - const columns: Array> = useMemo( + const columns: Array>> = useMemo( () => [ { field: 'name', @@ -160,7 +160,7 @@ export const PolicyList = React.memo(() => { } > [...policyItems], [policyItems])} columns={columns} loading={loading} pagination={paginationSetup} From 99332b8a6e71c159ae4e70fcf5408f3ec01b44ec Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 15 Apr 2020 17:52:21 +0200 Subject: [PATCH 19/44] Fix CODEOWNERS and sass lint paths (#63552) --- .github/CODEOWNERS | 3 ++- .sass-lint.yml | 3 ++- .../layer_panel/style_settings/_style_settings.scss | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8a8a79e7c5f65..05972edacfe88 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # App /x-pack/plugins/lens/ @elastic/kibana-app -/x-pack/legacy/plugins/graph/ @elastic/kibana-app +/x-pack/plugins/graph/ @elastic/kibana-app /src/legacy/server/url_shortening/ @elastic/kibana-app /src/legacy/server/sample_data/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dashboard/ @elastic/kibana-app @@ -95,6 +95,7 @@ # Maps /x-pack/legacy/plugins/maps/ @elastic/kibana-gis +/x-pack/plugins/maps/ @elastic/kibana-gis /x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis /x-pack/test/functional/apps/maps/ @elastic/kibana-gis /x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis diff --git a/.sass-lint.yml b/.sass-lint.yml index 0c33eaf794c69..5c2c88a1dad5d 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -9,9 +9,10 @@ files: - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' - 'x-pack/plugins/lens/**/*.s+(a|c)ss' + - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' + - 'x-pack/plugins/maps/**/*.s+(a|c)ss' ignore: - 'x-pack/legacy/plugins/canvas/shareable_runtime/**/*.s+(a|c)ss' - - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' rules: quotes: - 2 diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss index 249b6dfca5c76..f9ad412f7e48a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss @@ -1,3 +1,3 @@ .mapStyleSettings__fixedBox { - width: $euiSize * 7.5; -} \ No newline at end of file + width: $euiSize * 7.5; +} From e44cf28c98b4c05b9c5899d9138b8a639782ddfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Wed, 15 Apr 2020 12:19:15 -0400 Subject: [PATCH 20/44] Fix alerting documentation encryption key requirement (#63512) * Fix documentation to indicate encryption key config required regardless if security is used or not * Update docs/user/alerting/index.asciidoc Co-Authored-By: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/user/alerting/index.asciidoc Co-Authored-By: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/user/alerting/index.asciidoc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc index f556cf71bf06c..df11f5f03a7de 100644 --- a/docs/user/alerting/index.asciidoc +++ b/docs/user/alerting/index.asciidoc @@ -154,10 +154,13 @@ Pre-packaged *alert types* simplify setup, hide the details complex domain-speci [[alerting-setup-prerequisites]] == Setup and prerequisites +If you are using an *on-premises* Elastic Stack deployment: + +* In the kibana.yml configuration file, add the <> setting. + If you are using an *on-premises* Elastic Stack deployment with <>: -* TLS must be configured for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. -* In the kibana.yml configuration file, add the <> +* Transport Layer Security (TLS) must be configured for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. [float] [[alerting-security]] From 7058070e15f8e558a7403585ba57c6c0802e33ee Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 15 Apr 2020 18:40:34 +0200 Subject: [PATCH 21/44] [ML] Extract apiDoc params from the schema definitions (#62933) * [ML] WIP apiDoc schema extractor * [ML] extract actual type * [ML] refactor schema definitions * [ML] Update README.md * [ML] extract nested * [ML] call job validation endpoint with complete payload * [ML] escape special chars and fix line breaks * [ML] clean up extractDocEntries * [ML] serializeWithType * [ML] add missing annotations * [ML] fix parent schema assigment * [ML] support object composition * [ML] support multiple schemas per block * [ML] fix for collections * [ML] fix calendarIdsSchema * [ML] add ml package.json with apidoc commands * [ML] use the single output markdown file * [ML] fix typo * [ML] change the Calendars order * [ML] adjust the order in adidoc.json * [ML] update api version * [ML] update tsconfig.json include * [ML] update packages/kbn-pm/dist/index.js * [ML] update ML overrides in .eslintrc.js * [ML] yarn.lock symlink * Revert "[ML] yarn.lock symlink" This reverts commit 07f06801 Co-authored-by: Elastic Machine --- .eslintrc.js | 8 +- packages/kbn-pm/dist/index.js | 31 +- x-pack/plugins/ml/package.json | 15 + x-pack/plugins/ml/server/routes/README.md | 11 +- .../plugins/ml/server/routes/annotations.ts | 20 +- .../ml/server/routes/anomaly_detectors.ts | 133 ++---- x-pack/plugins/ml/server/routes/apidoc.json | 52 ++- .../routes/apidoc_scripts/schema_extractor.ts | 168 ++++++++ .../routes/apidoc_scripts/schema_parser.ts | 37 ++ .../routes/apidoc_scripts/schema_worker.ts | 80 ++++ .../routes/apidoc_scripts/tsconfig.json | 14 + .../ml/server/routes/apidoc_scripts/types.ts | 42 ++ .../routes/apidoc_scripts/version_filter.ts | 21 + x-pack/plugins/ml/server/routes/calendars.ts | 58 ++- .../ml/server/routes/data_frame_analytics.ts | 63 ++- .../ml/server/routes/data_visualizer.ts | 17 +- x-pack/plugins/ml/server/routes/datafeeds.ts | 46 +- .../ml/server/routes/fields_service.ts | 4 + .../ml/server/routes/file_data_visualizer.ts | 44 +- x-pack/plugins/ml/server/routes/filters.ts | 22 +- x-pack/plugins/ml/server/routes/indices.ts | 11 +- .../ml/server/routes/job_audit_messages.ts | 14 +- .../plugins/ml/server/routes/job_service.ts | 42 +- .../ml/server/routes/job_validation.ts | 12 +- x-pack/plugins/ml/server/routes/modules.ts | 2 +- .../ml/server/routes/results_service.ts | 21 +- .../routes/schemas/annotations_schema.ts | 11 +- .../schemas/anomaly_detectors_schema.ts | 73 +++- .../server/routes/schemas/calendars_schema.ts | 11 +- .../routes/schemas/data_analytics_schema.ts | 26 +- .../routes/schemas/data_visualizer_schema.ts | 52 +-- .../server/routes/schemas/datafeeds_schema.ts | 6 + .../schemas/file_data_visualizer_schema.ts | 43 ++ .../server/routes/schemas/filters_schema.ts | 15 +- .../server/routes/schemas/indices_schema.ts | 11 + .../schemas/job_audit_messages_schema.ts | 13 + .../routes/schemas/job_service_schema.ts | 16 +- .../routes/schemas/job_validation_schema.ts | 4 +- .../routes/schemas/results_service_schema.ts | 20 +- yarn.lock | 392 +++++++++++++++++- 40 files changed, 1334 insertions(+), 347 deletions(-) create mode 100644 x-pack/plugins/ml/package.json create mode 100644 x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts create mode 100644 x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts create mode 100644 x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts create mode 100644 x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json create mode 100644 x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts create mode 100644 x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts create mode 100644 x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts create mode 100644 x-pack/plugins/ml/server/routes/schemas/indices_schema.ts create mode 100644 x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts diff --git a/.eslintrc.js b/.eslintrc.js index 246702aedf863..dc2eaa993ce8b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -536,9 +536,15 @@ module.exports = { * ML overrides */ { - files: ['x-pack/legacy/plugins/ml/**/*.js'], + files: ['x-pack/plugins/ml/**/*.js'], rules: { 'no-shadow': 'error', + 'import/no-extraneous-dependencies': [ + 'error', + { + packageDir: './x-pack', + }, + ], }, }, diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index fa8884e2ece75..6a2d02ee778dd 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -42623,28 +42623,21 @@ module.exports = require("tty"); const os = __webpack_require__(11); const hasFlag = __webpack_require__(12); -const {env} = process; +const env = process.env; let forceColor; if (hasFlag('no-color') || hasFlag('no-colors') || - hasFlag('color=false') || - hasFlag('color=never')) { - forceColor = 0; + hasFlag('color=false')) { + forceColor = false; } else if (hasFlag('color') || hasFlag('colors') || hasFlag('color=true') || hasFlag('color=always')) { - forceColor = 1; + forceColor = true; } if ('FORCE_COLOR' in env) { - if (env.FORCE_COLOR === true || env.FORCE_COLOR === 'true') { - forceColor = 1; - } else if (env.FORCE_COLOR === false || env.FORCE_COLOR === 'false') { - forceColor = 0; - } else { - forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3); - } + forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; } function translateLevel(level) { @@ -42661,7 +42654,7 @@ function translateLevel(level) { } function supportsColor(stream) { - if (forceColor === 0) { + if (forceColor === false) { return 0; } @@ -42675,15 +42668,11 @@ function supportsColor(stream) { return 2; } - if (stream && !stream.isTTY && forceColor === undefined) { + if (stream && !stream.isTTY && forceColor !== true) { return 0; } - const min = forceColor || 0; - - if (env.TERM === 'dumb') { - return min; - } + const min = forceColor ? 1 : 0; if (process.platform === 'win32') { // Node.js 7.5.0 is the first version of Node.js to include a patch to @@ -42744,6 +42733,10 @@ function supportsColor(stream) { return 1; } + if (env.TERM === 'dumb') { + return min; + } + return min; } diff --git a/x-pack/plugins/ml/package.json b/x-pack/plugins/ml/package.json new file mode 100644 index 0000000000000..739dd806fcbb9 --- /dev/null +++ b/x-pack/plugins/ml/package.json @@ -0,0 +1,15 @@ +{ + "author": "Elastic", + "name": "ml", + "version": "0.0.0", + "private": true, + "license": "Elastic-License", + "scripts": { + "build:apiDocScripts": "cd server/routes/apidoc_scripts && tsc", + "apiDocs": "yarn build:apiDocScripts && cd ./server/routes/ && apidoc --parse-workers apischema=./apidoc_scripts/target/schema_worker.js --parse-parsers apischema=./apidoc_scripts/target/schema_parser.js --parse-filters apiversion=./apidoc_scripts/target/version_filter.js -i . -o ../routes_doc && apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md" + }, + "devDependencies": { + "apidoc": "^0.20.1", + "apidoc-markdown": "^5.0.0" + } +} diff --git a/x-pack/plugins/ml/server/routes/README.md b/x-pack/plugins/ml/server/routes/README.md index 1d08335af3d2e..70af73c37dadd 100644 --- a/x-pack/plugins/ml/server/routes/README.md +++ b/x-pack/plugins/ml/server/routes/README.md @@ -6,11 +6,14 @@ Each route handler requires [apiDoc](https://github.com/apidoc/apidoc) annotatio to generate documentation. The [apidoc-markdown](https://github.com/rigwild/apidoc-markdown) package is also required in order to generate the markdown. -For now the process is pretty manual. You need to make sure the packages mentioned above are installed globally -to execute the following command from the directory in which this README file is located. +There are custom parser and worker (`x-pack/plugins/ml/server/routes/apidoc_scripts`) to process api schemas for each documentation entry. It's written with typescript so make sure all the scripts in the folder are compiled before executing `apidoc` command. + +Make sure you have run `yarn kbn bootstrap` to get all requires dev dependencies. Then execute the following command from the ml plugin folder: ``` -apidoc -i . -o ../routes_doc && apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md +yarn run apiDocs ``` +It compiles all the required scripts and generates the documentation both in HTML and Markdown formats. + It will create a new directory `routes_doc` (next to the `routes` folder) which contains the documentation in HTML format -as well as `ML_API.md` file. \ No newline at end of file +as well as `ML_API.md` file. diff --git a/x-pack/plugins/ml/server/routes/annotations.ts b/x-pack/plugins/ml/server/routes/annotations.ts index 56c0b639e2c85..d5abebda00caa 100644 --- a/x-pack/plugins/ml/server/routes/annotations.ts +++ b/x-pack/plugins/ml/server/routes/annotations.ts @@ -5,10 +5,8 @@ */ import Boom from 'boom'; -import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; import { SecurityPluginSetup } from '../../../security/server'; import { isAnnotationsFeatureAvailable } from '../lib/check_annotations'; import { annotationServiceProvider } from '../models/annotation_service'; @@ -45,10 +43,7 @@ export function annotationRoutes( * @apiName GetAnnotations * @apiDescription Gets annotations. * - * @apiParam {String[]} jobIds List of job IDs - * @apiParam {String} earliestMs - * @apiParam {Number} latestMs - * @apiParam {Number} maxAnnotations Max limit of annotations returned + * @apiSchema (body) getAnnotationsSchema * * @apiSuccess {Boolean} success * @apiSuccess {Object} annotations @@ -57,7 +52,7 @@ export function annotationRoutes( { path: '/api/ml/annotations', validate: { - body: schema.object(getAnnotationsSchema), + body: getAnnotationsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -83,14 +78,13 @@ export function annotationRoutes( * @apiName IndexAnnotations * @apiDescription Index the annotation. * - * @apiParam {Object} annotation - * @apiParam {String} username + * @apiSchema (body) indexAnnotationSchema */ router.put( { path: '/api/ml/annotations/index', validate: { - body: schema.object(indexAnnotationSchema), + body: indexAnnotationSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -124,17 +118,17 @@ export function annotationRoutes( /** * @apiGroup Annotations * - * @api {delete} /api/ml/annotations/index Deletes annotation + * @api {delete} /api/ml/annotations/delete/:annotationId Deletes annotation * @apiName DeleteAnnotation * @apiDescription Deletes specified annotation * - * @apiParam {String} annotationId + * @apiSchema (params) deleteAnnotationSchema */ router.delete( { path: '/api/ml/annotations/delete/{annotationId}', validate: { - params: schema.object(deleteAnnotationSchema), + params: deleteAnnotationSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index d03e76072c315..a675eb58dc792 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -10,6 +10,13 @@ import { RouteInitialization } from '../types'; import { anomalyDetectionJobSchema, anomalyDetectionUpdateJobSchema, + jobIdSchema, + getRecordsSchema, + getBucketsSchema, + getOverallBucketsSchema, + getCategoriesSchema, + forecastAnomalyDetector, + getBucketParamsSchema, } from './schemas/anomaly_detectors_schema'; /** @@ -50,15 +57,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetAnomalyDetectorsById * @apiDescription Returns the anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.get( { path: '/api/ml/anomaly_detectors/{jobId}', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -108,15 +113,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetAnomalyDetectorsStatsById * @apiDescription Returns anomaly detection job statistics. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.get( { path: '/api/ml/anomaly_detectors/{jobId}/_stats', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -139,15 +142,14 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName CreateAnomalyDetectors * @apiDescription Creates an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) anomalyDetectionJobSchema */ router.put( { path: '/api/ml/anomaly_detectors/{jobId}', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, body: schema.object(anomalyDetectionJobSchema), }, }, @@ -174,16 +176,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName UpdateAnomalyDetectors * @apiDescription Updates certain properties of an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) anomalyDetectionUpdateJobSchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_update', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ ...anomalyDetectionUpdateJobSchema }), + params: jobIdSchema, + body: anomalyDetectionUpdateJobSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -209,15 +210,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName OpenAnomalyDetectorsJob * @apiDescription Opens an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_open', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -242,15 +241,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName CloseAnomalyDetectorsJob * @apiDescription Closes an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_close', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -279,15 +276,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName DeleteAnomalyDetectorsJob * @apiDescription Deletes specified anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.delete( { path: '/api/ml/anomaly_detectors/{jobId}', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -315,8 +310,6 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/anomaly_detectors/_validate/detector Validate detector * @apiName ValidateAnomalyDetector * @apiDescription Validates specified detector. - * - * @apiParam {String} jobId Job ID. */ router.post( { @@ -346,16 +339,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName ForecastAnomalyDetector * @apiDescription Creates a forecast for the specified anomaly detection job, predicting the future behavior of a time series by using its historical behavior. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) forecastAnomalyDetector */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_forecast', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ duration: schema.any() }), + params: jobIdSchema, + body: forecastAnomalyDetector, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -382,7 +374,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetRecords * @apiDescription Retrieves anomaly records for a job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) getRecordsSchema * * @apiSuccess {Number} count * @apiSuccess {Object[]} records @@ -391,23 +384,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/anomaly_detectors/{jobId}/results/records', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ - desc: schema.maybe(schema.boolean()), - end: schema.maybe(schema.string()), - exclude_interim: schema.maybe(schema.boolean()), - page: schema.maybe( - schema.object({ - from: schema.maybe(schema.number()), - size: schema.maybe(schema.number()), - }) - ), - record_score: schema.maybe(schema.number()), - sort: schema.maybe(schema.string()), - start: schema.maybe(schema.string()), - }), + params: jobIdSchema, + body: getRecordsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -432,8 +410,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetBuckets * @apiDescription The get buckets API presents a chronological view of the records, grouped by bucket. * - * @apiParam {String} jobId Job ID. - * @apiParam {String} timestamp. + * @apiSchema (params) getBucketParamsSchema + * @apiSchema (body) getBucketsSchema * * @apiSuccess {Number} count * @apiSuccess {Object[]} buckets @@ -442,25 +420,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/anomaly_detectors/{jobId}/results/buckets/{timestamp?}', validate: { - params: schema.object({ - jobId: schema.string(), - timestamp: schema.maybe(schema.string()), - }), - body: schema.object({ - anomaly_score: schema.maybe(schema.number()), - desc: schema.maybe(schema.boolean()), - end: schema.maybe(schema.string()), - exclude_interim: schema.maybe(schema.boolean()), - expand: schema.maybe(schema.boolean()), - page: schema.maybe( - schema.object({ - from: schema.maybe(schema.number()), - size: schema.maybe(schema.number()), - }) - ), - sort: schema.maybe(schema.string()), - start: schema.maybe(schema.string()), - }), + params: getBucketParamsSchema, + body: getBucketsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -486,7 +447,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetOverallBuckets * @apiDescription Retrieves overall bucket results that summarize the bucket results of multiple anomaly detection jobs. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) getOverallBucketsSchema * * @apiSuccess {Number} count * @apiSuccess {Object[]} overall_buckets @@ -495,15 +457,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/anomaly_detectors/{jobId}/results/overall_buckets', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ - topN: schema.number(), - bucketSpan: schema.string(), - start: schema.number(), - end: schema.number(), - }), + params: jobIdSchema, + body: getOverallBucketsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -531,17 +486,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetCategories * @apiDescription Returns the categories results for the specified job ID and category ID. * - * @apiParam {String} jobId Job ID. - * @apiParam {String} categoryId Category ID. + * @apiSchema (params) getCategoriesSchema */ router.get( { path: '/api/ml/anomaly_detectors/{jobId}/results/categories/{categoryId}', validate: { - params: schema.object({ - categoryId: schema.string(), - jobId: schema.string(), - }), + params: getCategoriesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index c5aa3e4d792fd..4848de6db7049 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -1,6 +1,6 @@ { "name": "ml_kibana_api", - "version": "0.1.0", + "version": "7.8.0", "description": "ML Kibana API", "title": "ML Kibana API", "order": [ @@ -9,61 +9,65 @@ "GetDataFrameAnalyticsById", "GetDataFrameAnalyticsStats", "GetDataFrameAnalyticsStatsById", - "UpdateDataFrameAnalytics", "EvaluateDataFrameAnalytics", "ExplainDataFrameAnalytics", - "DeleteDataFrameAnalytics", "StartDataFrameAnalyticsJob", "StopsDataFrameAnalyticsJob", "GetDataFrameAnalyticsMessages", + "UpdateDataFrameAnalytics", + "DeleteDataFrameAnalytics", + "DataVisualizer", "GetOverallStats", "GetStatsForFields", + "AnomalyDetectors", + "CreateAnomalyDetectors", + "OpenAnomalyDetectorsJob", "GetAnomalyDetectors", "GetAnomalyDetectorsById", "GetAnomalyDetectorsStats", "GetAnomalyDetectorsStatsById", - "CreateAnomalyDetectors", - "UpdateAnomalyDetectors", - "OpenAnomalyDetectorsJob", "CloseAnomalyDetectorsJob", - "DeleteAnomalyDetectorsJob", "ValidateAnomalyDetector", "ForecastAnomalyDetector", "GetRecords", "GetBuckets", "GetOverallBuckets", "GetCategories", + "UpdateAnomalyDetectors", + "DeleteAnomalyDetectorsJob", + "FileDataVisualizer", "AnalyzeFile", "ImportFile", + "ResultsService", "GetAnomaliesTableData", "GetCategoryDefinition", "GetMaxAnomalyScore", "GetCategoryExamples", "GetPartitionFieldsValues", + "DataRecognizer", "RecognizeIndex", "GetModule", "SetupModule", "CheckExistingModuleJobs", + "Annotations", "GetAnnotations", "IndexAnnotations", "DeleteAnnotation", + "JobService", "ForceStartDatafeeds", "StopDatafeeds", - "DeleteJobs", "CloseJobs", "JobsSummary", "JobsWithTimeRange", "CreateFullJobsList", "GetAllGroups", - "UpdateGroups", - "DeletingJobTasks", "JobsExist", "NewJobCaps", "NewJobLineChart", @@ -72,42 +76,60 @@ "GetLookBackProgress", "ValidateCategoryExamples", "TopCategories", + "UpdateGroups", + "DeletingJobTasks", + "DeleteJobs", + + "Calendars", + "PutCalendars", + "GetCalendars", + "GetCalendarById", + "UpdateCalendarById", + "DeleteCalendarById", + "Filters", + "CreateFilter", "GetFilters", "GetFilterById", - "CreateFilter", + "GetFiltersStats", "UpdateFilter", "DeleteFilter", - "GetFiltersStats", + "Indices", "FieldCaps", + "SystemRoutes", "HasPrivileges", "MlCapabilities", "MlNodeCount", "MlInfo", "MlEsSearch", + "JobAuditMessages", "GetJobAuditMessages", "GetAllJobAuditMessages", + "JobValidation", "EstimateBucketSpan", "CalculateModelMemoryLimit", "ValidateCardinality", "ValidateJob", + "NotificationSettings", "GetNotificationSettings", + "DatafeedService", + "CreateDatafeed", + "PreviewDatafeed", "GetDatafeeds", "GetDatafeed", "GetDatafeedsStats", "GetDatafeedStats", - "CreateDatafeed", "UpdateDatafeed", - "DeleteDatafeed", "StartDatafeed", "StopDatafeed", - "PreviewDatafeed", + "DeleteDatafeed", + "FieldsService", "GetCardinalityOfFields", "GetTimeFieldRange" diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts new file mode 100644 index 0000000000000..01adcb462689e --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts @@ -0,0 +1,168 @@ +/* + * 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 * as ts from 'typescript'; + +export interface DocEntry { + name: string; + documentation?: string; + type: string; + optional?: boolean; + nested?: DocEntry[]; +} + +/** Generate documentation for all schema definitions in a set of .ts files */ +export function extractDocumentation( + fileNames: string[], + options: ts.CompilerOptions = { + target: ts.ScriptTarget.ES2015, + module: ts.ModuleKind.CommonJS, + } +): Map { + // Build a program using the set of root file names in fileNames + const program = ts.createProgram(fileNames, options); + + // Get the checker, we will use it to find more about properties + const checker: ts.TypeChecker = program.getTypeChecker(); + + // Result map + const result = new Map(); + + // Visit every sourceFile in the program + for (const sourceFile of program.getSourceFiles()) { + if (!sourceFile.isDeclarationFile) { + // Walk the tree to search for schemas + ts.forEachChild(sourceFile, visit); + } + } + + return result; + + /** visit nodes finding exported schemas */ + function visit(node: ts.Node) { + if (isNodeExported(node) && ts.isVariableDeclaration(node)) { + const schemaName = node.name.getText(); + const schemaType = checker.getTypeAtLocation(node); + result.set(schemaName, extractDocEntries(schemaType!)); + } + + if (node.getChildCount() > 0) { + ts.forEachChild(node, visit); + } + } + + /** + * Extracts doc entries for the schema definition + * @param schemaType + */ + function extractDocEntries(schemaType: ts.Type): DocEntry[] { + const collection: DocEntry[] = []; + + const members = getTypeMembers(schemaType); + + if (!members) { + return collection; + } + + members.forEach(member => { + collection.push(serializeProperty(member)); + }); + + return collection; + } + + /** + * Resolves members of the type + * @param type + */ + function getTypeMembers(type: ts.Type): ts.Symbol[] | undefined { + const argsOfType = checker.getTypeArguments((type as unknown) as ts.TypeReference); + + let members = type.getProperties(); + + if (argsOfType && argsOfType.length > 0) { + members = argsOfType[0].getProperties(); + } + + return members; + } + + function resolveTypeArgument(type: ts.Type): ts.SymbolTable | string { + // required to extract members + type.getProperty('type'); + + // @ts-ignores + let members = type.members; + + const typeArguments = checker.getTypeArguments((type as unknown) as ts.TypeReference); + + if (type.aliasTypeArguments) { + // @ts-ignores + members = type.aliasTypeArguments[0].members; + } + + if (typeArguments.length > 0) { + members = resolveTypeArgument(typeArguments[0]); + } + + if (members === undefined) { + members = checker.typeToString(type); + } + + return members; + } + + function serializeProperty(symbol: ts.Symbol): DocEntry { + // @ts-ignore + const typeOfSymbol = symbol.type; + const typeArguments = checker.getTypeArguments((typeOfSymbol as unknown) as ts.TypeReference); + + let resultType: ts.Type = typeOfSymbol; + + let members; + if (typeArguments.length > 0) { + members = resolveTypeArgument(typeArguments[0]); + resultType = typeArguments[0]; + } + + let typeAsString = checker.typeToString(resultType); + + const nestedEntries: DocEntry[] = []; + if (members && typeof members !== 'string' && members.size > 0) { + // we hit an object or collection + typeAsString = + resultType.symbol.name === 'Array' || typeOfSymbol.symbol.name === 'Array' + ? `${symbol.getName()}[]` + : symbol.getName(); + + members.forEach(member => { + nestedEntries.push(serializeProperty(member)); + }); + } + + return { + name: symbol.getName(), + documentation: getCommentString(symbol), + type: typeAsString, + ...(nestedEntries.length > 0 ? { nested: nestedEntries } : {}), + }; + } + + function getCommentString(symbol: ts.Symbol): string { + return ts.displayPartsToString(symbol.getDocumentationComment(checker)).replace(/\n/g, ' '); + } + + /** + * True if this is visible outside this file, false otherwise + */ + function isNodeExported(node: ts.Node): boolean { + return ( + // eslint-disable-next-line no-bitwise + (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 || + (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) + ); + } +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts new file mode 100644 index 0000000000000..eabe7dcd7bd8f --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +function parse(content?: string) { + const schema = typeof content === 'string' && content.trim(); + + if (!schema) { + return null; + } + + const result = schema.match(/\((\w+)\)\s+(\w+)/); + + if (result === null || result.length < 3) { + throw new Error( + 'Invalid schema definition. Required format is `@apiSchema () `' + ); + } + + const group = result[1]; + + return { + group, + name: result[2], + }; +} + +/** + * Exports + */ +module.exports = { + parse, + path: 'local.schemas', + method: 'push', +}; diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts new file mode 100644 index 0000000000000..7514e482783b3 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts @@ -0,0 +1,80 @@ +/* + * 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 * as fs from 'fs'; +import * as path from 'path'; +import { DocEntry, extractDocumentation } from './schema_extractor'; +import { ApiParameter, Block } from './types'; + +export function postProcess(parsedFiles: any[]): void { + const schemasDirPath = `${__dirname}${path.sep}..${path.sep}..${path.sep}schemas${path.sep}`; + const schemaFiles = fs + .readdirSync(schemasDirPath) + .map(filename => path.resolve(schemasDirPath + filename)); + + const schemaDocs = extractDocumentation(schemaFiles); + + parsedFiles.forEach(parsedFile => { + parsedFile.forEach((block: Block) => { + const { + local: { schemas }, + } = block; + if (!schemas || schemas.length === 0) return; + + for (const schema of schemas) { + const { name: schemaName, group: paramsGroup } = schema; + const schemaFields = schemaDocs.get(schemaName); + + if (!schemaFields) return; + + updateBlockParameters(schemaFields, block, paramsGroup); + } + }); + }); +} + +/** + * Extracts schema's doc entries to apidoc parameters + * @param docEntries + * @param block + * @param paramsGroup + */ +function updateBlockParameters(docEntries: DocEntry[], block: Block, paramsGroup: string): void { + if (!block.local.parameter) { + block.local.parameter = { + fields: {}, + }; + } + + if (!block.local.parameter.fields![paramsGroup]) { + block.local.parameter.fields![paramsGroup] = []; + } + const collection = block.local.parameter.fields![paramsGroup] as ApiParameter[]; + + for (const field of docEntries) { + collection.push({ + group: paramsGroup, + type: escapeSpecial(field.type), + size: undefined, + allowedValues: undefined, + optional: !!field.optional, + field: field.name, + defaultValue: undefined, + description: field.documentation, + }); + + if (field.nested) { + updateBlockParameters(field.nested, block, field.name); + } + } +} + +/** + * Escape special character to make sure the markdown table isn't broken + */ +function escapeSpecial(str: string): string { + return str.replace(/\|/g, '\\|'); +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json b/x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json new file mode 100644 index 0000000000000..e3108b8c759f4 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "target": "es6", + "moduleResolution": "node" + }, + "include": [ + "schema_worker.ts", + "schema_parser.ts", + "schema_extractor.ts", + "version_filter.ts" + ] +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts new file mode 100644 index 0000000000000..08a443905ee05 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts @@ -0,0 +1,42 @@ +/* + * 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 ApiParameter { + group: string; + type: any; + size: undefined; + allowedValues: undefined; + optional: boolean; + field: string; + defaultValue: undefined; + description?: string; +} + +interface Local { + group: string; + type: string; + url: string; + title: string; + name: string; + description: string; + parameter: { + fields?: { + [key: string]: ApiParameter[] | undefined; + }; + }; + success: { fields: ObjectConstructor[] }; + version: string; + filename: string; + schemas?: Array<{ + name: string; + group: string; + }>; +} + +export interface Block { + global: any; + local: Local; +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts new file mode 100644 index 0000000000000..8cbe38d667b2c --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.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 { Block } from './types'; + +const API_VERSION = '7.8.0'; + +/** + * Post Filter parsed results. + * Updates api version of the endpoints. + */ +export function postFilter(parsedFiles: any[]) { + parsedFiles.forEach(parsedFile => { + parsedFile.forEach((block: Block) => { + block.local.version = API_VERSION; + }); + }); +} diff --git a/x-pack/plugins/ml/server/routes/calendars.ts b/x-pack/plugins/ml/server/routes/calendars.ts index 34950c6ed79f7..a17601f74ae93 100644 --- a/x-pack/plugins/ml/server/routes/calendars.ts +++ b/x-pack/plugins/ml/server/routes/calendars.ts @@ -5,10 +5,9 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; -import { calendarSchema } from './schemas/calendars_schema'; +import { calendarSchema, calendarIdSchema, calendarIdsSchema } from './schemas/calendars_schema'; import { CalendarManager, Calendar, FormCalendar } from '../models/calendar'; function getAllCalendars(context: RequestHandlerContext) { @@ -42,7 +41,13 @@ function getCalendarsByIds(context: RequestHandlerContext, calendarIds: string) } export function calendars({ router, mlLicense }: RouteInitialization) { - // Gets calendars - size limit has been explicitly set to 1000 + /** + * @apiGroup Calendars + * + * @api {get} /api/ml/calendars Gets calendars + * @apiName GetCalendars + * @apiDescription Gets calendars - size limit has been explicitly set to 1000 + */ router.get( { path: '/api/ml/calendars', @@ -61,11 +66,20 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {get} /api/ml/calendars/:calendarIds Gets a calendar + * @apiName GetCalendarById + * @apiDescription Gets calendar by id + * + * @apiSchema (params) calendarIdsSchema + */ router.get( { path: '/api/ml/calendars/{calendarIds}', validate: { - params: schema.object({ calendarIds: schema.string() }), + params: calendarIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -88,11 +102,20 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {put} /api/ml/calendars Creates a calendar + * @apiName PutCalendars + * @apiDescription Creates a calendar + * + * @apiSchema (body) calendarSchema + */ router.put( { path: '/api/ml/calendars', validate: { - body: schema.object({ ...calendarSchema }), + body: calendarSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -109,12 +132,22 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {put} /api/ml/calendars/:calendarId Updates a calendar + * @apiName UpdateCalendarById + * @apiDescription Updates a calendar + * + * @apiSchema (params) calendarIdSchema + * @apiSchema (body) calendarSchema + */ router.put( { path: '/api/ml/calendars/{calendarId}', validate: { - params: schema.object({ calendarId: schema.string() }), - body: schema.object({ ...calendarSchema }), + params: calendarIdSchema, + body: calendarSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -132,11 +165,20 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {delete} /api/ml/calendars/:calendarId Deletes a calendar + * @apiName DeleteCalendarById + * @apiDescription Deletes a calendar + * + * @apiSchema (params) calendarIdSchema + */ router.delete( { path: '/api/ml/calendars/{calendarId}', validate: { - params: schema.object({ calendarId: schema.string() }), + params: calendarIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 7ed1aa02b24ab..dd9e0ea66aa9d 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; import { RouteInitialization } from '../types'; @@ -12,6 +11,8 @@ import { dataAnalyticsJobConfigSchema, dataAnalyticsEvaluateSchema, dataAnalyticsExplainSchema, + analyticsIdSchema, + stopsDataFrameAnalyticsJobQuerySchema, } from './schemas/data_analytics_schema'; /** @@ -31,9 +32,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat router.get( { path: '/api/ml/data_frame/analytics', - validate: { - params: schema.object({ analyticsId: schema.maybe(schema.string()) }), - }, + validate: false, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { try { @@ -54,13 +53,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName GetDataFrameAnalyticsById * @apiDescription Returns the data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { - params: schema.object({ analyticsId: schema.string() }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -111,13 +110,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName GetDataFrameAnalyticsStatsById * @apiDescription Returns data frame analytics job statistics. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}/_stats', validate: { - params: schema.object({ analyticsId: schema.string() }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -146,16 +145,15 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiDescription This API creates a data frame analytics job that performs an analysis * on the source index and stores the outcome in a destination index. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema + * @apiSchema (body) dataAnalyticsJobConfigSchema */ router.put( { path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { - params: schema.object({ - analyticsId: schema.string(), - }), - body: schema.object(dataAnalyticsJobConfigSchema), + params: analyticsIdSchema, + body: dataAnalyticsJobConfigSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -183,12 +181,14 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @api {post} /api/ml/data_frame/_evaluate Evaluate the data frame analytics for an annotated index * @apiName EvaluateDataFrameAnalytics * @apiDescription Evaluates the data frame analytics for an annotated index. + * + * @apiSchema (body) dataAnalyticsEvaluateSchema */ router.post( { path: '/api/ml/data_frame/_evaluate', validate: { - body: schema.object({ ...dataAnalyticsEvaluateSchema }), + body: dataAnalyticsEvaluateSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -216,19 +216,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiDescription This API provides explanations for a data frame analytics config * that either exists already or one that has not been created yet. * - * @apiParam {String} [description] - * @apiParam {Object} [dest] - * @apiParam {Object} source - * @apiParam {String} source.index - * @apiParam {Object} analysis - * @apiParam {Object} [analyzed_fields] - * @apiParam {String} [model_memory_limit] + * @apiSchema (body) dataAnalyticsExplainSchema */ router.post( { path: '/api/ml/data_frame/analytics/_explain', validate: { - body: schema.object({ ...dataAnalyticsExplainSchema }), + body: dataAnalyticsExplainSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -255,15 +249,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName DeleteDataFrameAnalytics * @apiDescription Deletes specified data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.delete( { path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { - params: schema.object({ - analyticsId: schema.string(), - }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -291,15 +283,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName StartDataFrameAnalyticsJob * @apiDescription Starts a data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.post( { path: '/api/ml/data_frame/analytics/{analyticsId}/_start', validate: { - params: schema.object({ - analyticsId: schema.string(), - }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -324,16 +314,15 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName StopsDataFrameAnalyticsJob * @apiDescription Stops a data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema + * @apiSchema (query) stopsDataFrameAnalyticsJobQuerySchema */ router.post( { path: '/api/ml/data_frame/analytics/{analyticsId}/_stop', validate: { - params: schema.object({ - analyticsId: schema.string(), - force: schema.maybe(schema.boolean()), - }), + params: analyticsIdSchema, + query: stopsDataFrameAnalyticsJobQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -367,13 +356,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName GetDataFrameAnalyticsMessages * @apiDescription Returns the list of audit messages for data frame analytics jobs. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}/messages', validate: { - params: schema.object({ analyticsId: schema.string() }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/data_visualizer.ts b/x-pack/plugins/ml/server/routes/data_visualizer.ts index b37c80b815e1a..a4c0d5553a4b2 100644 --- a/x-pack/plugins/ml/server/routes/data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/data_visualizer.ts @@ -11,6 +11,7 @@ import { Field } from '../models/data_visualizer/data_visualizer'; import { dataVisualizerFieldStatsSchema, dataVisualizerOverallStatsSchema, + indexPatternTitleSchema, } from './schemas/data_visualizer_schema'; import { RouteInitialization } from '../types'; @@ -75,12 +76,16 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) * @apiName GetStatsForFields * @apiDescription Returns fields stats of the index pattern. * - * @apiParam {String} indexPatternTitle Index pattern title. + * @apiSchema (params) indexPatternTitleSchema + * @apiSchema (body) dataVisualizerFieldStatsSchema */ router.post( { path: '/api/ml/data_visualizer/get_field_stats/{indexPatternTitle}', - validate: dataVisualizerFieldStatsSchema, + validate: { + params: indexPatternTitleSchema, + body: dataVisualizerFieldStatsSchema, + }, }, mlLicense.basicLicenseAPIGuard(async (context, request, response) => { try { @@ -127,12 +132,16 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) * @apiName GetOverallStats * @apiDescription Returns overall stats of the index pattern. * - * @apiParam {String} indexPatternTitle Index pattern title. + * @apiSchema (params) indexPatternTitleSchema + * @apiSchema (body) dataVisualizerOverallStatsSchema */ router.post( { path: '/api/ml/data_visualizer/get_overall_stats/{indexPatternTitle}', - validate: dataVisualizerOverallStatsSchema, + validate: { + params: indexPatternTitleSchema, + body: dataVisualizerOverallStatsSchema, + }, }, mlLicense.basicLicenseAPIGuard(async (context, request, response) => { try { diff --git a/x-pack/plugins/ml/server/routes/datafeeds.ts b/x-pack/plugins/ml/server/routes/datafeeds.ts index c1ee839340996..ec667e1d305f5 100644 --- a/x-pack/plugins/ml/server/routes/datafeeds.ts +++ b/x-pack/plugins/ml/server/routes/datafeeds.ts @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; -import { startDatafeedSchema, datafeedConfigSchema } from './schemas/datafeeds_schema'; +import { + startDatafeedSchema, + datafeedConfigSchema, + datafeedIdSchema, + deleteDatafeedQuerySchema, +} from './schemas/datafeeds_schema'; /** * Routes for datafeed service @@ -44,12 +48,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {get} /api/ml/datafeeds/:datafeedId Get datafeed for given datafeed id * @apiName GetDatafeed * @apiDescription Retrieves configuration information for datafeed + * + * @apiSchema (params) datafeedIdSchema */ router.get( { path: '/api/ml/datafeeds/{datafeedId}', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -97,12 +103,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {get} /api/ml/datafeeds/:datafeedId/_stats Get datafeed stats for given datafeed id * @apiName GetDatafeedStats * @apiDescription Retrieves usage information for datafeed + * + * @apiSchema (params) datafeedIdSchema */ router.get( { path: '/api/ml/datafeeds/{datafeedId}/_stats', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -127,12 +135,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {put} /api/ml/datafeeds/:datafeedId Creates datafeed * @apiName CreateDatafeed * @apiDescription Instantiates a datafeed + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (body) datafeedConfigSchema */ router.put( { path: '/api/ml/datafeeds/{datafeedId}', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, body: datafeedConfigSchema, }, }, @@ -159,12 +170,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/datafeeds/:datafeedId/_update Updates datafeed for given datafeed id * @apiName UpdateDatafeed * @apiDescription Updates certain properties of a datafeed + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (body) datafeedConfigSchema */ router.post( { path: '/api/ml/datafeeds/{datafeedId}/_update', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, body: datafeedConfigSchema, }, }, @@ -191,13 +205,16 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {delete} /api/ml/datafeeds/:datafeedId Deletes datafeed * @apiName DeleteDatafeed * @apiDescription Deletes an existing datafeed + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (query) deleteDatafeedQuerySchema */ router.delete( { path: '/api/ml/datafeeds/{datafeedId}', validate: { - params: schema.object({ datafeedId: schema.string() }), - query: schema.maybe(schema.object({ force: schema.maybe(schema.any()) })), + params: datafeedIdSchema, + query: deleteDatafeedQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -227,12 +244,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/datafeeds/:datafeedId/_start Starts datafeed for given datafeed id(s) * @apiName StartDatafeed * @apiDescription Starts one or more datafeeds + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (body) startDatafeedSchema */ router.post( { path: '/api/ml/datafeeds/{datafeedId}/_start', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, body: startDatafeedSchema, }, }, @@ -262,12 +282,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/datafeeds/:datafeedId/_stop Stops datafeed for given datafeed id(s) * @apiName StopDatafeed * @apiDescription Stops one or more datafeeds + * + * @apiSchema (params) datafeedIdSchema */ router.post( { path: '/api/ml/datafeeds/{datafeedId}/_stop', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -293,12 +315,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {get} /api/ml/datafeeds/:datafeedId/_preview Preview datafeed for given datafeed id * @apiName PreviewDatafeed * @apiDescription Previews a datafeed + * + * @apiSchema (params) datafeedIdSchema */ router.get( { path: '/api/ml/datafeeds/{datafeedId}/_preview', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/fields_service.ts b/x-pack/plugins/ml/server/routes/fields_service.ts index db7613b163457..9a5f47409c8a0 100644 --- a/x-pack/plugins/ml/server/routes/fields_service.ts +++ b/x-pack/plugins/ml/server/routes/fields_service.ts @@ -35,6 +35,8 @@ export function fieldsService({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/fields_service/field_cardinality Get cardinality of fields * @apiName GetCardinalityOfFields * @apiDescription Returns the cardinality of one or more fields. Returns an Object whose keys are the names of the fields, with values equal to the cardinality of the field + * + * @apiSchema (body) getCardinalityOfFieldsSchema */ router.post( { @@ -63,6 +65,8 @@ export function fieldsService({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/fields_service/time_field_range Get time field range * @apiName GetTimeFieldRange * @apiDescription Returns the timefield range for the given index + * + * @apiSchema (body) getTimeFieldRangeSchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index 9f30847d9eb2e..3f3fc3f547b6a 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -22,6 +22,11 @@ import { import { RouteInitialization } from '../types'; import { updateTelemetry } from '../lib/telemetry'; +import { + analyzeFileQuerySchema, + importFileBodySchema, + importFileQuerySchema, +} from './schemas/file_data_visualizer_schema'; function analyzeFiles(context: RequestHandlerContext, data: InputData, overrides: InputOverrides) { const { analyzeFile } = fileDataVisualizerProvider(context.ml!.mlClient.callAsCurrentUser); @@ -51,30 +56,15 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat * @api {post} /api/ml/file_data_visualizer/analyze_file Analyze file data * @apiName AnalyzeFile * @apiDescription Performs analysis of the file data. + * + * @apiSchema (query) analyzeFileQuerySchema */ router.post( { path: '/api/ml/file_data_visualizer/analyze_file', validate: { body: schema.any(), - query: schema.maybe( - schema.object({ - charset: schema.maybe(schema.string()), - column_names: schema.maybe(schema.string()), - delimiter: schema.maybe(schema.string()), - explain: schema.maybe(schema.string()), - format: schema.maybe(schema.string()), - grok_pattern: schema.maybe(schema.string()), - has_header_row: schema.maybe(schema.string()), - line_merge_size_limit: schema.maybe(schema.string()), - lines_to_sample: schema.maybe(schema.string()), - quote: schema.maybe(schema.string()), - should_trim_fields: schema.maybe(schema.string()), - timeout: schema.maybe(schema.string()), - timestamp_field: schema.maybe(schema.string()), - timestamp_format: schema.maybe(schema.string()), - }) - ), + query: analyzeFileQuerySchema, }, options: { body: { @@ -99,24 +89,16 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat * @api {post} /api/ml/file_data_visualizer/import Import file data * @apiName ImportFile * @apiDescription Imports file data into elasticsearch index. + * + * @apiSchema (query) importFileQuerySchema + * @apiSchema (body) importFileBodySchema */ router.post( { path: '/api/ml/file_data_visualizer/import', validate: { - query: schema.object({ - id: schema.maybe(schema.string()), - }), - body: schema.object({ - index: schema.maybe(schema.string()), - data: schema.arrayOf(schema.any()), - settings: schema.maybe(schema.any()), - mappings: schema.any(), - ingestPipeline: schema.object({ - id: schema.maybe(schema.string()), - pipeline: schema.maybe(schema.any()), - }), - }), + query: importFileQuerySchema, + body: importFileBodySchema, }, options: { body: { diff --git a/x-pack/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts index e827ed96b12af..738c25070358d 100644 --- a/x-pack/plugins/ml/server/routes/filters.ts +++ b/x-pack/plugins/ml/server/routes/filters.ts @@ -5,10 +5,9 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; -import { createFilterSchema, updateFilterSchema } from './schemas/filters_schema'; +import { createFilterSchema, filterIdSchema, updateFilterSchema } from './schemas/filters_schema'; import { FilterManager, FormFilter } from '../models/filter'; // TODO - add function for returning a list of just the filter IDs. @@ -79,6 +78,8 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetFilterById * @apiDescription Retrieves the filter with the specified ID. * + * @apiSchema (params) filterIdSchema + * * @apiSuccess {Boolean} success * @apiSuccess {Object} filter the filter with the specified ID */ @@ -86,7 +87,7 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/filters/{filterId}', validate: { - params: schema.object({ filterId: schema.string() }), + params: filterIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -108,6 +109,8 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName CreateFilter * @apiDescription Instantiates a filter, for use by custom rules in anomaly detection. * + * @apiSchema (body) createFilterSchema + * * @apiSuccess {Boolean} success * @apiSuccess {Object} filter created filter */ @@ -115,7 +118,7 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/filters', validate: { - body: schema.object(createFilterSchema), + body: createFilterSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -139,6 +142,9 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName UpdateFilter * @apiDescription Updates the description of a filter, adds items or removes items. * + * @apiSchema (params) filterIdSchema + * @apiSchema (body) updateFilterSchema + * * @apiSuccess {Boolean} success * @apiSuccess {Object} filter updated filter */ @@ -146,8 +152,8 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/filters/{filterId}', validate: { - params: schema.object({ filterId: schema.string() }), - body: schema.object(updateFilterSchema), + params: filterIdSchema, + body: updateFilterSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -172,13 +178,13 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName DeleteFilter * @apiDescription Deletes the filter with the specified ID. * - * @apiParam {String} filterId the ID of the filter to delete + * @apiSchema (params) filterIdSchema */ router.delete( { path: '/api/ml/filters/{filterId}', validate: { - params: schema.object({ filterId: schema.string() }), + params: filterIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/indices.ts b/x-pack/plugins/ml/server/routes/indices.ts index fe66cc8b01396..e434936beba63 100644 --- a/x-pack/plugins/ml/server/routes/indices.ts +++ b/x-pack/plugins/ml/server/routes/indices.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; +import { indicesSchema } from './schemas/indices_schema'; /** * Indices routes. @@ -15,18 +15,17 @@ export function indicesRoutes({ router, mlLicense }: RouteInitialization) { /** * @apiGroup Indices * - * @api {post} /api/ml/indices/field_caps + * @api {post} /api/ml/indices/field_caps Field caps * @apiName FieldCaps * @apiDescription Retrieves the capabilities of fields among multiple indices. + * + * @apiSchema (body) indicesSchema */ router.post( { path: '/api/ml/indices/field_caps', validate: { - body: schema.object({ - index: schema.maybe(schema.string()), - fields: schema.maybe(schema.arrayOf(schema.string())), - }), + body: indicesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts index 5c6d8023cc172..71499748691f6 100644 --- a/x-pack/plugins/ml/server/routes/job_audit_messages.ts +++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { jobAuditMessagesProvider } from '../models/job_audit_messages'; +import { jobAuditMessagesQuerySchema, jobIdSchema } from './schemas/job_audit_messages_schema'; /** * Routes for job audit message routes @@ -19,13 +19,16 @@ export function jobAuditMessagesRoutes({ router, mlLicense }: RouteInitializatio * @api {get} /api/ml/job_audit_messages/messages/:jobId Get audit messages * @apiName GetJobAuditMessages * @apiDescription Returns audit messages for specified job ID + * + * @apiSchema (params) jobIdSchema + * @apiSchema (query) jobAuditMessagesQuerySchema */ router.get( { path: '/api/ml/job_audit_messages/messages/{jobId}', validate: { - params: schema.object({ jobId: schema.maybe(schema.string()) }), - query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })), + params: jobIdSchema, + query: jobAuditMessagesQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -52,13 +55,14 @@ export function jobAuditMessagesRoutes({ router, mlLicense }: RouteInitializatio * @api {get} /api/ml/job_audit_messages/messages Get all audit messages * @apiName GetAllJobAuditMessages * @apiDescription Returns all audit messages + * + * @apiSchema (query) jobAuditMessagesQuerySchema */ router.get( { path: '/api/ml/job_audit_messages/messages', validate: { - params: schema.object({ jobId: schema.maybe(schema.string()) }), - query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })), + query: jobAuditMessagesQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 718f9e81603b1..493974cbafe36 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -53,12 +53,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/force_start_datafeeds Start datafeeds * @apiName ForceStartDatafeeds * @apiDescription Starts one or more datafeeds + * + * @apiSchema (body) forceStartDatafeedSchema */ router.post( { path: '/api/ml/jobs/force_start_datafeeds', validate: { - body: schema.object(forceStartDatafeedSchema), + body: forceStartDatafeedSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -82,12 +84,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/stop_datafeeds Stop datafeeds * @apiName StopDatafeeds * @apiDescription Stops one or more datafeeds + * + * @apiSchema (body) datafeedIdsSchema */ router.post( { path: '/api/ml/jobs/stop_datafeeds', validate: { - body: schema.object(datafeedIdsSchema), + body: datafeedIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -111,12 +115,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/delete_jobs Delete jobs * @apiName DeleteJobs * @apiDescription Deletes an existing anomaly detection job + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/delete_jobs', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -140,12 +146,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/close_jobs Close jobs * @apiName CloseJobs * @apiDescription Closes one or more anomaly detection jobs + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/close_jobs', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -169,12 +177,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs_summary Jobs summary * @apiName JobsSummary * @apiDescription Creates a summary jobs list. Jobs include job stats, datafeed stats, and calendars. + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs_summary', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -198,6 +208,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs_with_time_range Jobs with time range * @apiName JobsWithTimeRange * @apiDescription Creates a list of jobs with data about the job's time range + * + * @apiSchema (body) jobsWithTimerangeSchema */ router.post( { @@ -226,12 +238,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs Create jobs list * @apiName CreateFullJobsList * @apiDescription Creates a list of jobs + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -281,6 +295,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/update_groups Update job groups * @apiName UpdateGroups * @apiDescription Updates 'groups' property of an anomaly detection job + * + * @apiSchema (body) updateGroupsSchema */ router.post( { @@ -336,12 +352,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs_exist Check if jobs exist * @apiName JobsExist * @apiDescription Checks if each of the jobs in the specified list of IDs exist + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs_exist', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -397,6 +415,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/new_job_line_chart Get job line chart data * @apiName NewJobLineChart * @apiDescription Returns line chart data for anomaly detection job + * + * @apiSchema (body) chartSchema */ router.post( { @@ -447,6 +467,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/new_job_population_chart Get population job chart data * @apiName NewJobPopulationChart * @apiDescription Returns population job chart data + * + * @apiSchema (body) chartSchema */ router.post( { @@ -523,6 +545,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/look_back_progress Get lookback progress * @apiName GetLookBackProgress * @apiDescription Returns current progress of anomaly detection job + * + * @apiSchema (body) lookBackProgressSchema */ router.post( { @@ -552,6 +576,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/categorization_field_examples Get categorization field examples * @apiName ValidateCategoryExamples * @apiDescription Validates category examples + * + * @apiSchema (body) categorizationFieldExamplesSchema */ router.post( { @@ -611,6 +637,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/top_categories Get top categories * @apiName TopCategories * @apiDescription Returns list of top categories + * + * @apiSchema (body) topCategoriesSchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index 75d9cdf375049..dd2bd9deadf43 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { RequestHandlerContext } from 'kibana/server'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { TypeOf } from '@kbn/config-schema'; import { AnalysisConfig } from '../../common/types/anomaly_detection_jobs'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; @@ -48,6 +48,8 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @api {post} /api/ml/validate/estimate_bucket_span Estimate bucket span * @apiName EstimateBucketSpan * @apiDescription Estimates minimum viable bucket span based on the characteristics of a pre-viewed subset of the data + * + * @apiSchema (body) estimateBucketSpanSchema */ router.post( { @@ -94,6 +96,8 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @apiName CalculateModelMemoryLimit * @apiDescription Calls _estimate_model_memory endpoint to retrieve model memory estimation. * + * @apiSchema (body) modelMemoryLimitSchema + * * @apiSuccess {String} modelMemoryLimit */ router.post( @@ -122,12 +126,14 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @api {post} /api/ml/validate/cardinality Validate cardinality * @apiName ValidateCardinality * @apiDescription Validates cardinality for the given job configuration + * + * @apiSchema (body) validateCardinalitySchema */ router.post( { path: '/api/ml/validate/cardinality', validate: { - body: schema.object(validateCardinalitySchema), + body: validateCardinalitySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -152,6 +158,8 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @api {post} /api/ml/validate/job Validates job * @apiName ValidateJob * @apiDescription Validates the given job configuration + * + * @apiSchema (body) validateJobSchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts index 358cd0ac2871c..2d462b6dc207a 100644 --- a/x-pack/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -152,7 +152,7 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { * @apiName SetupModule * @apiDescription Created module items. * - * @apiParam {String} moduleId Module id + * @apiSchema (body) setupModuleBodySchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts index 9849410eaf0d4..89c267340fe52 100644 --- a/x-pack/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -5,7 +5,6 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -80,12 +79,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/anomalies_table_data Prepare anomalies records for table display * @apiName GetAnomaliesTableData * @apiDescription Retrieves anomaly records for an anomaly detection job and formats them for anomalies table display + * + * @apiSchema (body) anomaliesTableDataSchema */ router.post( { path: '/api/ml/results/anomalies_table_data', validate: { - body: schema.object(anomaliesTableDataSchema), + body: anomaliesTableDataSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -107,12 +108,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/category_definition Returns category definition * @apiName GetCategoryDefinition * @apiDescription Returns the definition of the category with the specified ID and job ID + * + * @apiSchema (body) categoryDefinitionSchema */ router.post( { path: '/api/ml/results/category_definition', validate: { - body: schema.object(categoryDefinitionSchema), + body: categoryDefinitionSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -134,12 +137,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/max_anomaly_score Returns the maximum anomaly_score * @apiName GetMaxAnomalyScore * @apiDescription Returns the maximum anomaly score of the bucket results for the request job ID(s) and time range + * + * @apiSchema (body) maxAnomalyScoreSchema */ router.post( { path: '/api/ml/results/max_anomaly_score', validate: { - body: schema.object(maxAnomalyScoreSchema), + body: maxAnomalyScoreSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -161,12 +166,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/category_examples Returns category examples * @apiName GetCategoryExamples * @apiDescription Returns examples for the categories with the specified IDs from the job with the supplied ID + * + * @apiSchema (body) categoryExamplesSchema */ router.post( { path: '/api/ml/results/category_examples', validate: { - body: schema.object(categoryExamplesSchema), + body: categoryExamplesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -188,12 +195,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/partition_fields_values Returns partition fields values * @apiName GetPartitionFieldsValues * @apiDescription Returns the partition fields with values that match the provided criteria for the specified job ID. + * + * @apiSchema (body) partitionFieldValuesSchema */ router.post( { path: '/api/ml/results/partition_fields_values', validate: { - body: schema.object(partitionFieldValuesSchema), + body: partitionFieldValuesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts index 7d3d6aabb129c..fade2093ac842 100644 --- a/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -export const indexAnnotationSchema = { +export const indexAnnotationSchema = schema.object({ timestamp: schema.number(), end_timestamp: schema.number(), annotation: schema.string(), @@ -16,15 +16,16 @@ export const indexAnnotationSchema = { create_username: schema.maybe(schema.string()), modified_time: schema.maybe(schema.number()), modified_username: schema.maybe(schema.string()), + /** Document id */ _id: schema.maybe(schema.string()), key: schema.maybe(schema.string()), -}; +}); -export const getAnnotationsSchema = { +export const getAnnotationsSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), earliestMs: schema.oneOf([schema.nullable(schema.number()), schema.maybe(schema.number())]), latestMs: schema.oneOf([schema.nullable(schema.number()), schema.maybe(schema.number())]), maxAnnotations: schema.number(), -}; +}); -export const deleteAnnotationSchema = { annotationId: schema.string() }; +export const deleteAnnotationSchema = schema.object({ annotationId: schema.string() }); diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 22c3d94dfb29e..ab1305d9bc354 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -26,6 +26,7 @@ const detectorSchema = schema.object({ over_field_name: schema.maybe(schema.string()), partition_field_name: schema.maybe(schema.string()), detector_description: schema.maybe(schema.string()), + /** Custom rules */ custom_rules: customRulesSchema, }); @@ -37,20 +38,24 @@ const customUrlSchema = { const customSettingsSchema = schema.object( { + /** Indicates the creator entity */ created_by: schema.maybe(schema.string()), - custom_urls: schema.maybe(schema.arrayOf(schema.maybe(schema.object({ ...customUrlSchema })))), + custom_urls: schema.maybe(schema.arrayOf(schema.maybe(schema.object(customUrlSchema)))), }, { unknowns: 'allow' } // Create / Update job API allows other fields to be added to custom_settings. ); -export const anomalyDetectionUpdateJobSchema = { +export const anomalyDetectionUpdateJobSchema = schema.object({ description: schema.maybe(schema.string()), detectors: schema.maybe( schema.arrayOf( schema.maybe( schema.object({ + /** Detector index */ detector_index: schema.number(), + /** Description */ description: schema.maybe(schema.string()), + /** Custom rules */ custom_rules: customRulesSchema, }) ) @@ -64,7 +69,7 @@ export const anomalyDetectionUpdateJobSchema = { }) ), groups: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), -}; +}); export const analysisConfigSchema = schema.object({ bucket_span: schema.maybe(schema.string()), @@ -78,6 +83,7 @@ export const anomalyDetectionJobSchema = { analysis_config: analysisConfigSchema, analysis_limits: schema.maybe( schema.object({ + /** Limit of categorization examples */ categorization_examples_limit: schema.maybe(schema.number()), model_memory_limit: schema.maybe(schema.string()), }) @@ -88,6 +94,7 @@ export const anomalyDetectionJobSchema = { allow_lazy_open: schema.maybe(schema.any()), data_counts: schema.maybe(schema.any()), data_description: schema.object({ + /** Format */ format: schema.maybe(schema.string()), time_field: schema.string(), time_format: schema.maybe(schema.string()), @@ -110,3 +117,63 @@ export const anomalyDetectionJobSchema = { results_retention_days: schema.maybe(schema.number()), state: schema.maybe(schema.string()), }; + +export const jobIdSchema = schema.object({ + /** Job id */ + jobId: schema.string(), +}); + +export const getRecordsSchema = schema.object({ + desc: schema.maybe(schema.boolean()), + end: schema.maybe(schema.string()), + exclude_interim: schema.maybe(schema.boolean()), + page: schema.maybe( + schema.object({ + from: schema.maybe(schema.number()), + size: schema.maybe(schema.number()), + }) + ), + record_score: schema.maybe(schema.number()), + sort: schema.maybe(schema.string()), + start: schema.maybe(schema.string()), +}); + +export const getBucketsSchema = schema.object({ + anomaly_score: schema.maybe(schema.number()), + desc: schema.maybe(schema.boolean()), + end: schema.maybe(schema.string()), + exclude_interim: schema.maybe(schema.boolean()), + expand: schema.maybe(schema.boolean()), + /** Page definition */ + page: schema.maybe( + schema.object({ + /** Page offset */ + from: schema.maybe(schema.number()), + /** Size of the page */ + size: schema.maybe(schema.number()), + }) + ), + sort: schema.maybe(schema.string()), + start: schema.maybe(schema.string()), +}); + +export const getBucketParamsSchema = schema.object({ + jobId: schema.string(), + timestamp: schema.maybe(schema.string()), +}); + +export const getOverallBucketsSchema = schema.object({ + topN: schema.number(), + bucketSpan: schema.string(), + start: schema.number(), + end: schema.number(), +}); + +export const getCategoriesSchema = schema.object({ + /** Category id */ + categoryId: schema.string(), + /** Job id */ + jobId: schema.string(), +}); + +export const forecastAnomalyDetector = schema.object({ duration: schema.any() }); diff --git a/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts index f5e59d983a9aa..6d8f94311816d 100644 --- a/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -export const calendarSchema = { +export const calendarSchema = schema.object({ calendar_id: schema.maybe(schema.string()), calendarId: schema.string(), job_ids: schema.arrayOf(schema.maybe(schema.string())), @@ -22,4 +22,11 @@ export const calendarSchema = { }) ) ), -}; +}); + +export const calendarIdSchema = schema.object({ calendarId: schema.string() }); + +export const calendarIdsSchema = schema.object({ + /** Comma-separated list of calendar IDs */ + calendarIds: schema.string(), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts index 21454fa884b82..f1d4947a7abc5 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -export const dataAnalyticsJobConfigSchema = { +export const dataAnalyticsJobConfigSchema = schema.object({ description: schema.maybe(schema.string()), dest: schema.object({ index: schema.string(), @@ -17,7 +17,9 @@ export const dataAnalyticsJobConfigSchema = { query: schema.maybe(schema.any()), _source: schema.maybe( schema.object({ + /** Fields to include in results */ includes: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + /** Fields to exclude from results */ excludes: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), }) ), @@ -26,9 +28,9 @@ export const dataAnalyticsJobConfigSchema = { analysis: schema.any(), analyzed_fields: schema.any(), model_memory_limit: schema.string(), -}; +}); -export const dataAnalyticsEvaluateSchema = { +export const dataAnalyticsEvaluateSchema = schema.object({ index: schema.string(), query: schema.maybe(schema.any()), evaluation: schema.maybe( @@ -37,15 +39,27 @@ export const dataAnalyticsEvaluateSchema = { classification: schema.maybe(schema.any()), }) ), -}; +}); -export const dataAnalyticsExplainSchema = { +export const dataAnalyticsExplainSchema = schema.object({ description: schema.maybe(schema.string()), dest: schema.maybe(schema.any()), + /** Source */ source: schema.object({ index: schema.string(), }), analysis: schema.any(), analyzed_fields: schema.maybe(schema.any()), model_memory_limit: schema.maybe(schema.string()), -}; +}); + +export const analyticsIdSchema = schema.object({ + /** + * Analytics ID + */ + analyticsId: schema.string(), +}); + +export const stopsDataFrameAnalyticsJobQuerySchema = schema.object({ + force: schema.maybe(schema.boolean()), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts index 0c10b2d5b4f16..1a1d02f991b55 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts @@ -6,33 +6,27 @@ import { schema } from '@kbn/config-schema'; -export const dataVisualizerFieldStatsSchema = { - params: schema.object({ - indexPatternTitle: schema.string(), - }), - body: schema.object({ - query: schema.any(), - fields: schema.arrayOf(schema.any()), - samplerShardSize: schema.number(), - timeFieldName: schema.maybe(schema.string()), - earliest: schema.maybe(schema.number()), - latest: schema.maybe(schema.number()), - interval: schema.maybe(schema.string()), - maxExamples: schema.number(), - }), -}; +export const indexPatternTitleSchema = schema.object({ + indexPatternTitle: schema.string(), +}); -export const dataVisualizerOverallStatsSchema = { - params: schema.object({ - indexPatternTitle: schema.string(), - }), - body: schema.object({ - query: schema.any(), - aggregatableFields: schema.arrayOf(schema.string()), - nonAggregatableFields: schema.arrayOf(schema.string()), - samplerShardSize: schema.number(), - timeFieldName: schema.maybe(schema.string()), - earliest: schema.maybe(schema.number()), - latest: schema.maybe(schema.number()), - }), -}; +export const dataVisualizerFieldStatsSchema = schema.object({ + query: schema.any(), + fields: schema.arrayOf(schema.any()), + samplerShardSize: schema.number(), + timeFieldName: schema.maybe(schema.string()), + earliest: schema.maybe(schema.number()), + latest: schema.maybe(schema.number()), + interval: schema.maybe(schema.string()), + maxExamples: schema.number(), +}); + +export const dataVisualizerOverallStatsSchema = schema.object({ + query: schema.any(), + aggregatableFields: schema.arrayOf(schema.string()), + nonAggregatableFields: schema.arrayOf(schema.string()), + samplerShardSize: schema.number(), + timeFieldName: schema.maybe(schema.string()), + earliest: schema.maybe(schema.number()), + latest: schema.maybe(schema.number()), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts index 466e70197e3d1..2cfb9d7d275d5 100644 --- a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts @@ -42,3 +42,9 @@ export const datafeedConfigSchema = schema.object({ }) ), }); + +export const datafeedIdSchema = schema.object({ datafeedId: schema.string() }); + +export const deleteDatafeedQuerySchema = schema.maybe( + schema.object({ force: schema.maybe(schema.any()) }) +); diff --git a/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts new file mode 100644 index 0000000000000..9a80cf795cabf --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const analyzeFileQuerySchema = schema.maybe( + schema.object({ + charset: schema.maybe(schema.string()), + column_names: schema.maybe(schema.string()), + delimiter: schema.maybe(schema.string()), + explain: schema.maybe(schema.string()), + format: schema.maybe(schema.string()), + grok_pattern: schema.maybe(schema.string()), + has_header_row: schema.maybe(schema.string()), + line_merge_size_limit: schema.maybe(schema.string()), + lines_to_sample: schema.maybe(schema.string()), + quote: schema.maybe(schema.string()), + should_trim_fields: schema.maybe(schema.string()), + timeout: schema.maybe(schema.string()), + timestamp_field: schema.maybe(schema.string()), + timestamp_format: schema.maybe(schema.string()), + }) +); + +export const importFileQuerySchema = schema.object({ + id: schema.maybe(schema.string()), +}); + +export const importFileBodySchema = schema.object({ + index: schema.maybe(schema.string()), + data: schema.arrayOf(schema.any()), + settings: schema.maybe(schema.any()), + /** Mappings */ + mappings: schema.any(), + /** Ingest pipeline definition */ + ingestPipeline: schema.object({ + id: schema.maybe(schema.string()), + pipeline: schema.maybe(schema.any()), + }), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts b/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts index dffee56565c73..d33d34c7096ce 100644 --- a/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts @@ -6,14 +6,21 @@ import { schema } from '@kbn/config-schema'; -export const createFilterSchema = { +export const createFilterSchema = schema.object({ filterId: schema.string(), description: schema.maybe(schema.string()), items: schema.arrayOf(schema.string()), -}; +}); -export const updateFilterSchema = { +export const updateFilterSchema = schema.object({ description: schema.maybe(schema.string()), addItems: schema.maybe(schema.arrayOf(schema.string())), removeItems: schema.maybe(schema.arrayOf(schema.string())), -}; +}); + +export const filterIdSchema = schema.object({ + /** + * ID of the filter + */ + filterId: schema.string(), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/indices_schema.ts b/x-pack/plugins/ml/server/routes/schemas/indices_schema.ts new file mode 100644 index 0000000000000..f1b06392292f0 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/indices_schema.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; + +export const indicesSchema = schema.object({ + index: schema.maybe(schema.string()), + fields: schema.maybe(schema.arrayOf(schema.string())), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts new file mode 100644 index 0000000000000..b94a004384eb1 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const jobIdSchema = schema.object({ jobId: schema.maybe(schema.string()) }); + +export const jobAuditMessagesQuerySchema = schema.maybe( + schema.object({ from: schema.maybe(schema.any()) }) +); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index deb62678a777c..d2036b8a7c0fa 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -29,21 +29,25 @@ export const chartSchema = { splitFieldValue: schema.maybe(schema.nullable(schema.string())), }; -export const datafeedIdsSchema = { datafeedIds: schema.arrayOf(schema.maybe(schema.string())) }; +export const datafeedIdsSchema = schema.object({ + datafeedIds: schema.arrayOf(schema.maybe(schema.string())), +}); -export const forceStartDatafeedSchema = { +export const forceStartDatafeedSchema = schema.object({ datafeedIds: schema.arrayOf(schema.maybe(schema.string())), start: schema.maybe(schema.number()), end: schema.maybe(schema.number()), -}; +}); -export const jobIdsSchema = { +export const jobIdsSchema = schema.object({ jobIds: schema.maybe( schema.oneOf([schema.string(), schema.arrayOf(schema.maybe(schema.string()))]) ), -}; +}); -export const jobsWithTimerangeSchema = { dateFormatTz: schema.maybe(schema.string()) }; +export const jobsWithTimerangeSchema = { + dateFormatTz: schema.maybe(schema.string()), +}; export const lookBackProgressSchema = { jobId: schema.string(), diff --git a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts index 3ded6e770eed5..f12c85962a28d 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts @@ -37,7 +37,7 @@ export const validateJobSchema = schema.object({ job: schema.object(anomalyDetectionJobSchema), }); -export const validateCardinalitySchema = { +export const validateCardinalitySchema = schema.object({ ...anomalyDetectionJobSchema, datafeed_config: datafeedConfigSchema, -}; +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts index 32d829db7f81b..f7317e534b33b 100644 --- a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts @@ -12,7 +12,7 @@ const criteriaFieldSchema = schema.object({ fieldValue: schema.any(), }); -export const anomaliesTableDataSchema = { +export const anomaliesTableDataSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), criteriaFields: schema.arrayOf(criteriaFieldSchema), influencers: schema.arrayOf( @@ -26,29 +26,29 @@ export const anomaliesTableDataSchema = { maxRecords: schema.number(), maxExamples: schema.maybe(schema.number()), influencersFilterQuery: schema.maybe(schema.any()), -}; +}); -export const categoryDefinitionSchema = { +export const categoryDefinitionSchema = schema.object({ jobId: schema.maybe(schema.string()), categoryId: schema.string(), -}; +}); -export const maxAnomalyScoreSchema = { +export const maxAnomalyScoreSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), earliestMs: schema.maybe(schema.number()), latestMs: schema.maybe(schema.number()), -}; +}); -export const categoryExamplesSchema = { +export const categoryExamplesSchema = schema.object({ jobId: schema.string(), categoryIds: schema.arrayOf(schema.string()), maxExamples: schema.number(), -}; +}); -export const partitionFieldValuesSchema = { +export const partitionFieldValuesSchema = schema.object({ jobId: schema.string(), searchTerm: schema.maybe(schema.any()), criteriaFields: schema.arrayOf(criteriaFieldSchema), earliestMs: schema.number(), latestMs: schema.number(), -}; +}); diff --git a/yarn.lock b/yarn.lock index fdfdcada62e2c..19d28aea1acfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2557,6 +2557,11 @@ resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -3461,6 +3466,13 @@ "@svgr/plugin-svgo" "^4.2.0" loader-utils "^1.2.3" +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@testim/chrome-version@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9" @@ -5926,6 +5938,40 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apidoc-core@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/apidoc-core/-/apidoc-core-0.11.1.tgz#b04a7e0292e4ac0d714b40789f1b92f414486c81" + integrity sha512-pt/ICBdFQCZTgL38Aw1XB3G9AajDU1JA5E3yoDEgg0mqbPTCkOL8AyWdysjvNtQS/kkXgSPazCZaZzZYqrPHog== + dependencies: + fs-extra "^8.1.0" + glob "^7.1.4" + iconv-lite "^0.5.0" + klaw-sync "^6.0.0" + lodash "~4.17.15" + semver "~6.3.0" + +apidoc-markdown@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/apidoc-markdown/-/apidoc-markdown-5.0.0.tgz#e2d59d7cbbaa10402b09cec3e8ec17a03a27be59" + integrity sha512-gp4I4MvtgJvZPikEd7lwn149jjnC454CanPhm5demROdHCuakY+3YtIKEgVrJOqnS2iwbeeF+u4riB9CoO11+A== + dependencies: + ejs "^3.0.1" + semver "^7.1.3" + yargs "^15.1.0" + +apidoc@^0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/apidoc/-/apidoc-0.20.1.tgz#b29a2e2ae47e2df6a29e1f1527b94edf91853072" + integrity sha512-V54vkZ2lDFBiGn0qusZmHbMi4svuFBq0rjZAIe3nwYvBY7iztW78vKOyHyTr9ASaTB7EGe8hhLbpEnYAIO31TQ== + dependencies: + apidoc-core "^0.11.1" + commander "^2.20.0" + fs-extra "^8.1.0" + lodash "^4.17.15" + markdown-it "^10.0.0" + nodemon "^2.0.2" + winston "^3.2.1" + apollo-cache-control@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.1.1.tgz#173d14ceb3eb9e7cb53de7eb8b61bee6159d4171" @@ -7660,6 +7706,20 @@ boxen@^3.0.0: type-fest "^0.3.0" widest-line "^2.0.0" +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -8129,6 +8189,19 @@ cacheable-request@^2.1.1: normalize-url "2.0.1" responselike "1.0.2" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + cachedir@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -8635,6 +8708,21 @@ chokidar@^2.0.0, chokidar@^2.1.2, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.2.2: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.0.1, chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -8957,7 +9045,7 @@ clone-regexp@^1.0.0: is-regexp "^1.0.0" is-supported-regexp-flag "^1.0.0" -clone-response@1.0.2: +clone-response@1.0.2, clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= @@ -9423,6 +9511,18 @@ configstore@^3.1.2: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + connect-history-api-fallback@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" @@ -10021,6 +10121,11 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + cson-parser@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/cson-parser/-/cson-parser-1.3.5.tgz#7ec675e039145533bf2a6a856073f1599d9c2d24" @@ -10858,6 +10963,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -11406,6 +11516,13 @@ dot-prop@^4.1.0, dot-prop@^4.1.1: dependencies: is-obj "^1.0.0" +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + dotenv-defaults@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.0.2.tgz#441cf5f067653fca4bbdce9dd3b803f6f84c585d" @@ -11603,6 +11720,11 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== +ejs@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.0.2.tgz#745b01cdcfe38c1c6a2da3bbb2d9957060a31226" + integrity sha512-IncmUpn1yN84hy2shb0POJ80FWrfGNY0cxO9f4v+/sG7qcBvAtVWUA1IdzY/8EYUmOVhoKJVdJjNd3AZcnxOjA== + elastic-apm-http-client@^9.2.0: version "9.2.1" resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-9.2.1.tgz#e0e980ceb9975ff770bdbf2f5cdaac39fd70e8e6" @@ -12137,6 +12259,11 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -13965,7 +14092,7 @@ fs-exists-sync@^0.1.0: resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= -fs-extra@8.1.0, fs-extra@^8.0.1: +fs-extra@8.1.0, fs-extra@^8.0.1, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== @@ -14062,7 +14189,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.1.0, fsevents@~2.1.1: +fsevents@~2.1.0, fsevents@~2.1.1, fsevents@~2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== @@ -14294,7 +14421,7 @@ get-stream@^2.2.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -14548,6 +14675,13 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + dependencies: + ini "^1.3.5" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -14868,6 +15002,23 @@ got@^8.3.1, got@^8.3.2: url-parse-lax "^3.0.0" url-to-options "^1.0.1" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.4: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -15572,6 +15723,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.1, has@^1.0.3, has@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -15898,6 +16054,11 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -16162,6 +16323,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + ignore@^3.1.2, ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" @@ -16977,6 +17143,14 @@ is-installed-globally@0.1.0, is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + is-integer@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c" @@ -17047,6 +17221,11 @@ is-npm@^1.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" @@ -17079,6 +17258,11 @@ is-obj@^1.0.0, is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + is-object@^1.0.1, is-object@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" @@ -17343,6 +17527,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + is2@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a" @@ -18636,6 +18825,13 @@ keyv@3.0.0: dependencies: json-buffer "3.0.0" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -18672,6 +18868,13 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" @@ -18732,6 +18935,13 @@ latest-version@^3.0.0, latest-version@^3.1.0: dependencies: package-json "^4.0.0" +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-ass@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -19599,11 +19809,16 @@ lowercase-keys@1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= -lowercase-keys@^1.0.0: +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lowlight@~1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.1.tgz#ed7c3dffc36f8c1f263735c0fe0c907847c11250" @@ -20247,7 +20462,7 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0: +mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== @@ -21224,6 +21439,22 @@ nodemailer@^4.7.0: resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.7.0.tgz#4420e06abfffd77d0618f184ea49047db84f4ad8" integrity sha512-IludxDypFpYw4xpzKdMAozBSkzKHmNBvGanUREjJItgJ2NYcK/s8+PggVhj7c2yGFQykKsnnmv1+Aqo0ZfjHmw== +nodemon@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.3.tgz#e9c64df8740ceaef1cb00e1f3da57c0a93ef3714" + integrity sha512-lLQLPS90Lqwc99IHe0U94rDgvjo+G9I4uEIxRG3evSLROcqQ9hwc0AxlSHKS4T1JW/IMj/7N5mthiN58NL/5kw== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.2" + update-notifier "^4.0.0" + "nomnom@>= 1.5.x": version "1.8.1" resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7" @@ -21246,6 +21477,13 @@ nopt@^2.2.0: dependencies: abbrev "1" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -21307,6 +21545,11 @@ normalize-url@^3.3.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" @@ -21993,6 +22236,11 @@ p-cancelable@^0.4.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -22175,6 +22423,16 @@ package-json@^5.0.0: registry-url "^3.1.0" semver "^5.5.0" +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + pad-component@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/pad-component/-/pad-component-0.0.1.tgz#ad1f22ce1bf0fdc0d6ddd908af17f351a404b8ac" @@ -22678,6 +22936,11 @@ picomatch@^2.0.4, picomatch@^2.0.5: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== +picomatch@^2.0.7: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -23322,6 +23585,11 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== +pstree.remy@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" + integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== + public-encrypt@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" @@ -23509,6 +23777,13 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +pupa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" + integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== + dependencies: + escape-goat "^2.0.0" + puppeteer-core@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-1.19.0.tgz#3c3f98edb5862583e3a9c19cbc0da57ccc63ba5c" @@ -23766,7 +24041,7 @@ raw-loader@~0.5.1: resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" integrity sha1-DD0L6u2KAclm2Xh793goElKpeao= -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -24850,6 +25125,13 @@ readdirp@~3.2.0: dependencies: picomatch "^2.0.4" +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== + dependencies: + picomatch "^2.0.7" + readline2@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" @@ -25187,6 +25469,13 @@ registry-auth-token@^3.0.1, registry-auth-token@^3.3.2: rc "^1.1.6" safe-buffer "^5.0.1" +registry-auth-token@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" + integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA== + dependencies: + rc "^1.2.8" + registry-url@^3.0.0, registry-url@^3.0.3, registry-url@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" @@ -25194,6 +25483,13 @@ registry-url@^3.0.0, registry-url@^3.0.3, registry-url@^3.1.0: dependencies: rc "^1.0.1" +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + regjsgen@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" @@ -25793,7 +26089,7 @@ resolve@~1.10.1: dependencies: path-parse "^1.0.6" -responselike@1.0.2: +responselike@1.0.2, responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -26382,6 +26678,13 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -26431,7 +26734,7 @@ semver@^5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -26441,6 +26744,11 @@ semver@^6.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ== +semver@^7.1.3: + version "7.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.0.tgz#91f7c70ec944a63e5dc7a74cde2da375d8e0853c" + integrity sha512-uyvgU/igkrMgNHwLgXvlpD9jEADbJhB0+JXSywoO47JgJ6c16iau9F9cjtc/E5o0PoqRYTiTIAPRKaYe84z6eQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -27594,7 +27902,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== @@ -28372,6 +28680,11 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" +term-size@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" + integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== + terser-webpack-plugin@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" @@ -28766,6 +29079,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -28850,6 +29168,13 @@ topojson-client@3.0.0, topojson-client@^3.0.0: dependencies: commander "2" +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@>=2.3.3, tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -29728,6 +30053,13 @@ unc-path-regex@^0.1.2: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= +undefsafe@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" + integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== + dependencies: + debug "^2.2.0" + underscore.string@~3.3.4: version "3.3.5" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" @@ -29915,6 +30247,13 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + unist-util-is@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b" @@ -30084,6 +30423,25 @@ update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" +update-notifier@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" + integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== + dependencies: + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + upper-case-first@^1.1.0, upper-case-first@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" @@ -31385,6 +31743,13 @@ widest-line@^2.0.1: dependencies: string-width "^2.1.1" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + win-release@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" @@ -31682,6 +32047,11 @@ xdg-basedir@^3.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xhr@^2.0.1: version "2.4.1" resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.4.1.tgz#ba982cced205ae5eec387169ac9dc77ca4853d38" @@ -32003,7 +32373,7 @@ yargs@^13.2.2, yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.1" -yargs@^15.0.2, yargs@^15.3.1: +yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1: version "15.3.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== From b6708514228e30ada75071df87425f8ef6eebe2d Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 15 Apr 2020 12:48:16 -0400 Subject: [PATCH 22/44] Add uptime CODEOWNER entries. (#63616) --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 05972edacfe88..4a061d7415990 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -80,6 +80,8 @@ /x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/logs-metrics-ui @elastic/apm-ui @elastic/uptime @elastic/ingest-management /x-pack/legacy/plugins/monitoring/ @elastic/stack-monitoring-ui +/x-pack/legacy/plugins/uptime @elastic/uptime +/x-pack/plugins/uptime @elastic/uptime # Machine Learning /x-pack/legacy/plugins/ml/ @elastic/ml-ui From c57297c95b7f6766092f772c0e8fffd569035ce4 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 15 Apr 2020 19:55:43 +0200 Subject: [PATCH 23/44] Bugfix clear saved query crashes kibana on Discover in some cases (#63554) * actual hotfix * clean up redundant code * add functional test --- .../discover/np_ready/angular/discover.js | 6 +----- .../query_string_input/language_switcher.tsx | 1 + .../ui/search_bar/create_search_bar.tsx | 5 +++-- .../ui/search_bar/lib/use_saved_query.ts | 4 ++-- .../apps/discover/_saved_queries.js | 19 ++++++++++++++++++ test/functional/services/query_bar.ts | 20 +++++++++++++++++++ 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index d770e8334b3af..72276a38f6ac2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -828,13 +828,9 @@ function discoverController( if (newSavedQueryId) { setAppState({ savedQuery: newSavedQueryId }); } else { - //reset filters and query string, remove savedQuery from state + // remove savedQueryId from state const state = { ...appStateContainer.getState(), - query: getDefaultQuery( - localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') - ), - filters: [], }; delete state.savedQuery; appStateContainer.set(state); diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 63f6997ce2fc3..ded48d462722d 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -61,6 +61,7 @@ export function QueryLanguageSwitcher(props: Props) { size="xs" onClick={() => setIsPopoverOpen(!isPopoverOpen)} className="euiFormControlLayout__append" + data-test-subj={'switchQueryLanguageButton'} > {props.language === 'lucene' ? luceneLabel : kqlLabel} diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 5ca334d6bdcfe..7723254f3aa51 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -124,7 +124,8 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) const onQuerySubmitRef = useRef(props.onQuerySubmit); const defaultQuery = { query: '', - language: core.uiSettings.get('search:queryLanguage'), + language: + storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'), }; const [query, setQuery] = useState(props.query || defaultQuery); @@ -161,7 +162,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) setQuery, savedQueryId: props.savedQueryId, notifications: core.notifications, - uiSettings: core.uiSettings, + defaultLanguage: defaultQuery.language, }); // Fire onQuerySubmit on query or timerange change diff --git a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts index 817e890b7b42b..79aee3438d7aa 100644 --- a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts +++ b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts @@ -29,8 +29,8 @@ interface UseSavedQueriesProps { queryService: DataPublicPluginStart['query']; setQuery: Function; notifications: CoreStart['notifications']; - uiSettings: CoreStart['uiSettings']; savedQueryId?: string; + defaultLanguage: string; } interface UseSavedQueriesReturn { @@ -41,7 +41,7 @@ interface UseSavedQueriesReturn { export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesReturn => { // Handle saved queries - const defaultLanguage = props.uiSettings.get('search:queryLanguage'); + const defaultLanguage = props.defaultLanguage; const [savedQuery, setSavedQuery] = useState(); // Effect is used to convert a saved query id into an object diff --git a/test/functional/apps/discover/_saved_queries.js b/test/functional/apps/discover/_saved_queries.js index 3cdaccf32cdc3..76f3a3aea365f 100644 --- a/test/functional/apps/discover/_saved_queries.js +++ b/test/functional/apps/discover/_saved_queries.js @@ -147,6 +147,25 @@ export default function({ getService, getPageObjects }) { await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); expect(await queryBar.getQueryString()).to.eql(''); }); + + // https://github.com/elastic/kibana/issues/63505 + it('allows clearing if non default language was remembered in localstorage', async () => { + await queryBar.switchQueryLanguage('lucene'); + await PageObjects.common.navigateToApp('discover'); // makes sure discovered is reloaded without any state in url + await queryBar.expectQueryLanguageOrFail('lucene'); // make sure lucene is remembered after refresh (comes from localstorage) + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await queryBar.expectQueryLanguageOrFail('kql'); + await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); + await queryBar.expectQueryLanguageOrFail('lucene'); + }); + + // fails: bug in discover https://github.com/elastic/kibana/issues/63561 + // unskip this test when bug is fixed + it.skip('changing language removes saved query', async () => { + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await queryBar.switchQueryLanguage('lucene'); + expect(await queryBar.getQueryString()).to.eql(''); + }); }); }); } diff --git a/test/functional/services/query_bar.ts b/test/functional/services/query_bar.ts index ace8b97155c09..7c7fd2d81f170 100644 --- a/test/functional/services/query_bar.ts +++ b/test/functional/services/query_bar.ts @@ -17,6 +17,7 @@ * under the License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function QueryBarProvider({ getService, getPageObjects }: FtrProviderContext) { @@ -25,6 +26,7 @@ export function QueryBarProvider({ getService, getPageObjects }: FtrProviderCont const log = getService('log'); const PageObjects = getPageObjects(['header', 'common']); const find = getService('find'); + const browser = getService('browser'); class QueryBar { async getQueryString(): Promise { @@ -62,6 +64,24 @@ export function QueryBarProvider({ getService, getPageObjects }: FtrProviderCont public async clickQuerySubmitButton(): Promise { await testSubjects.click('querySubmitButton'); } + + public async switchQueryLanguage(lang: 'kql' | 'lucene'): Promise { + await testSubjects.click('switchQueryLanguageButton'); + const kqlToggle = await testSubjects.find('languageToggle'); + const currentLang = + (await kqlToggle.getAttribute('aria-checked')) === 'true' ? 'kql' : 'lucene'; + if (lang !== currentLang) { + await kqlToggle.click(); + } + + await browser.pressKeys(browser.keys.ESCAPE); // close popover + await this.expectQueryLanguageOrFail(lang); // make sure lang is switched + } + + public async expectQueryLanguageOrFail(lang: 'kql' | 'lucene'): Promise { + const queryLanguageButton = await testSubjects.find('switchQueryLanguageButton'); + expect((await queryLanguageButton.getVisibleText()).toLowerCase()).to.eql(lang); + } } return new QueryBar(); From 716211f2db5be3e545889f18b0f596f443ead7f6 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 15 Apr 2020 14:12:14 -0400 Subject: [PATCH 24/44] Update README.md (#63622) --- src/plugins/embeddable/README.md | 36 ++++++++++---------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/plugins/embeddable/README.md b/src/plugins/embeddable/README.md index 1fe91f426f43f..343a1cc32cd10 100644 --- a/src/plugins/embeddable/README.md +++ b/src/plugins/embeddable/README.md @@ -1,39 +1,25 @@ -# The Embeddable API V2 +# Embeddables -The Embeddable API's main goal is to have documented and standardized ways to share and exchange information and functionality across applications and plugins. +Embeddables are re-usable widgets that can be rendered in any environment or plugin. Developers can embed them directly in their plugin. End users can dynamically add them to any embeddable _containers_. -There are three main pieces of this infrastructure: - - Embeddables & Containers - - Actions - - Triggers +## Embeddable containers -## Embeddables & Containers +Containers are a special type of embeddable that can contain nested embeddables. Embeddables can be dynamically added to embeddable _containers_. Currently only dashboard uses this interface. -Embeddables are isolated, serializable, renderable widgets. A developer can hard code an embeddable inside their -application, or they can use some built in actions to allow users to dynamically add them to *containers*. - -Containers are a special type of embeddable that can contain nested embeddables. - -## Actions - -Actions are pluggable pieces of functionality exposed to the user that take an embeddable as context, plus an optional action context. - -## Triggers - -Triggers are the way actions are connected to a user action. We ship with two default triggers, `CONTEXT_MENU_TRIGGER` and `APPLY_FILTER`. - -Actions attached to the `CONTEXT_MENU_TRIGGER` will be displayed in supported embeddables context menu to the user. Actions attached to the `APPLY_FILTER` trigger will show up when any embeddable emits this trigger. +## Examples -A developer can register new triggers that their embeddables, or external components, can emit (as long as they have an embeddable to pass along as context). +Many example embeddables are implemented and registered [here](https://github.com/elastic/kibana/tree/master/examples/embeddable_examples). They can be played around with and explored [in the Embeddable Explorer example plugin](https://github.com/elastic/kibana/tree/master/examples/embeddable_explorer). Just run kibana with -## Examples +``` +yarn start --run-examples +``` -Many examples can be viewed in the functionally tested `kbn_tp_embeddable_explorer` plugin, as well as the jest tested classes inside the `embeddable_api/public/test_samples` folder. +and navigate to the Embeddable explorer app. ## Testing Run unit tests ```shell -node scripts/jest embeddable_api +node scripts/jest embeddable ``` From ac549ac6f5fe47ec3eacde87a1b1827c68ece119 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Wed, 15 Apr 2020 14:57:44 -0400 Subject: [PATCH 25/44] [Lens] Only show copy on save for previously saved docs (#63535) * [Lens] Only show copy on save for previously saved docs * Update app.test.tsx import after kibana platform changes Co-authored-by: Marta Bondyra --- .../lens/public/app_plugin/app.test.tsx | 22 +++++++++++++++++++ x-pack/plugins/lens/public/app_plugin/app.tsx | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index d49c128dff604..41d0e3a7aa9a0 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -12,6 +12,7 @@ import { EditorFrameInstance } from '../types'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { Document, SavedObjectStore } from '../persistence'; import { mount } from 'enzyme'; +import { SavedObjectSaveModal } from '../../../../../src/plugins/saved_objects/public'; import { esFilters, FilterManager, @@ -650,6 +651,27 @@ describe('Lens App', () => { }, }); }); + + it('does not show the copy button on first save', async () => { + const args = defaultArgs; + args.editorFrame = frame; + + instance = mount(); + + const onChange = frame.mount.mock.calls[0][1].onChange; + await act(async () => + onChange({ + filterableIndexPatterns: [], + doc: ({ expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + await act(async () => getButton(instance).run(instance.getDOMNode())); + instance.update(); + + expect(instance.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false); + }); }); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 2d8f1650e4008..28135dd12a724 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -387,7 +387,7 @@ export function App({ }} onClose={() => setState(s => ({ ...s, isSaveModalVisible: false }))} title={lastKnownDoc.title || ''} - showCopyOnSave={!addToDashboardMode} + showCopyOnSave={!!lastKnownDoc.id && !addToDashboardMode} objectType={i18n.translate('xpack.lens.app.saveModalType', { defaultMessage: 'Lens visualization', })} From 6c670b77f88777bcdbdaade738d59363e6ba7eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Wed, 15 Apr 2020 15:54:36 -0400 Subject: [PATCH 26/44] Split action types into own page (#63516) * Split action types into own page * Update docs/user/alerting/action-types.asciidoc Co-Authored-By: gchaps <33642766+gchaps@users.noreply.github.com> * Apply changes based on feedback Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/user/alerting/action-types.asciidoc | 191 +++--------------- .../user/alerting/action-types/email.asciidoc | 29 +++ .../user/alerting/action-types/index.asciidoc | 24 +++ .../alerting/action-types/pagerduty.asciidoc | 33 +++ .../alerting/action-types/server-log.asciidoc | 21 ++ .../user/alerting/action-types/slack.asciidoc | 22 ++ .../alerting/action-types/webhook.asciidoc | 26 +++ 7 files changed, 183 insertions(+), 163 deletions(-) create mode 100644 docs/user/alerting/action-types/email.asciidoc create mode 100644 docs/user/alerting/action-types/index.asciidoc create mode 100644 docs/user/alerting/action-types/pagerduty.asciidoc create mode 100644 docs/user/alerting/action-types/server-log.asciidoc create mode 100644 docs/user/alerting/action-types/slack.asciidoc create mode 100644 docs/user/alerting/action-types/webhook.asciidoc diff --git a/docs/user/alerting/action-types.asciidoc b/docs/user/alerting/action-types.asciidoc index 02c09736e1fa0..2913bf28dd765 100644 --- a/docs/user/alerting/action-types.asciidoc +++ b/docs/user/alerting/action-types.asciidoc @@ -2,181 +2,46 @@ [[action-types]] == Action and connector types -{kib} provides the following types of actions: +Actions are Kibana services or integrations with third-party systems that run as background tasks on the Kibana server when alert conditions are met. {kib} provides the following types of actions: -* <> -* <> -* <> -* <> -* <> -* <> +[cols="2"] +|=== -This section describes how to configure connectors and actions for each type. +a| <> -[NOTE] -============================================== -Some action types are paid commercial features, while others are free. -For a comparison of the Elastic license levels, -see https://www.elastic.co/subscriptions[the subscription page]. -============================================== - -[float] -[[email-action-type]] -=== Email - -The email action type uses the SMTP protocol to send mail message, using an integration of https://nodemailer.com/[Nodemailer]. Email message text is sent as both plain text and html text. - -[float] -[[email-connector-configuration]] -==== Connector configuration - -Email connectors have the following configuration properties: - -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -Sender:: The from address for all emails sent with this connector, specified in `user@host-name` format. -Host:: Host name of the service provider. If you are using the <> setting, make sure this hostname is whitelisted. -Port:: The port to connect to on the service provider. -Secure:: If true the connection will use TLS when connecting to the service provider. See https://nodemailer.com/smtp/#tls-options[nodemailer TLS documentation] for more information. -Username:: username for 'login' type authentication. -Password:: password for 'login' type authentication. - -[float] -[[email-action-configuration]] -==== Action configuration - -Email actions have the following configuration properties: - -To, CC, BCC:: Each is a list of addresses. Addresses can be specified in `user@host-name` format, or in `name ` format. One of To, CC, or BCC must contain an entry. -Subject:: The subject line of the email. -Message:: The message text of the email. Markdown format is supported. - -[float] -[[index-action-type]] -=== Index - -The index action type will index a document into {es}. - -[float] -[[index-connector-configuration]] -==== Connector configuration - -Index connectors have the following configuration properties: - -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -Index:: The {es} index to be written to. -Refresh:: Setting for the {ref}/docs-refresh.html[refresh] policy for the write request. -Execution time field:: This field will be automatically set to the time the alert condition was detected. - -[float] -[[index-action-configuration]] -==== Action configuration - -Index actions have the following properties: - -Document:: The document to index in json format. - -[float] -[[pagerduty-action-type]] -=== PagerDuty - -The PagerDuty action type uses the https://v2.developer.pagerduty.com/docs/events-api-v2[v2 Events API] to trigger, acknowledge, and resolve PagerDuty alerts. - -[float] -[[pagerduty-connector-configuration]] -==== Connector configuration +| Send email from your server. -PagerDuty connectors have the following configuration properties: +a| <> -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -API URL:: An optional PagerDuty event URL. Defaults to `https://events.pagerduty.com/v2/enqueue`. If you are using the <> setting, make sure the hostname is whitelisted. -Routing Key:: A 32 character PagerDuty Integration Key for an integration on a service or on a global ruleset. +| Index data into Elasticsearch. -[float] -[[pagerduty-action-configuration]] -==== Action configuration +a| <> -PagerDuty actions have the following properties: +| Send an event in PagerDuty. -Severity:: The perceived severity of on the affected system. This can be one of `Critical`, `Error`, `Warning` or `Info`(default). -Event action:: One of `Trigger` (default), `Resolve`, or `Acknowledge`. See https://v2.developer.pagerduty.com/docs/events-api-v2#event-action[event action] for more details. -Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if unset defaults to `action:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. -Timestamp:: An *optional* https://v2.developer.pagerduty.com/v2/docs/types#datetime[ISO-8601 format date-time], indicating the time the event was detected or generated. -Component:: An *optional* value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. -Group:: An *optional* value indicating the logical grouping of components of a service, for example `app-stack`. -Source:: An *optional* value indicating the affected system, preferably a hostname or fully qualified domain name. Defaults to the {kib} saved object id of the action. -Summary:: An *optional* text summary of the event, defaults to `No summary provided`. The maximum length is 1024 characters. -Class:: An *optional* value indicating the class/type of the event, for example `ping failure` or `cpu load`. +a| <> -For more details on these properties, see https://v2.developer.pagerduty.com/v2/docs/send-an-event-events-api-v2[PagerDuty v2 event parameters]. +| Add a message to a Kibana log. -[float] -[[server-log-action-type]] -=== Server log +a| <> -This action type writes and entry to the {kib} server log. +| Send a message to a Slack channel or user. -[float] -[[server-log-connector-configuration]] -==== Connector configuration +a| <> -Server log connectors have the following configuration properties: +| Send a request to a web service. +|=== -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. - -[float] -[[server-log-action-configuration]] -==== Action configuration - -Server log actions have the following properties: - -Message:: The message to log. - -[float] -[[slack-action-type]] -=== Slack - -The Slack action type uses https://api.slack.com/incoming-webhooks[Slack Incoming Webhooks]. - -[float] -[[slack-connector-configuration]] -==== Connector configuration - -Slack connectors have the following configuration properties: - -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is whitelisted. - -[float] -[[slack-action-configuration]] -==== Action configuration - -Slack actions have the following properties: - -Message:: The message text, converted to the `text` field in the Webhook JSON payload. Currently only the text field is supported. Markdown, images, and other advanced formatting are not yet supported. - -[float] -[[webhook-action-type]] -=== Webhook - -The Webhook action type uses https://github.com/axios/axios[axios] to send a POST or PUT request to a web service. - -[float] -[[webhook-connector-configuration]] -==== Connector configuration - -Webhook connectors have the following configuration properties: - -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -URL:: The request URL. If you are using the <> setting, make sure the hostname is whitelisted. -Method:: HTTP request method, either `post`(default) or `put`. -Headers:: A set of key-value pairs sent as headers with the request -User:: An optional username. If set, HTTP basic authentication is used. Currently only basic authentication is supported. -Password:: An optional password. If set, HTTP basic authentication is used. Currently only basic authentication is supported. - -[float] -[[webhook-action-configuration]] -==== Action configuration - -Webhook actions have the following properties: +[NOTE] +============================================== +Some action types are paid commercial features, while others are free. +For a comparison of the Elastic subscription levels, +see https://www.elastic.co/subscriptions[the subscription page]. +============================================== -Body:: A json payload sent to the request URL. \ No newline at end of file +include::action-types/email.asciidoc[] +include::action-types/index.asciidoc[] +include::action-types/pagerduty.asciidoc[] +include::action-types/server-log.asciidoc[] +include::action-types/slack.asciidoc[] +include::action-types/webhook.asciidoc[] diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc new file mode 100644 index 0000000000000..be3623dd9e59c --- /dev/null +++ b/docs/user/alerting/action-types/email.asciidoc @@ -0,0 +1,29 @@ +[role="xpack"] +[[email-action-type]] +== Email action type + +The email action type uses the SMTP protocol to send mail message, using an integration of https://nodemailer.com/[Nodemailer]. Email message text is sent as both plain text and html text. + +[float] +[[email-connector-configuration]] +==== Connector configuration + +Email connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +Sender:: The from address for all emails sent with this connector, specified in `user@host-name` format. +Host:: Host name of the service provider. If you are using the <> setting, make sure this hostname is whitelisted. +Port:: The port to connect to on the service provider. +Secure:: If true the connection will use TLS when connecting to the service provider. See https://nodemailer.com/smtp/#tls-options[nodemailer TLS documentation] for more information. +Username:: username for 'login' type authentication. +Password:: password for 'login' type authentication. + +[float] +[[email-action-configuration]] +==== Action configuration + +Email actions have the following configuration properties: + +To, CC, BCC:: Each is a list of addresses. Addresses can be specified in `user@host-name` format, or in `name ` format. One of To, CC, or BCC must contain an entry. +Subject:: The subject line of the email. +Message:: The message text of the email. Markdown format is supported. \ No newline at end of file diff --git a/docs/user/alerting/action-types/index.asciidoc b/docs/user/alerting/action-types/index.asciidoc new file mode 100644 index 0000000000000..75d9e57b1f212 --- /dev/null +++ b/docs/user/alerting/action-types/index.asciidoc @@ -0,0 +1,24 @@ +[role="xpack"] +[[index-action-type]] +== Index action type + +The index action type will index a document into {es}. + +[float] +[[index-connector-configuration]] +==== Connector configuration + +Index connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +Index:: The {es} index to be written to. +Refresh:: Setting for the {ref}/docs-refresh.html[refresh] policy for the write request. +Execution time field:: This field will be automatically set to the time the alert condition was detected. + +[float] +[[index-action-configuration]] +==== Action configuration + +Index actions have the following properties: + +Document:: The document to index in json format. \ No newline at end of file diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc new file mode 100644 index 0000000000000..50a1f31e4a9ae --- /dev/null +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -0,0 +1,33 @@ +[role="xpack"] +[[pagerduty-action-type]] +== PagerDuty action type + +The PagerDuty action type uses the https://v2.developer.pagerduty.com/docs/events-api-v2[v2 Events API] to trigger, acknowledge, and resolve PagerDuty alerts. + +[float] +[[pagerduty-connector-configuration]] +==== Connector configuration + +PagerDuty connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +API URL:: An optional PagerDuty event URL. Defaults to `https://events.pagerduty.com/v2/enqueue`. If you are using the <> setting, make sure the hostname is whitelisted. +Routing Key:: A 32 character PagerDuty Integration Key for an integration on a service or on a global ruleset. + +[float] +[[pagerduty-action-configuration]] +==== Action configuration + +PagerDuty actions have the following properties: + +Severity:: The perceived severity of on the affected system. This can be one of `Critical`, `Error`, `Warning` or `Info`(default). +Event action:: One of `Trigger` (default), `Resolve`, or `Acknowledge`. See https://v2.developer.pagerduty.com/docs/events-api-v2#event-action[event action] for more details. +Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if unset defaults to `action:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. +Timestamp:: An *optional* https://v2.developer.pagerduty.com/v2/docs/types#datetime[ISO-8601 format date-time], indicating the time the event was detected or generated. +Component:: An *optional* value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. +Group:: An *optional* value indicating the logical grouping of components of a service, for example `app-stack`. +Source:: An *optional* value indicating the affected system, preferably a hostname or fully qualified domain name. Defaults to the {kib} saved object id of the action. +Summary:: An *optional* text summary of the event, defaults to `No summary provided`. The maximum length is 1024 characters. +Class:: An *optional* value indicating the class/type of the event, for example `ping failure` or `cpu load`. + +For more details on these properties, see https://v2.developer.pagerduty.com/v2/docs/send-an-event-events-api-v2[PagerDuty v2 event parameters]. \ No newline at end of file diff --git a/docs/user/alerting/action-types/server-log.asciidoc b/docs/user/alerting/action-types/server-log.asciidoc new file mode 100644 index 0000000000000..4efbdf3bea099 --- /dev/null +++ b/docs/user/alerting/action-types/server-log.asciidoc @@ -0,0 +1,21 @@ +[role="xpack"] +[[server-log-action-type]] +== Server log action type + +This action type writes and entry to the {kib} server log. + +[float] +[[server-log-connector-configuration]] +==== Connector configuration + +Server log connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. + +[float] +[[server-log-action-configuration]] +==== Action configuration + +Server log actions have the following properties: + +Message:: The message to log. \ No newline at end of file diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc new file mode 100644 index 0000000000000..a4bacbf162e46 --- /dev/null +++ b/docs/user/alerting/action-types/slack.asciidoc @@ -0,0 +1,22 @@ +[role="xpack"] +[[slack-action-type]] +== Slack action type + +The Slack action type uses https://api.slack.com/incoming-webhooks[Slack Incoming Webhooks]. + +[float] +[[slack-connector-configuration]] +==== Connector configuration + +Slack connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is whitelisted. + +[float] +[[slack-action-configuration]] +==== Action configuration + +Slack actions have the following properties: + +Message:: The message text, converted to the `text` field in the Webhook JSON payload. Currently only the text field is supported. Markdown, images, and other advanced formatting are not yet supported. \ No newline at end of file diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc new file mode 100644 index 0000000000000..8c211aa83af89 --- /dev/null +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -0,0 +1,26 @@ +[role="xpack"] +[[webhook-action-type]] +== Webhook action type + +The Webhook action type uses https://github.com/axios/axios[axios] to send a POST or PUT request to a web service. + +[float] +[[webhook-connector-configuration]] +==== Connector configuration + +Webhook connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +URL:: The request URL. If you are using the <> setting, make sure the hostname is whitelisted. +Method:: HTTP request method, either `post`(default) or `put`. +Headers:: A set of key-value pairs sent as headers with the request +User:: An optional username. If set, HTTP basic authentication is used. Currently only basic authentication is supported. +Password:: An optional password. If set, HTTP basic authentication is used. Currently only basic authentication is supported. + +[float] +[[webhook-action-configuration]] +==== Action configuration + +Webhook actions have the following properties: + +Body:: A json payload sent to the request URL. \ No newline at end of file From 23e3f1aab5f9ec405e2c9fe98e4f0bd34ee46db4 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 15 Apr 2020 14:05:19 -0700 Subject: [PATCH 27/44] [Reporting] Add "warning" status as an alternate type of completed job (#63498) * [Reporting] Add "warning" as a status * test * fix warning status handling * Simplify logic * fix syntax * more different statuses * fix warning * feedbacks --- .../server/lib/esqueue/__tests__/worker.js | 21 +++++++++++++++++++ .../constants/{statuses.js => statuses.ts} | 1 + .../reporting/server/lib/esqueue/worker.js | 6 +++++- .../server/routes/lib/get_document_payload.ts | 5 +++-- x-pack/legacy/plugins/reporting/types.d.ts | 3 ++- x-pack/plugins/reporting/constants.ts | 2 ++ x-pack/plugins/reporting/index.d.ts | 7 ++++++- .../buttons/report_download_button.tsx | 2 +- .../public/components/report_listing.test.tsx | 21 +++++++++---------- .../public/components/report_listing.tsx | 12 ++++++++++- .../reporting/public/lib/stream_handler.ts | 3 ++- 11 files changed, 64 insertions(+), 19 deletions(-) rename x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/{statuses.js => statuses.ts} (89%) diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js index ad93a1882746d..ea80a652bb506 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js @@ -760,6 +760,27 @@ describe('Worker class', function() { }); }); + it('handle warnings in the output by reflecting a warning status', () => { + const workerFn = () => { + return Promise.resolve({ + ...payload, + warnings: [`Don't run with scissors!`], + }); + }; + worker = new Worker(mockQueue, 'test', workerFn, defaultWorkerOptions); + + return worker + ._performJob({ + test: true, + ...job, + }) + .then(() => { + sinon.assert.calledOnce(updateSpy); + const doc = updateSpy.firstCall.args[1].body.doc; + expect(doc).to.have.property('status', constants.JOB_STATUS_WARNINGS); + }); + }); + it('should emit completion event', function(done) { worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.ts similarity index 89% rename from x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.js rename to x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.ts index 620a567e18fe7..7c7f1431adf23 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/statuses.ts @@ -8,6 +8,7 @@ export const statuses = { JOB_STATUS_PENDING: 'pending', JOB_STATUS_PROCESSING: 'processing', JOB_STATUS_COMPLETED: 'completed', + JOB_STATUS_WARNINGS: 'completed_with_warnings', JOB_STATUS_FAILED: 'failed', JOB_STATUS_CANCELLED: 'cancelled', }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js index 113059fa2fa47..ab0bb6740f078 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js @@ -285,8 +285,12 @@ export class Worker extends events.EventEmitter { const completedTime = moment().toISOString(); const docOutput = this._formatOutput(output); + const status = + output && output.warnings && output.warnings.length > 0 + ? constants.JOB_STATUS_WARNINGS + : constants.JOB_STATUS_COMPLETED; const doc = { - status: constants.JOB_STATUS_COMPLETED, + status, completed_at: completedTime, output: docOutput, }; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts index aef37754681ec..c243d0b4266ea 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -9,6 +9,7 @@ import contentDisposition from 'content-disposition'; import * as _ from 'lodash'; import { CSV_JOB_TYPE } from '../../../common/constants'; import { ExportTypeDefinition, ExportTypesRegistry, JobDocOutput, JobSource } from '../../../types'; +import { statuses } from '../../lib/esqueue/constants/statuses'; interface ICustomHeaders { [x: string]: any; @@ -99,11 +100,11 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist const { status, jobtype: jobType, payload: { title } = { title: '' } } = doc._source; const { output } = doc._source; - if (status === 'completed') { + if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { return getCompleted(output, jobType, title); } - if (status === 'failed') { + if (status === statuses.JOB_STATUS_FAILED) { return getFailure(output); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index eec7da7dc6733..2e7da6663ab03 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -8,6 +8,7 @@ import { EventEmitter } from 'events'; import { ResponseObject } from 'hapi'; import { Legacy } from 'kibana'; import { CallCluster } from '../../../../src/legacy/core_plugins/elasticsearch'; +import { JobStatus } from '../../../plugins/reporting'; // reporting new platform import { CancellationToken } from './common/cancellation_token'; import { ReportingCore } from './server/core'; import { LevelLogger } from './server/lib/level_logger'; @@ -150,7 +151,7 @@ export interface JobSource { jobtype: string; output: JobDocOutput; payload: JobDocPayload; - status: string; // completed, failed, etc + status: JobStatus; }; } diff --git a/x-pack/plugins/reporting/constants.ts b/x-pack/plugins/reporting/constants.ts index 8f47a0a6b2ac1..5756d29face12 100644 --- a/x-pack/plugins/reporting/constants.ts +++ b/x-pack/plugins/reporting/constants.ts @@ -24,6 +24,7 @@ export const REPORTING_MANAGEMENT_HOME = '/app/kibana#/management/kibana/reporti // Statuses export const JOB_STATUS_FAILED = 'failed'; export const JOB_STATUS_COMPLETED = 'completed'; +export const JOB_STATUS_WARNINGS = 'completed_with_warnings'; export enum JobStatuses { PENDING = 'pending', @@ -31,6 +32,7 @@ export enum JobStatuses { COMPLETED = 'completed', FAILED = 'failed', CANCELLED = 'cancelled', + WARNINGS = 'completed_with_warnings', } // Types diff --git a/x-pack/plugins/reporting/index.d.ts b/x-pack/plugins/reporting/index.d.ts index 7c1a2ebd7d9de..26d661e29bd94 100644 --- a/x-pack/plugins/reporting/index.d.ts +++ b/x-pack/plugins/reporting/index.d.ts @@ -14,7 +14,12 @@ import { } from '../../../src/core/public'; export type JobId = string; -export type JobStatus = 'completed' | 'pending' | 'processing' | 'failed'; +export type JobStatus = + | 'completed' + | 'completed_with_warnings' + | 'pending' + | 'processing' + | 'failed'; export type HttpService = HttpSetup; export type NotificationsService = NotificationsStart; diff --git a/x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx b/x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx index b0674c149609d..6c13264ebcb1f 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx +++ b/x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx @@ -14,7 +14,7 @@ type Props = { record: ListingJob } & ListingProps; export const ReportDownloadButton: FunctionComponent = (props: Props) => { const { record, apiClient, intl } = props; - if (record.status !== JobStatuses.COMPLETED) { + if (record.status !== JobStatuses.COMPLETED && record.status !== JobStatuses.WARNINGS) { return null; } diff --git a/x-pack/plugins/reporting/public/components/report_listing.test.tsx b/x-pack/plugins/reporting/public/components/report_listing.test.tsx index 9b541261a690b..380a3b3295b9f 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.test.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.test.tsx @@ -17,17 +17,16 @@ import { ReportListing } from './report_listing'; const reportingAPIClient = { list: () => Promise.resolve([ - { _index: '.reporting-2019.08.18', _id: 'jzoik8dh1q2i89fb5f19znm6', _source: { payload: { layout: { id: 'preserve_layout', dimensions: { width: 1635, height: 792 } }, type: 'dashboard', title: 'Names', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:24.869Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoik7tn1q2i89fb5f60e5ve', _score: null, _source: { payload: { layout: { id: 'preserve_layout', dimensions: { width: 1635, height: 792 } }, type: 'dashboard', title: 'Names', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:24.155Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoik5tb1q2i89fb5fckchny', _score: null, _source: { payload: { layout: { id: 'png', dimensions: { width: 1898, height: 876 } }, title: 'cool dashboard', type: 'dashboard', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:21.551Z', jobtype: 'PNG', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoik5a11q2i89fb5f130t2m', _score: null, _source: { payload: { layout: { id: 'png', dimensions: { width: 1898, height: 876 } }, title: 'cool dashboard', type: 'dashboard', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:20.857Z', jobtype: 'PNG', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoik3ka1q2i89fb5fdx93g7', _score: null, _source: { payload: { layout: { id: 'preserve_layout', dimensions: { width: 1898, height: 876 } }, type: 'dashboard', title: 'cool dashboard', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:18.634Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoik2vt1q2i89fb5ffw723n', _score: null, _source: { payload: { layout: { id: 'preserve_layout', dimensions: { width: 1898, height: 876 } }, type: 'dashboard', title: 'cool dashboard', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:17.753Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoik1851q2i89fb5fdge6e7', _score: null, _source: { payload: { layout: { id: 'preserve_layout', dimensions: { width: 1080, height: 720 } }, type: 'canvas workpad', title: 'My Canvas Workpad - Dark', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:15.605Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoijyre1q2i89fb5fa7xzvi', _score: null, _source: { payload: { type: 'dashboard', title: 'tests-panels', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:12.410Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jzoijv5h1q2i89fb5ffklnhx', _score: null, _source: { payload: { type: 'dashboard', title: 'tests-panels', }, max_attempts: 3, browser_type: 'chromium', created_at: '2019-08-23T19:34:07.733Z', jobtype: 'printable_pdf', created_by: 'elastic', attempts: 0, status: 'pending', }, }, // prettier-ignore - { _index: '.reporting-2019.08.18', _id: 'jznhgk7r1bx789fb5f6hxok7', _score: null, _source: { kibana_name: 'spicy.local', browser_type: 'chromium', created_at: '2019-08-23T02:15:47.799Z', jobtype: 'printable_pdf', created_by: 'elastic', kibana_id: 'ca75e26c-2b7d-464f-aef0-babb67c735a0', output: { content_type: 'application/pdf', size: 877114 }, completed_at: '2019-08-23T02:15:57.707Z', payload: { type: 'dashboard (legacy)', title: 'tests-panels', }, max_attempts: 3, started_at: '2019-08-23T02:15:48.794Z', attempts: 1, status: 'completed', }, }, // prettier-ignore - ]), + { _id: 'k90e51pk1ieucbae0c3t8wo2', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 0, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T21:01:13.062Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '1970-01-01T00:00:00.000Z', status: 'pending', timeout: 300000, }, sort: [1586898073064], }, // prettier-ignore + { _id: 'k90e51pk1ieucbae0c3t8wo1', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T21:01:13.062Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T21:06:14.526Z', started_at: '2020-04-14T21:01:14.526Z', status: 'processing', timeout: 300000, }, sort: [1586898073064], }, + { _id: 'k90cmthd1gv8cbae0c2le8bo', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T20:19:14.748Z', created_at: '2020-04-14T20:19:02.977Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T20:19:02.976Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T20:24:04.073Z', started_at: '2020-04-14T20:19:04.073Z', status: 'completed', timeout: 300000, }, sort: [1586895542977], }, + { _id: 'k906958e1d4wcbae0c9hip1a', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:21:08.223Z', created_at: '2020-04-14T17:20:27.326Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 49468, warnings: [ 'An error occurred when trying to read the page for visualization panel info. You may need to increase \'xpack.reporting.capture.timeouts.waitForElements\'. TimeoutError: waiting for selector "[data-shared-item],[data-shared-items-count]" failed: timeout 30000ms exceeded', ], }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:20:27.326Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e8-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:25:29.444Z', started_at: '2020-04-14T17:20:29.444Z', status: 'completed_with_warnings', timeout: 300000, }, sort: [1586884827326], }, + { _id: 'k9067y2a1d4wcbae0cad38n0', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:53.244Z', created_at: '2020-04-14T17:19:31.379Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:19:31.378Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:24:39.883Z', started_at: '2020-04-14T17:19:39.883Z', status: 'completed', timeout: 300000, }, sort: [1586884771379], }, + { _id: 'k9067s1m1d4wcbae0cdnvcms', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:36.822Z', created_at: '2020-04-14T17:19:23.578Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:19:23.578Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:24:25.247Z', started_at: '2020-04-14T17:19:25.247Z', status: 'completed', timeout: 300000, }, sort: [1586884763578], }, + { _id: 'k9065q3s1d4wcbae0c00fxlh', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:18:03.910Z', created_at: '2020-04-14T17:17:47.752Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:17:47.750Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:22:50.379Z', started_at: '2020-04-14T17:17:50.379Z', status: 'completed', timeout: 300000, }, sort: [1586884667752], }, + { _id: 'k905zdw11d34cbae0c3y6tzh', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:13:03.719Z', created_at: '2020-04-14T17:12:51.985Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:12:51.984Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:17:52.431Z', started_at: '2020-04-14T17:12:52.431Z', status: 'completed', timeout: 300000, }, sort: [1586884371985], }, + { _id: 'k8t4ylcb07mi9d006214ifyg', _index: '.reporting-2020.04.05', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-09T19:10:10.049Z', created_at: '2020-04-09T19:09:52.139Z', created_by: 'elastic', jobtype: 'PNG', kibana_id: 'f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'png', objectType: 'visualization', }, output: { content_type: 'image/png', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-09T19:09:52.137Z', layout: { dimensions: { height: 1575, width: 1423, }, id: 'png', }, objectType: 'visualization', relativeUrl: "/s/hsyjklk/app/kibana#/visualize/edit/94d1fe40-7a94-11ea-b373-0749f92ad295?_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!((enabled:!t,id:'1',params:(),schema:metric,type:count)),params:(addLegend:!f,addTooltip:!t,metric:(colorSchema:'Green%20to%20Red',colorsRange:!((from:0,to:10000)),invertColors:!f,labels:(show:!t),metricColorMode:None,percentageMode:!f,style:(bgColor:!f,bgFill:%23000,fontSize:60,labelColor:!f,subText:''),useRanges:!f),type:metric),title:count,type:metric))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15y,to:now))&indexPattern=d81752b0-7434-11ea-be36-1f978cda44d4&type=metric", title: 'count', }, priority: 10, process_expiration: '2020-04-09T19:14:54.570Z', started_at: '2020-04-09T19:09:54.570Z', status: 'completed', timeout: 300000, }, sort: [1586459392139], }, + ]), // prettier-ignore total: () => Promise.resolve(18), } as any; diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx index 93dd293876f82..885e9577471a0 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.tsx @@ -87,6 +87,12 @@ const jobStatusLabelsMap = new Map([ defaultMessage: 'Completed', }), ], + [ + JobStatuses.WARNINGS, + i18n.translate('xpack.reporting.jobStatuses.warningText', { + defaultMessage: 'Completed with warnings', + }), + ], [ JobStatuses.FAILED, i18n.translate('xpack.reporting.jobStatuses.failedText', { @@ -410,7 +416,11 @@ class ReportListingUi extends Component { statusTimestamp = this.formatDate(record.started_at); } else if ( record.completed_at && - (status === JobStatuses.COMPLETED || status === JobStatuses.FAILED) + ([ + JobStatuses.COMPLETED, + JobStatuses.FAILED, + JobStatuses.WARNINGS, + ] as string[]).includes(status) ) { statusTimestamp = this.formatDate(record.completed_at); } diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 1aae30f6fdfb0..3c121f1712685 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -11,6 +11,7 @@ import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JOB_STATUS_COMPLETED, JOB_STATUS_FAILED, + JOB_STATUS_WARNINGS, } from '../../constants'; import { @@ -112,7 +113,7 @@ export class ReportingNotifierStreamHandler { _source: { status: jobStatus }, } = job; if (storedJobs.includes(jobId)) { - if (jobStatus === JOB_STATUS_COMPLETED) { + if (jobStatus === JOB_STATUS_COMPLETED || jobStatus === JOB_STATUS_WARNINGS) { completedJobs.push(summarizeJob(job)); } else if (jobStatus === JOB_STATUS_FAILED) { failedJobs.push(summarizeJob(job)); From f4c81b440d489c6a49ea67eeb2a911d7dcf6631e Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 15 Apr 2020 14:52:32 -0700 Subject: [PATCH 28/44] [Reporting] Switch Serverside Config Wrapper to NP (#62500) * New config * fix translations json * add csv.useByteOrderMarkEncoding to schema * imports cleanup * restore "get default chromium sandbox disabled" functionality * integrate getDefaultChromiumSandboxDisabled * fix tests * --wip-- [skip ci] * add more schema tests * diff prettiness * trash legacy files that moved to NP * create_config tests * Hoist create_config * better disableSandbox tests * fix ts * fix export * fix bad code * make comments better * fix i18n * comment * automatically setting... logs * replace log_configuration * fix lint * This is f2 * improve startup log about sandbox info * update docs with log reference * revert log removal Co-authored-by: Elastic Machine --- docs/user/reporting/chromium-sandbox.asciidoc | 10 +- .../__snapshots__/index.test.ts.snap | 387 ------------------ x-pack/legacy/plugins/reporting/config.ts | 183 --------- x-pack/legacy/plugins/reporting/index.test.ts | 34 -- x-pack/legacy/plugins/reporting/index.ts | 12 - .../plugins/reporting/log_configuration.ts | 35 -- .../browsers/chromium/driver_factory/args.ts | 2 +- .../reporting/server/browsers/index.ts | 1 - .../plugins/reporting/server/config/config.js | 21 - .../plugins/reporting/server/config/index.ts | 134 +----- .../legacy/plugins/reporting/server/legacy.ts | 10 +- .../lib/esqueue/helpers/index_timestamp.js | 1 + .../legacy/plugins/reporting/server/plugin.ts | 5 - .../plugins/reporting/server/types.d.ts | 3 +- .../create_mock_reportingplugin.ts | 1 - x-pack/plugins/reporting/config.ts | 10 - x-pack/plugins/reporting/kibana.json | 5 +- .../server/config/create_config.test.ts | 165 ++++++++ .../reporting/server/config/create_config.ts | 124 ++++++ ...default_chromium_sandbox_disabled.test.ts} | 12 +- .../default_chromium_sandbox_disabled.ts | 11 +- .../plugins/reporting/server/config/index.ts | 23 ++ .../reporting/server/config/schema.test.ts | 134 ++++++ .../plugins/reporting/server/config/schema.ts | 174 ++++++++ x-pack/plugins/reporting/server/index.ts | 14 + x-pack/plugins/reporting/server/plugin.ts | 38 ++ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 28 files changed, 709 insertions(+), 842 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap delete mode 100644 x-pack/legacy/plugins/reporting/config.ts delete mode 100644 x-pack/legacy/plugins/reporting/index.test.ts delete mode 100644 x-pack/legacy/plugins/reporting/log_configuration.ts delete mode 100644 x-pack/legacy/plugins/reporting/server/config/config.js delete mode 100644 x-pack/plugins/reporting/config.ts create mode 100644 x-pack/plugins/reporting/server/config/create_config.test.ts create mode 100644 x-pack/plugins/reporting/server/config/create_config.ts rename x-pack/{legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.test.js => plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts} (82%) rename x-pack/{legacy/plugins/reporting/server/browsers => plugins/reporting/server/config}/default_chromium_sandbox_disabled.ts (80%) create mode 100644 x-pack/plugins/reporting/server/config/index.ts create mode 100644 x-pack/plugins/reporting/server/config/schema.test.ts create mode 100644 x-pack/plugins/reporting/server/config/schema.ts create mode 100644 x-pack/plugins/reporting/server/index.ts create mode 100644 x-pack/plugins/reporting/server/plugin.ts diff --git a/docs/user/reporting/chromium-sandbox.asciidoc b/docs/user/reporting/chromium-sandbox.asciidoc index 5d4fbfb153a0b..bfef5b8b86c6b 100644 --- a/docs/user/reporting/chromium-sandbox.asciidoc +++ b/docs/user/reporting/chromium-sandbox.asciidoc @@ -11,12 +11,12 @@ sandboxing techniques differ for each operating system. The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. {reporting} will automatically disable the sandbox when it is running on Debian and CentOS as additional steps are required to enable -unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} logs: -`Enabling the Chromium sandbox provides an additional layer of protection`. +unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs: +`Chromium sandbox provides an additional layer of protection, but is not supported for your OS. +Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` -If your kernel is 3.8 or newer, it's -recommended to enable usernamespaces and set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your -`kibana.yml` to enable the sandbox. +Reporting will automatically enable the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's +recommended to set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces. ==== Docker When running {kib} in a Docker container, all container processes are run within a usernamespace with seccomp-bpf and diff --git a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap deleted file mode 100644 index 3ae3079da136b..0000000000000 --- a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,387 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`config schema with context {"dev":false,"dist":false} produces correct config 1`] = ` -Object { - "capture": Object { - "browser": Object { - "autoDownload": true, - "chromium": Object { - "disableSandbox": "", - "maxScreenshotDimension": 1950, - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, - "concurrency": 4, - "loadDelay": 3000, - "maxAttempts": 1, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "protocol": "http:", - }, - Object { - "allow": true, - "protocol": "https:", - }, - Object { - "allow": true, - "protocol": "ws:", - }, - Object { - "allow": true, - "protocol": "wss:", - }, - Object { - "allow": true, - "protocol": "data:", - }, - Object { - "allow": false, - }, - ], - }, - "settleTime": 1000, - "timeout": 20000, - "timeouts": Object { - "openUrl": 30000, - "renderComplete": 30000, - "waitForElements": 30000, - }, - "viewport": Object { - "height": 1200, - "width": 1950, - }, - "zoom": 2, - }, - "csv": Object { - "checkForFormulas": true, - "enablePanelActionDownload": true, - "maxSizeBytes": 10485760, - "scroll": Object { - "duration": "30s", - "size": 500, - }, - "useByteOrderMarkEncoding": false, - }, - "enabled": true, - "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "index": ".reporting", - "kibanaServer": Object {}, - "poll": Object { - "jobCompletionNotifier": Object { - "interval": 10000, - "intervalErrorMultiplier": 5, - }, - "jobsRefresh": Object { - "interval": 5000, - "intervalErrorMultiplier": 5, - }, - }, - "queue": Object { - "indexInterval": "week", - "pollEnabled": true, - "pollInterval": 3000, - "pollIntervalErrorMultiplier": 10, - "timeout": 120000, - }, - "roles": Object { - "allow": Array [ - "reporting_user", - ], - }, -} -`; - -exports[`config schema with context {"dev":false,"dist":true} produces correct config 1`] = ` -Object { - "capture": Object { - "browser": Object { - "autoDownload": false, - "chromium": Object { - "disableSandbox": "", - "maxScreenshotDimension": 1950, - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, - "concurrency": 4, - "loadDelay": 3000, - "maxAttempts": 3, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "protocol": "http:", - }, - Object { - "allow": true, - "protocol": "https:", - }, - Object { - "allow": true, - "protocol": "ws:", - }, - Object { - "allow": true, - "protocol": "wss:", - }, - Object { - "allow": true, - "protocol": "data:", - }, - Object { - "allow": false, - }, - ], - }, - "settleTime": 1000, - "timeout": 20000, - "timeouts": Object { - "openUrl": 30000, - "renderComplete": 30000, - "waitForElements": 30000, - }, - "viewport": Object { - "height": 1200, - "width": 1950, - }, - "zoom": 2, - }, - "csv": Object { - "checkForFormulas": true, - "enablePanelActionDownload": true, - "maxSizeBytes": 10485760, - "scroll": Object { - "duration": "30s", - "size": 500, - }, - "useByteOrderMarkEncoding": false, - }, - "enabled": true, - "index": ".reporting", - "kibanaServer": Object {}, - "poll": Object { - "jobCompletionNotifier": Object { - "interval": 10000, - "intervalErrorMultiplier": 5, - }, - "jobsRefresh": Object { - "interval": 5000, - "intervalErrorMultiplier": 5, - }, - }, - "queue": Object { - "indexInterval": "week", - "pollEnabled": true, - "pollInterval": 3000, - "pollIntervalErrorMultiplier": 10, - "timeout": 120000, - }, - "roles": Object { - "allow": Array [ - "reporting_user", - ], - }, -} -`; - -exports[`config schema with context {"dev":true,"dist":false} produces correct config 1`] = ` -Object { - "capture": Object { - "browser": Object { - "autoDownload": true, - "chromium": Object { - "disableSandbox": "", - "maxScreenshotDimension": 1950, - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, - "concurrency": 4, - "loadDelay": 3000, - "maxAttempts": 1, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "protocol": "http:", - }, - Object { - "allow": true, - "protocol": "https:", - }, - Object { - "allow": true, - "protocol": "ws:", - }, - Object { - "allow": true, - "protocol": "wss:", - }, - Object { - "allow": true, - "protocol": "data:", - }, - Object { - "allow": false, - }, - ], - }, - "settleTime": 1000, - "timeout": 20000, - "timeouts": Object { - "openUrl": 30000, - "renderComplete": 30000, - "waitForElements": 30000, - }, - "viewport": Object { - "height": 1200, - "width": 1950, - }, - "zoom": 2, - }, - "csv": Object { - "checkForFormulas": true, - "enablePanelActionDownload": true, - "maxSizeBytes": 10485760, - "scroll": Object { - "duration": "30s", - "size": 500, - }, - "useByteOrderMarkEncoding": false, - }, - "enabled": true, - "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "index": ".reporting", - "kibanaServer": Object {}, - "poll": Object { - "jobCompletionNotifier": Object { - "interval": 10000, - "intervalErrorMultiplier": 5, - }, - "jobsRefresh": Object { - "interval": 5000, - "intervalErrorMultiplier": 5, - }, - }, - "queue": Object { - "indexInterval": "week", - "pollEnabled": true, - "pollInterval": 3000, - "pollIntervalErrorMultiplier": 10, - "timeout": 120000, - }, - "roles": Object { - "allow": Array [ - "reporting_user", - ], - }, -} -`; - -exports[`config schema with context {"dev":true,"dist":true} produces correct config 1`] = ` -Object { - "capture": Object { - "browser": Object { - "autoDownload": false, - "chromium": Object { - "disableSandbox": "", - "maxScreenshotDimension": 1950, - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, - "concurrency": 4, - "loadDelay": 3000, - "maxAttempts": 3, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "protocol": "http:", - }, - Object { - "allow": true, - "protocol": "https:", - }, - Object { - "allow": true, - "protocol": "ws:", - }, - Object { - "allow": true, - "protocol": "wss:", - }, - Object { - "allow": true, - "protocol": "data:", - }, - Object { - "allow": false, - }, - ], - }, - "settleTime": 1000, - "timeout": 20000, - "timeouts": Object { - "openUrl": 30000, - "renderComplete": 30000, - "waitForElements": 30000, - }, - "viewport": Object { - "height": 1200, - "width": 1950, - }, - "zoom": 2, - }, - "csv": Object { - "checkForFormulas": true, - "enablePanelActionDownload": true, - "maxSizeBytes": 10485760, - "scroll": Object { - "duration": "30s", - "size": 500, - }, - "useByteOrderMarkEncoding": false, - }, - "enabled": true, - "index": ".reporting", - "kibanaServer": Object {}, - "poll": Object { - "jobCompletionNotifier": Object { - "interval": 10000, - "intervalErrorMultiplier": 5, - }, - "jobsRefresh": Object { - "interval": 5000, - "intervalErrorMultiplier": 5, - }, - }, - "queue": Object { - "indexInterval": "week", - "pollEnabled": true, - "pollInterval": 3000, - "pollIntervalErrorMultiplier": 10, - "timeout": 120000, - }, - "roles": Object { - "allow": Array [ - "reporting_user", - ], - }, -} -`; diff --git a/x-pack/legacy/plugins/reporting/config.ts b/x-pack/legacy/plugins/reporting/config.ts deleted file mode 100644 index 5eceb84c83e43..0000000000000 --- a/x-pack/legacy/plugins/reporting/config.ts +++ /dev/null @@ -1,183 +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 { BROWSER_TYPE } from './common/constants'; -// @ts-ignore untyped module -import { config as appConfig } from './server/config/config'; -import { getDefaultChromiumSandboxDisabled } from './server/browsers'; - -export async function config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - kibanaServer: Joi.object({ - protocol: Joi.string().valid(['http', 'https']), - hostname: Joi.string().invalid('0'), - port: Joi.number().integer(), - }).default(), - queue: Joi.object({ - indexInterval: Joi.string().default('week'), - pollEnabled: Joi.boolean().default(true), - pollInterval: Joi.number() - .integer() - .default(3000), - pollIntervalErrorMultiplier: Joi.number() - .integer() - .default(10), - timeout: Joi.number() - .integer() - .default(120000), - }).default(), - capture: Joi.object({ - timeouts: Joi.object({ - openUrl: Joi.number() - .integer() - .default(30000), - waitForElements: Joi.number() - .integer() - .default(30000), - renderComplete: Joi.number() - .integer() - .default(30000), - }).default(), - networkPolicy: Joi.object({ - enabled: Joi.boolean().default(true), - rules: Joi.array() - .items( - Joi.object({ - allow: Joi.boolean().required(), - protocol: Joi.string(), - host: Joi.string(), - }) - ) - .default([ - { allow: true, protocol: 'http:' }, - { allow: true, protocol: 'https:' }, - { allow: true, protocol: 'ws:' }, - { allow: true, protocol: 'wss:' }, - { allow: true, protocol: 'data:' }, - { allow: false }, // Default action is to deny! - ]), - }).default(), - zoom: Joi.number() - .integer() - .default(2), - viewport: Joi.object({ - width: Joi.number() - .integer() - .default(1950), - height: Joi.number() - .integer() - .default(1200), - }).default(), - timeout: Joi.number() - .integer() - .default(20000), // deprecated - loadDelay: Joi.number() - .integer() - .default(3000), - settleTime: Joi.number() - .integer() - .default(1000), // deprecated - concurrency: Joi.number() - .integer() - .default(appConfig.concurrency), // deprecated - browser: Joi.object({ - type: Joi.any() - .valid(BROWSER_TYPE) - .default(BROWSER_TYPE), - autoDownload: Joi.boolean().when('$dist', { - is: true, - then: Joi.default(false), - otherwise: Joi.default(true), - }), - chromium: Joi.object({ - inspect: Joi.boolean() - .when('$dev', { - is: false, - then: Joi.valid(false), - else: Joi.default(false), - }) - .default(), - disableSandbox: Joi.boolean().default(await getDefaultChromiumSandboxDisabled()), - proxy: Joi.object({ - enabled: Joi.boolean().default(false), - server: Joi.string() - .uri({ scheme: ['http', 'https'] }) - .when('enabled', { - is: Joi.valid(false), - then: Joi.valid(null), - else: Joi.required(), - }), - bypass: Joi.array() - .items(Joi.string().regex(/^[^\s]+$/)) - .when('enabled', { - is: Joi.valid(false), - then: Joi.valid(null), - else: Joi.default([]), - }), - }).default(), - maxScreenshotDimension: Joi.number() - .integer() - .default(1950), - }).default(), - }).default(), - maxAttempts: Joi.number() - .integer() - .greater(0) - .when('$dist', { - is: true, - then: Joi.default(3), - otherwise: Joi.default(1), - }) - .default(), - }).default(), - csv: Joi.object({ - useByteOrderMarkEncoding: Joi.boolean().default(false), - checkForFormulas: Joi.boolean().default(true), - enablePanelActionDownload: Joi.boolean().default(true), - maxSizeBytes: Joi.number() - .integer() - .default(1024 * 1024 * 10), // bytes in a kB * kB in a mB * 10 - scroll: Joi.object({ - duration: Joi.string() - .regex(/^[0-9]+(d|h|m|s|ms|micros|nanos)$/, { name: 'DurationString' }) - .default('30s'), - size: Joi.number() - .integer() - .default(500), - }).default(), - }).default(), - encryptionKey: Joi.when(Joi.ref('$dist'), { - is: true, - then: Joi.string(), - otherwise: Joi.string().default('a'.repeat(32)), - }), - roles: Joi.object({ - allow: Joi.array() - .items(Joi.string()) - .default(['reporting_user']), - }).default(), - index: Joi.string().default('.reporting'), - poll: Joi.object({ - jobCompletionNotifier: Joi.object({ - interval: Joi.number() - .integer() - .default(10000), - intervalErrorMultiplier: Joi.number() - .integer() - .default(5), - }).default(), - jobsRefresh: Joi.object({ - interval: Joi.number() - .integer() - .default(5000), - intervalErrorMultiplier: Joi.number() - .integer() - .default(5), - }).default(), - }).default(), - }).default(); -} diff --git a/x-pack/legacy/plugins/reporting/index.test.ts b/x-pack/legacy/plugins/reporting/index.test.ts deleted file mode 100644 index 8148adab67874..0000000000000 --- a/x-pack/legacy/plugins/reporting/index.test.ts +++ /dev/null @@ -1,34 +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 { reporting } from './index'; -import { getConfigSchema } from '../../../test_utils'; - -// The snapshot records the number of cpus available -// to make the snapshot deterministic `os.cpus` needs to be mocked -// but the other members on `os` must remain untouched -jest.mock('os', () => { - const os = jest.requireActual('os'); - os.cpus = () => [{}, {}, {}, {}]; - return os; -}); - -// eslint-disable-next-line jest/valid-describe -const describeWithContext = describe.each([ - [{ dev: false, dist: false }], - [{ dev: true, dist: false }], - [{ dev: false, dist: true }], - [{ dev: true, dist: true }], -]); - -describeWithContext('config schema with context %j', context => { - it('produces correct config', async () => { - const schema = await getConfigSchema(reporting); - const value: any = await schema.validate({}, { context }); - value.capture.browser.chromium.disableSandbox = ''; - await expect(value).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index a5d27d0545da1..fb95e2c2edc24 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; import { resolve } from 'path'; import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; -import { config as reportingConfig } from './config'; import { legacyInit } from './server/legacy'; import { ReportingPluginSpecOptions } from './types'; @@ -17,10 +16,8 @@ const kbToBase64Length = (kb: number) => Math.floor((kb * 1024 * 8) / 6); export const reporting = (kibana: any) => { return new kibana.Plugin({ id: PLUGIN_ID, - configPrefix: 'xpack.reporting', publicDir: resolve(__dirname, 'public'), require: ['kibana', 'elasticsearch', 'xpack_main'], - config: reportingConfig, uiExports: { uiSettingDefaults: { @@ -47,14 +44,5 @@ export const reporting = (kibana: any) => { async init(server: Legacy.Server) { return legacyInit(server, this); }, - - deprecations({ unused }: any) { - return [ - unused('capture.concurrency'), - unused('capture.timeout'), - unused('capture.settleTime'), - unused('kibanaApp'), - ]; - }, } as ReportingPluginSpecOptions); }; diff --git a/x-pack/legacy/plugins/reporting/log_configuration.ts b/x-pack/legacy/plugins/reporting/log_configuration.ts deleted file mode 100644 index 7aaed2038bd52..0000000000000 --- a/x-pack/legacy/plugins/reporting/log_configuration.ts +++ /dev/null @@ -1,35 +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 getosSync, { LinuxOs } from 'getos'; -import { promisify } from 'util'; -import { BROWSER_TYPE } from './common/constants'; -import { CaptureConfig } from './server/types'; -import { Logger } from './types'; - -const getos = promisify(getosSync); - -export async function logConfiguration(captureConfig: CaptureConfig, logger: Logger) { - const { - browser: { - type: browserType, - chromium: { disableSandbox }, - }, - } = captureConfig; - - logger.debug(`Browser type: ${browserType}`); - if (browserType === BROWSER_TYPE) { - logger.debug(`Chromium sandbox disabled: ${disableSandbox}`); - } - - const os = await getos(); - const { os: osName, dist, release } = os as LinuxOs; - if (dist) { - logger.debug(`Running on os "${osName}", distribution "${dist}", release "${release}"`); - } else { - logger.debug(`Running on os "${osName}"`); - } -} diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/args.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/args.ts index a2f7a1f3ad0da..928f3b8377809 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/args.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/args.ts @@ -10,7 +10,7 @@ type ViewportConfig = CaptureConfig['viewport']; type BrowserConfig = CaptureConfig['browser']['chromium']; interface LaunchArgs { - userDataDir: BrowserConfig['userDataDir']; + userDataDir: string; viewport: ViewportConfig; disableSandbox: BrowserConfig['disableSandbox']; proxy: BrowserConfig['proxy']; diff --git a/x-pack/legacy/plugins/reporting/server/browsers/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/index.ts index 1e42e2736962e..7f902c84308f6 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/index.ts @@ -8,7 +8,6 @@ import * as chromiumDefinition from './chromium'; export { ensureAllBrowsersDownloaded } from './download'; export { createBrowserDriverFactory } from './create_browser_driver_factory'; -export { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; export { HeadlessChromiumDriver } from './chromium/driver'; export { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; diff --git a/x-pack/legacy/plugins/reporting/server/config/config.js b/x-pack/legacy/plugins/reporting/server/config/config.js deleted file mode 100644 index 08e4db464b003..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/config/config.js +++ /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 { cpus } from 'os'; - -const defaultCPUCount = 2; - -function cpuCount() { - try { - return cpus().length; - } catch (e) { - return defaultCPUCount; - } -} - -export const config = { - concurrency: cpuCount(), -}; diff --git a/x-pack/legacy/plugins/reporting/server/config/index.ts b/x-pack/legacy/plugins/reporting/server/config/index.ts index b7b67b57932eb..c6b915be3a94a 100644 --- a/x-pack/legacy/plugins/reporting/server/config/index.ts +++ b/x-pack/legacy/plugins/reporting/server/config/index.ts @@ -6,10 +6,9 @@ import { Legacy } from 'kibana'; import { CoreSetup } from 'src/core/server'; -import { i18n } from '@kbn/i18n'; -import crypto from 'crypto'; import { get } from 'lodash'; -import { NetworkPolicy } from '../../types'; +import { ConfigType as ReportingConfigType } from '../../../../../plugins/reporting/server'; +export { ReportingConfigType }; // make config.get() aware of the value type it returns interface Config { @@ -56,131 +55,6 @@ export interface ReportingConfig extends Config { kbnConfig: Config; } -type BrowserType = 'chromium'; - -interface BrowserConfig { - inspect: boolean; - userDataDir: string; - viewport: { width: number; height: number }; - disableSandbox: boolean; - proxy: { - enabled: boolean; - server?: string; - bypass?: string[]; - }; -} - -interface CaptureConfig { - browser: { - type: BrowserType; - autoDownload: boolean; - chromium: BrowserConfig; - }; - maxAttempts: number; - networkPolicy: NetworkPolicy; - loadDelay: number; - timeouts: { - openUrl: number; - waitForElements: number; - renderComplete: number; - }; - viewport: any; - zoom: any; -} - -interface QueueConfig { - indexInterval: string; - pollEnabled: boolean; - pollInterval: number; - pollIntervalErrorMultiplier: number; - timeout: number; -} - -interface ScrollConfig { - duration: string; - size: number; -} - -export interface ReportingConfigType { - capture: CaptureConfig; - csv: { - scroll: ScrollConfig; - enablePanelActionDownload: boolean; - checkForFormulas: boolean; - maxSizeBytes: number; - useByteOrderMarkEncoding: boolean; - }; - encryptionKey: string; - kibanaServer: any; - index: string; - queue: QueueConfig; - roles: any; -} - -const addConfigDefaults = ( - server: Legacy.Server, - core: CoreSetup, - baseConfig: ReportingConfigType -) => { - // encryption key - let encryptionKey = baseConfig.encryptionKey; - if (encryptionKey === undefined) { - server.log( - ['reporting', 'config', 'warning'], - i18n.translate('xpack.reporting.selfCheckEncryptionKey.warning', { - defaultMessage: - `Generating a random key for {setting}. To prevent pending reports ` + - `from failing on restart, please set {setting} in kibana.yml`, - values: { - setting: 'xpack.reporting.encryptionKey', - }, - }) - ); - encryptionKey = crypto.randomBytes(16).toString('hex'); - } - - const { kibanaServer: reportingServer } = baseConfig; - const serverInfo = core.http.getServerInfo(); - - // kibanaServer.hostname, default to server.host, don't allow "0" - let kibanaServerHostname = reportingServer.hostname ? reportingServer.hostname : serverInfo.host; - if (kibanaServerHostname === '0') { - server.log( - ['reporting', 'config', 'warning'], - i18n.translate('xpack.reporting.selfCheckHostname.warning', { - defaultMessage: - `Found 'server.host: "0"' in settings. This is incompatible with Reporting. ` + - `To enable Reporting to work, '{setting}: 0.0.0.0' is being automatically to the configuration. ` + - `You can change to 'server.host: 0.0.0.0' or add '{setting}: 0.0.0.0' in kibana.yml to prevent this message.`, - values: { - setting: 'xpack.reporting.kibanaServer.hostname', - }, - }) - ); - kibanaServerHostname = '0.0.0.0'; - } - - // kibanaServer.port, default to server.port - const kibanaServerPort = reportingServer.port - ? reportingServer.port - : serverInfo.port; // prettier-ignore - - // kibanaServer.protocol, default to server.protocol - const kibanaServerProtocol = reportingServer.protocol - ? reportingServer.protocol - : serverInfo.protocol; - - return { - ...baseConfig, - encryptionKey, - kibanaServer: { - hostname: kibanaServerHostname, - port: kibanaServerPort, - protocol: kibanaServerProtocol, - }, - }; -}; - export const buildConfig = ( core: CoreSetup, server: Legacy.Server, @@ -204,10 +78,8 @@ export const buildConfig = ( }, }; - // spreading arguments as an array allows the return type to be known by the compiler - reportingConfig = addConfigDefaults(server, core, reportingConfig); return { - get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null), + get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null), // spreading arguments as an array allows the return type to be known by the compiler kbnConfig: { get: (...keys: string[]) => get(kbnConfig, keys.join('.'), null), }, diff --git a/x-pack/legacy/plugins/reporting/server/legacy.ts b/x-pack/legacy/plugins/reporting/server/legacy.ts index 679b42aca6de5..d044dc866ed0e 100644 --- a/x-pack/legacy/plugins/reporting/server/legacy.ts +++ b/x-pack/legacy/plugins/reporting/server/legacy.ts @@ -5,7 +5,9 @@ */ import { Legacy } from 'kibana'; +import { take } from 'rxjs/operators'; import { PluginInitializerContext } from 'src/core/server'; +import { PluginsSetup } from '../../../../plugins/reporting/server'; import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { ReportingPluginSpecOptions } from '../types'; import { buildConfig } from './config'; @@ -17,7 +19,6 @@ const buildLegacyDependencies = ( reportingPlugin: ReportingPluginSpecOptions ): LegacySetup => ({ route: server.route.bind(server), - config: server.config, plugins: { xpack_main: server.plugins.xpack_main, reporting: reportingPlugin, @@ -32,14 +33,13 @@ export const legacyInit = async ( reportingLegacyPlugin: ReportingPluginSpecOptions ) => { const { core: coreSetup } = server.newPlatform.setup; - const legacyConfig = server.config(); - const reportingConfig = buildConfig(coreSetup, server, legacyConfig.get('xpack.reporting')); - + const { config$ } = (server.newPlatform.setup.plugins.reporting as PluginsSetup).__legacy; + const reportingConfig = await config$.pipe(take(1)).toPromise(); const __LEGACY = buildLegacyDependencies(server, reportingLegacyPlugin); const pluginInstance = plugin( server.newPlatform.coreContext as PluginInitializerContext, - reportingConfig + buildConfig(coreSetup, server, reportingConfig) ); await pluginInstance.setup(coreSetup, { elasticsearch: coreSetup.elasticsearch, diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js index 6cdbe8f968f75..ceb4ef43b2d9d 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js @@ -8,6 +8,7 @@ import moment from 'moment'; export const intervals = ['year', 'month', 'week', 'day', 'hour', 'minute']; +// TODO: This helper function can be removed by using `schema.duration` objects in the reporting config schema export function indexTimestamp(intervalStr, separator = '-') { if (separator.match(/[a-z]/i)) throw new Error('Interval separator can not be a letter'); diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index c9ed2e81c6792..e0fa99106a93e 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -5,15 +5,12 @@ */ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server'; -import { logConfiguration } from '../log_configuration'; import { createBrowserDriverFactory } from './browsers'; import { ReportingCore, ReportingConfig } from './core'; import { createQueueFactory, enqueueJobFactory, LevelLogger, runValidations } from './lib'; import { setFieldFormats } from './services'; import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types'; import { registerReportingUsageCollector } from './usage'; -// @ts-ignore no module definition -import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; export class ReportingPlugin implements Plugin { @@ -61,8 +58,6 @@ export class ReportingPlugin setFieldFormats(plugins.data.fieldFormats); - logConfiguration(this.config.get('capture'), this.logger); - return {}; } diff --git a/x-pack/legacy/plugins/reporting/server/types.d.ts b/x-pack/legacy/plugins/reporting/server/types.d.ts index bec00688432cc..fb77eae4e7eea 100644 --- a/x-pack/legacy/plugins/reporting/server/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/types.d.ts @@ -11,7 +11,7 @@ import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/ import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { ReportingPluginSpecOptions } from '../types'; -import { ReportingConfig, ReportingConfigType } from './core'; +import { ReportingConfigType } from './core'; export interface ReportingSetupDeps { elasticsearch: ElasticsearchServiceSetup; @@ -30,7 +30,6 @@ export type ReportingSetup = object; export type ReportingStart = object; export interface LegacySetup { - config: Legacy.Server['config']; plugins: { xpack_main: XPackMainPlugin & { status?: any; diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts index 34ff91d1972a0..ec00023b4d449 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts @@ -11,7 +11,6 @@ jest.mock('../server/browsers'); jest.mock('../server/lib/create_queue'); jest.mock('../server/lib/enqueue_job'); jest.mock('../server/lib/validate'); -jest.mock('../log_configuration'); import { EventEmitter } from 'events'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths diff --git a/x-pack/plugins/reporting/config.ts b/x-pack/plugins/reporting/config.ts deleted file mode 100644 index f1d6b1a8f248f..0000000000000 --- a/x-pack/plugins/reporting/config.ts +++ /dev/null @@ -1,10 +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 reportingPollConfig = { - jobCompletionNotifier: { interval: 10000, intervalErrorMultiplier: 5 }, - jobsRefresh: { interval: 5000, intervalErrorMultiplier: 5 }, -}; diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json index 3da2d2a094706..d068711b87c9d 100644 --- a/x-pack/plugins/reporting/kibana.json +++ b/x-pack/plugins/reporting/kibana.json @@ -2,6 +2,9 @@ "id": "reporting", "version": "8.0.0", "kibanaVersion": "kibana", + "optionalPlugins": [ + "usageCollection" + ], "configPath": ["xpack", "reporting"], "requiredPlugins": [ "home", @@ -12,6 +15,6 @@ "share", "kibanaLegacy" ], - "server": false, + "server": true, "ui": true } diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts new file mode 100644 index 0000000000000..3107866be6496 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -0,0 +1,165 @@ +/* + * 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 * as Rx from 'rxjs'; +import { CoreSetup, Logger, PluginInitializerContext } from 'src/core/server'; +import { ConfigType as ReportingConfigType } from './schema'; +import { createConfig$ } from './create_config'; + +interface KibanaServer { + host?: string; + port?: number; + protocol?: string; +} + +const makeMockInitContext = (config: { + capture?: Partial; + encryptionKey?: string; + kibanaServer: Partial; +}): PluginInitializerContext => + ({ + config: { + create: () => + Rx.of({ + ...config, + capture: config.capture || { browser: { chromium: { disableSandbox: false } } }, + kibanaServer: config.kibanaServer || {}, + }), + }, + } as PluginInitializerContext); + +const makeMockCoreSetup = (serverInfo: KibanaServer): CoreSetup => + ({ http: { getServerInfo: () => serverInfo } } as any); + +describe('Reporting server createConfig$', () => { + let mockCoreSetup: CoreSetup; + let mockInitContext: PluginInitializerContext; + let mockLogger: Logger; + + beforeEach(() => { + mockCoreSetup = makeMockCoreSetup({ host: 'kibanaHost', port: 5601, protocol: 'http' }); + mockInitContext = makeMockInitContext({ + kibanaServer: {}, + }); + mockLogger = ({ warn: jest.fn(), debug: jest.fn() } as unknown) as Logger; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('creates random encryption key and default config using host, protocol, and port from server info', async () => { + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result.encryptionKey).toMatch(/\S{32,}/); // random 32 characters + expect(result.kibanaServer).toMatchInlineSnapshot(` + Object { + "hostname": "kibanaHost", + "port": 5601, + "protocol": "http", + } + `); + expect((mockLogger.warn as any).mock.calls.length).toBe(1); + expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([ + 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in kibana.yml', + ]); + }); + + it('uses the user-provided encryption key', async () => { + mockInitContext = makeMockInitContext({ + encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', + kibanaServer: {}, + }); + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result.encryptionKey).toMatch('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'); + expect((mockLogger.warn as any).mock.calls.length).toBe(0); + }); + + it('uses the user-provided encryption key, reporting kibanaServer settings to override server info', async () => { + mockInitContext = makeMockInitContext({ + encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', + kibanaServer: { + hostname: 'reportingHost', + port: 5677, + protocol: 'httpsa', + }, + }); + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result).toMatchInlineSnapshot(` + Object { + "capture": Object { + "browser": Object { + "chromium": Object { + "disableSandbox": false, + }, + }, + }, + "encryptionKey": "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii", + "kibanaServer": Object { + "hostname": "reportingHost", + "port": 5677, + "protocol": "httpsa", + }, + } + `); + expect((mockLogger.warn as any).mock.calls.length).toBe(0); + }); + + it('show warning when kibanaServer.hostName === "0"', async () => { + mockInitContext = makeMockInitContext({ + encryptionKey: 'aaaaaaaaaaaaabbbbbbbbbbbbaaaaaaaaa', + kibanaServer: { hostname: '0' }, + }); + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result.kibanaServer).toMatchInlineSnapshot(` + Object { + "hostname": "0.0.0.0", + "port": 5601, + "protocol": "http", + } + `); + expect((mockLogger.warn as any).mock.calls.length).toBe(1); + expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([ + `Found 'server.host: \"0\"' in Kibana configuration. This is incompatible with Reporting. To enable Reporting to work, 'xpack.reporting.kibanaServer.hostname: 0.0.0.0' is being automatically ` + + `to the configuration. You can change the setting to 'server.host: 0.0.0.0' or add 'xpack.reporting.kibanaServer.hostname: 0.0.0.0' in kibana.yml to prevent this message.`, + ]); + }); + + it('uses user-provided disableSandbox: false', async () => { + mockInitContext = makeMockInitContext({ + encryptionKey: '888888888888888888888888888888888', + capture: { browser: { chromium: { disableSandbox: false } } }, + } as ReportingConfigType); + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false }); + expect((mockLogger.warn as any).mock.calls.length).toBe(0); + }); + + it('uses user-provided disableSandbox: true', async () => { + mockInitContext = makeMockInitContext({ + encryptionKey: '888888888888888888888888888888888', + capture: { browser: { chromium: { disableSandbox: true } } }, + } as ReportingConfigType); + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true }); + expect((mockLogger.warn as any).mock.calls.length).toBe(0); + }); + + it('provides a default for disableSandbox', async () => { + mockInitContext = makeMockInitContext({ + encryptionKey: '888888888888888888888888888888888', + } as ReportingConfigType); + const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise(); + + expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) }); + expect((mockLogger.warn as any).mock.calls.length).toBe(0); + }); +}); diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts new file mode 100644 index 0000000000000..1e6e8bbde5d27 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/create_config.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. + */ + +import { i18n } from '@kbn/i18n/'; +import { TypeOf } from '@kbn/config-schema'; +import crypto from 'crypto'; +import { capitalize } from 'lodash'; +import { map, mergeMap } from 'rxjs/operators'; +import { CoreSetup, Logger, PluginInitializerContext } from 'src/core/server'; +import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; +import { ConfigSchema } from './schema'; + +/* + * Set up dynamic config defaults + * - xpack.capture.browser.chromium.disableSandbox + * - xpack.kibanaServer + * - xpack.reporting.encryptionKey + */ +export function createConfig$(core: CoreSetup, context: PluginInitializerContext, logger: Logger) { + return context.config.create>().pipe( + map(config => { + // encryption key + let encryptionKey = config.encryptionKey; + if (encryptionKey === undefined) { + logger.warn( + i18n.translate('xpack.reporting.serverConfig.randomEncryptionKey', { + defaultMessage: + 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + + 'restart, please set xpack.reporting.encryptionKey in kibana.yml', + }) + ); + encryptionKey = crypto.randomBytes(16).toString('hex'); + } + const { kibanaServer: reportingServer } = config; + const serverInfo = core.http.getServerInfo(); + // kibanaServer.hostname, default to server.host, don't allow "0" + let kibanaServerHostname = reportingServer.hostname + ? reportingServer.hostname + : serverInfo.host; + if (kibanaServerHostname === '0') { + logger.warn( + i18n.translate('xpack.reporting.serverConfig.invalidServerHostname', { + defaultMessage: + `Found 'server.host: "0"' in Kibana configuration. This is incompatible with Reporting. ` + + `To enable Reporting to work, '{configKey}: 0.0.0.0' is being automatically to the configuration. ` + + `You can change the setting to 'server.host: 0.0.0.0' or add '{configKey}: 0.0.0.0' in kibana.yml to prevent this message.`, + values: { configKey: 'xpack.reporting.kibanaServer.hostname' }, + }) + ); + kibanaServerHostname = '0.0.0.0'; + } + // kibanaServer.port, default to server.port + const kibanaServerPort = reportingServer.port + ? reportingServer.port + : serverInfo.port; // prettier-ignore + // kibanaServer.protocol, default to server.protocol + const kibanaServerProtocol = reportingServer.protocol + ? reportingServer.protocol + : serverInfo.protocol; + return { + ...config, + encryptionKey, + kibanaServer: { + hostname: kibanaServerHostname, + port: kibanaServerPort, + protocol: kibanaServerProtocol, + }, + }; + }), + mergeMap(async config => { + if (config.capture.browser.chromium.disableSandbox != null) { + // disableSandbox was set by user + return config; + } + + // disableSandbox was not set by user, apply default for OS + const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); + const osName = [os.os, os.dist, os.release] + .filter(Boolean) + .map(capitalize) + .join(' '); + + logger.debug( + i18n.translate('xpack.reporting.serverConfig.osDetected', { + defaultMessage: `Running on OS: '{osName}'`, + values: { osName }, + }) + ); + + if (disableSandbox === true) { + logger.warn( + i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxDisabled', { + defaultMessage: `Chromium sandbox provides an additional layer of protection, but is not supported for {osName} OS. Automatically setting '{configKey}: true'.`, + values: { + configKey: 'xpack.reporting.capture.browser.chromium.disableSandbox', + osName, + }, + }) + ); + } else { + logger.info( + i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxEnabled', { + defaultMessage: `Chromium sandbox provides an additional layer of protection, and is supported for {osName} OS. Automatically enabling Chromium sandbox.`, + values: { osName }, + }) + ); + } + + return { + ...config, + capture: { + ...config.capture, + browser: { + ...config.capture.browser, + chromium: { ...config.capture.browser.chromium, disableSandbox }, + }, + }, + }; + }) + ); +} diff --git a/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.test.js b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts similarity index 82% rename from x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.test.js rename to x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts index a022506f9e2da..307c96bb34909 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.test.js +++ b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts @@ -11,11 +11,17 @@ jest.mock('getos', () => { import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; import getos from 'getos'; -function defaultTest(os, expectedDefault) { +interface TestObject { + os: string; + dist?: string; + release?: string; +} + +function defaultTest(os: TestObject, expectedDefault: boolean) { test(`${expectedDefault ? 'disabled' : 'enabled'} on ${JSON.stringify(os)}`, async () => { - getos.mockImplementation(cb => cb(null, os)); + (getos as jest.Mock).mockImplementation(cb => cb(null, os)); const actualDefault = await getDefaultChromiumSandboxDisabled(); - expect(actualDefault).toBe(expectedDefault); + expect(actualDefault.disableSandbox).toBe(expectedDefault); }); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.ts b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.ts similarity index 80% rename from x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.ts rename to x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.ts index a21a4b33722ff..525041d5c772b 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.ts +++ b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.ts @@ -31,12 +31,17 @@ const distroSupportsUnprivilegedUsernamespaces = (distro: string) => { return true; }; -export async function getDefaultChromiumSandboxDisabled() { +interface OsSummary { + disableSandbox: boolean; + os: { os: string; dist?: string; release?: string }; +} + +export async function getDefaultChromiumSandboxDisabled(): Promise { const os = await getos(); if (os.os === 'linux' && !distroSupportsUnprivilegedUsernamespaces(os.dist)) { - return true; + return { os, disableSandbox: true }; } else { - return false; + return { os, disableSandbox: false }; } } diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts new file mode 100644 index 0000000000000..f0a0a093aa8c0 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { PluginConfigDescriptor } from 'kibana/server'; +import { ConfigSchema, ConfigType } from './schema'; + +export { createConfig$ } from './create_config'; + +export const config: PluginConfigDescriptor = { + schema: ConfigSchema, + deprecations: ({ unused }) => [ + unused('capture.browser.chromium.maxScreenshotDimension'), + unused('capture.concurrency'), + unused('capture.settleTime'), + unused('capture.timeout'), + unused('kibanaApp'), + ], +}; + +export { ConfigSchema, ConfigType }; diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts new file mode 100644 index 0000000000000..41285c2bfa133 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConfigSchema } from './schema'; + +describe('Reporting Config Schema', () => { + it(`context {"dev":false,"dist":false} produces correct config`, () => { + expect(ConfigSchema.validate({}, { dev: false, dist: false })).toMatchObject({ + capture: { + browser: { + autoDownload: true, + chromium: { proxy: { enabled: false } }, + type: 'chromium', + }, + loadDelay: 3000, + maxAttempts: 1, + networkPolicy: { + enabled: true, + rules: [ + { allow: true, host: undefined, protocol: 'http:' }, + { allow: true, host: undefined, protocol: 'https:' }, + { allow: true, host: undefined, protocol: 'ws:' }, + { allow: true, host: undefined, protocol: 'wss:' }, + { allow: true, host: undefined, protocol: 'data:' }, + { allow: false, host: undefined, protocol: undefined }, + ], + }, + viewport: { height: 1200, width: 1950 }, + zoom: 2, + }, + csv: { + checkForFormulas: true, + enablePanelActionDownload: true, + maxSizeBytes: 10485760, + scroll: { duration: '30s', size: 500 }, + }, + encryptionKey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + index: '.reporting', + kibanaServer: {}, + poll: { + jobCompletionNotifier: { interval: 10000, intervalErrorMultiplier: 5 }, + jobsRefresh: { interval: 5000, intervalErrorMultiplier: 5 }, + }, + queue: { + indexInterval: 'week', + pollEnabled: true, + pollInterval: 3000, + pollIntervalErrorMultiplier: 10, + timeout: 120000, + }, + roles: { allow: ['reporting_user'] }, + }); + }); + + it(`context {"dev":false,"dist":true} produces correct config`, () => { + expect(ConfigSchema.validate({}, { dev: false, dist: true })).toMatchObject({ + capture: { + browser: { + autoDownload: false, + chromium: { + inspect: false, + proxy: { enabled: false }, + }, + type: 'chromium', + }, + loadDelay: 3000, + maxAttempts: 3, + networkPolicy: { + enabled: true, + rules: [ + { allow: true, host: undefined, protocol: 'http:' }, + { allow: true, host: undefined, protocol: 'https:' }, + { allow: true, host: undefined, protocol: 'ws:' }, + { allow: true, host: undefined, protocol: 'wss:' }, + { allow: true, host: undefined, protocol: 'data:' }, + { allow: false, host: undefined, protocol: undefined }, + ], + }, + viewport: { height: 1200, width: 1950 }, + zoom: 2, + }, + csv: { + checkForFormulas: true, + enablePanelActionDownload: true, + maxSizeBytes: 10485760, + scroll: { duration: '30s', size: 500 }, + }, + index: '.reporting', + kibanaServer: {}, + poll: { + jobCompletionNotifier: { interval: 10000, intervalErrorMultiplier: 5 }, + jobsRefresh: { interval: 5000, intervalErrorMultiplier: 5 }, + }, + queue: { + indexInterval: 'week', + pollEnabled: true, + pollInterval: 3000, + pollIntervalErrorMultiplier: 10, + timeout: 120000, + }, + roles: { allow: ['reporting_user'] }, + }); + }); + + it(`allows optional settings`, () => { + // encryption key + expect( + ConfigSchema.validate({ encryptionKey: 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' }) + .encryptionKey + ).toBe('qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); + + // disableSandbox + expect( + ConfigSchema.validate({ capture: { browser: { chromium: { disableSandbox: true } } } }) + .capture.browser.chromium + ).toMatchObject({ disableSandbox: true, proxy: { enabled: false } }); + + // kibanaServer + expect( + ConfigSchema.validate({ kibanaServer: { hostname: 'Frodo' } }).kibanaServer + ).toMatchObject({ hostname: 'Frodo' }); + }); + + it(`logs the proper validation messages`, () => { + // kibanaServer + const throwValidationErr = () => ConfigSchema.validate({ kibanaServer: { hostname: '0' } }); + expect(throwValidationErr).toThrowError( + `[kibanaServer.hostname]: must not be "0" for the headless browser to correctly resolve the host` + ); + }); +}); diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts new file mode 100644 index 0000000000000..67d70c1513c15 --- /dev/null +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import moment from 'moment'; + +const KibanaServerSchema = schema.object({ + hostname: schema.maybe( + schema.string({ + validate(value) { + if (value === '0') { + return 'must not be "0" for the headless browser to correctly resolve the host'; + } + }, + hostname: true, + }) + ), + port: schema.maybe(schema.number()), + protocol: schema.maybe( + schema.string({ + validate(value) { + if (!/^https?$/.test(value)) { + return 'must be "http" or "https"'; + } + }, + }) + ), +}); // default values are all dynamic in createConfig$ + +const QueueSchema = schema.object({ + indexInterval: schema.string({ defaultValue: 'week' }), + pollEnabled: schema.boolean({ defaultValue: true }), + pollInterval: schema.number({ defaultValue: 3000 }), + pollIntervalErrorMultiplier: schema.number({ defaultValue: 10 }), + timeout: schema.number({ defaultValue: moment.duration(2, 'm').asMilliseconds() }), +}); + +const RulesSchema = schema.object({ + allow: schema.boolean(), + host: schema.maybe(schema.string()), + protocol: schema.maybe(schema.string()), +}); + +const CaptureSchema = schema.object({ + timeouts: schema.object({ + openUrl: schema.number({ defaultValue: 30000 }), + waitForElements: schema.number({ defaultValue: 30000 }), + renderComplete: schema.number({ defaultValue: 30000 }), + }), + networkPolicy: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + rules: schema.arrayOf(RulesSchema, { + defaultValue: [ + { host: undefined, allow: true, protocol: 'http:' }, + { host: undefined, allow: true, protocol: 'https:' }, + { host: undefined, allow: true, protocol: 'ws:' }, + { host: undefined, allow: true, protocol: 'wss:' }, + { host: undefined, allow: true, protocol: 'data:' }, + { host: undefined, allow: false, protocol: undefined }, // Default action is to deny! + ], + }), + }), + zoom: schema.number({ defaultValue: 2 }), + viewport: schema.object({ + width: schema.number({ defaultValue: 1950 }), + height: schema.number({ defaultValue: 1200 }), + }), + loadDelay: schema.number({ + defaultValue: moment.duration(3, 's').asMilliseconds(), + }), // TODO: use schema.duration + browser: schema.object({ + autoDownload: schema.conditional( + schema.contextRef('dist'), + true, + schema.boolean({ defaultValue: false }), + schema.boolean({ defaultValue: true }) + ), + chromium: schema.object({ + inspect: schema.conditional( + schema.contextRef('dist'), + true, + schema.boolean({ defaultValue: false }), + schema.maybe(schema.never()) + ), + disableSandbox: schema.maybe(schema.boolean()), // default value is dynamic in createConfig$ + proxy: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + server: schema.conditional( + schema.siblingRef('enabled'), + true, + schema.uri({ scheme: ['http', 'https'] }), + schema.maybe(schema.never()) + ), + bypass: schema.conditional( + schema.siblingRef('enabled'), + true, + schema.arrayOf(schema.string({ hostname: true })), + schema.maybe(schema.never()) + ), + }), + }), + type: schema.string({ defaultValue: 'chromium' }), + }), + maxAttempts: schema.conditional( + schema.contextRef('dist'), + true, + schema.number({ defaultValue: 3 }), + schema.number({ defaultValue: 1 }) + ), +}); + +const CsvSchema = schema.object({ + checkForFormulas: schema.boolean({ defaultValue: true }), + enablePanelActionDownload: schema.boolean({ defaultValue: true }), + maxSizeBytes: schema.number({ + defaultValue: 1024 * 1024 * 10, // 10MB + }), // TODO: use schema.byteSize + useByteOrderMarkEncoding: schema.boolean({ defaultValue: false }), + scroll: schema.object({ + duration: schema.string({ + defaultValue: '30s', + validate(value) { + if (!/^[0-9]+(d|h|m|s|ms|micros|nanos)$/.test(value)) { + return 'must be a duration string'; + } + }, + }), + size: schema.number({ defaultValue: 500 }), + }), +}); + +const EncryptionKeySchema = schema.conditional( + schema.contextRef('dist'), + true, + schema.maybe(schema.string({ minLength: 32 })), // default value is dynamic in createConfig$ + schema.string({ minLength: 32, defaultValue: 'a'.repeat(32) }) +); + +const RolesSchema = schema.object({ + allow: schema.arrayOf(schema.string(), { defaultValue: ['reporting_user'] }), +}); + +const IndexSchema = schema.string({ defaultValue: '.reporting' }); + +const PollSchema = schema.object({ + jobCompletionNotifier: schema.object({ + interval: schema.number({ + defaultValue: moment.duration(10, 's').asMilliseconds(), + }), // TODO: use schema.duration + intervalErrorMultiplier: schema.number({ defaultValue: 5 }), + }), + jobsRefresh: schema.object({ + interval: schema.number({ + defaultValue: moment.duration(5, 's').asMilliseconds(), + }), // TODO: use schema.duration + intervalErrorMultiplier: schema.number({ defaultValue: 5 }), + }), +}); + +export const ConfigSchema = schema.object({ + kibanaServer: KibanaServerSchema, + queue: QueueSchema, + capture: CaptureSchema, + csv: CsvSchema, + encryptionKey: EncryptionKeySchema, + roles: RolesSchema, + index: IndexSchema, + poll: PollSchema, +}); + +export type ConfigType = TypeOf; diff --git a/x-pack/plugins/reporting/server/index.ts b/x-pack/plugins/reporting/server/index.ts new file mode 100644 index 0000000000000..2b1844cf2e10e --- /dev/null +++ b/x-pack/plugins/reporting/server/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { ReportingPlugin } from './plugin'; + +export { config, ConfigSchema } from './config'; +export { ConfigType, PluginsSetup } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new ReportingPlugin(initializerContext); diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts new file mode 100644 index 0000000000000..905ed2b237c86 --- /dev/null +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; +import { ConfigType, createConfig$ } from './config'; + +export interface PluginsSetup { + /** @deprecated */ + __legacy: { + config$: Observable; + }; +} + +export class ReportingPlugin implements Plugin { + private readonly log: Logger; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.log = this.initializerContext.logger.get(); + } + + public async setup(core: CoreSetup): Promise { + return { + __legacy: { + config$: createConfig$(core, this.initializerContext, this.log).pipe(first()), + }, + }; + } + + public start() {} + public stop() {} +} + +export { ConfigType }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a5d2c20447ad5..8e4b74c4c08fd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12431,7 +12431,6 @@ "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "印刷用に最適化", "xpack.reporting.selfCheck.ok": "レポートプラグイン自己チェックOK!", "xpack.reporting.selfCheck.warning": "レポートプラグイン自己チェックで警告が発生しました: {err}", - "xpack.reporting.selfCheckEncryptionKey.warning": "{setting}のランダムキーを生成しています。保留中のレポートの再開が失敗しないように、kibana.ymlで{setting}を設定してください", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ef749e9218e11..4cf18fc287cc6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12435,7 +12435,6 @@ "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "打印优化", "xpack.reporting.selfCheck.ok": "Reporting 插件自检正常!", "xpack.reporting.selfCheck.warning": "Reporting 插件自检生成警告:{err}", - "xpack.reporting.selfCheckEncryptionKey.warning": "正在为 {setting} 生成随机密钥。要防止待处理报告在重新启动时失败,请在 kibana.yml 中设置 {setting}", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告", From 3d41ca6d276b982cdf1113fd293e6bbb876eea03 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 15 Apr 2020 17:00:56 -0700 Subject: [PATCH 29/44] [Reporting] Make usable default element positions (#63191) * [Reporting] Make usable default element posistions * revert unrelated changes * fix ts Co-authored-by: Elastic Machine --- .../export_types/common/layouts/layout.ts | 2 +- .../common/lib/screenshots/observable.test.ts | 184 +++++++++++++++++- .../common/lib/screenshots/observable.ts | 33 +++- .../common/lib/screenshots/types.ts | 1 + .../chromium/driver/chromium_driver.ts | 13 +- .../create_mock_browserdriverfactory.ts | 18 +- .../create_mock_layoutinstance.ts | 2 +- 7 files changed, 219 insertions(+), 34 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts b/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts index 2c43517dbcaa9..5cd2f3e636a93 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts @@ -54,7 +54,7 @@ export abstract class Layout { public abstract getPdfPageSize(pageSizeParams: PageSizeParams): string | Size; - public abstract getViewport(itemsCount: number): ViewZoomWidthHeight; + public abstract getViewport(itemsCount: number): ViewZoomWidthHeight | null; public abstract getBrowserZoom(): number; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts index 75ac3dca4ffa0..68d660257a56d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.test.ts @@ -22,6 +22,7 @@ import { LevelLogger } from '../../../../server/lib'; import { createMockBrowserDriverFactory, createMockLayoutInstance } from '../../../../test_helpers'; import { ConditionalHeaders, HeadlessChromiumDriver } from '../../../../types'; import { CaptureConfig } from '../../../../server/types'; +import * as contexts from './constants'; import { screenshotsObservableFactory } from './observable'; import { ElementsPositionAndAttribute } from './types'; @@ -57,10 +58,30 @@ describe('Screenshot Observable Pipeline', () => { expect(result).toMatchInlineSnapshot(` Array [ Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object { + "description": "Default ", + "title": "Default Mock Title", + }, + "position": Object { + "boundingClientRect": Object { + "height": 600, + "left": 0, + "top": 0, + "width": 800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], "error": undefined, "screenshots": Array [ Object { - "base64EncodedData": "allyourBase64 of boundingClientRect,scroll", + "base64EncodedData": "allyourBase64", "description": "Default ", "title": "Default Mock Title", }, @@ -95,6 +116,26 @@ describe('Screenshot Observable Pipeline', () => { expect(result).toMatchInlineSnapshot(` Array [ Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object { + "description": "Default ", + "title": "Default Mock Title", + }, + "position": Object { + "boundingClientRect": Object { + "height": 600, + "left": 0, + "top": 0, + "width": 800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], "error": undefined, "screenshots": Array [ Object { @@ -106,6 +147,26 @@ describe('Screenshot Observable Pipeline', () => { "timeRange": "Default GetTimeRange Result", }, Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object { + "description": "Default ", + "title": "Default Mock Title", + }, + "position": Object { + "boundingClientRect": Object { + "height": 600, + "left": 0, + "top": 0, + "width": 800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], "error": undefined, "screenshots": Array [ Object { @@ -150,10 +211,27 @@ describe('Screenshot Observable Pipeline', () => { await expect(getScreenshot()).resolves.toMatchInlineSnapshot(` Array [ Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 200, + "left": 0, + "top": 0, + "width": 200, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], "error": [Error: An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. Error: Mock error!], "screenshots": Array [ Object { - "base64EncodedData": "allyourBase64 of boundingClientRect,scroll", + "base64EncodedData": "allyourBase64", "description": undefined, "title": undefined, }, @@ -161,10 +239,27 @@ describe('Screenshot Observable Pipeline', () => { "timeRange": null, }, Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 200, + "left": 0, + "top": 0, + "width": 200, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], "error": [Error: An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. Error: Mock error!], "screenshots": Array [ Object { - "base64EncodedData": "allyourBase64 of boundingClientRect,scroll", + "base64EncodedData": "allyourBase64", "description": undefined, "title": undefined, }, @@ -208,10 +303,27 @@ describe('Screenshot Observable Pipeline', () => { await expect(getScreenshot()).resolves.toMatchInlineSnapshot(` Array [ Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 200, + "left": 0, + "top": 0, + "width": 200, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], "error": "Instant timeout has fired!", "screenshots": Array [ Object { - "base64EncodedData": "allyourBase64 of boundingClientRect,scroll", + "base64EncodedData": "allyourBase64", "description": undefined, "title": undefined, }, @@ -221,5 +333,69 @@ describe('Screenshot Observable Pipeline', () => { ] `); }); + + it(`uses defaults for element positions and size when Kibana page is not ready`, async () => { + // mocks + const mockBrowserEvaluate = jest.fn(); + mockBrowserEvaluate.mockImplementation(() => { + const lastCallIndex = mockBrowserEvaluate.mock.calls.length - 1; + const { context: mockCall } = mockBrowserEvaluate.mock.calls[lastCallIndex][1]; + + if (mockCall === contexts.CONTEXT_ELEMENTATTRIBUTES) { + return Promise.resolve(null); + } else { + return Promise.resolve(); + } + }); + mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, { + evaluate: mockBrowserEvaluate, + }); + mockLayout.getViewport = () => null; + + // test + const getScreenshots$ = screenshotsObservableFactory(mockConfig, mockBrowserDriverFactory); + const getScreenshot = async () => { + return await getScreenshots$({ + logger, + urls: ['/welcome/home/start/index.php3?page=./home.php3'], + conditionalHeaders: {} as ConditionalHeaders, + layout: mockLayout, + browserTimezone: 'UTC', + }).toPromise(); + }; + + await expect(getScreenshot()).resolves.toMatchInlineSnapshot(` + Array [ + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 1200, + "left": 0, + "top": 0, + "width": 1800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": undefined, + "screenshots": Array [ + Object { + "base64EncodedData": "allyourBase64", + "description": undefined, + "title": undefined, + }, + ], + "timeRange": undefined, + }, + ] + `); + }); }); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts index 53a11c18abd79..519a3289395b9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts @@ -18,6 +18,9 @@ import { ScreenSetupData, ScreenshotObservableOpts, ScreenshotResults } from './ import { waitForRenderComplete } from './wait_for_render'; import { waitForVisualizations } from './wait_for_visualizations'; +const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200; +const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800; + export function screenshotsObservableFactory( captureConfig: CaptureConfig, browserDriverFactory: HeadlessChromiumDriverFactory @@ -42,7 +45,7 @@ export function screenshotsObservableFactory( mergeMap(() => openUrl(captureConfig, driver, url, conditionalHeaders, logger)), mergeMap(() => getNumberOfItems(captureConfig, driver, layout, logger)), mergeMap(async itemsCount => { - const viewport = layout.getViewport(itemsCount); + const viewport = layout.getViewport(itemsCount) || getDefaultViewPort(); await Promise.all([ driver.setViewport(viewport, logger), waitForVisualizations(captureConfig, driver, itemsCount, layout, logger), @@ -83,7 +86,12 @@ export function screenshotsObservableFactory( : getDefaultElementPosition(layout.getViewport(1)); const screenshots = await getScreenshots(driver, elements, logger); const { timeRange, error: setupError } = data; - return { timeRange, screenshots, error: setupError }; + return { + timeRange, + screenshots, + error: setupError, + elementsPositionAndAttributes: elements, + }; } ) ); @@ -97,17 +105,30 @@ export function screenshotsObservableFactory( }; } +/* + * If Kibana is showing a non-HTML error message, the viewport might not be + * provided by the browser. + */ +const getDefaultViewPort = () => ({ + height: DEFAULT_SCREENSHOT_CLIP_HEIGHT, + width: DEFAULT_SCREENSHOT_CLIP_WIDTH, + zoom: 1, +}); /* * If an error happens setting up the page, we don't know if there actually * are any visualizations showing. These defaults should help capture the page * enough for the user to see the error themselves */ -const getDefaultElementPosition = ({ height, width }: { height: number; width: number }) => [ - { +const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => { + const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT; + const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH; + + const defaultObject = { position: { boundingClientRect: { top: 0, left: 0, height, width }, scroll: { x: 0, y: 0 }, }, attributes: {}, - }, -]; + }; + return [defaultObject]; +}; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts index 76613c2d631d6..e113a5d228cd7 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts @@ -45,4 +45,5 @@ export interface ScreenshotResults { timeRange: TimeRange | null; screenshots: Screenshot[]; error?: Error; + elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 4b80e129c04da..dfaa87021c31c 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -190,19 +190,14 @@ export class HeadlessChromiumDriver { } public async screenshot(elementPosition: ElementPosition): Promise { - let clip; - if (elementPosition) { - const { boundingClientRect, scroll = { x: 0, y: 0 } } = elementPosition; - clip = { + const { boundingClientRect, scroll } = elementPosition; + const screenshot = await this.page.screenshot({ + clip: { x: boundingClientRect.left + scroll.x, y: boundingClientRect.top + scroll.y, height: boundingClientRect.height, width: boundingClientRect.width, - }; - } - - const screenshot = await this.page.screenshot({ - clip, + }, }); return screenshot.toString('base64'); diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts index 6e95bed2ecf92..1be10f6a2056f 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts @@ -34,7 +34,7 @@ const getMockElementsPositionAndAttributes = ( ): ElementsPositionAndAttribute[] => [ { position: { - boundingClientRect: { top: 0, left: 0, width: 10, height: 11 }, + boundingClientRect: { top: 0, left: 0, width: 800, height: 600 }, scroll: { x: 0, y: 0 }, }, attributes: { title, description }, @@ -78,7 +78,7 @@ mockBrowserEvaluate.mockImplementation(() => { }); const mockScreenshot = jest.fn(); mockScreenshot.mockImplementation((item: ElementsPositionAndAttribute) => { - return Promise.resolve(`allyourBase64 of ${Object.keys(item)}`); + return Promise.resolve(`allyourBase64`); }); const getCreatePage = (driver: HeadlessChromiumDriver) => jest.fn().mockImplementation(() => Rx.of({ driver, exit$: Rx.never() })); @@ -94,31 +94,23 @@ export const createMockBrowserDriverFactory = async ( logger: Logger, opts: Partial = {} ): Promise => { - const captureConfig = { + const captureConfig: CaptureConfig = { timeouts: { openUrl: 30000, waitForElements: 30000, renderComplete: 30000 }, browser: { type: 'chromium', chromium: { inspect: false, disableSandbox: false, - userDataDir: '/usr/data/dir', - viewport: { width: 12, height: 12 }, proxy: { enabled: false, server: undefined, bypass: undefined }, }, autoDownload: false, - inspect: true, - userDataDir: '/usr/data/dir', - viewport: { width: 12, height: 12 }, - disableSandbox: false, - proxy: { enabled: false, server: undefined, bypass: undefined }, - maxScreenshotDimension: undefined, }, networkPolicy: { enabled: true, rules: [] }, viewport: { width: 800, height: 600 }, loadDelay: 2000, - zoom: 1, + zoom: 2, maxAttempts: 1, - } as CaptureConfig; + }; const binaryPath = '/usr/local/share/common/secure/'; const mockBrowserDriverFactory = await createDriverFactory(binaryPath, logger, captureConfig); diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts index be60b56dcc0c1..81090e7616501 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_layoutinstance.ts @@ -12,7 +12,7 @@ import { CaptureConfig } from '../server/types'; export const createMockLayoutInstance = (captureConfig: CaptureConfig) => { const mockLayout = createLayout(captureConfig, { id: LayoutTypes.PRESERVE_LAYOUT, - dimensions: { height: 12, width: 12 }, + dimensions: { height: 100, width: 100 }, }) as LayoutInstance; mockLayout.selectors = { renderComplete: 'renderedSelector', From 31ed266d733d408052dbd5ad396ff28d62ea822a Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 16 Apr 2020 00:53:22 -0600 Subject: [PATCH 30/44] [SIEM] [Cases] Insert timeline and reporters/tags in table bug fixes (#63642) --- .../insert_timeline_popover/index.test.tsx | 3 +- .../insert_timeline_popover/index.tsx | 3 +- .../timeline/properties/helpers.tsx | 7 +++ .../case/use_get_reporters.test.tsx | 13 +++++ .../containers/case/use_get_tags.test.tsx | 24 +++++++-- .../public/containers/case/use_get_tags.tsx | 13 +++-- .../pages/case/components/all_cases/index.tsx | 31 +++++++++--- .../all_cases/table_filters.test.tsx | 50 +++++++++++++++++-- .../components/all_cases/table_filters.tsx | 29 +++++++++-- 9 files changed, 149 insertions(+), 24 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx index adac26a8ac92b..e63bce388ae80 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx @@ -26,6 +26,7 @@ const mockLocationWithState = { state: { insertTimeline: { timelineId: 'timeline-id', + timelineSavedObjectId: '34578-3497-5893-47589-34759', timelineTitle: 'Timeline title', }, }, @@ -49,7 +50,7 @@ describe('Insert timeline popover ', () => { payload: { id: 'timeline-id', show: false }, type: 'x-pack/siem/local/timeline/SHOW_TIMELINE', }); - expect(onTimelineChange).toBeCalledWith('Timeline title', 'timeline-id'); + expect(onTimelineChange).toBeCalledWith('Timeline title', '34578-3497-5893-47589-34759'); }); it('should do nothing when router state', () => { jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx index cf1a4ebec9bb6..573e010868bab 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx @@ -23,6 +23,7 @@ interface InsertTimelinePopoverProps { interface RouterState { insertTimeline: { timelineId: string; + timelineSavedObjectId: string; timelineTitle: string; }; } @@ -46,7 +47,7 @@ export const InsertTimelinePopoverComponent: React.FC = ({ ); onTimelineChange( routerState.insertTimeline.timelineTitle, - routerState.insertTimeline.timelineId + routerState.insertTimeline.timelineSavedObjectId ); setRouterState(null); } diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx index 0a2ab5c9d186a..6f7e1f782d3f6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx @@ -21,6 +21,7 @@ import React, { useCallback } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; +import { useSelector } from 'react-redux'; import { Note } from '../../../lib/note'; import { Notes } from '../../notes'; @@ -29,6 +30,8 @@ import { NOTES_PANEL_WIDTH } from './notes_size'; import { ButtonContainer, DescriptionContainer, LabelText, NameField, StyledStar } from './styles'; import * as i18n from './translations'; import { SiemPageName } from '../../../pages/home/types'; +import { timelineSelectors } from '../../../store/timeline'; +import { State } from '../../../store'; export const historyToolTip = 'The chronological history of actions related to this timeline'; export const streamLiveToolTip = 'Update the Timeline as new data arrives'; @@ -121,6 +124,9 @@ interface NewCaseProps { export const NewCase = React.memo(({ onClosePopover, timelineId, timelineTitle }) => { const history = useHistory(); + const { savedObjectId } = useSelector((state: State) => + timelineSelectors.selectTimeline(state, timelineId) + ); const handleClick = useCallback(() => { onClosePopover(); history.push({ @@ -128,6 +134,7 @@ export const NewCase = React.memo(({ onClosePopover, timelineId, t state: { insertTimeline: { timelineId, + timelineSavedObjectId: savedObjectId, timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE, }, }, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx index 3629fbc60e4d3..27b963eb6cb54 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx @@ -61,6 +61,19 @@ describe('useGetReporters', () => { }); }); + it('refetch reporters', async () => { + const spyOnGetReporters = jest.spyOn(api, 'getReporters'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetReporters() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.fetchReporters(); + expect(spyOnGetReporters).toHaveBeenCalledTimes(2); + }); + }); + it('unhappy path', async () => { const spyOnGetReporters = jest.spyOn(api, 'getReporters'); spyOnGetReporters.mockImplementation(() => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx index 3df83d1c8a596..2d70c4390e4dd 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx @@ -5,7 +5,7 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useGetTags, TagsState } from './use_get_tags'; +import { useGetTags, UseGetTags } from './use_get_tags'; import { tags } from './mock'; import * as api from './api'; @@ -20,12 +20,13 @@ describe('useGetTags', () => { it('init', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); await waitForNextUpdate(); expect(result.current).toEqual({ tags: [], isLoading: true, isError: false, + fetchTags: result.current.fetchTags, }); }); }); @@ -33,7 +34,7 @@ describe('useGetTags', () => { it('calls getTags api', async () => { const spyOnGetTags = jest.spyOn(api, 'getTags'); await act(async () => { - const { waitForNextUpdate } = renderHook(() => useGetTags()); + const { waitForNextUpdate } = renderHook(() => useGetTags()); await waitForNextUpdate(); await waitForNextUpdate(); expect(spyOnGetTags).toBeCalledWith(abortCtrl.signal); @@ -42,17 +43,29 @@ describe('useGetTags', () => { it('fetch tags', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); await waitForNextUpdate(); await waitForNextUpdate(); expect(result.current).toEqual({ tags, isLoading: false, isError: false, + fetchTags: result.current.fetchTags, }); }); }); + it('refetch tags', async () => { + const spyOnGetTags = jest.spyOn(api, 'getTags'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.fetchTags(); + expect(spyOnGetTags).toHaveBeenCalledTimes(2); + }); + }); + it('unhappy path', async () => { const spyOnGetTags = jest.spyOn(api, 'getTags'); spyOnGetTags.mockImplementation(() => { @@ -60,7 +73,7 @@ describe('useGetTags', () => { }); await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); await waitForNextUpdate(); await waitForNextUpdate(); @@ -68,6 +81,7 @@ describe('useGetTags', () => { tags: [], isLoading: false, isError: true, + fetchTags: result.current.fetchTags, }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx index 7c58316ac3fe9..99bb65fa160f7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx @@ -20,6 +20,10 @@ type Action = | { type: 'FETCH_SUCCESS'; payload: string[] } | { type: 'FETCH_FAILURE' }; +export interface UseGetTags extends TagsState { + fetchTags: () => void; +} + const dataFetchReducer = (state: TagsState, action: Action): TagsState => { switch (action.type) { case 'FETCH_INIT': @@ -47,7 +51,7 @@ const dataFetchReducer = (state: TagsState, action: Action): TagsState => { }; const initialData: string[] = []; -export const useGetTags = (): TagsState => { +export const useGetTags = (): UseGetTags => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: true, isError: false, @@ -55,7 +59,7 @@ export const useGetTags = (): TagsState => { }); const [, dispatchToaster] = useStateToaster(); - useEffect(() => { + const callFetch = () => { let didCancel = false; const abortCtrl = new AbortController(); @@ -82,6 +86,9 @@ export const useGetTags = (): TagsState => { abortCtrl.abort(); didCancel = true; }; + }; + useEffect(() => { + callFetch(); }, []); - return state; + return { ...state, fetchTags: callFetch }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index c50b7d8c17abc..a6eca717a82a3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { EuiBasicTable, EuiButton, @@ -129,13 +129,25 @@ export const AllCases = React.memo(({ userCanCrud }) => { id: '', }); const [deleteBulk, setDeleteBulk] = useState([]); - - const refreshCases = useCallback(() => { - refetchCases(); - fetchCasesStatus(); - setSelectedCases([]); - setDeleteBulk([]); - }, []); + const filterRefetch = useRef<() => void>(); + const setFilterRefetch = useCallback( + (refetchFilter: () => void) => { + filterRefetch.current = refetchFilter; + }, + [filterRefetch.current] + ); + const refreshCases = useCallback( + (dataRefresh = true) => { + if (dataRefresh) refetchCases(); + fetchCasesStatus(); + setSelectedCases([]); + setDeleteBulk([]); + if (filterRefetch.current != null) { + filterRefetch.current(); + } + }, + [filterOptions, queryParams, filterRefetch.current] + ); useEffect(() => { if (isDeleted) { @@ -247,6 +259,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { }; } setQueryParams(newQueryParams); + refreshCases(false); }, [queryParams] ); @@ -259,6 +272,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { setQueryParams({ sortField: SortFieldCase.createdAt }); } setFilters(newFilterOptions); + refreshCases(false); }, [filterOptions, queryParams] ); @@ -347,6 +361,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { tags: filterOptions.tags, status: filterOptions.status, }} + setFilterRefetch={setFilterRefetch} /> {isCasesLoading && isDataEmpty ? (
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx index 615d052347203..21dcc9732440d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx @@ -19,17 +19,20 @@ jest.mock('../../../../containers/case/use_get_tags'); const onFilterChanged = jest.fn(); const fetchReporters = jest.fn(); +const fetchTags = jest.fn(); +const setFilterRefetch = jest.fn(); const props = { countClosedCases: 1234, countOpenCases: 99, onFilterChanged, initial: DEFAULT_FILTER_OPTIONS, + setFilterRefetch, }; describe('CasesTableFilters ', () => { beforeEach(() => { jest.resetAllMocks(); - (useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'] }); + (useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags }); (useGetReporters as jest.Mock).mockReturnValue({ reporters: ['casetester'], respReporters: [{ username: 'casetester' }], @@ -57,7 +60,7 @@ describe('CasesTableFilters ', () => { .text() ).toEqual('Closed cases (1234)'); }); - it('should call onFilterChange when tags change', () => { + it('should call onFilterChange when selected tags change', () => { const wrapper = mount( @@ -74,7 +77,7 @@ describe('CasesTableFilters ', () => { expect(onFilterChanged).toBeCalledWith({ tags: ['coke'] }); }); - it('should call onFilterChange when reporters change', () => { + it('should call onFilterChange when selected reporters change', () => { const wrapper = mount( @@ -118,4 +121,45 @@ describe('CasesTableFilters ', () => { expect(onFilterChanged).toBeCalledWith({ status: 'closed' }); }); + it('should call on load setFilterRefetch', () => { + mount( + + + + ); + expect(setFilterRefetch).toHaveBeenCalled(); + }); + it('should remove tag from selected tags when tag no longer exists', () => { + const ourProps = { + ...props, + initial: { + ...DEFAULT_FILTER_OPTIONS, + tags: ['pepsi', 'rc'], + }, + }; + mount( + + + + ); + expect(onFilterChanged).toHaveBeenCalledWith({ tags: ['pepsi'] }); + }); + it('should remove reporter from selected reporters when reporter no longer exists', () => { + const ourProps = { + ...props, + initial: { + ...DEFAULT_FILTER_OPTIONS, + reporters: [ + { username: 'casetester', full_name: null, email: null }, + { username: 'batman', full_name: null, email: null }, + ], + }, + }; + mount( + + + + ); + expect(onFilterChanged).toHaveBeenCalledWith({ reporters: [{ username: 'casetester' }] }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx index da477a56c0a22..901fb133753e8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { isEqual } from 'lodash/fp'; import { EuiFieldSearch, @@ -25,6 +25,7 @@ interface CasesTableFiltersProps { countOpenCases: number | null; onFilterChanged: (filterOptions: Partial) => void; initial: FilterOptions; + setFilterRefetch: (val: () => void) => void; } /** @@ -41,6 +42,7 @@ const CasesTableFiltersComponent = ({ countOpenCases, onFilterChanged, initial = defaultInitial, + setFilterRefetch, }: CasesTableFiltersProps) => { const [selectedReporters, setSelectedReporters] = useState( initial.reporters.map(r => r.full_name ?? r.username ?? '') @@ -48,8 +50,29 @@ const CasesTableFiltersComponent = ({ const [search, setSearch] = useState(initial.search); const [selectedTags, setSelectedTags] = useState(initial.tags); const [showOpenCases, setShowOpenCases] = useState(initial.status === 'open'); - const { tags } = useGetTags(); - const { reporters, respReporters } = useGetReporters(); + const { tags, fetchTags } = useGetTags(); + const { reporters, respReporters, fetchReporters } = useGetReporters(); + const refetch = useCallback(() => { + fetchTags(); + fetchReporters(); + }, [fetchReporters, fetchTags]); + useEffect(() => { + if (setFilterRefetch != null) { + setFilterRefetch(refetch); + } + }, [refetch, setFilterRefetch]); + useEffect(() => { + if (selectedReporters.length) { + const newReporters = selectedReporters.filter(r => reporters.includes(r)); + handleSelectedReporters(newReporters); + } + }, [reporters]); + useEffect(() => { + if (selectedTags.length) { + const newTags = selectedTags.filter(t => tags.includes(t)); + handleSelectedTags(newTags); + } + }, [tags]); const handleSelectedReporters = useCallback( newReporters => { From ce9c6e258a5f4b32f9db2de8638beac5e1f5dd4c Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 16 Apr 2020 10:32:28 +0300 Subject: [PATCH 31/44] Index pattern management UI -> TypeScript and New Platform Ready (indexed_fields_table) (#63364) * MIgrated indexed_fields_table to typescript. * Updated docs * Fixed comments * Fixed types --- ...a-public.indexpatternfield.indexpattern.md | 11 + ...n-plugins-data-public.indexpatternfield.md | 1 + .../indexed_fields_table.test.tsx.snap} | 12 +- .../table.test.tsx.snap} | 13 +- .../components/table/{index.js => index.ts} | 0 .../components/table/table.js | 231 -------------- .../table.test.js => table.test.tsx} | 72 +++-- .../components/table/table.tsx | 281 ++++++++++++++++++ .../{index.js => index.ts} | 0 ....test.js => indexed_fields_table.test.tsx} | 46 ++- ...elds_table.js => indexed_fields_table.tsx} | 49 +-- ...ormat.test.js => get_field_format.test.ts} | 17 +- ...et_field_format.js => get_field_format.ts} | 3 +- .../lib/{index.js => index.ts} | 0 .../indexed_fields_table/types.ts | 25 ++ .../public/index_patterns/fields/field.ts | 1 + src/plugins/data/public/public.api.md | 2 + 17 files changed, 457 insertions(+), 307 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/{__jest__/__snapshots__/indexed_fields_table.test.js.snap => __snapshots__/indexed_fields_table.test.tsx.snap} (90%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/{__jest__/__snapshots__/table.test.js.snap => __snapshots__/table.test.tsx.snap} (92%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/{index.js => index.ts} (100%) delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.js rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/{__jest__/table.test.js => table.test.tsx} (66%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.tsx rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/{index.js => index.ts} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/{__jest__/indexed_fields_table.test.js => indexed_fields_table.test.tsx} (70%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/{indexed_fields_table.js => indexed_fields_table.tsx} (68%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/{__jest__/get_field_format.test.js => get_field_format.test.ts} (74%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/{get_field_format.js => get_field_format.ts} (84%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/{index.js => index.ts} (100%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/types.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md new file mode 100644 index 0000000000000..d1a1ee0905c6e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) + +## IndexPatternField.indexPattern property + +Signature: + +```typescript +indexPattern?: IndexPattern; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 121ae80734dfd..df0de6ce0e541 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -27,6 +27,7 @@ export declare class Field implements IFieldType | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | string[] | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | boolean | | | [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | any | | +| [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) | | IndexPattern | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | | [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | | diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap similarity index 90% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap index b38036a0c2bf0..db2a032b1e4d9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/__snapshots__/indexed_fields_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap @@ -16,9 +16,10 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` "excluded": false, "format": undefined, "indexPattern": undefined, - "info": undefined, + "info": Array [], "name": "Elastic", "searchable": true, + "type": "name", }, ] } @@ -42,7 +43,7 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` "excluded": false, "format": undefined, "indexPattern": undefined, - "info": undefined, + "info": Array [], "name": "timestamp", "type": "date", }, @@ -68,16 +69,17 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "excluded": false, "format": undefined, "indexPattern": undefined, - "info": undefined, + "info": Array [], "name": "Elastic", "searchable": true, + "type": "name", }, Object { "displayName": "timestamp", "excluded": false, "format": undefined, "indexPattern": undefined, - "info": undefined, + "info": Array [], "name": "timestamp", "type": "date", }, @@ -86,7 +88,7 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "excluded": false, "format": undefined, "indexPattern": undefined, - "info": undefined, + "info": Array [], "name": "conflictingField", "type": "conflict", }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap index f3aa2c5da4b67..2d51b1722cfb2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap @@ -98,19 +98,26 @@ exports[`Table should render normally 1`] = ` Array [ Object { "displayName": "Elastic", - "info": Object {}, + "excluded": false, + "format": "", + "info": Array [], "name": "Elastic", "searchable": true, + "type": "name", }, Object { "displayName": "timestamp", - "info": Object {}, + "excluded": false, + "format": "YYYY-MM-DD", + "info": Array [], "name": "timestamp", "type": "date", }, Object { "displayName": "conflictingField", - "info": Object {}, + "excluded": false, + "format": "", + "info": Array [], "name": "conflictingField", "type": "conflict", }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.js deleted file mode 100644 index 29e160cf1c182..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.js +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { EuiIcon, EuiInMemoryTable, EuiIconTip } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -export class Table extends PureComponent { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - items: PropTypes.array.isRequired, - editField: PropTypes.func.isRequired, - }; - - renderBooleanTemplate(value, label) { - return value ? : ; - } - - renderFieldName(name, field) { - const { indexPattern } = this.props; - - const infoLabel = i18n.translate( - 'kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel', - { defaultMessage: 'Additional field information' } - ); - const timeLabel = i18n.translate( - 'kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel', - { defaultMessage: 'Primary time field' } - ); - const timeContent = i18n.translate( - 'kbn.management.editIndexPattern.fields.table.primaryTimeTooltip', - { defaultMessage: 'This field represents the time that events occurred.' } - ); - - return ( - - {name} - {field.info && field.info.length ? ( - -   - ( -
{info}
- ))} - /> -
- ) : null} - {indexPattern.timeFieldName === name ? ( - -   - - - ) : null} -
- ); - } - - renderFieldType(type, isConflict) { - const label = i18n.translate('kbn.management.editIndexPattern.fields.table.multiTypeAria', { - defaultMessage: 'Multiple type field', - }); - const content = i18n.translate( - 'kbn.management.editIndexPattern.fields.table.multiTypeTooltip', - { - defaultMessage: - 'The type of this field changes across indices. It is unavailable for many analysis functions.', - } - ); - - return ( - - {type} - {isConflict ? ( - -   - - - ) : ( - '' - )} - - ); - } - - render() { - const { items, editField } = this.props; - - const pagination = { - initialPageSize: 10, - pageSizeOptions: [5, 10, 25, 50], - }; - - const columns = [ - { - field: 'displayName', - name: i18n.translate('kbn.management.editIndexPattern.fields.table.nameHeader', { - defaultMessage: 'Name', - }), - dataType: 'string', - sortable: true, - render: (value, field) => { - return this.renderFieldName(value, field); - }, - width: '38%', - 'data-test-subj': 'indexedFieldName', - }, - { - field: 'type', - name: i18n.translate('kbn.management.editIndexPattern.fields.table.typeHeader', { - defaultMessage: 'Type', - }), - dataType: 'string', - sortable: true, - render: value => { - return this.renderFieldType(value, value === 'conflict'); - }, - 'data-test-subj': 'indexedFieldType', - }, - { - field: 'format', - name: i18n.translate('kbn.management.editIndexPattern.fields.table.formatHeader', { - defaultMessage: 'Format', - }), - dataType: 'string', - sortable: true, - }, - { - field: 'searchable', - name: i18n.translate('kbn.management.editIndexPattern.fields.table.searchableHeader', { - defaultMessage: 'Searchable', - }), - description: i18n.translate( - 'kbn.management.editIndexPattern.fields.table.searchableDescription', - { defaultMessage: 'These fields can be used in the filter bar' } - ), - dataType: 'boolean', - sortable: true, - render: value => - this.renderBooleanTemplate( - value, - i18n.translate('kbn.management.editIndexPattern.fields.table.isSearchableAria', { - defaultMessage: 'Is searchable', - }) - ), - }, - { - field: 'aggregatable', - name: i18n.translate('kbn.management.editIndexPattern.fields.table.aggregatableLabel', { - defaultMessage: 'Aggregatable', - }), - description: i18n.translate( - 'kbn.management.editIndexPattern.fields.table.aggregatableDescription', - { defaultMessage: 'These fields can be used in visualization aggregations' } - ), - dataType: 'boolean', - sortable: true, - render: value => - this.renderBooleanTemplate( - value, - i18n.translate('kbn.management.editIndexPattern.fields.table.isAggregatableAria', { - defaultMessage: 'Is aggregatable', - }) - ), - }, - { - field: 'excluded', - name: i18n.translate('kbn.management.editIndexPattern.fields.table.excludedLabel', { - defaultMessage: 'Excluded', - }), - description: i18n.translate( - 'kbn.management.editIndexPattern.fields.table.excludedDescription', - { defaultMessage: 'Fields that are excluded from _source when it is fetched' } - ), - dataType: 'boolean', - sortable: true, - render: value => - this.renderBooleanTemplate( - value, - i18n.translate('kbn.management.editIndexPattern.fields.table.isExcludedAria', { - defaultMessage: 'Is excluded', - }) - ), - }, - { - name: '', - actions: [ - { - name: i18n.translate('kbn.management.editIndexPattern.fields.table.editLabel', { - defaultMessage: 'Edit', - }), - description: i18n.translate( - 'kbn.management.editIndexPattern.fields.table.editDescription', - { defaultMessage: 'Edit' } - ), - icon: 'pencil', - onClick: editField, - type: 'icon', - 'data-test-subj': 'editFieldFormat', - }, - ], - width: '40px', - }, - ]; - - return ( - - ); - } -} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx similarity index 66% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx index 4fd9ef7485bdf..d0479a9a9e032 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx @@ -19,31 +19,53 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; - -import { Table } from '../table'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; +import { IndexedFieldItem } from '../../types'; +import { Table } from './table'; const indexPattern = { timeFieldName: 'timestamp', -}; - -const items = [ - { name: 'Elastic', displayName: 'Elastic', searchable: true, info: {} }, - { name: 'timestamp', displayName: 'timestamp', type: 'date', info: {} }, - { name: 'conflictingField', displayName: 'conflictingField', type: 'conflict', info: {} }, +} as IIndexPattern; + +const items: IndexedFieldItem[] = [ + { + name: 'Elastic', + displayName: 'Elastic', + searchable: true, + info: [], + type: 'name', + excluded: false, + format: '', + }, + { + name: 'timestamp', + displayName: 'timestamp', + type: 'date', + info: [], + excluded: false, + format: 'YYYY-MM-DD', + }, + { + name: 'conflictingField', + displayName: 'conflictingField', + type: 'conflict', + info: [], + excluded: false, + format: '', + }, ]; describe('Table', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + test('should render normally', () => { + const component = shallow( {}} /> ); expect(component).toMatchSnapshot(); }); - it('should render normal field name', async () => { - const component = shallowWithI18nProvider( + test('should render normal field name', () => { + const component = shallow(
{}} /> ); @@ -51,8 +73,8 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); - it('should render timestamp field name', async () => { - const component = shallowWithI18nProvider( + test('should render timestamp field name', () => { + const component = shallow(
{}} /> ); @@ -60,8 +82,8 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); - it('should render the boolean template (true)', async () => { - const component = shallowWithI18nProvider( + test('should render the boolean template (true)', () => { + const component = shallow(
{}} /> ); @@ -69,8 +91,8 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); - it('should render the boolean template (false)', async () => { - const component = shallowWithI18nProvider( + test('should render the boolean template (false)', () => { + const component = shallow(
{}} /> ); @@ -78,8 +100,8 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); - it('should render normal type', async () => { - const component = shallowWithI18nProvider( + test('should render normal type', () => { + const component = shallow(
{}} /> ); @@ -87,8 +109,8 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); - it('should render conflicting type', async () => { - const component = shallowWithI18nProvider( + test('should render conflicting type', () => { + const component = shallow(
{}} /> ); @@ -96,10 +118,10 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); - it('should allow edits', () => { + test('should allow edits', () => { const editField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.tsx new file mode 100644 index 0000000000000..aa8e8b8e13a07 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/table.tsx @@ -0,0 +1,281 @@ +/* + * 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, { PureComponent } from 'react'; + +import { EuiIcon, EuiInMemoryTable, EuiIconTip, EuiBasicTableColumn } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; +import { IndexedFieldItem } from '../../types'; + +// localized labels +const additionalInfoAriaLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel', + { defaultMessage: 'Additional field information' } +); + +const primaryTimeAriaLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel', + { defaultMessage: 'Primary time field' } +); + +const primaryTimeTooltip = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.primaryTimeTooltip', + { defaultMessage: 'This field represents the time that events occurred.' } +); + +const multiTypeAriaLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.multiTypeAria', + { + defaultMessage: 'Multiple type field', + } +); + +const multiTypeTooltip = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.multiTypeTooltip', + { + defaultMessage: + 'The type of this field changes across indices. It is unavailable for many analysis functions.', + } +); + +const nameHeader = i18n.translate('kbn.management.editIndexPattern.fields.table.nameHeader', { + defaultMessage: 'Name', +}); + +const typeHeader = i18n.translate('kbn.management.editIndexPattern.fields.table.typeHeader', { + defaultMessage: 'Type', +}); + +const formatHeader = i18n.translate('kbn.management.editIndexPattern.fields.table.formatHeader', { + defaultMessage: 'Format', +}); + +const searchableHeader = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.searchableHeader', + { + defaultMessage: 'Searchable', + } +); + +const searchableDescription = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.searchableDescription', + { defaultMessage: 'These fields can be used in the filter bar' } +); + +const isSearchableAriaLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.isSearchableAria', + { + defaultMessage: 'Is searchable', + } +); + +const aggregatableLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.aggregatableLabel', + { + defaultMessage: 'Aggregatable', + } +); + +const aggregatableDescription = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.aggregatableDescription', + { defaultMessage: 'These fields can be used in visualization aggregations' } +); + +const isAggregatableAriaLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.isAggregatableAria', + { + defaultMessage: 'Is aggregatable', + } +); + +const excludedLabel = i18n.translate('kbn.management.editIndexPattern.fields.table.excludedLabel', { + defaultMessage: 'Excluded', +}); + +const excludedDescription = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.excludedDescription', + { defaultMessage: 'Fields that are excluded from _source when it is fetched' } +); + +const isExcludedAriaLabel = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.isExcludedAria', + { + defaultMessage: 'Is excluded', + } +); + +const editLabel = i18n.translate('kbn.management.editIndexPattern.fields.table.editLabel', { + defaultMessage: 'Edit', +}); + +const editDescription = i18n.translate( + 'kbn.management.editIndexPattern.fields.table.editDescription', + { defaultMessage: 'Edit' } +); + +interface IndexedFieldProps { + indexPattern: IIndexPattern; + items: IndexedFieldItem[]; + editField: (field: IndexedFieldItem) => void; +} + +export class Table extends PureComponent { + renderBooleanTemplate(value: string, arialLabel: string) { + return value ? : ; + } + + renderFieldName(name: string, field: IndexedFieldItem) { + const { indexPattern } = this.props; + + return ( + + {name} + {field.info && field.info.length ? ( + +   + ( +
{info}
+ ))} + /> +
+ ) : null} + {indexPattern.timeFieldName === name ? ( + +   + + + ) : null} +
+ ); + } + + renderFieldType(type: string, isConflict: boolean) { + return ( + + {type} + {isConflict ? ( + +   + + + ) : ( + '' + )} + + ); + } + + render() { + const { items, editField } = this.props; + + const pagination = { + initialPageSize: 10, + pageSizeOptions: [5, 10, 25, 50], + }; + + const columns: Array> = [ + { + field: 'displayName', + name: nameHeader, + dataType: 'string', + sortable: true, + render: (value: string, field: IndexedFieldItem) => { + return this.renderFieldName(value, field); + }, + width: '38%', + 'data-test-subj': 'indexedFieldName', + }, + { + field: 'type', + name: typeHeader, + dataType: 'string', + sortable: true, + render: (value: string) => { + return this.renderFieldType(value, value === 'conflict'); + }, + 'data-test-subj': 'indexedFieldType', + }, + { + field: 'format', + name: formatHeader, + dataType: 'string', + sortable: true, + }, + { + field: 'searchable', + name: searchableHeader, + description: searchableDescription, + dataType: 'boolean', + sortable: true, + render: (value: string) => this.renderBooleanTemplate(value, isSearchableAriaLabel), + }, + { + field: 'aggregatable', + name: aggregatableLabel, + description: aggregatableDescription, + dataType: 'boolean', + sortable: true, + render: (value: string) => this.renderBooleanTemplate(value, isAggregatableAriaLabel), + }, + { + field: 'excluded', + name: excludedLabel, + description: excludedDescription, + dataType: 'boolean', + sortable: true, + render: (value: string) => this.renderBooleanTemplate(value, isExcludedAriaLabel), + }, + { + name: '', + actions: [ + { + name: editLabel, + description: editDescription, + icon: 'pencil', + onClick: editField, + type: 'icon', + 'data-test-subj': 'editFieldFormat', + }, + ], + width: '40px', + }, + ]; + + return ( + + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/indexed_fields_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx similarity index 70% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/indexed_fields_table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 26e271ea477da..f8b78a92e098e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/__jest__/indexed_fields_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -19,8 +19,8 @@ import React from 'react'; import { shallow } from 'enzyme'; - -import { IndexedFieldsTable } from '../indexed_fields_table'; +import { IndexPatternField, IIndexPattern } from '../../../../../../../../../plugins/data/public'; +import { IndexedFieldsTable } from './indexed_fields_table'; jest.mock('@elastic/eui', () => ({ EuiFlexGroup: 'eui-flex-group', @@ -29,7 +29,7 @@ jest.mock('@elastic/eui', () => ({ EuiInMemoryTable: 'eui-in-memory-table', })); -jest.mock('../components/table', () => ({ +jest.mock('./components/table', () => ({ // Note: this seems to fix React complaining about non lowercase attributes Table: () => { return 'table'; @@ -37,27 +37,37 @@ jest.mock('../components/table', () => ({ })); const helpers = { - redirectToRoute: () => {}, + redirectToRoute: (obj: any) => {}, + getFieldInfo: () => [], }; const fields = [ - { name: 'Elastic', displayName: 'Elastic', searchable: true }, + { + name: 'Elastic', + displayName: 'Elastic', + searchable: true, + type: 'name', + }, { name: 'timestamp', displayName: 'timestamp', type: 'date' }, { name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' }, -]; +] as IndexPatternField[]; -const indexPattern = { +const indexPattern = ({ getNonScriptedFields: () => fields, -}; +} as unknown) as IIndexPattern; describe('IndexedFieldsTable', () => { - it('should render normally', async () => { + test('should render normally', async () => { const component = shallow( {}} + fieldWildcardMatcher={() => { + return () => false; + }} + indexedFieldTypeFilter="" + fieldFilter="" /> ); @@ -67,13 +77,17 @@ describe('IndexedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should filter based on the query bar', async () => { + test('should filter based on the query bar', async () => { const component = shallow( {}} + fieldWildcardMatcher={() => { + return () => false; + }} + indexedFieldTypeFilter="" + fieldFilter="" /> ); @@ -84,13 +98,17 @@ describe('IndexedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should filter based on the type filter', async () => { + test('should filter based on the type filter', async () => { const component = shallow( {}} + fieldWildcardMatcher={() => { + return () => false; + }} + indexedFieldTypeFilter="" + fieldFilter="" /> ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx similarity index 68% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 074e5784f3dae..7c2bb565615d7 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -18,26 +18,33 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { createSelector } from 'reselect'; - +import { IndexPatternField, IIndexPattern } from '../../../../../../../../../plugins/data/public'; import { Table } from './components/table'; import { getFieldFormat } from './lib'; +import { IndexedFieldItem } from './types'; -export class IndexedFieldsTable extends Component { - static propTypes = { - fields: PropTypes.array.isRequired, - indexPattern: PropTypes.object.isRequired, - fieldFilter: PropTypes.string, - indexedFieldTypeFilter: PropTypes.string, - helpers: PropTypes.shape({ - redirectToRoute: PropTypes.func.isRequired, - getFieldInfo: PropTypes.func, - }), - fieldWildcardMatcher: PropTypes.func.isRequired, +interface IndexedFieldsTableProps { + fields: IndexPatternField[]; + indexPattern: IIndexPattern; + fieldFilter?: string; + indexedFieldTypeFilter?: string; + helpers: { + redirectToRoute: (obj: any) => void; + getFieldInfo: (indexPattern: IIndexPattern, field: string) => string[]; }; + fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean; +} + +interface IndexedFieldsTableState { + fields: IndexedFieldItem[]; +} - constructor(props) { +export class IndexedFieldsTable extends Component< + IndexedFieldsTableProps, + IndexedFieldsTableState +> { + constructor(props: IndexedFieldsTableProps) { super(props); this.state = { @@ -45,7 +52,7 @@ export class IndexedFieldsTable extends Component { }; } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: IndexedFieldsTableProps) { if (nextProps.fields !== this.props.fields) { this.setState({ fields: this.mapFields(nextProps.fields), @@ -53,10 +60,11 @@ export class IndexedFieldsTable extends Component { } } - mapFields(fields) { + mapFields(fields: IndexPatternField[]): IndexedFieldItem[] { const { indexPattern, fieldWildcardMatcher, helpers } = this.props; const sourceFilters = - indexPattern.sourceFilters && indexPattern.sourceFilters.map(f => f.value); + indexPattern.sourceFilters && + indexPattern.sourceFilters.map((f: Record) => f.value); const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters || []); return ( @@ -76,9 +84,10 @@ export class IndexedFieldsTable extends Component { } getFilteredFields = createSelector( - state => state.fields, - (state, props) => props.fieldFilter, - (state, props) => props.indexedFieldTypeFilter, + (state: IndexedFieldsTableState) => state.fields, + (state: IndexedFieldsTableState, props: IndexedFieldsTableProps) => props.fieldFilter, + (state: IndexedFieldsTableState, props: IndexedFieldsTableProps) => + props.indexedFieldTypeFilter, (fields, fieldFilter, indexedFieldTypeFilter) => { if (fieldFilter) { const normalizedFieldFilter = fieldFilter.toLowerCase(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/__jest__/get_field_format.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.test.ts similarity index 74% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/__jest__/get_field_format.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.test.ts index 7090f70199919..fc7477c074ac2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/__jest__/get_field_format.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.test.ts @@ -17,9 +17,10 @@ * under the License. */ -import { getFieldFormat } from '../get_field_format'; +import { IIndexPattern } from '../../../../../../../../../../plugins/data/public'; +import { getFieldFormat } from './get_field_format'; -const indexPattern = { +const indexPattern = ({ fieldFormatMap: { Elastic: { type: { @@ -27,26 +28,26 @@ const indexPattern = { }, }, }, -}; +} as unknown) as IIndexPattern; describe('getFieldFormat', () => { - it('should handle no arguments', () => { + test('should handle no arguments', () => { expect(getFieldFormat()).toEqual(''); }); - it('should handle no field name', () => { + test('should handle no field name', () => { expect(getFieldFormat(indexPattern)).toEqual(''); }); - it('should handle empty name', () => { + test('should handle empty name', () => { expect(getFieldFormat(indexPattern, '')).toEqual(''); }); - it('should handle undefined field name', () => { + test('should handle undefined field name', () => { expect(getFieldFormat(indexPattern, 'none')).toEqual(undefined); }); - it('should retrieve field format', () => { + test('should retrieve field format', () => { expect(getFieldFormat(indexPattern, 'Elastic')).toEqual('string'); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.ts similarity index 84% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.ts index 9402694bb1371..1d6f267430f07 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/get_field_format.ts @@ -18,8 +18,9 @@ */ import { get } from 'lodash'; +import { IIndexPattern } from '../../../../../../../../../../plugins/data/public'; -export function getFieldFormat(indexPattern, fieldName) { +export function getFieldFormat(indexPattern?: IIndexPattern, fieldName?: string): string { return indexPattern && fieldName ? get(indexPattern, ['fieldFormatMap', fieldName, 'type', 'title']) : ''; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/lib/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/types.ts new file mode 100644 index 0000000000000..f27f4608bf5d5 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/types.ts @@ -0,0 +1,25 @@ +/* + * 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 { IFieldType } from '../../../../../../../../../plugins/data/public'; + +export interface IndexedFieldItem extends IFieldType { + info: string[]; + excluded: boolean; +} diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index 6370dcdf2db6f..0fb92393d56f7 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -44,6 +44,7 @@ export class Field implements IFieldType { scripted?: boolean; subType?: IFieldSubType; displayName?: string; + indexPattern?: IndexPattern; format: any; $$spec: FieldSpec; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 62967a7071d82..6e06d063e7ebe 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -943,6 +943,8 @@ export class IndexPatternField implements IFieldType { // (undocumented) format: any; // (undocumented) + indexPattern?: IndexPattern; + // (undocumented) lang?: string; // (undocumented) name: string; From ee1cebdbe1411815953e7b9db4913a5c96a26bb4 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 16 Apr 2020 10:36:09 +0300 Subject: [PATCH 32/44] MIgrated index_header to react (#63490) --- .../create_edit_field/create_edit_field.html | 6 - .../create_edit_field/create_edit_field.js | 3 + .../edit_index_pattern.html | 7 +- .../edit_index_pattern/edit_index_pattern.js | 33 ++++- .../index_header/{index.js => index.ts} | 2 +- .../index_header/index_header.html | 59 -------- .../index_header/index_header.js | 40 ------ .../index_header/index_header.tsx | 134 ++++++++++++++++++ 8 files changed, 171 insertions(+), 113 deletions(-) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/{index.js => index.ts} (94%) delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.html delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.js create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.html b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.html index fee8525a6af41..2decaf423183e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.html +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.html @@ -1,11 +1,5 @@
-
- -
-
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js index 3569e9caf4e27..95d6cb6878e53 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js @@ -34,6 +34,8 @@ import { FieldEditor } from 'ui/field_editor'; import { I18nContext } from 'ui/i18n'; import { i18n } from '@kbn/i18n'; +import { IndexHeader } from '../index_header'; + const REACT_FIELD_EDITOR_ID = 'reactFieldEditor'; const renderFieldEditor = ( $scope, @@ -49,6 +51,7 @@ const renderFieldEditor = ( render( + - +

diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index c65054f583ef2..69184a513f53a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import './index_header'; +import { IndexHeader } from './index_header'; import './create_edit_field'; import { docTitle } from 'ui/doc_title'; import { KbnUrlProvider } from 'ui/url'; @@ -44,6 +44,7 @@ import { createEditIndexPatternPageStateContainer } from './edit_index_pattern_s const REACT_SOURCE_FILTERS_DOM_ELEMENT_ID = 'reactSourceFiltersTable'; const REACT_INDEXED_FIELDS_DOM_ELEMENT_ID = 'reactIndexedFieldsTable'; const REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID = 'reactScriptedFieldsTable'; +const REACT_INDEX_HEADER_DOM_ELEMENT_ID = 'reactIndexHeader'; const TAB_INDEXED_FIELDS = 'indexedFields'; const TAB_SCRIPTED_FIELDS = 'scriptedFields'; @@ -160,6 +161,33 @@ function destroyIndexedFieldsTable() { node && unmountComponentAtNode(node); } +function destroyIndexHeader() { + const node = document.getElementById(REACT_INDEX_HEADER_DOM_ELEMENT_ID); + node && unmountComponentAtNode(node); +} + +function renderIndexHeader($scope, config) { + $scope.$$postDigest(() => { + const node = document.getElementById(REACT_INDEX_HEADER_DOM_ELEMENT_ID); + if (!node) { + return; + } + + render( + + + , + node + ); + }); +} + function handleTabChange($scope, newTab) { destroyIndexedFieldsTable(); destroySourceFiltersTable(); @@ -391,6 +419,9 @@ uiModules destroyIndexedFieldsTable(); destroyScriptedFieldsTable(); destroySourceFiltersTable(); + destroyIndexHeader(); destroyState(); }); + + renderIndexHeader($scope, config); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index.ts index 7c288286bd61e..44c05a55b36f9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index.ts @@ -17,4 +17,4 @@ * under the License. */ -import './index_header'; +export { IndexHeader } from './index_header'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.html b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.html deleted file mode 100644 index d6b91d96f13d3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.html +++ /dev/null @@ -1,59 +0,0 @@ -

-
- -

- - {{indexPattern.title}} -

-
- -
- - - - - -
-
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.js deleted file mode 100644 index 87bce06c1146c..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiModules } from 'ui/modules'; -import template from './index_header.html'; -uiModules.get('apps/management').directive('kbnManagementIndexPatternsHeader', function(config) { - return { - restrict: 'E', - template, - replace: true, - scope: { - indexPattern: '=', - setDefault: '&', - refreshFields: '&', - delete: '&', - }, - link: function($scope, $el, attrs) { - $scope.delete = attrs.delete ? $scope.delete : null; - $scope.setDefault = attrs.setDefault ? $scope.setDefault : null; - $scope.refreshFields = attrs.refreshFields ? $scope.refreshFields : null; - config.bindToScope($scope, 'defaultIndex'); - }, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx new file mode 100644 index 0000000000000..866d10ecb0e19 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index_header/index_header.tsx @@ -0,0 +1,134 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiToolTip, + EuiFlexItem, + EuiIcon, + EuiTitle, + EuiButtonIcon, +} from '@elastic/eui'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; + +interface IndexHeaderProps { + defaultIndex: string; + indexPattern: IIndexPattern; + setDefault?: () => void; + refreshFields?: () => void; + deleteIndexPattern?: () => void; +} + +const setDefaultAriaLabel = i18n.translate('kbn.management.editIndexPattern.setDefaultAria', { + defaultMessage: 'Set as default index.', +}); + +const setDefaultTooltip = i18n.translate('kbn.management.editIndexPattern.setDefaultTooltip', { + defaultMessage: 'Set as default index.', +}); + +const refreshAriaLabel = i18n.translate('kbn.management.editIndexPattern.refreshAria', { + defaultMessage: 'Reload field list.', +}); + +const refreshTooltip = i18n.translate('kbn.management.editIndexPattern.refreshTooltip', { + defaultMessage: 'Refresh field list.', +}); + +const removeAriaLabel = i18n.translate('kbn.management.editIndexPattern.removeAria', { + defaultMessage: 'Remove index pattern.', +}); + +const removeTooltip = i18n.translate('kbn.management.editIndexPattern.removeTooltip', { + defaultMessage: 'Remove index pattern.', +}); + +export function IndexHeader({ + defaultIndex, + indexPattern, + setDefault, + refreshFields, + deleteIndexPattern, +}: IndexHeaderProps) { + return ( + + + + {defaultIndex === indexPattern.id && ( + + + + )} + + +

{indexPattern.title}

+
+
+
+
+ + + {setDefault && ( + + + + + + )} + + {refreshFields && ( + + + + + + )} + + {deleteIndexPattern && ( + + + + + + )} + + +
+ ); +} From 3ade2d358d2d819736981432b413a84b10c22b7e Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Thu, 16 Apr 2020 00:42:25 -0700 Subject: [PATCH 33/44] Closes #63109 for Service Map by resetting edges styles for the selected node (#63655) --- .../apm/public/components/app/ServiceMap/Cytoscape.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index e4b656ae8160d..53c86f92ee557 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -211,7 +211,9 @@ export function Cytoscape({ resetConnectedEdgeStyle(event.target); }; const unselectHandler: cytoscape.EventHandler = event => { - resetConnectedEdgeStyle(); + resetConnectedEdgeStyle( + serviceName ? event.cy.getElementById(serviceName) : undefined + ); }; const debugHandler: cytoscape.EventHandler = event => { const debugEnabled = sessionStorage.getItem('apm_debug') === 'true'; From bb9f8845ae69bf402f9870f0697577c5e8325ef8 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 16 Apr 2020 08:45:38 +0100 Subject: [PATCH 34/44] [alerting] Adds an alertServices mock and uses it in siem, monitoring and uptime (#63489) Work on #61313 has revealed that we don't have amock for AlertServices, which creates coupling between us and any solution depending on us, which makes it harder to make changes in our own code. This PR adds mocks and uses them in SIEM, Monitoring and Uptime, so that we can make future changes without having to change outside solutions. --- .../rules_notification_alert_type.test.ts | 43 ++-- .../signals/get_filter.test.ts | 28 +-- .../signals/get_input_output_index.test.ts | 50 ++-- .../signals/search_after_bulk_create.test.ts | 36 ++- .../signals/signal_rule_alert_type.test.ts | 93 ++----- .../signals/single_bulk_create.test.ts | 22 +- .../signals/single_search_after.test.ts | 14 +- x-pack/plugins/alerting/server/mocks.ts | 39 +++ .../metric_threshold_executor.test.ts | 237 ++++++++++-------- .../server/alerts/cluster_state.test.ts | 23 +- .../server/alerts/license_expiration.test.ts | 22 +- .../lib/alerts/__tests__/status_check.test.ts | 59 ++--- 12 files changed, 305 insertions(+), 361 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts index 50ac10347e062..f537b22bac1eb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts @@ -4,42 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { loggerMock } from 'src/core/server/logging/logger.mock'; import { getResult } from '../routes/__mocks__/request_responses'; import { rulesNotificationAlertType } from './rules_notification_alert_type'; import { buildSignalsSearchQuery } from './build_signals_query'; -import { AlertInstance } from '../../../../../../../plugins/alerting/server'; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; import { NotificationExecutorOptions } from './types'; jest.mock('./build_signals_query'); describe('rules_notification_alert_type', () => { let payload: NotificationExecutorOptions; let alert: ReturnType; - let alertInstanceMock: Record; - let alertInstanceFactoryMock: () => AlertInstance; - let savedObjectsClient: ReturnType; let logger: ReturnType; - let callClusterMock: jest.Mock; + let alertServices: AlertServicesMock; beforeEach(() => { - alertInstanceMock = { - scheduleActions: jest.fn(), - replaceState: jest.fn(), - }; - alertInstanceMock.replaceState.mockReturnValue(alertInstanceMock); - alertInstanceFactoryMock = jest.fn().mockReturnValue(alertInstanceMock); - callClusterMock = jest.fn(); - savedObjectsClient = savedObjectsClientMock.create(); + alertServices = alertsMock.createAlertServices(); logger = loggerMock.create(); payload = { alertId: '1111', - services: { - savedObjectsClient, - alertInstanceFactory: alertInstanceFactoryMock, - callCluster: callClusterMock, - }, + services: alertServices, params: { ruleAlertId: '2222' }, state: {}, spaceId: '', @@ -58,7 +43,7 @@ describe('rules_notification_alert_type', () => { describe('executor', () => { it('throws an error if rule alert was not found', async () => { - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', attributes: {}, type: 'type', @@ -72,13 +57,13 @@ describe('rules_notification_alert_type', () => { it('should call buildSignalsSearchQuery with proper params', async () => { const ruleAlert = getResult(); - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], attributes: ruleAlert, }); - callClusterMock.mockResolvedValue({ + alertServices.callCluster.mockResolvedValue({ count: 0, }); @@ -96,36 +81,38 @@ describe('rules_notification_alert_type', () => { it('should not call alertInstanceFactory if signalsCount was 0', async () => { const ruleAlert = getResult(); - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], attributes: ruleAlert, }); - callClusterMock.mockResolvedValue({ + alertServices.callCluster.mockResolvedValue({ count: 0, }); await alert.executor(payload); - expect(alertInstanceFactoryMock).not.toHaveBeenCalled(); + expect(alertServices.alertInstanceFactory).not.toHaveBeenCalled(); }); it('should call scheduleActions if signalsCount was greater than 0', async () => { const ruleAlert = getResult(); - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], attributes: ruleAlert, }); - callClusterMock.mockResolvedValue({ + alertServices.callCluster.mockResolvedValue({ count: 10, }); await alert.executor(payload); - expect(alertInstanceFactoryMock).toHaveBeenCalled(); + expect(alertServices.alertInstanceFactory).toHaveBeenCalled(); + + const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; expect(alertInstanceMock.replaceState).toHaveBeenCalledWith( expect.objectContaining({ signals_count: 10 }) ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts index 86d1278031695..510667b211d25 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts @@ -5,42 +5,28 @@ */ import { getQueryFilter, getFilter } from './get_filter'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { PartialFilter } from '../types'; -import { AlertServices } from '../../../../../../../plugins/alerting/server'; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; describe('get_filter', () => { - let savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ - attributes: { - query: { query: 'host.name: linux', language: 'kuery' }, - filters: [], - }, - })); - let servicesMock: AlertServices = { - savedObjectsClient, - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - }; + let servicesMock: AlertServicesMock; beforeAll(() => { jest.resetAllMocks(); }); beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock = alertsMock.createAlertServices(); + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: { query: { query: 'host.name: linux', language: 'kuery' }, language: 'kuery', filters: [], }, })); - servicesMock = { - savedObjectsClient, - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - }; }); afterEach(() => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts index 18286dc7754e0..ccd882228d4de 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts @@ -4,22 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { getInputIndex } from './get_input_output_index'; import { defaultIndexPattern } from '../../../../default_index_pattern'; -import { AlertServices } from '../../../../../../../plugins/alerting/server'; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; describe('get_input_output_index', () => { - let savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ - attributes: {}, - })); - let servicesMock: AlertServices = { - savedObjectsClient, - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - }; + let servicesMock: AlertServicesMock; beforeAll(() => { jest.resetAllMocks(); @@ -30,20 +21,21 @@ describe('get_input_output_index', () => { }); beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock = alertsMock.createAlertServices(); + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: {}, })); - servicesMock = { - savedObjectsClient, - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - }; }); describe('getInputOutputIndex', () => { test('Returns inputIndex if inputIndex is passed in', async () => { - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: {}, })); const inputIndex = await getInputIndex(servicesMock, '8.0.0', ['test-input-index-1']); @@ -51,7 +43,10 @@ describe('get_input_output_index', () => { }); test('Returns a saved object inputIndex if passed in inputIndex is undefined', async () => { - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: { [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'], }, @@ -61,7 +56,10 @@ describe('get_input_output_index', () => { }); test('Returns a saved object inputIndex if passed in inputIndex is null', async () => { - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: { [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'], }, @@ -71,7 +69,10 @@ describe('get_input_output_index', () => { }); test('Returns a saved object inputIndex default from constants if inputIndex passed in is null and the key is also null', async () => { - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: { [DEFAULT_INDEX_KEY]: null, }, @@ -81,7 +82,10 @@ describe('get_input_output_index', () => { }); test('Returns a saved object inputIndex default from constants if inputIndex passed in is undefined and the key is also null', async () => { - savedObjectsClient.get = jest.fn().mockImplementation(() => ({ + servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ + id, + type, + references: [], attributes: { [DEFAULT_INDEX_KEY]: null, }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 81600b0b8dd9b..9e2f36fe2653a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -16,20 +16,16 @@ import { } from './__mocks__/es_results'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; import uuid from 'uuid'; -export const mockService = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), -}; - describe('searchAfterAndBulkCreate', () => { + let mockService: AlertServicesMock; let inputIndexPattern: string[] = []; beforeEach(() => { jest.clearAllMocks(); inputIndexPattern = ['auditbeat-*']; + mockService = alertsMock.createAlertServices(); }); test('if successful with empty search results', async () => { @@ -65,7 +61,7 @@ describe('searchAfterAndBulkCreate', () => { const sampleParams = sampleRuleAlertParams(30); const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); mockService.callCluster - .mockReturnValueOnce({ + .mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -79,8 +75,8 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) - .mockReturnValueOnce({ + .mockResolvedValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) + .mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -94,8 +90,8 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) - .mockReturnValueOnce({ + .mockResolvedValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) + .mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -139,7 +135,7 @@ describe('searchAfterAndBulkCreate', () => { test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + mockService.callCluster.mockResolvedValue(sampleBulkCreateDuplicateResult); const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, @@ -169,7 +165,7 @@ describe('searchAfterAndBulkCreate', () => { test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ + mockService.callCluster.mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -212,7 +208,7 @@ describe('searchAfterAndBulkCreate', () => { test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ + mockService.callCluster.mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -256,7 +252,7 @@ describe('searchAfterAndBulkCreate', () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); mockService.callCluster - .mockReturnValueOnce({ + .mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -270,7 +266,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); + .mockResolvedValueOnce(sampleDocSearchResultsNoSortId()); const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, @@ -301,7 +297,7 @@ describe('searchAfterAndBulkCreate', () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); mockService.callCluster - .mockReturnValueOnce({ + .mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -315,7 +311,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockReturnValueOnce(sampleEmptyDocSearchResults()); + .mockResolvedValueOnce(sampleEmptyDocSearchResults()); const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, @@ -346,7 +342,7 @@ describe('searchAfterAndBulkCreate', () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); mockService.callCluster - .mockReturnValueOnce({ + .mockResolvedValueOnce({ took: 100, errors: false, items: [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 03fb5832fdf42..31b407da111ea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -5,11 +5,10 @@ */ import moment from 'moment'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { loggerMock } from 'src/core/server/logging/logger.mock'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { signalRulesAlertType } from './signal_rule_alert_type'; -import { AlertInstance } from '../../../../../../../plugins/alerting/server'; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; import { ruleStatusServiceFactory } from './rule_status_service'; import { getGapBetweenRuns } from './utils'; import { RuleExecutorOptions } from './types'; @@ -28,18 +27,9 @@ jest.mock('../notifications/schedule_notification_actions'); jest.mock('./find_ml_signals'); jest.mock('./bulk_create_ml_signals'); -const getPayload = ( - ruleAlert: RuleAlertType, - alertInstanceFactoryMock: () => AlertInstance, - savedObjectsClient: ReturnType, - callClusterMock: jest.Mock -) => ({ +const getPayload = (ruleAlert: RuleAlertType, services: AlertServicesMock) => ({ alertId: ruleAlert.id, - services: { - savedObjectsClient, - alertInstanceFactory: alertInstanceFactoryMock, - callCluster: callClusterMock, - }, + services, params: { ...ruleAlert.params, actions: [], @@ -78,24 +68,14 @@ describe('rules_notification_alert_type', () => { modulesProvider: jest.fn(), resultsServiceProvider: jest.fn(), }; - let payload: RuleExecutorOptions; + let payload: jest.Mocked; let alert: ReturnType; - let alertInstanceMock: Record; - let alertInstanceFactoryMock: () => AlertInstance; - let savedObjectsClient: ReturnType; let logger: ReturnType; - let callClusterMock: jest.Mock; + let alertServices: AlertServicesMock; let ruleStatusService: Record; beforeEach(() => { - alertInstanceMock = { - scheduleActions: jest.fn(), - replaceState: jest.fn(), - }; - alertInstanceMock.replaceState.mockReturnValue(alertInstanceMock); - alertInstanceFactoryMock = jest.fn().mockReturnValue(alertInstanceMock); - callClusterMock = jest.fn(); - savedObjectsClient = savedObjectsClientMock.create(); + alertServices = alertsMock.createAlertServices(); logger = loggerMock.create(); ruleStatusService = { success: jest.fn(), @@ -111,20 +91,20 @@ describe('rules_notification_alert_type', () => { searchAfterTimes: [], createdSignalsCount: 10, }); - callClusterMock.mockResolvedValue({ + alertServices.callCluster.mockResolvedValue({ hits: { total: { value: 10 }, }, }); const ruleAlert = getResult(); - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], attributes: ruleAlert, }); - payload = getPayload(ruleAlert, alertInstanceFactoryMock, savedObjectsClient, callClusterMock); + payload = getPayload(ruleAlert, alertServices); alert = signalRulesAlertType({ logger, @@ -164,7 +144,7 @@ describe('rules_notification_alert_type', () => { }, ]; - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], @@ -195,7 +175,7 @@ describe('rules_notification_alert_type', () => { }, ]; - savedObjectsClient.get.mockResolvedValue({ + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], @@ -214,12 +194,7 @@ describe('rules_notification_alert_type', () => { describe('ML rule', () => { it('should throw an error if ML plugin was not available', async () => { const ruleAlert = getMlResult(); - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); + payload = getPayload(ruleAlert, alertServices); alert = signalRulesAlertType({ logger, version, @@ -235,12 +210,7 @@ describe('rules_notification_alert_type', () => { it('should throw an error if machineLearningJobId or anomalyThreshold was not null', async () => { const ruleAlert = getMlResult(); ruleAlert.params.anomalyThreshold = undefined; - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); + payload = getPayload(ruleAlert, alertServices); await alert.executor(payload); expect(logger.error).toHaveBeenCalled(); expect(logger.error.mock.calls[0][0]).toContain( @@ -250,12 +220,7 @@ describe('rules_notification_alert_type', () => { it('should throw an error if Machine learning job summary was null', async () => { const ruleAlert = getMlResult(); - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); + payload = getPayload(ruleAlert, alertServices); jobsSummaryMock.mockResolvedValue([]); await alert.executor(payload); expect(logger.warn).toHaveBeenCalled(); @@ -268,12 +233,7 @@ describe('rules_notification_alert_type', () => { it('should log an error if Machine learning job was not started', async () => { const ruleAlert = getMlResult(); - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); + payload = getPayload(ruleAlert, alertServices); jobsSummaryMock.mockResolvedValue([ { id: 'some_job_id', @@ -297,12 +257,7 @@ describe('rules_notification_alert_type', () => { it('should not call ruleStatusService.success if no anomalies were found', async () => { const ruleAlert = getMlResult(); - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); + payload = getPayload(ruleAlert, alertServices); jobsSummaryMock.mockResolvedValue([]); (findMlSignals as jest.Mock).mockResolvedValue({ hits: { @@ -320,12 +275,7 @@ describe('rules_notification_alert_type', () => { it('should call ruleStatusService.success if signals were created', async () => { const ruleAlert = getMlResult(); - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); + payload = getPayload(ruleAlert, alertServices); jobsSummaryMock.mockResolvedValue([ { id: 'some_job_id', @@ -360,13 +310,8 @@ describe('rules_notification_alert_type', () => { id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', }, ]; - payload = getPayload( - ruleAlert, - alertInstanceFactoryMock, - savedObjectsClient, - callClusterMock - ); - savedObjectsClient.get.mockResolvedValue({ + payload = getPayload(ruleAlert, alertServices); + alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', references: [], diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 45365b446cbf0..3401d7417ec62 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -16,17 +16,13 @@ import { sampleBulkCreateErrorResult, sampleDocWithAncestors, } from './__mocks__/es_results'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; import { singleBulkCreate, filterDuplicateRules } from './single_bulk_create'; - -export const mockService = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), -}; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; describe('singleBulkCreate', () => { + const mockService: AlertServicesMock = alertsMock.createAlertServices(); + beforeEach(() => { jest.clearAllMocks(); }); @@ -135,7 +131,7 @@ describe('singleBulkCreate', () => { test('create successful bulk create', async () => { const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ + mockService.callCluster.mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -169,7 +165,7 @@ describe('singleBulkCreate', () => { test('create successful bulk create with docs with no versioning', async () => { const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ + mockService.callCluster.mockResolvedValueOnce({ took: 100, errors: false, items: [ @@ -203,7 +199,7 @@ describe('singleBulkCreate', () => { test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValue(false); + mockService.callCluster.mockResolvedValue(false); const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, @@ -230,7 +226,7 @@ describe('singleBulkCreate', () => { test('create successful bulk create when bulk create has duplicate errors', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + mockService.callCluster.mockResolvedValue(sampleBulkCreateDuplicateResult); const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, @@ -259,7 +255,7 @@ describe('singleBulkCreate', () => { test('create successful bulk create when bulk create has multiple error statuses', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; - mockService.callCluster.mockReturnValue(sampleBulkCreateErrorResult); + mockService.callCluster.mockResolvedValue(sampleBulkCreateErrorResult); const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, @@ -354,7 +350,7 @@ describe('singleBulkCreate', () => { test('create successful and returns proper createdItemsCount', async () => { const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + mockService.callCluster.mockResolvedValue(sampleBulkCreateDuplicateResult); const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts index 9b726c38d3d96..dbeab70595e4f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts @@ -4,28 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { sampleDocSearchResultsNoSortId, mockLogger, sampleDocSearchResultsWithSortId, } from './__mocks__/es_results'; import { singleSearchAfter } from './single_search_after'; - -export const mockService = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), -}; +import { alertsMock, AlertServicesMock } from '../../../../../../../plugins/alerting/server/mocks'; describe('singleSearchAfter', () => { + const mockService: AlertServicesMock = alertsMock.createAlertServices(); + beforeEach(() => { jest.clearAllMocks(); }); test('if singleSearchAfter works without a given sort id', async () => { let searchAfterSortId; - mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId); + mockService.callCluster.mockResolvedValue(sampleDocSearchResultsNoSortId); await expect( singleSearchAfter({ searchAfterSortId, @@ -41,7 +37,7 @@ describe('singleSearchAfter', () => { }); test('if singleSearchAfter works with a given sort id', async () => { const searchAfterSortId = '1234567891111'; - mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId); + mockService.callCluster.mockResolvedValue(sampleDocSearchResultsWithSortId); const { searchResult } = await singleSearchAfter({ searchAfterSortId, index: [], diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index 55ad722dcf881..a9e224142a632 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -6,6 +6,8 @@ import { alertsClientMock } from './alerts_client.mock'; import { PluginSetupContract, PluginStartContract } from './plugin'; +import { savedObjectsClientMock } from '../../../../src/core/server/mocks'; +import { AlertInstance } from './alert_instance'; export { alertsClientMock }; @@ -24,7 +26,44 @@ const createStartMock = () => { return mock; }; +export type AlertInstanceMock = jest.Mocked; +const createAlertInstanceFactoryMock = () => { + const mock = { + hasScheduledActions: jest.fn(), + isThrottled: jest.fn(), + getScheduledActionOptions: jest.fn(), + unscheduleActions: jest.fn(), + getState: jest.fn(), + scheduleActions: jest.fn(), + replaceState: jest.fn(), + updateLastScheduledActions: jest.fn(), + toJSON: jest.fn(), + toRaw: jest.fn(), + }; + + // support chaining + mock.replaceState.mockReturnValue(mock); + mock.unscheduleActions.mockReturnValue(mock); + mock.scheduleActions.mockReturnValue(mock); + + return (mock as unknown) as AlertInstanceMock; +}; + +const createAlertServicesMock = () => { + const alertInstanceFactoryMock = createAlertInstanceFactoryMock(); + return { + alertInstanceFactory: jest + .fn, [string]>() + .mockReturnValue(alertInstanceFactoryMock), + callCluster: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; +}; +export type AlertServicesMock = ReturnType; + export const alertsMock = { + createAlertInstanceFactory: createAlertInstanceFactoryMock, createSetup: createSetupMock, createStart: createStartMock, + createAlertServices: createAlertServicesMock, }; 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 38cd0cec145f9..000d0823311b3 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 @@ -8,62 +8,77 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { + alertsMock, + AlertServicesMock, + AlertInstanceMock, +} from '../../../../../alerting/server/mocks'; const executor = createMetricThresholdExecutor('test') as (opts: { params: AlertExecutorOptions['params']; services: { callCluster: AlertExecutorOptions['params']['callCluster'] }; }) => Promise; -const alertInstances = new Map(); -const services = { - callCluster(_: string, { body, index }: any) { - if (index === 'alternatebeat-*') return mocks.changedSourceIdResponse; - const metric = body.query.bool.filter[1]?.exists.field; - if (body.aggs.groupings) { - if (body.aggs.groupings.composite.after) { - return mocks.compositeEndResponse; - } - if (metric === 'test.metric.2') { - return mocks.alternateCompositeResponse; - } - return mocks.basicCompositeResponse; +const services: AlertServicesMock = alertsMock.createAlertServices(); +services.callCluster.mockImplementation((_: string, { body, index }: any) => { + if (index === 'alternatebeat-*') return mocks.changedSourceIdResponse; + const metric = body.query.bool.filter[1]?.exists.field; + if (body.aggs.groupings) { + if (body.aggs.groupings.composite.after) { + return mocks.compositeEndResponse; } if (metric === 'test.metric.2') { - return mocks.alternateMetricResponse; + return mocks.alternateCompositeResponse; } - return mocks.basicMetricResponse; - }, - alertInstanceFactory(instanceID: string) { - let state: any; - const actionQueue: any[] = []; - const instance = { - actionQueue: [], - get state() { - return state; - }, - get mostRecentAction() { - return actionQueue.pop(); - }, - }; - alertInstances.set(instanceID, instance); + return mocks.basicCompositeResponse; + } + if (metric === 'test.metric.2') { + return mocks.alternateMetricResponse; + } + return mocks.basicMetricResponse; +}); +services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => { + if (sourceId === 'alternate') return { - instanceID, - scheduleActions(id: string, action: any) { - actionQueue.push({ id, action }); - }, - replaceState(newState: any) { - state = newState; - }, + id: 'alternate', + attributes: { metricAlias: 'alternatebeat-*' }, + type, + references: [], }; - }, - savedObjectsClient: { - get(_: string, sourceId: string) { - if (sourceId === 'alternate') - return { id: 'alternate', attributes: { metricAlias: 'alternatebeat-*' } }; - return { id: 'default', attributes: { metricAlias: 'metricbeat-*' } }; - }, - }, -}; + return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] }; +}); + +interface AlertTestInstance { + instance: AlertInstanceMock; + actionQueue: any[]; + state: any; +} +const alertInstances = new Map(); +services.alertInstanceFactory.mockImplementation((instanceID: string) => { + const alertInstance: AlertTestInstance = { + instance: alertsMock.createAlertInstanceFactory(), + actionQueue: [], + state: {}, + }; + alertInstances.set(instanceID, alertInstance); + alertInstance.instance.replaceState.mockImplementation((newState: any) => { + alertInstance.state = newState; + return alertInstance.instance; + }); + alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => { + alertInstance.actionQueue.push({ id, action }); + return alertInstance.instance; + }); + return alertInstance.instance; +}); + +function mostRecentAction(id: string) { + return alertInstances.get(id)!.actionQueue.pop(); +} + +function getState(id: string) { + return alertInstances.get(id)!.state; +} const baseCriterion = { aggType: 'avg', @@ -90,65 +105,65 @@ describe('The metric threshold alert type', () => { }); test('alerts as expected with the > comparator', async () => { await execute(Comparator.GT, [0.75]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.GT, [1.5]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('alerts as expected with the < comparator', async () => { await execute(Comparator.LT, [1.5]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.LT, [0.75]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('alerts as expected with the >= comparator', async () => { await execute(Comparator.GT_OR_EQ, [0.75]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.GT_OR_EQ, [1.0]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.GT_OR_EQ, [1.5]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('alerts as expected with the <= comparator', async () => { await execute(Comparator.LT_OR_EQ, [1.5]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.LT_OR_EQ, [1.0]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.LT_OR_EQ, [0.75]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('alerts as expected with the between comparator', async () => { await execute(Comparator.BETWEEN, [0, 1.5]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.BETWEEN, [0, 0.75]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('reports expected values to the action context', async () => { await execute(Comparator.GT, [0.75]); - const mostRecentAction = alertInstances.get(instanceID).mostRecentAction; - expect(mostRecentAction.action.group).toBe('*'); - expect(mostRecentAction.action.valueOf.condition0).toBe(1); - expect(mostRecentAction.action.thresholdOf.condition0).toStrictEqual([0.75]); - expect(mostRecentAction.action.metricOf.condition0).toBe('test.metric.1'); + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.valueOf.condition0).toBe(1); + expect(action.thresholdOf.condition0).toStrictEqual([0.75]); + expect(action.metricOf.condition0).toBe('test.metric.1'); }); test('fetches the index pattern dynamically', async () => { await execute(Comparator.LT, [17], 'alternate'); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.LT, [1.5], 'alternate'); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); }); @@ -171,29 +186,29 @@ describe('The metric threshold alert type', () => { const instanceIdB = 'test-b'; test('sends an alert when all groups pass the threshold', async () => { await execute(Comparator.GT, [0.75]); - expect(alertInstances.get(instanceIdA).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceIdA).state.alertState).toBe(AlertStates.ALERT); - expect(alertInstances.get(instanceIdB).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceIdB).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceIdA).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceIdA).alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceIdB).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceIdB).alertState).toBe(AlertStates.ALERT); }); test('sends an alert when only some groups pass the threshold', async () => { await execute(Comparator.LT, [1.5]); - expect(alertInstances.get(instanceIdA).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceIdA).state.alertState).toBe(AlertStates.ALERT); - expect(alertInstances.get(instanceIdB).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceIdB).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceIdA).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceIdA).alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + expect(getState(instanceIdB).alertState).toBe(AlertStates.OK); }); test('sends no alert when no groups pass the threshold', async () => { await execute(Comparator.GT, [5]); - expect(alertInstances.get(instanceIdA).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceIdA).state.alertState).toBe(AlertStates.OK); - expect(alertInstances.get(instanceIdB).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceIdB).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceIdA)).toBe(undefined); + expect(getState(instanceIdA).alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + expect(getState(instanceIdB).alertState).toBe(AlertStates.OK); }); test('reports group values to the action context', async () => { await execute(Comparator.GT, [0.75]); - expect(alertInstances.get(instanceIdA).mostRecentAction.action.group).toBe('a'); - expect(alertInstances.get(instanceIdB).mostRecentAction.action.group).toBe('b'); + expect(mostRecentAction(instanceIdA).action.group).toBe('a'); + expect(mostRecentAction(instanceIdB).action.group).toBe('b'); }); }); @@ -226,34 +241,34 @@ describe('The metric threshold alert type', () => { test('sends an alert when all criteria cross the threshold', async () => { const instanceID = 'test-*'; await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); }); test('sends no alert when some, but not all, criteria cross the threshold', async () => { const instanceID = 'test-*'; await execute(Comparator.LT_OR_EQ, [1.0], [3.0]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('alerts only on groups that meet all criteria when querying with a groupBy parameter', async () => { const instanceIdA = 'test-a'; const instanceIdB = 'test-b'; await execute(Comparator.GT_OR_EQ, [1.0], [3.0], 'something'); - expect(alertInstances.get(instanceIdA).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceIdA).state.alertState).toBe(AlertStates.ALERT); - expect(alertInstances.get(instanceIdB).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceIdB).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceIdA).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceIdA).alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceIdB)).toBe(undefined); + expect(getState(instanceIdB).alertState).toBe(AlertStates.OK); }); test('sends all criteria to the action context', async () => { const instanceID = 'test-*'; await execute(Comparator.GT_OR_EQ, [1.0], [3.0]); - const mostRecentAction = alertInstances.get(instanceID).mostRecentAction; - expect(mostRecentAction.action.valueOf.condition0).toBe(1); - expect(mostRecentAction.action.valueOf.condition1).toBe(3.5); - expect(mostRecentAction.action.thresholdOf.condition0).toStrictEqual([1.0]); - expect(mostRecentAction.action.thresholdOf.condition1).toStrictEqual([3.0]); - expect(mostRecentAction.action.metricOf.condition0).toBe('test.metric.1'); - expect(mostRecentAction.action.metricOf.condition1).toBe('test.metric.2'); + const { action } = mostRecentAction(instanceID); + expect(action.valueOf.condition0).toBe(1); + expect(action.valueOf.condition1).toBe(3.5); + expect(action.thresholdOf.condition0).toStrictEqual([1.0]); + expect(action.thresholdOf.condition1).toStrictEqual([3.0]); + expect(action.metricOf.condition0).toBe('test.metric.1'); + expect(action.metricOf.condition1).toBe('test.metric.2'); }); }); describe('querying with the count aggregator', () => { @@ -275,11 +290,11 @@ describe('The metric threshold alert type', () => { }); test('alerts based on the doc_count value instead of the aggregatedValue', async () => { await execute(Comparator.GT, [2]); - expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute(Comparator.LT, [1.5]); - expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); - expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts index 6a9ca88437347..bcc1a8abe5cb0 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from 'src/core/server'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { getClusterState } from './cluster_state'; -import { AlertServices } from '../../../alerting/server'; import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } from './types'; import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; import { executeActions } from '../lib/alerts/cluster_state.lib'; import { AlertClusterStateState } from './enums'; +import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; jest.mock('../lib/alerts/cluster_state.lib', () => ({ executeActions: jest.fn(), @@ -26,18 +25,8 @@ jest.mock('../lib/alerts/get_prepared_alert', () => ({ }), })); -interface MockServices { - callCluster: jest.Mock; - alertInstanceFactory: jest.Mock; - savedObjectsClient: jest.Mock; -} - describe('getClusterState', () => { - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), - }; + const services: AlertServicesMock = alertsMock.createAlertServices(); const params: AlertCommonParams = { dateFormat: 'YYYY', @@ -107,7 +96,7 @@ describe('getClusterState', () => { it('should alert if green -> yellow', async () => { const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Yellow); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE), cluster, AlertClusterStateState.Yellow, emailAddress @@ -121,7 +110,7 @@ describe('getClusterState', () => { it('should alert if yellow -> green', async () => { const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Green); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE), cluster, AlertClusterStateState.Green, emailAddress, @@ -135,7 +124,7 @@ describe('getClusterState', () => { it('should alert if green -> red', async () => { const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Red); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE), cluster, AlertClusterStateState.Red, emailAddress @@ -149,7 +138,7 @@ describe('getClusterState', () => { it('should alert if red -> green', async () => { const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Green); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE), cluster, AlertClusterStateState.Green, emailAddress, diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 92047e300bc1f..f9d2ec3e1d48e 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -8,8 +8,6 @@ import moment from 'moment-timezone'; import { getLicenseExpiration } from './license_expiration'; import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { Logger } from 'src/core/server'; -import { AlertServices } from '../../../alerting/server'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { AlertCommonParams, AlertCommonState, @@ -18,6 +16,7 @@ import { } from './types'; import { executeActions } from '../lib/alerts/license_expiration.lib'; import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; +import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; jest.mock('../lib/alerts/license_expiration.lib', () => ({ executeActions: jest.fn(), @@ -32,18 +31,8 @@ jest.mock('../lib/alerts/get_prepared_alert', () => ({ }), })); -interface MockServices { - callCluster: jest.Mock; - alertInstanceFactory: jest.Mock; - savedObjectsClient: jest.Mock; -} - describe('getLicenseExpiration', () => { - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), - }; + const services: AlertServicesMock = alertsMock.createAlertServices(); const params: AlertCommonParams = { dateFormat: 'YYYY', @@ -106,6 +95,7 @@ describe('getLicenseExpiration', () => { } afterEach(() => { + jest.clearAllMocks(); (executeActions as jest.Mock).mockClear(); (getPreparedAlert as jest.Mock).mockClear(); }); @@ -135,7 +125,7 @@ describe('getLicenseExpiration', () => { const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION), cluster, moment.utc(expiryDateMS), dateFormat, @@ -157,7 +147,7 @@ describe('getLicenseExpiration', () => { const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS).toBe(0); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION), cluster, moment.utc(expiryDateMS), dateFormat, @@ -196,7 +186,7 @@ describe('getLicenseExpiration', () => { const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); expect(executeActions).toHaveBeenCalledWith( - undefined, + services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION), cluster, moment.utc(expiryDateMS), dateFormat, diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 08a3bc75fa8bd..2eb17d588d297 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -17,6 +17,7 @@ import { IRouter } from 'kibana/server'; import { UMServerLibs } from '../../lib'; import { UptimeCoreSetup } from '../../adapters'; import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; /** * The alert takes some dependencies as parameters; these are things like @@ -44,16 +45,21 @@ const bootstrapDependencies = (customRequests?: any) => { */ const mockOptions = ( params = { numTimes: 5, locations: [], timerange: { from: 'now-15m', to: 'now' } }, - services = { callCluster: 'mockESFunction', savedObjectsClient: mockSavedObjectsClient }, + services = alertsMock.createAlertServices(), state = {} -): any => ({ - params, - services, - state, -}); - -const mockSavedObjectsClient = { get: jest.fn() }; -mockSavedObjectsClient.get.mockReturnValue(defaultDynamicSettings); +): any => { + services.savedObjectsClient.get.mockResolvedValue({ + id: '', + type: '', + references: [], + attributes: defaultDynamicSettings, + }); + return { + params, + services, + state, + }; +}; describe('status check alert', () => { let toISOStringSpy: jest.SpyInstance; @@ -80,8 +86,10 @@ describe('status check alert', () => { expect(mockGetter.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { - "callES": "mockESFunction", - "dynamicSettings": undefined, + "callES": [MockFunction], + "dynamicSettings": Object { + "heartbeatIndices": "heartbeat-8*", + }, "locations": Array [], "numTimes": 5, "timerange": Object { @@ -112,27 +120,19 @@ describe('status check alert', () => { ]); const { server, libs } = bootstrapDependencies({ getMonitorStatus: mockGetter }); const alert = statusCheckAlertFactory(server, libs); - const mockInstanceFactory = jest.fn(); - const mockReplaceState = jest.fn(); - const mockScheduleActions = jest.fn(); - mockInstanceFactory.mockReturnValue({ - replaceState: mockReplaceState, - scheduleActions: mockScheduleActions, - }); const options = mockOptions(); - options.services = { - ...options.services, - alertInstanceFactory: mockInstanceFactory, - }; + const alertServices: AlertServicesMock = options.services; // @ts-ignore the executor can return `void`, but ours never does const state: Record = await alert.executor(options); expect(mockGetter).toHaveBeenCalledTimes(1); - expect(mockInstanceFactory).toHaveBeenCalledTimes(1); + expect(alertServices.alertInstanceFactory).toHaveBeenCalledTimes(1); expect(mockGetter.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { - "callES": "mockESFunction", - "dynamicSettings": undefined, + "callES": [MockFunction], + "dynamicSettings": Object { + "heartbeatIndices": "heartbeat-8*", + }, "locations": Array [], "numTimes": 5, "timerange": Object { @@ -142,8 +142,9 @@ describe('status check alert', () => { }, ] `); - expect(mockReplaceState).toHaveBeenCalledTimes(1); - expect(mockReplaceState.mock.calls[0]).toMatchInlineSnapshot(` + const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; + expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.replaceState.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "currentTriggerStarted": "foo date string", @@ -170,8 +171,8 @@ describe('status check alert', () => { }, ] `); - expect(mockScheduleActions).toHaveBeenCalledTimes(1); - expect(mockScheduleActions.mock.calls[0]).toMatchInlineSnapshot(` + expect(alertInstanceMock.scheduleActions).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.scheduleActions.mock.calls[0]).toMatchInlineSnapshot(` Array [ "xpack.uptime.alerts.actionGroups.monitorStatus", Object { From 02cba104696bf9ee3c06a29dfc523c727fbeca59 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 16 Apr 2020 09:51:03 +0200 Subject: [PATCH 35/44] Fix discover preserve url (#63580) --- x-pack/test/functional/apps/discover/preserve_url.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/test/functional/apps/discover/preserve_url.ts b/x-pack/test/functional/apps/discover/preserve_url.ts index 85142336a0a6b..9b9b2e2c60840 100644 --- a/x-pack/test/functional/apps/discover/preserve_url.ts +++ b/x-pack/test/functional/apps/discover/preserve_url.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['common', 'discover', 'spaceSelector', 'header']); - const appsMenu = getService('appsMenu'); const globalNav = getService('globalNav'); describe('preserve url', function() { @@ -26,8 +25,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('discover'); await PageObjects.discover.saveSearch('A Search'); await PageObjects.common.navigateToApp('home'); - await appsMenu.clickLink('Discover'); - await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.header.clickDiscover(); const activeTitle = await globalNav.getLastBreadcrumb(); expect(activeTitle).to.be('A Search'); }); @@ -42,7 +40,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.spaceSelector.expectHomePage('another-space'); // other space - await appsMenu.clickLink('Discover'); + await PageObjects.header.clickDiscover(); await PageObjects.discover.saveSearch('A Search in another space'); await PageObjects.spaceSelector.openSpacesNav(); @@ -50,7 +48,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.spaceSelector.expectHomePage('default'); // default space - await appsMenu.clickLink('Discover'); + await PageObjects.header.clickDiscover(); await PageObjects.discover.waitUntilSearchingHasFinished(); const activeTitleDefaultSpace = await globalNav.getLastBreadcrumb(); expect(activeTitleDefaultSpace).to.be('A Search'); @@ -60,7 +58,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.spaceSelector.expectHomePage('another-space'); // other space - await appsMenu.clickLink('Discover'); + await PageObjects.header.clickDiscover(); await PageObjects.discover.waitUntilSearchingHasFinished(); const activeTitleOtherSpace = await globalNav.getLastBreadcrumb(); expect(activeTitleOtherSpace).to.be('A Search in another space'); From 7b74aa9d69acffa1dc08b97aa57b4cb407fc7aa5 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 16 Apr 2020 10:17:15 +0100 Subject: [PATCH 36/44] [SIEM] move away from Joi for importing/exporting timeline (#62125) * move away from joi * update schema for filterQuery * fix types * update schemas * remove boom * remove redundant params * reuse utils from case * update schemas for query params and body * fix types * update validation schema * fix unit test * update description for test cases * remove import from case * lifting common libs * fix dependency * lifting validation builder function * add unit test * fix for code review * reve comments * rename common utils * fix types --- .../routes/rules/import_rules_route.ts | 2 +- .../routes/rules/utils.test.ts | 45 +---- .../detection_engine/routes/rules/utils.ts | 9 - .../rules/create_rules_stream_from_ndjson.ts | 45 +---- .../detection_engine/rules/get_export_all.ts | 3 +- .../rules/get_export_by_object_ids.ts | 3 +- .../create_timelines_stream_from_ndjson.ts | 40 +++-- .../routes/__mocks__/request_responses.ts | 9 +- .../routes/export_timelines_route.test.ts | 23 ++- .../timeline/routes/export_timelines_route.ts | 22 +-- .../routes/import_timelines_route.test.ts | 5 +- .../timeline/routes/import_timelines_route.ts | 46 ++--- .../routes/schemas/export_timelines_schema.ts | 18 +- .../routes/schemas/import_timelines_schema.ts | 84 ++++------ .../lib/timeline/routes/schemas/schemas.ts | 158 +----------------- .../timeline/routes/utils/export_timelines.ts | 25 +-- .../plugins/siem/server/lib/timeline/types.ts | 19 +-- .../build_validation/route_validation.test.ts | 39 +++++ .../build_validation/route_validation.ts | 39 +++++ .../create_stream_from_ndjson.test.ts | 69 ++++++++ .../read_stream/create_stream_from_ndjson.ts | 73 ++++++++ 21 files changed, 378 insertions(+), 398 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.ts create mode 100644 x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 29ae5056a3ae8..57ccc7a7806ac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -26,13 +26,13 @@ import { buildSiemResponse, validateLicenseForRuleType, } from '../utils'; -import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { ImportRuleAlertRest } from '../../types'; import { patchRules } from '../../rules/patch_rules'; import { importRulesQuerySchema, importRulesPayloadSchema } from '../schemas/import_rules_schema'; import { ImportRulesSchema, importRulesSchema } from '../schemas/response/import_rules_schema'; import { getTupleDuplicateErrorsAndUniqueRules } from './utils'; import { validate } from './validate'; +import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; type PromiseFromStreams = ImportRuleAlertRest | Error; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index 31a0f37fe81c9..8b1b0cab3b2f2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -12,7 +12,6 @@ import { transformTags, getIdBulkError, transformOrBulkError, - transformDataToNdjson, transformAlertsToRules, transformOrImportError, getDuplicates, @@ -22,14 +21,13 @@ import { getResult } from '../__mocks__/request_responses'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { ImportRuleAlertRest, RuleAlertParamsRest, RuleTypeParams } from '../../types'; import { BulkError, ImportSuccessError } from '../utils'; -import { sampleRule } from '../../signals/__mocks__/es_results'; import { getSimpleRule, getOutputRuleAlertForRest } from '../__mocks__/utils'; -import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { createPromiseFromStreams } from '../../../../../../../../../src/legacy/utils/streams'; import { PartialAlert } from '../../../../../../../../plugins/alerting/server'; import { SanitizedAlert } from '../../../../../../../../plugins/alerting/server/types'; import { RuleAlertType } from '../../rules/types'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; type PromiseFromStreams = ImportRuleAlertRest | Error; @@ -396,47 +394,6 @@ describe('utils', () => { }); }); - describe('transformDataToNdjson', () => { - test('if rules are empty it returns an empty string', () => { - const ruleNdjson = transformDataToNdjson([]); - expect(ruleNdjson).toEqual(''); - }); - - test('single rule will transform with new line ending character for ndjson', () => { - const rule = sampleRule(); - const ruleNdjson = transformDataToNdjson([rule]); - expect(ruleNdjson.endsWith('\n')).toBe(true); - }); - - test('multiple rules will transform with two new line ending characters for ndjson', () => { - const result1 = sampleRule(); - const result2 = sampleRule(); - result2.id = 'some other id'; - result2.rule_id = 'some other id'; - result2.name = 'Some other rule'; - - const ruleNdjson = transformDataToNdjson([result1, result2]); - // this is how we count characters in JavaScript :-) - const count = ruleNdjson.split('\n').length - 1; - expect(count).toBe(2); - }); - - test('you can parse two rules back out without errors', () => { - const result1 = sampleRule(); - const result2 = sampleRule(); - result2.id = 'some other id'; - result2.rule_id = 'some other id'; - result2.name = 'Some other rule'; - - const ruleNdjson = transformDataToNdjson([result1, result2]); - const ruleStrings = ruleNdjson.split('\n'); - const reParsed1 = JSON.parse(ruleStrings[0]); - const reParsed2 = JSON.parse(ruleStrings[1]); - expect(reParsed1).toEqual(result1); - expect(reParsed2).toEqual(result2); - }); - }); - describe('transformAlertsToRules', () => { test('given an empty array returns an empty array', () => { expect(transformAlertsToRules([])).toEqual([]); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index 4d13fa1b6ae50..790603fa8cfc1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -152,15 +152,6 @@ export const transformAlertToRule = ( }); }; -export const transformDataToNdjson = (data: unknown[]): string => { - if (data.length !== 0) { - const dataString = data.map(rule => JSON.stringify(rule)).join('\n'); - return `${dataString}\n`; - } else { - return ''; - } -}; - export const transformAlertsToRules = ( alerts: RuleAlertType[] ): Array> => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts index 3e22999528101..27008d17d2192 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -4,39 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ import { Transform } from 'stream'; -import { has, isString } from 'lodash/fp'; import { ImportRuleAlertRest } from '../types'; import { createSplitStream, createMapStream, - createFilterStream, createConcatStream, } from '../../../../../../../../src/legacy/utils/streams'; import { importRulesSchema } from '../routes/schemas/import_rules_schema'; import { BadRequestError } from '../errors/bad_request_error'; - -export interface RulesObjectsExportResultDetails { - /** number of successfully exported objects */ - exportedCount: number; -} - -export const parseNdjsonStrings = (): Transform => { - return createMapStream((ndJsonStr: string) => { - if (isString(ndJsonStr) && ndJsonStr.trim() !== '') { - try { - return JSON.parse(ndJsonStr); - } catch (err) { - return err; - } - } - }); -}; - -export const filterExportedCounts = (): Transform => { - return createFilterStream( - obj => obj != null && !has('exported_count', obj) - ); -}; +import { + parseNdjsonStrings, + filterExportedCounts, + createLimitStream, +} from '../../../utils/read_stream/create_stream_from_ndjson'; export const validateRules = (): Transform => { return createMapStream((obj: ImportRuleAlertRest) => { @@ -53,21 +33,6 @@ export const validateRules = (): Transform => { }); }; -// Adaptation from: saved_objects/import/create_limit_stream.ts -export const createLimitStream = (limit: number): Transform => { - let counter = 0; - return new Transform({ - objectMode: true, - async transform(obj, _, done) { - if (counter >= limit) { - return done(new Error(`Can't import more than ${limit} rules`)); - } - counter++; - done(undefined, obj); - }, - }); -}; - // TODO: Capture both the line number and the rule_id if you have that information for the error message // eventually and then pass it down so we can give error messages on the line number diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts index 6a27abb66ce85..40c07f28ea848 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts @@ -7,7 +7,8 @@ import { AlertsClient } from '../../../../../../../plugins/alerting/server'; import { getNonPackagedRules } from './get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; -import { transformAlertsToRules, transformDataToNdjson } from '../routes/rules/utils'; +import { transformAlertsToRules } from '../routes/rules/utils'; +import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson'; export const getExportAll = async ( alertsClient: AlertsClient diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts index 6f642231ebbaf..048f09e95b062 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -8,8 +8,9 @@ import { AlertsClient } from '../../../../../../../plugins/alerting/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../rules/types'; import { readRules } from './read_rules'; -import { transformDataToNdjson, transformAlertToRule } from '../routes/rules/utils'; +import { transformAlertToRule } from '../routes/rules/utils'; import { OutputRuleAlertRest } from '../types'; +import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson'; interface ExportSuccesRule { statusCode: 200; diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/create_timelines_stream_from_ndjson.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/create_timelines_stream_from_ndjson.ts index 5373570a4f8cc..16654b2863ee5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/create_timelines_stream_from_ndjson.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/create_timelines_stream_from_ndjson.ts @@ -3,8 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import * as rt from 'io-ts'; import { Transform } from 'stream'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { failure } from 'io-ts/lib/PathReporter'; +import { identity } from 'fp-ts/lib/function'; import { createConcatStream, createSplitStream, @@ -14,26 +18,28 @@ import { parseNdjsonStrings, filterExportedCounts, createLimitStream, -} from '../detection_engine/rules/create_rules_stream_from_ndjson'; -import { importTimelinesSchema } from './routes/schemas/import_timelines_schema'; -import { BadRequestError } from '../detection_engine/errors/bad_request_error'; +} from '../../utils/read_stream/create_stream_from_ndjson'; + import { ImportTimelineResponse } from './routes/utils/import_timelines'; +import { ImportTimelinesSchemaRt } from './routes/schemas/import_timelines_schema'; + +type ErrorFactory = (message: string) => Error; -export const validateTimelines = (): Transform => { - return createMapStream((obj: ImportTimelineResponse) => { - if (!(obj instanceof Error)) { - const validated = importTimelinesSchema.validate(obj); - if (validated.error != null) { - return new BadRequestError(validated.error.message); - } else { - return validated.value; - } - } else { - return obj; - } - }); +export const createPlainError = (message: string) => new Error(message); + +export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => { + throw createError(failure(errors).join('\n')); }; +export const decodeOrThrow = ( + runtimeType: rt.Type, + createError: ErrorFactory = createPlainError +) => (inputValue: I) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); + +export const validateTimelines = (): Transform => + createMapStream((obj: ImportTimelineResponse) => decodeOrThrow(ImportTimelinesSchemaRt)(obj)); + export const createTimelinesStreamFromNdJson = (ruleLimit: number) => { return [ createSplitStream('\n'), diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/__mocks__/request_responses.ts index 0e73e4bdd6c97..a83c443773302 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/__mocks__/request_responses.ts @@ -6,11 +6,16 @@ import { TIMELINE_EXPORT_URL, TIMELINE_IMPORT_URL } from '../../../../../common/constants'; import { requestMock } from '../../../detection_engine/routes/__mocks__'; - +import stream from 'stream'; +const readable = new stream.Readable(); export const getExportTimelinesRequest = () => requestMock.create({ method: 'get', path: TIMELINE_EXPORT_URL, + query: { + file_name: 'mock_export_timeline.ndjson', + exclude_export_details: 'false', + }, body: { ids: ['f0e58720-57b6-11ea-b88d-3f1a31716be8', '890b8ae0-57df-11ea-a7c9-3976b7f1cb37'], }, @@ -22,7 +27,7 @@ export const getImportTimelinesRequest = (filename?: string) => path: TIMELINE_IMPORT_URL, query: { overwrite: false }, body: { - file: { hapi: { filename: filename ?? 'filename.ndjson' } }, + file: { ...readable, hapi: { filename: filename ?? 'filename.ndjson' } }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.test.ts index fe434b5399212..4eadede40f5d9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.test.ts @@ -83,7 +83,7 @@ describe('export timelines', () => { }); describe('request validation', () => { - test('disallows singular id query param', async () => { + test('return validation error for request body', async () => { const request = requestMock.create({ method: 'get', path: TIMELINE_EXPORT_URL, @@ -91,7 +91,26 @@ describe('export timelines', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('"id" is not allowed'); + expect(result.badRequest.mock.calls[0][0]).toEqual( + 'Invalid value undefined supplied to : { ids: Array }/ids: Array' + ); + }); + + test('return validation error for request params', async () => { + const request = requestMock.create({ + method: 'get', + path: TIMELINE_EXPORT_URL, + body: { id: 'someId' }, + }); + const result = server.validate(request); + + expect(result.badRequest.mock.calls[1][0]).toEqual( + [ + 'Invalid value undefined supplied to : { file_name: string, exclude_export_details: ("true" | "false") }/file_name: string', + 'Invalid value undefined supplied to : { file_name: string, exclude_export_details: ("true" | "false") }/exclude_export_details: ("true" | "false")/0: "true"', + 'Invalid value undefined supplied to : { file_name: string, exclude_export_details: ("true" | "false") }/exclude_export_details: ("true" | "false")/1: "false"', + ].join('\n') + ); }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.ts index b8e7be13fff34..fa849c1c325a9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/export_timelines_route.ts @@ -7,31 +7,24 @@ import { set as _set } from 'lodash/fp'; import { IRouter } from '../../../../../../../../src/core/server'; import { LegacyServices } from '../../../types'; -import { ExportTimelineRequestParams } from '../types'; -import { - transformError, - buildRouteValidation, - buildSiemResponse, -} from '../../detection_engine/routes/utils'; +import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; import { TIMELINE_EXPORT_URL } from '../../../../common/constants'; +import { getExportTimelineByObjectIds } from './utils/export_timelines'; import { - exportTimelinesSchema, exportTimelinesQuerySchema, + exportTimelinesRequestBodySchema, } from './schemas/export_timelines_schema'; - -import { getExportTimelineByObjectIds } from './utils/export_timelines'; +import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; export const exportTimelinesRoute = (router: IRouter, config: LegacyServices['config']) => { router.post( { path: TIMELINE_EXPORT_URL, validate: { - query: buildRouteValidation( - exportTimelinesQuerySchema - ), - body: buildRouteValidation(exportTimelinesSchema), + query: buildRouteValidation(exportTimelinesQuerySchema), + body: buildRouteValidation(exportTimelinesRequestBodySchema), }, options: { tags: ['access:siem'], @@ -42,6 +35,7 @@ export const exportTimelinesRoute = (router: IRouter, config: LegacyServices['co const siemResponse = buildSiemResponse(response); const savedObjectsClient = context.core.savedObjects.client; const exportSizeLimit = config().get('savedObjects.maxImportExportSize'); + if (request.body?.ids != null && request.body.ids.length > exportSizeLimit) { return siemResponse.error({ statusCode: 400, @@ -51,7 +45,7 @@ export const exportTimelinesRoute = (router: IRouter, config: LegacyServices['co const responseBody = await getExportTimelineByObjectIds({ client: savedObjectsClient, - request, + ids: request.body.ids, }); return response.ok({ diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.test.ts index e89aef4c70ecb..352f8f0a355fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.test.ts @@ -334,7 +334,10 @@ describe('import timelines', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'child "file" fails because ["file" is required]' + [ + 'Invalid value undefined supplied to : { file: (ReadableRt & { hapi: { filename: string } }) }/file: (ReadableRt & { hapi: { filename: string } })/0: ReadableRt', + 'Invalid value undefined supplied to : { file: (ReadableRt & { hapi: { filename: string } }) }/file: (ReadableRt & { hapi: { filename: string } })/1: { hapi: { filename: string } }', + ].join('\n') ); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts index 2b41b4e7843a7..ad7ee28d8ad51 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts @@ -6,8 +6,8 @@ import { extname } from 'path'; import { chunk, omit, set } from 'lodash/fp'; + import { - buildRouteValidation, buildSiemResponse, createBulkErrorObject, BulkError, @@ -23,7 +23,6 @@ import { isBulkError, isImportRegular, ImportTimelineResponse, - ImportTimelinesRequestParams, ImportTimelinesSchema, PromiseFromStreams, } from './utils/import_timelines'; @@ -31,14 +30,14 @@ import { import { IRouter } from '../../../../../../../../src/core/server'; import { TIMELINE_IMPORT_URL } from '../../../../common/constants'; import { SetupPlugins } from '../../../plugin'; -import { importTimelinesPayloadSchema } from './schemas/import_timelines_schema'; +import { ImportTimelinesPayloadSchemaRt } from './schemas/import_timelines_schema'; import { importRulesSchema } from '../../detection_engine/routes/schemas/response/import_rules_schema'; import { LegacyServices } from '../../../types'; import { Timeline } from '../saved_object'; import { validate } from '../../detection_engine/routes/rules/validate'; import { FrameworkRequest } from '../../framework'; - +import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; const CHUNK_PARSED_OBJECT_SIZE = 10; const timelineLib = new Timeline(); @@ -52,9 +51,7 @@ export const importTimelinesRoute = ( { path: `${TIMELINE_IMPORT_URL}`, validate: { - body: buildRouteValidation( - importTimelinesPayloadSchema - ), + body: buildRouteValidation(ImportTimelinesPayloadSchemaRt), }, options: { tags: ['access:siem'], @@ -65,28 +62,30 @@ export const importTimelinesRoute = ( }, }, async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - const savedObjectsClient = context.core.savedObjects.client; - if (!savedObjectsClient) { - return siemResponse.error({ statusCode: 404 }); - } - const { filename } = request.body.file.hapi; + try { + const siemResponse = buildSiemResponse(response); + const savedObjectsClient = context.core.savedObjects.client; + if (!savedObjectsClient) { + return siemResponse.error({ statusCode: 404 }); + } - const fileExtension = extname(filename).toLowerCase(); + const { file } = request.body; + const { filename } = file.hapi; - if (fileExtension !== '.ndjson') { - return siemResponse.error({ - statusCode: 400, - body: `Invalid file extension ${fileExtension}`, - }); - } + const fileExtension = extname(filename).toLowerCase(); - const objectLimit = config().get('savedObjects.maxImportExportSize'); + if (fileExtension !== '.ndjson') { + return siemResponse.error({ + statusCode: 400, + body: `Invalid file extension ${fileExtension}`, + }); + } + + const objectLimit = config().get('savedObjects.maxImportExportSize'); - try { const readStream = createTimelinesStreamFromNdJson(objectLimit); const parsedObjects = await createPromiseFromStreams([ - request.body.file, + file, ...readStream, ]); const [duplicateIdErrors, uniqueParsedObjects] = getTupleDuplicateErrorsAndUniqueTimeline( @@ -215,6 +214,7 @@ export const importTimelinesRoute = ( } } catch (err) { const error = transformError(err); + const siemResponse = buildSiemResponse(response); return siemResponse.error({ body: error.message, diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/export_timelines_schema.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/export_timelines_schema.ts index 04edbbd7046c9..6f8265903b2a7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/export_timelines_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/export_timelines_schema.ts @@ -4,17 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; +import * as rt from 'io-ts'; -/* eslint-disable @typescript-eslint/camelcase */ -import { ids, exclude_export_details, file_name } from './schemas'; -/* eslint-disable @typescript-eslint/camelcase */ - -export const exportTimelinesSchema = Joi.object({ - ids, -}).min(1); +export const exportTimelinesQuerySchema = rt.type({ + file_name: rt.string, + exclude_export_details: rt.union([rt.literal('true'), rt.literal('false')]), +}); -export const exportTimelinesQuerySchema = Joi.object({ - file_name: file_name.default('export.ndjson'), - exclude_export_details: exclude_export_details.default(false), +export const exportTimelinesRequestBodySchema = rt.type({ + ids: rt.array(rt.string), }); diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/import_timelines_schema.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/import_timelines_schema.ts index 61ffa9681c53a..056fdaf0d2515 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/import_timelines_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/import_timelines_schema.ts @@ -3,55 +3,41 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; -import { - columns, - created, - createdBy, - dataProviders, - dateRange, - description, - eventNotes, - eventType, - favorite, - filters, - globalNotes, - kqlMode, - kqlQuery, - savedObjectId, - savedQueryId, - sort, - title, - updated, - updatedBy, - version, - pinnedEventIds, -} from './schemas'; +import * as rt from 'io-ts'; -export const importTimelinesPayloadSchema = Joi.object({ - file: Joi.object().required(), -}); +import { Readable } from 'stream'; +import { either } from 'fp-ts/lib/Either'; +import { eventNotes, globalNotes, pinnedEventIds } from './schemas'; +import { SavedTimelineRuntimeType } from '../../types'; + +export const ImportTimelinesSchemaRt = rt.intersection([ + SavedTimelineRuntimeType, + rt.type({ + savedObjectId: rt.string, + version: rt.string, + }), + rt.type({ + globalNotes, + eventNotes, + pinnedEventIds, + }), +]); -export const importTimelinesSchema = Joi.object({ - columns, - created, - createdBy, - dataProviders, - dateRange, - description, - eventNotes, - eventType, - filters, - favorite, - globalNotes, - kqlMode, - kqlQuery, - savedObjectId, - savedQueryId, - sort, - title, - updated, - updatedBy, - version, - pinnedEventIds, +const ReadableRt = new rt.Type( + 'ReadableRt', + (u): u is Readable => u instanceof Readable, + (u, c) => + either.chain(rt.object.validate(u, c), s => { + const d = s as Readable; + return d.readable ? rt.success(d) : rt.failure(u, c); + }), + a => a +); +export const ImportTimelinesPayloadSchemaRt = rt.type({ + file: rt.intersection([ + ReadableRt, + rt.type({ + hapi: rt.type({ filename: rt.string }), + }), + ]), }); diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts index fc87a775a9e68..71627363ef0f8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts @@ -3,156 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; +import * as runtimeTypes from 'io-ts'; +import { unionWithNullType } from '../../../framework'; +import { SavedNoteRuntimeType } from '../../../note/types'; -const allowEmptyString = Joi.string().allow([null, '']); -const columnHeaderType = allowEmptyString; -export const created = Joi.number().allow(null); -export const createdBy = allowEmptyString; - -export const description = allowEmptyString; -export const end = Joi.number(); -export const eventId = allowEmptyString; -export const eventType = allowEmptyString; - -export const filters = Joi.array() - .items( - Joi.object({ - meta: Joi.object({ - alias: allowEmptyString, - controlledBy: allowEmptyString, - disabled: Joi.boolean().allow(null), - field: allowEmptyString, - formattedValue: allowEmptyString, - index: allowEmptyString, - key: allowEmptyString, - negate: Joi.boolean().allow(null), - params: allowEmptyString, - type: allowEmptyString, - value: allowEmptyString, - }), - exists: allowEmptyString, - match_all: allowEmptyString, - missing: allowEmptyString, - query: allowEmptyString, - range: allowEmptyString, - script: allowEmptyString, - }) - ) - .allow(null); - -const name = allowEmptyString; - -export const noteId = allowEmptyString; -export const note = allowEmptyString; - -export const start = Joi.number(); -export const savedQueryId = allowEmptyString; -export const savedObjectId = allowEmptyString; - -export const timelineId = allowEmptyString; -export const title = allowEmptyString; - -export const updated = Joi.number().allow(null); -export const updatedBy = allowEmptyString; -export const version = allowEmptyString; - -export const columns = Joi.array().items( - Joi.object({ - aggregatable: Joi.boolean().allow(null), - category: allowEmptyString, - columnHeaderType, - description, - example: allowEmptyString, - indexes: allowEmptyString, - id: allowEmptyString, - name, - placeholder: allowEmptyString, - searchable: Joi.boolean().allow(null), - type: allowEmptyString, - }).required() -); -export const dataProviders = Joi.array() - .items( - Joi.object({ - id: allowEmptyString, - name: allowEmptyString, - enabled: Joi.boolean().allow(null), - excluded: Joi.boolean().allow(null), - kqlQuery: allowEmptyString, - queryMatch: Joi.object({ - field: allowEmptyString, - displayField: allowEmptyString, - value: allowEmptyString, - displayValue: allowEmptyString, - operator: allowEmptyString, - }), - and: Joi.array() - .items( - Joi.object({ - id: allowEmptyString, - name, - enabled: Joi.boolean().allow(null), - excluded: Joi.boolean().allow(null), - kqlQuery: allowEmptyString, - queryMatch: Joi.object({ - field: allowEmptyString, - displayField: allowEmptyString, - value: allowEmptyString, - displayValue: allowEmptyString, - operator: allowEmptyString, - }).allow(null), - }) - ) - .allow(null), - }) - ) - .allow(null); -export const dateRange = Joi.object({ - start, - end, -}); -export const favorite = Joi.array().items( - Joi.object({ - keySearch: allowEmptyString, - fullName: allowEmptyString, - userName: allowEmptyString, - favoriteDate: Joi.number(), - }).allow(null) -); -const noteItem = Joi.object({ - noteId, - version, - eventId, - note, - timelineId, - created, - createdBy, - updated, - updatedBy, -}); -export const eventNotes = Joi.array().items(noteItem); -export const globalNotes = Joi.array().items(noteItem); -export const kqlMode = allowEmptyString; -export const kqlQuery = Joi.object({ - filterQuery: Joi.object({ - kuery: Joi.object({ - kind: allowEmptyString, - expression: allowEmptyString, - }).allow(null), - serializedQuery: allowEmptyString, - }).allow(null), -}); -export const pinnedEventIds = Joi.array() - .items(allowEmptyString) - .allow(null); -export const sort = Joi.object({ - columnId: allowEmptyString, - sortDirection: allowEmptyString, -}); -/* eslint-disable @typescript-eslint/camelcase */ - -export const ids = Joi.array().items(allowEmptyString); - -export const exclude_export_details = Joi.boolean(); -export const file_name = allowEmptyString; +export const eventNotes = runtimeTypes.array(unionWithNullType(SavedNoteRuntimeType)); +export const globalNotes = runtimeTypes.array(unionWithNullType(SavedNoteRuntimeType)); +export const pinnedEventIds = runtimeTypes.array(unionWithNullType(runtimeTypes.string)); diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/export_timelines.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/export_timelines.ts index 8a28100fbae82..52ee2a891c9bb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/export_timelines.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/export_timelines.ts @@ -26,12 +26,11 @@ import { import { ExportedTimelines, ExportTimelineSavedObjectsClient, - ExportTimelineRequest, ExportedNotes, TimelineSavedObject, } from '../../types'; +import { transformDataToNdjson } from '../../../../utils/read_stream/create_stream_from_ndjson'; -import { transformDataToNdjson } from '../../../detection_engine/routes/rules/utils'; export type TimelineSavedObjectsClient = Pick< SavedObjectsClient, | 'get' @@ -142,23 +141,17 @@ const getTimelines = async ( const getTimelinesFromObjects = async ( savedObjectsClient: ExportTimelineSavedObjectsClient, - request: ExportTimelineRequest + ids: string[] ): Promise => { - const timelines: TimelineSavedObject[] = await getTimelines(savedObjectsClient, request.body.ids); + const timelines: TimelineSavedObject[] = await getTimelines(savedObjectsClient, ids); // To Do for feature freeze // if (timelines.length !== request.body.ids.length) { // //figure out which is missing to tell user // } const [notes, pinnedEventIds] = await Promise.all([ - Promise.all( - request.body.ids.map(timelineId => getNotesByTimelineId(savedObjectsClient, timelineId)) - ), - Promise.all( - request.body.ids.map(timelineId => - getPinnedEventsByTimelineId(savedObjectsClient, timelineId) - ) - ), + Promise.all(ids.map(timelineId => getNotesByTimelineId(savedObjectsClient, timelineId))), + Promise.all(ids.map(timelineId => getPinnedEventsByTimelineId(savedObjectsClient, timelineId))), ]); const myNotes = notes.reduce( @@ -171,7 +164,7 @@ const getTimelinesFromObjects = async ( [] ); - const myResponse = request.body.ids.reduce((acc, timelineId) => { + const myResponse = ids.reduce((acc, timelineId) => { const myTimeline = timelines.find(t => t.savedObjectId === timelineId); if (myTimeline != null) { const timelineNotes = myNotes.filter(n => n.timelineId === timelineId); @@ -193,11 +186,11 @@ const getTimelinesFromObjects = async ( export const getExportTimelineByObjectIds = async ({ client, - request, + ids, }: { client: ExportTimelineSavedObjectsClient; - request: ExportTimelineRequest; + ids: string[]; }) => { - const timeline = await getTimelinesFromObjects(client, request); + const timeline = await getTimelinesFromObjects(client, ids); return transformDataToNdjson(timeline); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/types.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/types.ts index 35bf86c17db7e..523221192eca4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/types.ts @@ -14,7 +14,7 @@ import { PinnedEventToReturnSavedObjectRuntimeType, PinnedEventSavedObject, } from '../pinned_event/types'; -import { SavedObjectsClient, KibanaRequest } from '../../../../../../../src/core/server'; +import { SavedObjectsClient } from '../../../../../../../src/core/server'; /* * ColumnHeader Types @@ -204,20 +204,9 @@ export const AllTimelineSavedObjectRuntimeType = runtimeTypes.type({ export interface AllTimelineSavedObject extends runtimeTypes.TypeOf {} -export interface ExportTimelineRequestParams { - body: { ids: string[] }; - query: { - file_name: string; - exclude_export_details: boolean; - }; -} - -export type ExportTimelineRequest = KibanaRequest< - unknown, - ExportTimelineRequestParams['query'], - ExportTimelineRequestParams['body'], - 'post' ->; +/** + * Import/export timelines + */ export type ExportTimelineSavedObjectsClient = Pick< SavedObjectsClient, diff --git a/x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.test.ts b/x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.test.ts new file mode 100644 index 0000000000000..888cd5dfe5390 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { buildRouteValidation } from './route_validation'; +import * as rt from 'io-ts'; +import { RouteValidationResultFactory } from '../../../../../../../src/core/server/http'; + +describe('buildRouteValidation', () => { + const schema = rt.type({ + ids: rt.array(rt.string), + }); + const validationResult: RouteValidationResultFactory = { + ok: jest.fn().mockImplementation(validatedInput => validatedInput), + badRequest: jest.fn().mockImplementation(e => e), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('return validation error', () => { + const input = { id: 'someId' }; + const result = buildRouteValidation(schema)(input, validationResult); + + expect(result).toEqual( + 'Invalid value undefined supplied to : { ids: Array }/ids: Array' + ); + }); + + test('return validated input', () => { + const input = { ids: ['someId'] }; + const result = buildRouteValidation(schema)(input, validationResult); + + expect(result).toEqual(input); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.ts b/x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.ts new file mode 100644 index 0000000000000..1281c23cbc89a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/utils/build_validation/route_validation.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { failure } from 'io-ts/lib/PathReporter'; +import { + RouteValidationFunction, + RouteValidationResultFactory, + RouteValidationError, +} from '../../../../../../../src/core/server'; + +type RequestValidationResult = + | { + value: T; + error?: undefined; + } + | { + value?: undefined; + error: RouteValidationError; + }; + +export const buildRouteValidation = >( + schema: T +): RouteValidationFunction => ( + inputValue: unknown, + validationResult: RouteValidationResultFactory +) => + pipe( + schema.decode(inputValue), + fold>( + (errors: rt.Errors) => validationResult.badRequest(failure(errors).join('\n')), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); diff --git a/x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.test.ts b/x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.test.ts new file mode 100644 index 0000000000000..2b5b34edca140 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { transformDataToNdjson } from './create_stream_from_ndjson'; +import { ImportRuleAlertRest } from '../../lib/detection_engine/types'; +import { sampleRule } from '../../lib/detection_engine/signals/__mocks__/es_results'; + +export const getOutputSample = (): Partial => ({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', +}); + +export const getSampleAsNdjson = (sample: Partial): string => { + return `${JSON.stringify(sample)}\n`; +}; + +describe('create_rules_stream_from_ndjson', () => { + describe('transformDataToNdjson', () => { + test('if rules are empty it returns an empty string', () => { + const ruleNdjson = transformDataToNdjson([]); + expect(ruleNdjson).toEqual(''); + }); + + test('single rule will transform with new line ending character for ndjson', () => { + const rule = sampleRule(); + const ruleNdjson = transformDataToNdjson([rule]); + expect(ruleNdjson.endsWith('\n')).toBe(true); + }); + + test('multiple rules will transform with two new line ending characters for ndjson', () => { + const result1 = sampleRule(); + const result2 = sampleRule(); + result2.id = 'some other id'; + result2.rule_id = 'some other id'; + result2.name = 'Some other rule'; + + const ruleNdjson = transformDataToNdjson([result1, result2]); + // this is how we count characters in JavaScript :-) + const count = ruleNdjson.split('\n').length - 1; + expect(count).toBe(2); + }); + + test('you can parse two rules back out without errors', () => { + const result1 = sampleRule(); + const result2 = sampleRule(); + result2.id = 'some other id'; + result2.rule_id = 'some other id'; + result2.name = 'Some other rule'; + + const ruleNdjson = transformDataToNdjson([result1, result2]); + const ruleStrings = ruleNdjson.split('\n'); + const reParsed1 = JSON.parse(ruleStrings[0]); + const reParsed2 = JSON.parse(ruleStrings[1]); + expect(reParsed1).toEqual(result1); + expect(reParsed2).toEqual(result2); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.ts new file mode 100644 index 0000000000000..0b7966926b5dd --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/utils/read_stream/create_stream_from_ndjson.ts @@ -0,0 +1,73 @@ +/* + * 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 { Transform } from 'stream'; +import { has, isString } from 'lodash/fp'; +import { ImportRuleAlertRest } from '../../lib/detection_engine/types'; +import { createMapStream, createFilterStream } from '../../../../../../../src/legacy/utils/streams'; +import { importRulesSchema } from '../../lib/detection_engine/routes/schemas/import_rules_schema'; +import { BadRequestError } from '../../lib/detection_engine/errors/bad_request_error'; + +export interface RulesObjectsExportResultDetails { + /** number of successfully exported objects */ + exportedCount: number; +} + +export const parseNdjsonStrings = (): Transform => { + return createMapStream((ndJsonStr: string) => { + if (isString(ndJsonStr) && ndJsonStr.trim() !== '') { + try { + return JSON.parse(ndJsonStr); + } catch (err) { + return err; + } + } + }); +}; + +export const filterExportedCounts = (): Transform => { + return createFilterStream( + obj => obj != null && !has('exported_count', obj) + ); +}; + +export const validateRules = (): Transform => { + return createMapStream((obj: ImportRuleAlertRest) => { + if (!(obj instanceof Error)) { + const validated = importRulesSchema.validate(obj); + if (validated.error != null) { + return new BadRequestError(validated.error.message); + } else { + return validated.value; + } + } else { + return obj; + } + }); +}; + +// Adaptation from: saved_objects/import/create_limit_stream.ts +export const createLimitStream = (limit: number): Transform => { + let counter = 0; + return new Transform({ + objectMode: true, + async transform(obj, _, done) { + if (counter >= limit) { + return done(new Error(`Can't import more than ${limit} rules`)); + } + counter++; + done(undefined, obj); + }, + }); +}; + +export const transformDataToNdjson = (data: unknown[]): string => { + if (data.length !== 0) { + const dataString = data.map(rule => JSON.stringify(rule)).join('\n'); + return `${dataString}\n`; + } else { + return ''; + } +}; From f18aea5dce135ef8bf4961467cee35c3b825aed9 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 16 Apr 2020 11:53:51 +0200 Subject: [PATCH 37/44] add platform team definition of done (#59993) * add platform team definition of done * add link to ToC * add suggestions from Josh * add tooling * add testing note --- src/core/CONVENTIONS.md | 48 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 0f592d108c561..a82cc27839a1d 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -1,6 +1,6 @@ -# Kibana Conventions - -- [Kibana Conventions](#kibana-conventions) +- [Organisational Conventions](#organisational-conventions) + - [Definition of done](#definition-of-done) +- [Technical Conventions](#technical-conventions) - [Plugin Structure](#plugin-structure) - [The PluginInitializer](#the-plugininitializer) - [The Plugin class](#the-plugin-class) @@ -8,8 +8,34 @@ - [Services](#services) - [Usage Collection](#usage-collection) - [Saved Objects Types](#saved-objects-types) - -## Plugin Structure + - [Naming conventions](#naming-conventions) + +## Organisational Conventions +### Definition of done +Definition of done for a feature: +- has a dedicated Github issue describing problem space +- an umbrella task closed/updated with follow-ups +- all code review comments are resolved +- has been verified manually by at least one reviewer +- can be used by first & third party plugins +- there is no contradiction between client and server API +- works for OSS version + - works with and without a `server.basePath` configured + - cannot crash the Kibana server when it fails +- works for the commercial version with a license + - for a logged-in user + - for anonymous user + - compatible with Spaces +- has unit & integration tests for public contracts +- has functional tests for user scenarios +- uses standard tooling: + - code - `TypeScript` + - UI - `React` + - tests - `jest` & `FTR` +- has documentation for the public contract, provides a usage example + +## Technical Conventions +### Plugin Structure All Kibana plugins built at Elastic should follow the same structure. @@ -60,7 +86,7 @@ my_plugin/ - More should be fleshed out here... - Usage collectors for Telemetry should be defined in a separate `server/collectors/` directory. -### The PluginInitializer +#### The PluginInitializer ```ts // my_plugin/public/index.ts @@ -75,7 +101,7 @@ export { } ``` -### The Plugin class +#### The Plugin class ```ts // my_plugin/public/plugin.ts @@ -130,7 +156,7 @@ Difference between `setup` and `start`: The bulk of your plugin logic will most likely live inside _handlers_ registered during `setup`. -### Applications +#### Applications It's important that UI code is not included in the main bundle for your plugin. Our webpack configuration supports dynamic async imports to split out imports into a separate bundle. Every app's rendering logic and UI code should @@ -175,7 +201,7 @@ export class MyPlugin implements Plugin { } ``` -### Services +#### Services Service structure should mirror the plugin lifecycle to make reasoning about how the service is executed more clear. @@ -226,7 +252,7 @@ export class Plugin { } ``` -### Usage Collection +#### Usage Collection For creating and registering a Usage Collector. Collectors should be defined in a separate directory `server/collectors/`. You can read more about usage collectors on `src/plugins/usage_collection/README.md`. @@ -263,7 +289,7 @@ export function registerMyPluginUsageCollector(usageCollection?: UsageCollection } ``` -### Saved Objects Types +#### Saved Objects Types Saved object type definitions should be defined in their own `server/saved_objects` directory. From 188ebe8e2c769ae67f2aee5ca5efa12deec742be Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Thu, 16 Apr 2020 12:55:22 +0300 Subject: [PATCH 38/44] [i18n] Update CODEOWNERS (#63354) Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4a061d7415990..086d4ccf81b10 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -130,7 +130,6 @@ /packages/kbn-config-schema/ @elastic/kibana-platform /src/legacy/server/config/ @elastic/kibana-platform /src/legacy/server/http/ @elastic/kibana-platform -/src/legacy/server/i18n/ @elastic/kibana-platform /src/legacy/server/logging/ @elastic/kibana-platform /src/legacy/server/saved_objects/ @elastic/kibana-platform /src/legacy/server/status/ @elastic/kibana-platform @@ -149,7 +148,10 @@ /x-pack/test/api_integration/apis/security/ @elastic/kibana-security # Kibana Localization -/src/dev/i18n/ @elastic/kibana-localization +/src/dev/i18n/ @elastic/kibana-localization +/src/legacy/server/i18n/ @elastic/kibana-localization +/src/core/public/i18n/ @elastic/kibana-localization +/packages/kbn-i18n/ @elastic/kibana-localization # Pulse /packages/kbn-analytics/ @elastic/pulse From d20cbcb903b6ab69453da1afb7853673f56b1f3a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 16 Apr 2020 12:37:54 +0200 Subject: [PATCH 39/44] [Uptime] Update-paths-labeller-with-uptime (#63679) Co-authored-by: Elastic Machine --- .github/paths-labeller.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/paths-labeller.yml b/.github/paths-labeller.yml index 3d35cd74e0718..544dd577313df 100644 --- a/.github/paths-labeller.yml +++ b/.github/paths-labeller.yml @@ -8,3 +8,6 @@ - "Feature:ExpressionLanguage": - "src/plugins/expressions/**/*.*" - "src/plugins/bfetch/**/*.*" + - "Team:uptime": + - "x-pack/plugins/uptime/**/*.*" + - "x-pack/legacy/plugins/uptime/**/*.*" From 5ffdeb62e0ffc5a203d4207f27494d13106b868a Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 16 Apr 2020 13:46:23 +0200 Subject: [PATCH 40/44] Migrate vis_type_metric to kibana/new platform (#63096) * Move vis_type_metric to Kibana Platform * Adapt i18n * Cleanup tests * Modify CODEOWNERS * Cleanup SCSS --- .github/CODEOWNERS | 1 + .i18nrc.json | 2 +- .../core_plugins/vis_type_metric/index.ts | 44 ------------------- .../core_plugins/vis_type_metric/package.json | 4 -- src/plugins/vis_type_metric/config.ts | 26 +++++++++++ src/plugins/vis_type_metric/kibana.json | 8 ++++ .../__snapshots__/metric_vis_fn.test.ts.snap | 0 .../vis_type_metric/public/_metric_vis.scss | 0 .../metric_vis_component.test.tsx.snap | 0 .../components/metric_vis_component.test.tsx | 2 +- .../components/metric_vis_component.tsx | 8 ++-- .../public/components/metric_vis_options.tsx | 2 +- .../components/metric_vis_value.test.tsx | 0 .../public/components/metric_vis_value.tsx | 0 .../vis_type_metric/public/index.scss | 4 +- .../vis_type_metric/public/index.ts | 4 +- .../public/metric_vis_fn.test.ts | 5 +-- .../vis_type_metric/public/metric_vis_fn.ts | 4 +- .../public/metric_vis_type.test.ts | 2 - .../vis_type_metric/public/metric_vis_type.ts | 6 +-- .../vis_type_metric/public/plugin.ts | 15 ++++--- .../vis_type_metric/public/services.ts | 4 +- .../vis_type_metric/public/types.ts | 6 +-- .../vis_type_metric/server/index.ts} | 24 +++++----- 24 files changed, 76 insertions(+), 95 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_metric/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_metric/package.json create mode 100644 src/plugins/vis_type_metric/config.ts create mode 100644 src/plugins/vis_type_metric/kibana.json rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap (100%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/_metric_vis.scss (100%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap (100%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/components/metric_vis_component.test.tsx (97%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/components/metric_vis_component.tsx (96%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/components/metric_vis_options.tsx (99%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/components/metric_vis_value.test.tsx (100%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/components/metric_vis_value.tsx (100%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/index.scss (66%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/index.ts (92%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/metric_vis_fn.test.ts (90%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/metric_vis_fn.ts (98%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/metric_vis_type.test.ts (97%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/metric_vis_type.ts (93%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/plugin.ts (79%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/services.ts (86%) rename src/{legacy/core_plugins => plugins}/vis_type_metric/public/types.ts (86%) rename src/{legacy/core_plugins/vis_type_metric/public/legacy.ts => plugins/vis_type_metric/server/index.ts} (57%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 086d4ccf81b10..632f28efdc37e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,7 @@ /src/plugins/dashboard/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app /src/plugins/vis_type_timeseries/ @elastic/kibana-app +/src/plugins/vis_type_metric/ @elastic/kibana-app # Core UI # Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon diff --git a/.i18nrc.json b/.i18nrc.json index e18f529b92ac3..2edf178e37bd5 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -47,7 +47,7 @@ "uiActions": "src/plugins/ui_actions", "visDefaultEditor": "src/plugins/vis_default_editor", "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", - "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", + "visTypeMetric": "src/plugins/vis_type_metric", "visTypeTable": "src/legacy/core_plugins/vis_type_table", "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": ["src/legacy/core_plugins/vis_type_timeseries", "src/plugins/vis_type_timeseries"], diff --git a/src/legacy/core_plugins/vis_type_metric/index.ts b/src/legacy/core_plugins/vis_type_metric/index.ts deleted file mode 100644 index 8e6654e40f0fc..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const metricPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'metric_vis', - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars: server => ({}), - }, - init: (server: Legacy.Server) => ({}), - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default metricPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_metric/package.json b/src/legacy/core_plugins/vis_type_metric/package.json deleted file mode 100644 index e570261fc30dd..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "metric_vis", - "version": "kibana" -} diff --git a/src/plugins/vis_type_metric/config.ts b/src/plugins/vis_type_metric/config.ts new file mode 100644 index 0000000000000..6749bd83de39f --- /dev/null +++ b/src/plugins/vis_type_metric/config.ts @@ -0,0 +1,26 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/vis_type_metric/kibana.json b/src/plugins/vis_type_metric/kibana.json new file mode 100644 index 0000000000000..24135d257b317 --- /dev/null +++ b/src/plugins/vis_type_metric/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "visTypeMetric", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["data", "visualizations", "charts","expressions"] +} diff --git a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap b/src/plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap rename to src/plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap diff --git a/src/legacy/core_plugins/vis_type_metric/public/_metric_vis.scss b/src/plugins/vis_type_metric/public/_metric_vis.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_metric/public/_metric_vis.scss rename to src/plugins/vis_type_metric/public/_metric_vis.scss diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap rename to src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx similarity index 97% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx rename to src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx index 2bd423656b0f0..3969b28d75414 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component'; -import { ExprVis } from '../../../../../plugins/visualizations/public'; +import { ExprVis } from '../../../visualizations/public'; jest.mock('../services', () => ({ getFormatService: () => ({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx similarity index 96% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx rename to src/plugins/vis_type_metric/public/components/metric_vis_component.tsx index de2cc66a99c79..eb3986b6388fe 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -22,12 +22,12 @@ import React, { Component } from 'react'; import { isColorDark } from '@elastic/eui'; import { MetricVisValue } from './metric_vis_value'; import { Input } from '../metric_vis_fn'; -import { FieldFormatsContentType, IFieldFormat } from '../../../../../plugins/data/public'; -import { KibanaDatatable } from '../../../../../plugins/expressions/public'; -import { getHeatmapColors } from '../../../../../plugins/charts/public'; +import { FieldFormatsContentType, IFieldFormat } from '../../../data/public'; +import { KibanaDatatable } from '../../../expressions/public'; +import { getHeatmapColors } from '../../../charts/public'; import { VisParams, MetricVisMetric } from '../types'; import { getFormatService } from '../services'; -import { SchemaConfig, ExprVis } from '../../../../../plugins/visualizations/public'; +import { SchemaConfig, ExprVis } from '../../../visualizations/public'; export interface MetricVisComponentProps { visParams: VisParams; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx similarity index 99% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx rename to src/plugins/vis_type_metric/public/components/metric_vis_options.tsx index 56af0ee91878f..009d63ded39b4 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -38,7 +38,7 @@ import { RangeOption, SetColorSchemaOptionsValue, SetColorRangeValue, -} from '../../../../../plugins/charts/public'; +} from '../../../charts/public'; import { MetricVisParam, VisParams } from '../types'; function MetricVisOptions({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_value.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx rename to src/plugins/vis_type_metric/public/components/metric_vis_value.test.tsx diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_value.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx rename to src/plugins/vis_type_metric/public/components/metric_vis_value.tsx diff --git a/src/legacy/core_plugins/vis_type_metric/public/index.scss b/src/plugins/vis_type_metric/public/index.scss similarity index 66% rename from src/legacy/core_plugins/vis_type_metric/public/index.scss rename to src/plugins/vis_type_metric/public/index.scss index e4116b69bf0f1..638f9ac1ef93a 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/index.scss +++ b/src/plugins/vis_type_metric/public/index.scss @@ -1,5 +1,3 @@ -@import 'src/legacy/ui/public/styles/styling_constants'; - // Prefix all styles with "mtr" to avoid conflicts. // Examples // mtrChart @@ -7,4 +5,4 @@ // mtrChart__legend--small // mtrChart__legend-isLoading -@import './metric_vis'; +@import 'metric_vis'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/index.ts b/src/plugins/vis_type_metric/public/index.ts similarity index 92% rename from src/legacy/core_plugins/vis_type_metric/public/index.ts rename to src/plugins/vis_type_metric/public/index.ts index 7499babef58a7..3d3e1879a51d9 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/index.ts +++ b/src/plugins/vis_type_metric/public/index.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - -import { PluginInitializerContext } from '../../../../core/public'; +import './index.scss'; +import { PluginInitializerContext } from 'kibana/public'; import { MetricVisPlugin as Plugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts b/src/plugins/vis_type_metric/public/metric_vis_fn.test.ts similarity index 90% rename from src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts rename to src/plugins/vis_type_metric/public/metric_vis_fn.test.ts index 4094cd4eff060..3ed8f8f79a83f 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_fn.test.ts @@ -18,10 +18,7 @@ */ import { createMetricVisFn } from './metric_vis_fn'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; - -jest.mock('ui/new_platform'); +import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; describe('interpreter/functions#metric', () => { const fn = functionWrapper(createMetricVisFn()); diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/plugins/vis_type_metric/public/metric_vis_fn.ts similarity index 98% rename from src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts rename to src/plugins/vis_type_metric/public/metric_vis_fn.ts index 52784da1bd73b..3d16fed0fa385 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_fn.ts @@ -25,9 +25,9 @@ import { Range, Render, Style, -} from '../../../../plugins/expressions/public'; +} from '../../expressions/public'; import { visType, DimensionsVisParam, VisParams } from './types'; -import { ColorSchemas, vislibColorMaps, ColorModes } from '../../../../plugins/charts/public'; +import { ColorSchemas, vislibColorMaps, ColorModes } from '../../charts/public'; export type Input = KibanaDatatable; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/plugins/vis_type_metric/public/metric_vis_type.test.ts similarity index 97% rename from src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts rename to src/plugins/vis_type_metric/public/metric_vis_type.test.ts index 706693eff1007..636118c692aaa 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -20,8 +20,6 @@ import { createMetricVisTypeDefinition } from './metric_vis_type'; import { MetricVisComponent } from './components/metric_vis_component'; -jest.mock('ui/new_platform'); - describe('metric_vis - createMetricVisTypeDefinition', () => { it('has metric vis component set', () => { const def = createMetricVisTypeDefinition(); diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/plugins/vis_type_metric/public/metric_vis_type.ts similarity index 93% rename from src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts rename to src/plugins/vis_type_metric/public/metric_vis_type.ts index ab31c56921412..b7e9213283bee 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_type.ts @@ -21,9 +21,9 @@ import { i18n } from '@kbn/i18n'; import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; -import { ColorSchemas, colorSchemas, ColorModes } from '../../../../plugins/charts/public'; -import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../../../plugins/vis_default_editor/public'; +import { ColorSchemas, colorSchemas, ColorModes } from '../../charts/public'; +import { AggGroupNames } from '../../data/public'; +import { Schemas } from '../../vis_default_editor/public'; export const createMetricVisTypeDefinition = () => ({ name: 'metric', diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/plugins/vis_type_metric/public/plugin.ts similarity index 79% rename from src/legacy/core_plugins/vis_type_metric/public/plugin.ts rename to src/plugins/vis_type_metric/public/plugin.ts index cb65d5cafbdd2..a3951fa46c21c 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/plugins/vis_type_metric/public/plugin.ts @@ -17,15 +17,16 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; import { createMetricVisTypeDefinition } from './metric_vis_type'; -import { ChartsPluginSetup } from '../../../../plugins/charts/public'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { ChartsPluginSetup } from '../../charts/public'; +import { DataPublicPluginStart } from '../../data/public'; import { setFormatService } from './services'; +import { ConfigSchema } from '../config'; /** @internal */ export interface MetricVisPluginSetupDependencies { @@ -41,9 +42,9 @@ export interface MetricVisPluginStartDependencies { /** @internal */ export class MetricVisPlugin implements Plugin { - initializerContext: PluginInitializerContext; + initializerContext: PluginInitializerContext; - constructor(initializerContext: PluginInitializerContext) { + constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; } diff --git a/src/legacy/core_plugins/vis_type_metric/public/services.ts b/src/plugins/vis_type_metric/public/services.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_metric/public/services.ts rename to src/plugins/vis_type_metric/public/services.ts index b303ccd5aeed2..681afbaf0b268 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/services.ts +++ b/src/plugins/vis_type_metric/public/services.ts @@ -17,8 +17,8 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { createGetterSetter } from '../../kibana_utils/common'; +import { DataPublicPluginStart } from '../../data/public'; export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/plugins/vis_type_metric/public/types.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_metric/public/types.ts rename to src/plugins/vis_type_metric/public/types.ts index cdf62cba934d7..e1f2c7721a426 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/plugins/vis_type_metric/public/types.ts @@ -17,9 +17,9 @@ * under the License. */ -import { Range } from '../../../../plugins/expressions/public'; -import { SchemaConfig } from '../../../../plugins/visualizations/public'; -import { ColorModes, ColorSchemas, Labels, Style } from '../../../../plugins/charts/public'; +import { Range } from '../../expressions/public'; +import { SchemaConfig } from '../../visualizations/public'; +import { ColorModes, Labels, Style, ColorSchemas } from '../../charts/public'; export const visType = 'metric'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts b/src/plugins/vis_type_metric/server/index.ts similarity index 57% rename from src/legacy/core_plugins/vis_type_metric/public/legacy.ts rename to src/plugins/vis_type_metric/server/index.ts index ba883601e5d65..ce550dc81dd85 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts +++ b/src/plugins/vis_type_metric/server/index.ts @@ -17,18 +17,18 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; -import { MetricVisPluginSetupDependencies } from './plugin'; -import { plugin } from '.'; +import { PluginConfigDescriptor } from 'kibana/server'; -const plugins: Readonly = { - expressions: npSetup.plugins.expressions, - visualizations: npSetup.plugins.visualizations, - charts: npSetup.plugins.charts, -}; +import { configSchema, ConfigSchema } from '../config'; -const pluginInstance = plugin({} as PluginInitializerContext); +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('metric_vis.enabled', 'vis_type_metric.enabled'), + ], +}; -export const setup = pluginInstance.setup(npSetup.core, plugins); -export const start = pluginInstance.start(npStart.core, { data: npStart.plugins.data }); +export const plugin = () => ({ + setup() {}, + start() {}, +}); From a3f4acfc27653bae18c6a05a7b2979b25d330c28 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 16 Apr 2020 13:48:34 +0200 Subject: [PATCH 41/44] [Uptime] Update duration chart query filters (#63620) * updated duration query * updated snapshot * update fixtures Co-authored-by: Elastic Machine --- .../plugins/uptime/common/types/index.ts | 6 - .../charts/__tests__/duration_charts.test.tsx | 15 -- .../uptime/public/state/api/ml_anomaly.ts | 7 +- .../get_monitor_charts.test.ts.snap | 137 +----------------- .../lib/requests/get_monitor_duration.ts | 36 +---- .../uptime/rest/fixtures/monitor_charts.json | 126 +--------------- .../fixtures/monitor_charts_empty_sets.json | 5 +- .../rest/fixtures/monitor_latest_status.json | 11 +- 8 files changed, 19 insertions(+), 324 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/common/types/index.ts b/x-pack/legacy/plugins/uptime/common/types/index.ts index 1d0003addd761..fcbb92caf26d5 100644 --- a/x-pack/legacy/plugins/uptime/common/types/index.ts +++ b/x-pack/legacy/plugins/uptime/common/types/index.ts @@ -34,10 +34,4 @@ export interface LocationDurationLine { export interface MonitorDurationResult { /** The average values for the monitor duration. */ locationDurationLines: LocationDurationLine[]; - /** The counts of up/down checks for the monitor. */ - status: StatusData[]; - /** The maximum status doc count in this chart. */ - statusMaxCount: number; - /** The maximum duration value in this chart. */ - durationMaxValue: number; } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/duration_charts.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/duration_charts.test.tsx index aac84c69d8d7b..57f9ce9abbca6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/duration_charts.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/duration_charts.test.tsx @@ -43,21 +43,6 @@ describe('MonitorCharts component', () => { ], }, ], - status: [ - { x: 1548697620000, up: 74, down: null, total: 74 }, - { x: 1548697920000, up: 75, down: null, total: 75 }, - { x: 1548698220000, up: 75, down: null, total: 75 }, - { x: 1548698520000, up: 73, down: null, total: 73 }, - { x: 1548698820000, up: 75, down: null, total: 75 }, - { x: 1548699120000, up: 74, down: null, total: 74 }, - { x: 1548699420000, up: 75, down: null, total: 75 }, - { x: 1548699720000, up: 75, down: null, total: 75 }, - { x: 1548700020000, up: 75, down: null, total: 75 }, - { x: 1548700320000, up: 75, down: null, total: 75 }, - { x: 1548700620000, up: 75, down: null, total: 75 }, - ], - statusMaxCount: 75, - durationMaxValue: 6669234, }, }; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts b/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts index bcd2582fe18b9..f10745a50f56a 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts @@ -48,11 +48,8 @@ export const createMLJob = async ({ query: { bool: { filter: [ - { - term: { - 'monitor.id': lowerCaseMonitorId, - }, - }, + { term: { 'monitor.id': lowerCaseMonitorId } }, + { range: { 'monitor.duration.us': { gt: 0 } } }, ], }, }, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap index 7b717949c70c5..97b97f8440758 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap @@ -15,13 +15,6 @@ Array [ "field": "monitor.duration.us", }, }, - "status": Object { - "terms": Object { - "field": "monitor.status", - "shard_size": 2, - "size": 2, - }, - }, }, "terms": Object { "field": "observer.geo.name", @@ -55,8 +48,10 @@ Array [ }, }, Object { - "term": Object { - "monitor.status": "up", + "range": Object { + "monitor.duration.us": Object { + "gt": 0, + }, }, }, ], @@ -71,7 +66,6 @@ Array [ exports[`ElasticsearchMonitorsAdapter inserts empty buckets for missing data 1`] = ` Object { - "durationMaxValue": 0, "locationDurationLines": Array [ Object { "line": Array [ @@ -244,128 +238,5 @@ Object { "name": "us-west-4", }, ], - "status": Array [ - Object { - "down": null, - "total": 4, - "up": null, - "x": 1568411568000, - }, - Object { - "down": null, - "total": 0, - "up": null, - "x": 1568411604000, - }, - Object { - "down": null, - "total": 8, - "up": null, - "x": 1568411640000, - }, - Object { - "down": null, - "total": 8, - "up": null, - "x": 1568411784000, - }, - Object { - "down": null, - "total": 0, - "up": null, - "x": 1568411820000, - }, - Object { - "down": null, - "total": 0, - "up": null, - "x": 1568411856000, - }, - Object { - "down": null, - "total": 0, - "up": null, - "x": 1568411892000, - }, - Object { - "down": null, - "total": 4, - "up": null, - "x": 1568411928000, - }, - Object { - "down": null, - "total": 7, - "up": null, - "x": 1568411964000, - }, - Object { - "down": null, - "total": 5, - "up": null, - "x": 1568412036000, - }, - Object { - "down": null, - "total": 4, - "up": null, - "x": 1568412072000, - }, - Object { - "down": null, - "total": 3, - "up": null, - "x": 1568412108000, - }, - Object { - "down": null, - "total": 4, - "up": null, - "x": 1568412144000, - }, - Object { - "down": null, - "total": 4, - "up": null, - "x": 1568412180000, - }, - Object { - "down": null, - "total": 3, - "up": null, - "x": 1568412216000, - }, - Object { - "down": null, - "total": 1, - "up": null, - "x": 1568412252000, - }, - Object { - "down": null, - "total": 3, - "up": null, - "x": 1568412288000, - }, - Object { - "down": null, - "total": 8, - "up": null, - "x": 1568412324000, - }, - Object { - "down": null, - "total": 8, - "up": null, - "x": 1568412432000, - }, - Object { - "down": null, - "total": 1, - "up": null, - "x": 1568412468000, - }, - ], - "statusMaxCount": 0, } `; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts index 01bfc52489bf3..e9c745b0a8713 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts @@ -20,26 +20,6 @@ export interface GetMonitorChartsParams { dateEnd: string; } -const formatStatusBuckets = (time: any, buckets: any, docCount: any) => { - let up = null; - let down = null; - - buckets.forEach((bucket: any) => { - if (bucket.key === 'up') { - up = bucket.doc_count; - } else if (bucket.key === 'down') { - down = bucket.doc_count; - } - }); - - return { - x: time, - up, - down, - total: docCount, - }; -}; - /** * Fetches data used to populate monitor charts */ @@ -55,7 +35,7 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< filter: [ { range: { '@timestamp': { gte: dateStart, lte: dateEnd } } }, { term: { 'monitor.id': monitorId } }, - { term: { 'monitor.status': 'up' } }, + { range: { 'monitor.duration.us': { gt: 0 } } }, ], }, }, @@ -73,7 +53,6 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< missing: 'N/A', }, aggs: { - status: { terms: { field: 'monitor.status', size: 2, shard_size: 2 } }, duration: { stats: { field: 'monitor.duration.us' } }, }, }, @@ -94,15 +73,10 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< * * The third list is for an area chart expressing a range, and it requires an (x,y,y0) structure, * where y0 is the min value for the point and y is the max. - * - * Additionally, we supply the maximum value for duration and status, so the corresponding charts know - * what the domain size should be. */ + const monitorChartsData: MonitorDurationResult = { locationDurationLines: [], - status: [], - durationMaxValue: 0, - statusMaxCount: 0, }; /** @@ -119,9 +93,9 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< // a set of all the locations found for this result const resultLocations = new Set(); const linesByLocation: { [key: string]: LocationDurationLine } = {}; + dateHistogramBuckets.forEach(dateHistogramBucket => { const x = dateHistogramBucket.key; - const docCount = dateHistogramBucket?.doc_count ?? 0; // a set of all the locations for the current bucket const bucketLocations = new Set(); @@ -161,10 +135,6 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< } }); } - - monitorChartsData.status.push( - formatStatusBuckets(x, dateHistogramBucket?.status?.buckets ?? [], docCount) - ); }); return monitorChartsData; diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts.json index 8edcff158b0ae..e96b3b3b562b9 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts.json @@ -85,129 +85,5 @@ } ] } - ], - "status": [ - { - "x": 1568172664000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172694000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172724000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172754000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172784000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172814000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172844000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172874000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172904000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172934000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172964000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568172994000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173024000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173054000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173084000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173114000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173144000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173174000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173204000, - "up": null, - "down": null, - "total": 1 - }, - { - "x": 1568173234000, - "up": null, - "down": null, - "total": 1 - } - ], - "durationMaxValue": 0, - "statusMaxCount": 0 + ] } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts_empty_sets.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts_empty_sets.json index 674338101bc5b..5157eace006cf 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts_empty_sets.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_charts_empty_sets.json @@ -1,6 +1,3 @@ { - "locationDurationLines": [], - "status": [], - "durationMaxValue": 0, - "statusMaxCount": 0 + "locationDurationLines": [] } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json index 6a832ad8536f7..9a33be807670e 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json @@ -1,11 +1,16 @@ { "observer": { - "geo": { "name": "mpls", "location": "37.926868, -78.024902" }, + "geo": { + "name": "mpls", + "location": "37.926868, -78.024902" + }, "hostname": "avc-x1x" }, "@timestamp": "2019-09-11T03:40:34.371Z", "monitor": { - "duration": { "us": 24627 }, + "duration": { + "us": 24627 + }, "ip": "127.0.0.1", "name": "", "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71", @@ -23,4 +28,4 @@ }, "docId": "h5toHm0B0I9WX_CznN_V", "timestamp": "2019-09-11T03:40:34.371Z" -} +} \ No newline at end of file From 11b6f7fc7d4c797fdd1f85f6df95ba67344e98a2 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Thu, 16 Apr 2020 05:49:53 -0600 Subject: [PATCH 42/44] [Maps] Update remaining client dependencies to NP (#63297) * Routes dependencies updated (except for routes itself) * Replace all chrome deps except map controller * Replace npSetup and npStart with normal plugin init logic. Some clean up * Bind kibana services in map controller * Use kibana services in map controller * Convert remaining deps that aren't critical to legacy operation * Remove last angular bindings to gis map saved object loader * Consolidate kibana services in NP * Some fixes. Remove console logs * Fix type errors * Fix jest test path refs * Accomodate legacy 'hacks' and init services for vis type alias * Review feedback. Remove/update unused declarations * getFileUpload actually just needed Component tacked on the end * Handle visibility of toolbars for full screen mode using new core chrome. Should fix test * Import source types in getInitialLayers to ensure registry --- .../maps/public/actions/map_actions.test.js | 2 +- .../maps/public/angular/get_initial_layers.js | 19 ++++- .../public/angular/get_initial_layers.test.js | 9 +-- .../maps/public/angular/get_initial_query.js | 7 +- .../angular/get_initial_refresh_config.js | 7 +- .../angular/get_initial_time_filters.js | 7 +- .../plugins/maps/public/angular/map.html | 6 +- .../maps/public/angular/map_controller.js | 71 +++++++++++++------ .../services/gis_map_saved_object_loader.js | 29 ++++---- .../maps/public/components/map_listing.js | 8 +-- .../connected_components/gis_map/index.js | 7 +- .../connected_components/gis_map/view.js | 3 +- .../filter_editor/filter_editor.js | 13 ++-- .../layer_panel/join_editor/resources/join.js | 3 +- .../join_editor/resources/join_expression.js | 3 +- .../join_editor/resources/where_expression.js | 12 ++-- .../connected_components/layer_panel/view.js | 9 ++- .../feature_geometry_filter_form.js | 3 +- .../connected_components/map/mb/view.js | 5 +- .../maps/public/embeddable/map_embeddable.tsx | 5 +- .../embeddable/map_embeddable_factory.ts | 22 +++--- .../plugins/maps/public/help_menu_util.js | 9 ++- x-pack/legacy/plugins/maps/public/index.ts | 3 +- .../plugins/maps/public/kibana_services.d.ts | 23 ------ .../plugins/maps/public/kibana_services.js | 29 -------- x-pack/legacy/plugins/maps/public/plugin.ts | 28 +------- .../maps/public/register_vis_type_alias.js | 18 +++-- x-pack/legacy/plugins/maps/public/routes.js | 34 +++++---- .../maps/public/selectors/map_selectors.js | 3 +- .../public/selectors/map_selectors.test.js | 2 +- .../plugins/maps/public/kibana_services.d.ts | 55 ++++++++++++++ x-pack/plugins/maps/public/kibana_services.js | 46 ++++++++++++ x-pack/plugins/maps/public/plugin.ts | 30 ++++++-- 33 files changed, 328 insertions(+), 202 deletions(-) delete mode 100644 x-pack/legacy/plugins/maps/public/kibana_services.d.ts delete mode 100644 x-pack/legacy/plugins/maps/public/kibana_services.js create mode 100644 x-pack/plugins/maps/public/kibana_services.d.ts diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.test.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.test.js index c280b8af7ab80..7e2a3c827fa88 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.test.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.test.js @@ -5,7 +5,7 @@ */ jest.mock('../selectors/map_selectors', () => ({})); -jest.mock('../kibana_services', () => ({})); +jest.mock('../../../../../plugins/maps/public/kibana_services', () => ({})); import { mapExtentChanged, setMouseCoordinates } from './map_actions'; diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js index 5e497ff0736b2..686259aeaaba4 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js @@ -4,11 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ import _ from 'lodash'; +// Import each layer type, even those not used, to init in registry +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/wms_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/ems_file_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/es_search_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/kibana_regionmap_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/es_geo_grid_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../../plugins/maps/public/layers/sources/xyz_tms_source'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { KibanaTilemapSource } from '../../../../../plugins/maps/public/layers/sources/kibana_tilemap_source'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { EMSTMSSource } from '../../../../../plugins/maps/public/layers/sources/ems_tms_source'; -import { getInjectedVarFunc } from '../kibana_services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInjectedVarFunc } from '../../../../../plugins/maps/public/kibana_services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getKibanaTileMap } from '../../../../../plugins/maps/public/meta'; diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js index 5334beaaf714a..8c9185a16ea0e 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js @@ -7,7 +7,7 @@ jest.mock('../../../../../plugins/maps/public/meta', () => { return {}; }); -jest.mock('../kibana_services'); +jest.mock('../../../../../plugins/maps/public/kibana_services'); import { getInitialLayers } from './get_initial_layers'; @@ -15,7 +15,8 @@ const layerListNotProvided = undefined; describe('Saved object has layer list', () => { beforeEach(() => { - require('../kibana_services').getInjectedVarFunc = () => jest.fn(); + require('../../../../../plugins/maps/public/kibana_services').getInjectedVarFunc = () => + jest.fn(); }); it('Should get initial layers from saved object', () => { @@ -65,7 +66,7 @@ describe('EMS is enabled', () => { require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; - require('../kibana_services').getInjectedVarFunc = () => key => { + require('../../../../../plugins/maps/public/kibana_services').getInjectedVarFunc = () => key => { switch (key) { case 'emsTileLayerId': return { @@ -110,7 +111,7 @@ describe('EMS is not enabled', () => { return null; }; - require('../kibana_services').getInjectedVarFunc = () => key => { + require('../../../../../plugins/maps/public/kibana_services').getInjectedVarFunc = () => key => { switch (key) { case 'isEmsEnabled': return false; diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_query.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_query.js index fc8305b252cc3..c50ecb2b05dc0 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_query.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_query.js @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - -const settings = chrome.getUiSettingsClient(); +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getUiSettings } from '../../../../../plugins/maps/public/kibana_services'; export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage }) { + const settings = getUiSettings(); + if (appState.query) { return appState.query; } diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_refresh_config.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_refresh_config.js index 10a2580b78e6d..8735d45debfc4 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_refresh_config.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_refresh_config.js @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - -const uiSettings = chrome.getUiSettingsClient(); +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getUiSettings } from '../../../../../plugins/maps/public/kibana_services'; export function getInitialRefreshConfig({ mapStateJSON, globalState = {} }) { + const uiSettings = getUiSettings(); + if (mapStateJSON) { const mapState = JSON.parse(mapStateJSON); if (mapState.refreshConfig) { diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_time_filters.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_time_filters.js index 82439175841b2..74fbf603e99f5 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_time_filters.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_time_filters.js @@ -3,9 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - -const uiSettings = chrome.getUiSettingsClient(); +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getUiSettings } from '../../../../../plugins/maps/public/kibana_services'; export function getInitialTimeFilters({ mapStateJSON, globalState = {} }) { if (mapStateJSON) { @@ -15,6 +14,6 @@ export function getInitialTimeFilters({ mapStateJSON, globalState = {} }) { } } - const defaultTime = uiSettings.get('timepicker:timeDefaults'); + const defaultTime = getUiSettings().get('timepicker:timeDefaults'); return { ...defaultTime, ...globalState.time }; } diff --git a/x-pack/legacy/plugins/maps/public/angular/map.html b/x-pack/legacy/plugins/maps/public/angular/map.html index 2f34ffa660d6e..7d7dcf6f9c9a9 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map.html +++ b/x-pack/legacy/plugins/maps/public/angular/map.html @@ -3,11 +3,11 @@
{ - const { filterManager } = npStart.plugins.data.query; + const savedQueryService = getData().query.savedQueries; + const { filterManager } = getData().query; const savedMap = $route.current.locals.map; $scope.screenTitle = savedMap.title; let unsubscribe; @@ -115,6 +129,14 @@ app.controller( return _.get($state, 'filters', []); } + const visibleSubscription = getCoreChrome() + .getIsVisible$() + .subscribe(isVisible => { + $scope.$evalAsync(() => { + $scope.isVisible = isVisible; + }); + }); + $scope.$listen(globalState, 'fetch_with_changes', diff => { if (diff.includes('time') || diff.includes('filters')) { onQueryChange({ @@ -169,10 +191,10 @@ app.controller( }); /* Saved Queries */ - $scope.showSaveQuery = capabilities.get().maps.saveQuery; + $scope.showSaveQuery = getMapsCapabilities().saveQuery; $scope.$watch( - () => capabilities.get().maps.saveQuery, + () => getMapsCapabilities().saveQuery, newCapability => { $scope.showSaveQuery = newCapability; } @@ -342,7 +364,7 @@ app.controller( // clear old UI state store.dispatch(setSelectedLayer(null)); store.dispatch(updateFlyout(FLYOUT_STATE.NONE)); - store.dispatch(setReadOnly(!capabilities.get().maps.save)); + store.dispatch(setReadOnly(!getMapsCapabilities().save)); handleStoreChanges(store); unsubscribe = store.subscribe(() => { @@ -446,6 +468,8 @@ app.controller( $scope.$on('$destroy', () => { window.removeEventListener('beforeunload', beforeUnload); + visibleSubscription.unsubscribe(); + getCoreChrome().setIsVisible(true); if (unsubscribe) { unsubscribe(); @@ -457,7 +481,7 @@ app.controller( }); const updateBreadcrumbs = () => { - chrome.breadcrumbs.set([ + getCoreChrome().setBreadcrumbs([ { text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', { defaultMessage: 'Maps', @@ -482,7 +506,7 @@ app.controller( }; updateBreadcrumbs(); - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(); async function doSave(saveOptions) { await store.dispatch(clearTransientLayerStateAndCloseFlyout()); @@ -491,9 +515,9 @@ app.controller( try { id = await savedMap.save(saveOptions); - docTitle.change(savedMap.title); + getCoreChrome().docTitle.change(savedMap.title); } catch (err) { - toastNotifications.addDanger({ + getToasts().addDanger({ title: i18n.translate('xpack.maps.mapController.saveErrorMessage', { defaultMessage: `Error on saving '{title}'`, values: { title: savedMap.title }, @@ -505,7 +529,7 @@ app.controller( } if (id) { - toastNotifications.addSuccess({ + getToasts().addSuccess({ title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', { defaultMessage: `Saved '{title}'`, values: { title: savedMap.title }, @@ -539,6 +563,7 @@ app.controller( }), testId: 'mapsFullScreenMode', run() { + getCoreChrome().setIsVisible(false); store.dispatch(enableFullScreen()); }, }, @@ -556,7 +581,7 @@ app.controller( getInspector().open(inspectorAdapters, {}); }, }, - ...(capabilities.get().maps.save + ...(getMapsCapabilities().save ? [ { id: 'save', @@ -611,7 +636,7 @@ app.controller( showDescription={false} /> ); - showSaveModal(saveModal, npStart.core.i18n.Context); + showSaveModal(saveModal, getCoreI18n().Context); }, }, ] diff --git a/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js b/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js index bc636c0b200f8..710997a9c0d7f 100644 --- a/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js +++ b/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js @@ -4,24 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; import { createSavedGisMapClass } from './saved_gis_map'; -import { uiModules } from 'ui/modules'; import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; -import { npStart } from '../../../../../../../src/legacy/ui/public/new_platform'; +import { + getCoreChrome, + getSavedObjectsClient, + getIndexPatternService, + getCoreOverlays, + getData, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/maps/public/kibana_services'; -const module = uiModules.get('app/maps'); - -// This is the only thing that gets injected into controllers -module.service('gisMapSavedObjectLoader', function() { - const savedObjectsClient = npStart.core.savedObjects.client; +export const getMapsSavedObjectLoader = _.once(function() { const services = { - savedObjectsClient, - indexPatterns: npStart.plugins.data.indexPatterns, - search: npStart.plugins.data.search, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, + savedObjectsClient: getSavedObjectsClient(), + indexPatterns: getIndexPatternService(), + search: getData().search, + chrome: getCoreChrome(), + overlays: getCoreOverlays(), }; const SavedGisMap = createSavedGisMapClass(services); - return new SavedObjectLoader(SavedGisMap, npStart.core.savedObjects.client, npStart.core.chrome); + return new SavedObjectLoader(SavedGisMap, getSavedObjectsClient(), getCoreChrome()); }); diff --git a/x-pack/legacy/plugins/maps/public/components/map_listing.js b/x-pack/legacy/plugins/maps/public/components/map_listing.js index 6fb5930e81a20..ef1d524cb91dd 100644 --- a/x-pack/legacy/plugins/maps/public/components/map_listing.js +++ b/x-pack/legacy/plugins/maps/public/components/map_listing.js @@ -7,7 +7,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; -import { toastNotifications } from 'ui/notify'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getToasts } from '../../../../../plugins/maps/public/kibana_services'; import { EuiTitle, EuiFieldSearch, @@ -27,7 +28,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { addHelpMenuToAppChrome } from '../help_menu_util'; -import chrome from 'ui/chrome'; export const EMPTY_FILTER = ''; @@ -55,7 +55,7 @@ export class MapListing extends React.Component { componentDidMount() { this.fetchItems(); - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(); } debouncedFetch = _.debounce(async filter => { @@ -91,7 +91,7 @@ export class MapListing extends React.Component { try { await this.props.delete(this.state.selectedIds); } catch (error) { - toastNotifications.addDanger({ + getToasts().addDanger({ title: i18n.translate('xpack.maps.mapListing.unableToDeleteToastTitle', { defaultMessage: `Unable to delete map(s)`, }), diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js index 39cb2c469e054..2d8265bae9387 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js @@ -18,6 +18,8 @@ import { getQueryableUniqueIndexPatternIds, isToolbarOverlayHidden, } from '../../selectors/map_selectors'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getCoreChrome } from '../../../../../../plugins/maps/public/kibana_services'; function mapStateToProps(state = {}) { const flyoutDisplay = getFlyoutDisplay(state); @@ -37,7 +39,10 @@ function mapStateToProps(state = {}) { function mapDispatchToProps(dispatch) { return { triggerRefreshTimer: () => dispatch(triggerRefreshTimer()), - exitFullScreen: () => dispatch(exitFullScreen()), + exitFullScreen: () => { + dispatch(exitFullScreen()); + getCoreChrome().setIsVisible(true); + }, cancelAllInFlightRequests: () => dispatch(cancelAllInFlightRequests()), }; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js index 358313b8f5b6d..06097ebea1900 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js @@ -12,7 +12,8 @@ import { ToolbarOverlay } from '../toolbar_overlay/index'; import { LayerPanel } from '../layer_panel/index'; import { AddLayerPanel } from '../layer_addpanel/index'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; -import { ExitFullScreenButton } from 'ui/exit_full_screen'; +import { ExitFullScreenButton } from '../../../../../../../src/plugins/kibana_react/public'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getIndexPatternsFromIds } from '../../../../../../plugins/maps/public/index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js index f6bcac0dfc339..40fdac38493d4 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js @@ -20,12 +20,14 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { getIndexPatternService } from '../../../kibana_services'; +import { + getIndexPatternService, + getUiSettings, + getData, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; -import { npStart } from 'ui/new_platform'; -const { SearchBar } = npStart.plugins.data.ui; - export class FilterEditor extends Component { state = { isPopoverOpen: false, @@ -84,7 +86,8 @@ export class FilterEditor extends Component { _renderQueryPopover() { const layerQuery = this.props.layer.getQuery(); - const { uiSettings } = npStart.core; + const uiSettings = getUiSettings(); + const { SearchBar } = getData().ui; return ( diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index 7063c50edad6a..15824b82965e8 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -12,7 +12,8 @@ import { i18n } from '@kbn/i18n'; import { createSpatialFilterWithGeometry } from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; -import { UrlOverflowService } from 'ui/error_url_overflow'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { UrlOverflowService } from '../../../../../../../../src/plugins/kibana_legacy/public'; import rison from 'rison-node'; // over estimated and imprecise value to ensure filter has additional room for any meta keys added when filter is mapped. diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index fedc1902d80a2..1fe3d0d493ee7 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -18,7 +18,6 @@ import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/co import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; -import chrome from 'ui/chrome'; import { spritesheet } from '@elastic/maki'; import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; @@ -29,6 +28,8 @@ import { clampToLonBounds, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInjectedVarFunc } from '../../../../../../../plugins/maps/public/kibana_services'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); @@ -129,7 +130,7 @@ export class MBMapContainer extends React.Component { container: this.refs.mapContainer, style: mbStyle, scrollZoom: this.props.scrollZoom, - preserveDrawingBuffer: chrome.getInjected('preserveDrawingBuffer', false), + preserveDrawingBuffer: getInjectedVarFunc()('preserveDrawingBuffer', false), interactive: !this.props.disableInteractive, }; const initialView = _.get(this.props.goto, 'center'); diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx index bdd2d863e6920..b8e4c84ad56a1 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx @@ -11,7 +11,6 @@ import { render, unmountComponentAtNode } from 'react-dom'; import 'mapbox-gl/dist/mapbox-gl.css'; import { I18nContext } from 'ui/i18n'; -import { npStart } from 'ui/new_platform'; import { Subscription } from 'rxjs'; import { Unsubscribe } from 'redux'; import { @@ -59,6 +58,8 @@ import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_se import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { RenderToolTipContent } from '../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getUiActions } from '../../../../../plugins/maps/public/kibana_services'; interface MapEmbeddableConfig { editUrl?: string; @@ -269,7 +270,7 @@ export class MapEmbeddable extends Embeddable { - npStart.plugins.uiActions.executeTriggerActions(APPLY_FILTER_TRIGGER, { + getUiActions().executeTriggerActions(APPLY_FILTER_TRIGGER, { embeddable: this, filters, }); diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts index 5deb3057a449e..96c3baf634a83 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -5,14 +5,18 @@ */ import _ from 'lodash'; -import chrome from 'ui/chrome'; -import { capabilities } from 'ui/capabilities'; import { i18n } from '@kbn/i18n'; import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectLoader } from 'src/plugins/saved_objects/public'; import { IIndexPattern } from 'src/plugins/data/public'; +// @ts-ignore +import { getMapsSavedObjectLoader } from '../angular/services/gis_map_saved_object_loader'; import { MapEmbeddable, MapEmbeddableInput } from './map_embeddable'; -import { getIndexPatternService } from '../kibana_services'; +import { + getIndexPatternService, + getHttp, + getMapsCapabilities, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/kibana_services'; import { EmbeddableFactoryDefinition, IContainer, @@ -26,7 +30,6 @@ import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; import '../angular/services/gis_map_saved_object_loader'; -import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin'; // @ts-ignore import { bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, @@ -44,14 +47,12 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { }; constructor() { // Init required services. Necessary while in legacy - bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins); bindNpSetupCoreAndPlugins(npSetup.core, npSetup.plugins); - bindStartCoreAndPlugins(npStart.core, npStart.plugins); bindNpStartCoreAndPlugins(npStart.core, npStart.plugins); } async isEditable() { - return capabilities.get().maps.save as boolean; + return getMapsCapabilities().save as boolean; } // Not supported yet for maps types. @@ -96,8 +97,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { } async _fetchSavedMap(savedObjectId: string) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const savedObjectLoader = $injector.get('gisMapSavedObjectLoader'); + const savedObjectLoader = getMapsSavedObjectLoader(); return await savedObjectLoader.get(savedObjectId); } @@ -114,7 +114,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { { layerList, title: savedMap.title, - editUrl: chrome.addBasePath(createMapPath(savedObjectId)), + editUrl: getHttp().basePath.prepend(createMapPath(savedObjectId)), indexPatterns, editable: await this.isEditable(), }, diff --git a/x-pack/legacy/plugins/maps/public/help_menu_util.js b/x-pack/legacy/plugins/maps/public/help_menu_util.js index 72d51cc180eb3..70b9340b562cd 100644 --- a/x-pack/legacy/plugins/maps/public/help_menu_util.js +++ b/x-pack/legacy/plugins/maps/public/help_menu_util.js @@ -3,10 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getDocLinks, getCoreChrome } from '../../../../plugins/maps/public/kibana_services'; -export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set({ +export function addHelpMenuToAppChrome() { + const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = getDocLinks(); + + getCoreChrome().setHelpExtension({ appName: 'Maps', links: [ { diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts index b69485e251be4..8555594e909d3 100644 --- a/x-pack/legacy/plugins/maps/public/index.ts +++ b/x-pack/legacy/plugins/maps/public/index.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import './kibana_services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/kibana_services'; // import the uiExports that we want to "use" import 'uiExports/inspectorViews'; diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.d.ts b/x-pack/legacy/plugins/maps/public/kibana_services.d.ts deleted file mode 100644 index 89b1fee1aa842..0000000000000 --- a/x-pack/legacy/plugins/maps/public/kibana_services.d.ts +++ /dev/null @@ -1,23 +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 { IIndexPattern } from 'src/plugins/data/public'; - -export function getIndexPatternService(): { - get: (id: string) => IIndexPattern | undefined; -}; - -export function setLicenseId(args: unknown): void; -export function setInspector(args: unknown): void; -export function setFileUpload(args: unknown): void; -export function setIndexPatternSelect(args: unknown): void; -export function setHttp(args: unknown): void; -export function setTimeFilter(args: unknown): void; -export function setUiSettings(args: unknown): void; -export function setInjectedVarFunc(args: unknown): void; -export function setToasts(args: unknown): void; -export function setIndexPatternService(args: unknown): void; -export function setAutocompleteService(args: unknown): void; diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js deleted file mode 100644 index a6491fe1aa6d4..0000000000000 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ /dev/null @@ -1,29 +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. - */ - -let indexPatternService; -export const setIndexPatternService = dataIndexPatterns => - (indexPatternService = dataIndexPatterns); -export const getIndexPatternService = () => indexPatternService; - -let inspector; -export const setInspector = newInspector => (inspector = newInspector); -export const getInspector = () => { - return inspector; -}; - -let getInjectedVar; -export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); -export const getInjectedVarFunc = () => getInjectedVar; - -let indexPatternSelectComponent; -export const setIndexPatternSelect = indexPatternSelect => - (indexPatternSelectComponent = indexPatternSelect); -export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; - -let dataTimeFilter; -export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); -export const getTimeFilter = () => dataTimeFilter; diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index 0fa7e1106a6df..4ec068ff44029 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -13,19 +13,11 @@ import '../../../../plugins/maps/public/layers/load_layer_wizards'; import { Plugin, CoreStart, CoreSetup } from 'src/core/public'; // @ts-ignore -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; // @ts-ignore +import { wrapInI18nContext } from 'ui/i18n'; +// @ts-ignore import { MapListing } from './components/map_listing'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { - setInspector, - setIndexPatternSelect, - setTimeFilter, - setInjectedVarFunc, - setIndexPatternService, -} from './kibana_services'; // @ts-ignore import { bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, @@ -62,20 +54,6 @@ interface MapsPluginStartDependencies { // file_upload TODO: Export type from file upload and use here } -export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { - const { injectedMetadata } = core; - setInjectedVarFunc(injectedMetadata.getInjectedVar); - setInjectedVarFunc(core.injectedMetadata.getInjectedVar); -}; - -export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { - const { data, inspector } = plugins; - setInspector(inspector); - setIndexPatternSelect(data.ui.IndexPatternSelect); - setTimeFilter(data.query.timefilter.timefilter); - setIndexPatternService(data.indexPatterns); -}; - /** @internal */ export class MapsPlugin implements Plugin { public setup(core: CoreSetup, { __LEGACY: { uiModules }, np }: MapsPluginSetupDependencies) { @@ -85,14 +63,12 @@ export class MapsPlugin implements Plugin { return reactDirective(wrapInI18nContext(MapListing)); }); - bindSetupCoreAndPlugins(core, np); bindNpSetupCoreAndPlugins(core, np); np.home.featureCatalogue.register(featureCatalogueEntry); } public start(core: CoreStart, plugins: MapsPluginStartDependencies) { - bindStartCoreAndPlugins(core, plugins); bindNpStartCoreAndPlugins(core, plugins); } } diff --git a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js index 64a42173098ee..9dc07bcb5dc0e 100644 --- a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js +++ b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js @@ -4,12 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; -import { npSetup } from '../../../../../src/legacy/ui/public/new_platform'; import { i18n } from '@kbn/i18n'; import { APP_ID, APP_ICON, MAP_BASE_URL } from '../common/constants'; +import { + getInjectedVarFunc, + getVisualizations, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../plugins/maps/public/kibana_services'; +import { npSetup } from 'ui/new_platform'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { bindSetupCoreAndPlugins } from '../../../../plugins/maps/public/plugin'; -const showMapVisualizationTypes = chrome.getInjected('showMapVisualizationTypes', false); +bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins); + +const showMapVisualizationTypes = getInjectedVarFunc()('showMapVisualizationTypes', false); const description = i18n.translate('xpack.maps.visTypeAlias.description', { defaultMessage: 'Create and style maps with multiple layers and indices.', @@ -23,7 +31,7 @@ The Maps app offers more functionality and is easier to use.`, } ); -npSetup.plugins.visualizations.registerAlias({ +getVisualizations().registerAlias({ aliasUrl: MAP_BASE_URL, name: APP_ID, title: i18n.translate('xpack.maps.visTypeAlias.title', { @@ -37,5 +45,5 @@ npSetup.plugins.visualizations.registerAlias({ }); if (!showMapVisualizationTypes) { - npSetup.plugins.visualizations.hideTypes(['region_map', 'tile_map']); + getVisualizations().hideTypes(['region_map', 'tile_map']); } diff --git a/x-pack/legacy/plugins/maps/public/routes.js b/x-pack/legacy/plugins/maps/public/routes.js index 49705acb417e2..c082e0e1352c0 100644 --- a/x-pack/legacy/plugins/maps/public/routes.js +++ b/x-pack/legacy/plugins/maps/public/routes.js @@ -5,20 +5,23 @@ */ import { i18n } from '@kbn/i18n'; -import { capabilities } from 'ui/capabilities'; -import chrome from 'ui/chrome'; import routes from 'ui/routes'; -import { docTitle } from 'ui/doc_title'; import listingTemplate from './angular/listing_ng_wrapper.html'; import mapTemplate from './angular/map.html'; -import { npStart } from 'ui/new_platform'; +import { + getSavedObjectsClient, + getCoreChrome, + getMapsCapabilities, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../plugins/maps/public/kibana_services'; +import { getMapsSavedObjectLoader } from './angular/services/gis_map_saved_object_loader'; routes.enable(); routes .defaults(/.*/, { - badge: uiCapabilities => { - if (uiCapabilities.maps.save) { + badge: () => { + if (getMapsCapabilities().save) { return undefined; } @@ -35,7 +38,8 @@ routes }) .when('/', { template: listingTemplate, - controller($scope, gisMapSavedObjectLoader, config) { + controller($scope, config) { + const gisMapSavedObjectLoader = getMapsSavedObjectLoader(); $scope.listingLimit = config.get('savedObjects:listingLimit'); $scope.find = search => { return gisMapSavedObjectLoader.find(search, $scope.listingLimit); @@ -43,19 +47,17 @@ routes $scope.delete = ids => { return gisMapSavedObjectLoader.delete(ids); }; - $scope.readOnly = !capabilities.get().maps.save; + $scope.readOnly = !getMapsCapabilities().save; }, resolve: { hasMaps: function(kbnUrl) { - chrome - .getSavedObjectsClient() + getSavedObjectsClient() .find({ type: 'map', perPage: 1 }) .then(resp => { // Do not show empty listing page, just redirect to a new map if (resp.savedObjects.length === 0) { kbnUrl.redirect('/map'); } - return true; }); }, @@ -65,7 +67,8 @@ routes template: mapTemplate, controller: 'GisMapController', resolve: { - map: function(gisMapSavedObjectLoader, redirectWhenMissing) { + map: function(redirectWhenMissing) { + const gisMapSavedObjectLoader = getMapsSavedObjectLoader(); return gisMapSavedObjectLoader.get().catch( redirectWhenMissing({ map: '/', @@ -78,13 +81,14 @@ routes template: mapTemplate, controller: 'GisMapController', resolve: { - map: function(gisMapSavedObjectLoader, redirectWhenMissing, $route) { + map: function(redirectWhenMissing, $route) { + const gisMapSavedObjectLoader = getMapsSavedObjectLoader(); const id = $route.current.params.id; return gisMapSavedObjectLoader .get(id) .then(savedMap => { - npStart.core.chrome.recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, id); - docTitle.change(savedMap.title); + getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, id); + getCoreChrome().docTitle.change(savedMap.title); return savedMap; }) .catch( diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index 59346e4c6fb98..f350a2c944756 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -16,7 +16,8 @@ import { VectorLayer } from '../../../../../plugins/maps/public/layers/vector_la import { HeatmapLayer } from '../../../../../plugins/maps/public/layers/heatmap_layer'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { BlendedVectorLayer } from '../../../../../plugins/maps/public/layers/blended_vector_layer'; -import { getTimeFilter } from '../kibana_services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getTimeFilter } from '../../../../../plugins/maps/public/kibana_services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js index 77bd29259647c..b83be301653b8 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js @@ -14,7 +14,7 @@ jest.mock('../../../../../plugins/maps/public/reducers/non_serializable_instance return {}; }, })); -jest.mock('../kibana_services', () => ({ +jest.mock('../../../../../plugins/maps/public/kibana_services', () => ({ getTimeFilter: () => ({ getTime: () => { return { diff --git a/x-pack/plugins/maps/public/kibana_services.d.ts b/x-pack/plugins/maps/public/kibana_services.d.ts new file mode 100644 index 0000000000000..867557c296292 --- /dev/null +++ b/x-pack/plugins/maps/public/kibana_services.d.ts @@ -0,0 +1,55 @@ +/* + * 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 { IIndexPattern } from 'src/plugins/data/public'; + +export function getLicenseId(): any; +export function getInspector(): any; +export function getFileUploadComponent(): any; +export function getIndexPatternSelectComponent(): any; +export function getHttp(): any; +export function getTimeFilter(): any; +export function getInjectedVarFunc(): any; +export function getToasts(): any; +export function getIndexPatternService(): { + get: (id: string) => IIndexPattern | undefined; +}; +export function getAutocompleteService(): any; +export function getSavedObjectsClient(): any; +export function getMapsCapabilities(): any; +export function getVisualizations(): any; +export function getDocLinks(): any; +export function getCoreChrome(): any; +export function getUiSettings(): any; +export function getCoreOverlays(): any; +export function getData(): any; +export function getUiActions(): any; +export function getCore(): any; +export function getNavigation(): any; +export function getCoreI18n(): any; + +export function setLicenseId(args: unknown): void; +export function setInspector(args: unknown): void; +export function setFileUpload(args: unknown): void; +export function setIndexPatternSelect(args: unknown): void; +export function setHttp(args: unknown): void; +export function setTimeFilter(args: unknown): void; +export function setInjectedVarFunc(args: unknown): void; +export function setToasts(args: unknown): void; +export function setIndexPatternService(args: unknown): void; +export function setAutocompleteService(args: unknown): void; +export function setSavedObjectsClient(args: unknown): void; +export function setMapsCapabilities(args: unknown): void; +export function setVisualizations(args: unknown): void; +export function setDocLinks(args: unknown): void; +export function setCoreChrome(args: unknown): void; +export function setUiSettings(args: unknown): void; +export function setCoreOverlays(args: unknown): void; +export function setData(args: unknown): void; +export function setUiActions(args: unknown): void; +export function setCore(args: unknown): void; +export function setNavigation(args: unknown): void; +export function setCoreI18n(args: unknown): void; diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index d2ddecfdf915b..2a79314380330 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -89,3 +89,49 @@ export async function fetchSearchSourceAndRecordWithInspector({ return resp; } + +let savedObjectsClient; +export const setSavedObjectsClient = coreSavedObjectsClient => + (savedObjectsClient = coreSavedObjectsClient); +export const getSavedObjectsClient = () => savedObjectsClient; + +let chrome; +export const setCoreChrome = coreChrome => (chrome = coreChrome); +export const getCoreChrome = () => chrome; + +let mapsCapabilities; +export const setMapsCapabilities = coreAppMapsCapabilities => + (mapsCapabilities = coreAppMapsCapabilities); +export const getMapsCapabilities = () => mapsCapabilities; + +let visualizations; +export const setVisualizations = visPlugin => (visualizations = visPlugin); +export const getVisualizations = () => visualizations; + +let docLinks; +export const setDocLinks = coreDocLinks => (docLinks = coreDocLinks); +export const getDocLinks = () => docLinks; + +let overlays; +export const setCoreOverlays = coreOverlays => (overlays = coreOverlays); +export const getCoreOverlays = () => overlays; + +let data; +export const setData = dataPlugin => (data = dataPlugin); +export const getData = () => data; + +let uiActions; +export const setUiActions = pluginUiActions => (uiActions = pluginUiActions); +export const getUiActions = () => uiActions; + +let core; +export const setCore = kibanaCore => (core = kibanaCore); +export const getCore = () => core; + +let navigation; +export const setNavigation = pluginNavigation => (navigation = pluginNavigation); +export const getNavigation = () => navigation; + +let coreI18n; +export const setCoreI18n = kibanaCoreI18n => (coreI18n = kibanaCoreI18n); +export const getCoreI18n = () => coreI18n; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 14487b615e759..649627690ec9a 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -10,6 +10,12 @@ import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public'; import { MapView } from './inspector/views/map_view'; import { setAutocompleteService, + setCore, + setCoreChrome, + setCoreI18n, + setCoreOverlays, + setData, + setDocLinks, setFileUpload, setHttp, setIndexPatternSelect, @@ -17,9 +23,14 @@ import { setInjectedVarFunc, setInspector, setLicenseId, + setMapsCapabilities, + setNavigation, + setSavedObjectsClient, setTimeFilter, setToasts, + setUiActions, setUiSettings, + setVisualizations, // @ts-ignore } from './kibana_services'; @@ -31,15 +42,16 @@ export interface MapsPluginStartDependencies {} export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { const { licensing } = plugins; - const { injectedMetadata, http } = core; + const { injectedMetadata, uiSettings, http, notifications } = core; if (licensing) { licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); } setInjectedVarFunc(injectedMetadata.getInjectedVar); setHttp(http); - setUiSettings(core.uiSettings); - setInjectedVarFunc(core.injectedMetadata.getInjectedVar); - setToasts(core.notifications.toasts); + setToasts(notifications.toasts); + setInjectedVarFunc(injectedMetadata.getInjectedVar); + setVisualizations(plugins.visualizations); + setUiSettings(uiSettings); }; export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { @@ -50,6 +62,16 @@ export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { setTimeFilter(data.query.timefilter.timefilter); setIndexPatternService(data.indexPatterns); setAutocompleteService(data.autocomplete); + setCore(core); + setSavedObjectsClient(core.savedObjects.client); + setCoreChrome(core.chrome); + setCoreOverlays(core.overlays); + setMapsCapabilities(core.application.capabilities.maps); + setDocLinks(core.docLinks); + setData(plugins.data); + setUiActions(plugins.uiActions); + setNavigation(plugins.navigation); + setCoreI18n(core.i18n); }; /** From 373d49223ac6aa13bf327fcc0bb25bcaf673cfc8 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Thu, 16 Apr 2020 07:54:06 -0500 Subject: [PATCH 43/44] Style dashboard buttons the same way (#63403) * Stylize dashboard buttons the same way * update snapshots Co-authored-by: Elastic Machine --- .../dashboard_empty_screen.test.tsx.snap | 11 +++++------ .../dashboard/public/application/_dashboard_app.scss | 10 +--------- .../public/application/dashboard_empty_screen.tsx | 5 ++--- .../add_panel/saved_object_finder_create_new.tsx | 4 ++-- .../__snapshots__/top_nav_menu_item.test.tsx.snap | 5 ----- .../public/top_nav_menu/top_nav_menu_item.tsx | 2 +- 6 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap index e71e4f1b15134..7210879c5eacc 100644 --- a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -667,14 +667,13 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = `