diff --git a/.eslintrc.js b/.eslintrc.js index 89396e8252839..39a2646026a4e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -734,6 +734,19 @@ module.exports = { }, }, + /** + * Alerting Services overrides + */ + { + // typescript only for front and back end + files: [ + 'x-pack/{,legacy/}plugins/{alerting,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', + ], + rules: { + '@typescript-eslint/no-explicit-any': 'error', + }, + }, + /** * Lens overrides */ diff --git a/x-pack/legacy/plugins/actions/server/index.ts b/x-pack/legacy/plugins/actions/server/index.ts index 7235eda88149f..63dd6f99f9c24 100644 --- a/x-pack/legacy/plugins/actions/server/index.ts +++ b/x-pack/legacy/plugins/actions/server/index.ts @@ -6,8 +6,13 @@ import { Root } from 'joi'; import { Legacy } from 'kibana'; import mappings from './mappings.json'; +import { + LegacyPluginApi, + LegacyPluginSpec, + ArrayOrItem, +} from '../../../../../src/legacy/plugin_discovery/types'; -export function actions(kibana: any) { +export function actions(kibana: LegacyPluginApi): ArrayOrItem { return new kibana.Plugin({ id: 'actions', configPrefix: 'xpack.actions', @@ -29,5 +34,5 @@ export function actions(kibana: any) { uiExports: { mappings, }, - }); + } as Legacy.PluginSpecOptions); } diff --git a/x-pack/legacy/plugins/alerting/server/index.ts b/x-pack/legacy/plugins/alerting/server/index.ts index 5bf7cda51bda6..065af7dedebd9 100644 --- a/x-pack/legacy/plugins/alerting/server/index.ts +++ b/x-pack/legacy/plugins/alerting/server/index.ts @@ -7,8 +7,13 @@ import { Legacy } from 'kibana'; import { Root } from 'joi'; import mappings from './mappings.json'; +import { + LegacyPluginApi, + LegacyPluginSpec, + ArrayOrItem, +} from '../../../../../src/legacy/plugin_discovery/types'; -export function alerting(kibana: any) { +export function alerting(kibana: LegacyPluginApi): ArrayOrItem { return new kibana.Plugin({ id: 'alerting', configPrefix: 'xpack.alerting', @@ -31,5 +36,5 @@ export function alerting(kibana: any) { uiExports: { mappings, }, - }); + } as Legacy.PluginSpecOptions); } diff --git a/x-pack/legacy/plugins/task_manager/server/index.ts b/x-pack/legacy/plugins/task_manager/server/index.ts index ff25d8a1e0e5d..3ea687f7003f4 100644 --- a/x-pack/legacy/plugins/task_manager/server/index.ts +++ b/x-pack/legacy/plugins/task_manager/server/index.ts @@ -15,19 +15,26 @@ export { LegacyTaskManagerApi, getTaskManagerSetup, getTaskManagerStart } from ' // Once all plugins are migrated to NP, this can be removed // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TaskManager } from '../../../../plugins/task_manager/server/task_manager'; +import { + LegacyPluginApi, + LegacyPluginSpec, + ArrayOrItem, +} from '../../../../../src/legacy/plugin_discovery/types'; const savedObjectSchemas = { task: { hidden: true, isNamespaceAgnostic: true, convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`, + // legacy config is marked as any in core, no choice here + // eslint-disable-next-line @typescript-eslint/no-explicit-any indexPattern(config: any) { return config.get('xpack.task_manager.index'); }, }, }; -export function taskManager(kibana: any) { +export function taskManager(kibana: LegacyPluginApi): ArrayOrItem { return new kibana.Plugin({ id: 'task_manager', require: ['kibana', 'elasticsearch', 'xpack_main'], @@ -58,7 +65,11 @@ export function taskManager(kibana: any) { // instead we will start the internal Task Manager plugin when // all legacy plugins have finished initializing // Once all plugins are migrated to NP, this can be removed - this.kbnServer.afterPluginsInit(() => { + + // the typing for the lagcy server isn't quite correct, so + // we'll bypase it for now + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this as any).kbnServer.afterPluginsInit(() => { taskManagerPlugin.start(); }); return taskManagerPlugin; @@ -71,5 +82,5 @@ export function taskManager(kibana: any) { migrations, savedObjectSchemas, }, - }); + } as Legacy.PluginSpecOptions); } diff --git a/x-pack/legacy/plugins/task_manager/server/legacy.ts b/x-pack/legacy/plugins/task_manager/server/legacy.ts index cd2047b757e61..0d50828004a94 100644 --- a/x-pack/legacy/plugins/task_manager/server/legacy.ts +++ b/x-pack/legacy/plugins/task_manager/server/legacy.ts @@ -49,10 +49,10 @@ export function createLegacyApi(legacyTaskManager: Promise): Legacy fetch: (opts: SearchOpts) => legacyTaskManager.then((tm: TaskManager) => tm.fetch(opts)), get: (id: string) => legacyTaskManager.then((tm: TaskManager) => tm.get(id)), remove: (id: string) => legacyTaskManager.then((tm: TaskManager) => tm.remove(id)), - schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: any) => + schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: object) => legacyTaskManager.then((tm: TaskManager) => tm.schedule(taskInstance, options)), runNow: (taskId: string) => legacyTaskManager.then((tm: TaskManager) => tm.runNow(taskId)), - ensureScheduled: (taskInstance: TaskInstanceWithId, options?: any) => + ensureScheduled: (taskInstance: TaskInstanceWithId, options?: object) => legacyTaskManager.then((tm: TaskManager) => tm.ensureScheduled(taskInstance, options)), }; } diff --git a/x-pack/legacy/plugins/task_manager/server/migrations.ts b/x-pack/legacy/plugins/task_manager/server/migrations.ts index 97c4f97f59c58..1c2cf73d0fe13 100644 --- a/x-pack/legacy/plugins/task_manager/server/migrations.ts +++ b/x-pack/legacy/plugins/task_manager/server/migrations.ts @@ -7,7 +7,7 @@ import { SavedObject } from '../../../../../src/core/server'; export const migrations = { task: { - '7.4.0': (doc: SavedObject>) => ({ + '7.4.0': (doc: SavedObject>) => ({ ...doc, updated_at: new Date().toISOString(), }), @@ -18,7 +18,7 @@ export const migrations = { function moveIntervalIntoSchedule({ attributes: { interval, ...attributes }, ...doc -}: SavedObject>) { +}: SavedObject>) { return { ...doc, attributes: { diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 61b338d47b9f5..49e8f3e80b14a 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -19,6 +19,8 @@ export interface ActionResult { id: string; actionTypeId: string; name: string; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any config: Record; isPreconfigured: boolean; } diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index c1d979feacc1d..735ec349837a9 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -81,7 +81,7 @@ export class ActionTypeRegistry { title: actionType.name, type: `actions:${actionType.id}`, maxAttempts: actionType.maxAttempts || 1, - getRetry(attempts: number, error: any) { + getRetry(attempts: number, error: unknown) { if (error instanceof ExecutorError) { return error.retry == null ? false : error.retry; } diff --git a/x-pack/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts index 431bfb1e99c3b..64b43e1ab6bbc 100644 --- a/x-pack/plugins/actions/server/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client.mock.ts @@ -7,9 +7,10 @@ import { ActionsClient } from './actions_client'; type ActionsClientContract = PublicMethodsOf; +export type ActionsClientMock = jest.Mocked; const createActionsClientMock = () => { - const mocked: jest.Mocked = { + const mocked: ActionsClientMock = { create: jest.fn(), get: jest.fn(), delete: jest.fn(), @@ -19,6 +20,8 @@ const createActionsClientMock = () => { return mocked; }; -export const actionsClientMock = { +export const actionsClientMock: { + create: () => ActionsClientMock; +} = { create: createActionsClientMock, }; diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index f52e293296955..618bc8a85e856 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -135,7 +135,7 @@ export class ActionsClient { id, actionTypeId: result.attributes.actionTypeId as string, name: result.attributes.name as string, - config: result.attributes.config as Record, + config: result.attributes.config as Record, isPreconfigured: false, }; } @@ -228,7 +228,7 @@ async function injectExtraFindData( scopedClusterClient: IScopedClusterClient, actionResults: ActionResult[] ): Promise { - const aggs: Record = {}; + const aggs: Record = {}; for (const actionResult of actionResults) { aggs[actionResult.id] = { filter: { diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 658f8f3fd8cf9..265ff267f222e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -30,7 +30,7 @@ const NO_OP_FN = () => {}; const services = { log: NO_OP_FN, - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; @@ -52,7 +52,7 @@ describe('actionTypeRegistry.get() works', () => { describe('config validation', () => { test('config validation succeeds when config is valid', () => { - const config: Record = { + const config: Record = { service: 'gmail', from: 'bob@example.com', }; @@ -74,7 +74,7 @@ describe('config validation', () => { }); test('config validation fails when config is not valid', () => { - const baseConfig: Record = { + const baseConfig: Record = { from: 'bob@example.com', }; @@ -177,7 +177,7 @@ describe('config validation', () => { describe('secrets validation', () => { test('secrets validation succeeds when secrets is valid', () => { - const secrets: Record = { + const secrets: Record = { user: 'bob', password: 'supersecret', }; @@ -185,7 +185,7 @@ describe('secrets validation', () => { }); test('secrets validation succeeds when secrets props are null/undefined', () => { - const secrets: Record = { + const secrets: Record = { user: null, password: null, }; @@ -197,7 +197,7 @@ describe('secrets validation', () => { describe('params validation', () => { test('params validation succeeds when params is valid', () => { - const params: Record = { + const params: Record = { to: ['bob@example.com'], subject: 'this is a test', message: 'this is the message', diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts index ca8d089ad2946..7ddb123a4d780 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -30,10 +30,10 @@ const ConfigSchema = schema.object(ConfigSchemaProps); function validateConfig( configurationUtilities: ActionsConfigurationUtilities, - configObject: any + configObject: unknown ): string | void { // avoids circular reference ... - const config: ActionTypeConfigType = configObject; + const config = configObject as ActionTypeConfigType; // Make sure service is set, or if not, both host/port must be set. // If service is set, host/port are ignored, when the email is sent. @@ -95,9 +95,9 @@ const ParamsSchema = schema.object( } ); -function validateParams(paramsObject: any): string | void { +function validateParams(paramsObject: unknown): string | void { // avoids circular reference ... - const params: ActionParamsType = paramsObject; + const params = paramsObject as ActionParamsType; const { to, cc, bcc } = params; const addrs = to.length + cc.length + bcc.length; diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts index ec495aed7675a..ed57e44c3f0b3 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -43,7 +43,7 @@ describe('actionTypeRegistry.get() works', () => { describe('config validation', () => { test('config validation succeeds when config is valid', () => { - const config: Record = { + const config: Record = { index: 'testing-123', refresh: false, }; @@ -97,7 +97,7 @@ describe('config validation', () => { }); test('config validation fails when config is not valid', () => { - const baseConfig: Record = { + const baseConfig: Record = { indeX: 'bob', }; @@ -111,7 +111,7 @@ describe('config validation', () => { describe('params validation', () => { test('params validation succeeds when params is valid', () => { - const params: Record = { + const params: Record = { documents: [{ rando: 'thing' }], }; expect(validateParams(actionType, params)).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index 43e6197356a85..9b293521c979a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -72,13 +72,12 @@ async function executor( bulkBody.push(document); } - const bulkParams: any = { + const bulkParams: unknown = { index, body: bulkBody, + refresh: config.refresh, }; - bulkParams.refresh = config.refresh; - let result; try { result = await services.callCluster('bulk', bulkParams); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts index cc9d36ff86342..92f88ebe0be22 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts @@ -9,7 +9,7 @@ import { Services } from '../../types'; interface PostPagerdutyOptions { apiUrl: string; - data: any; + data: unknown; headers: Record; services: Services; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index 42160dc2fc22b..a02f79a49e8e2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -63,12 +63,15 @@ describe('send_email module', () => { }); test('handles unauthenticated email using not secure host/port', async () => { - const sendEmailOptions = getSendEmailOptions(); + const sendEmailOptions = getSendEmailOptions({ + transport: { + host: 'example.com', + port: 1025, + }, + }); delete sendEmailOptions.transport.service; delete sendEmailOptions.transport.user; delete sendEmailOptions.transport.password; - sendEmailOptions.transport.host = 'example.com'; - sendEmailOptions.transport.port = 1025; const result = await sendEmail(mockLogger, sendEmailOptions); expect(result).toBe(sendMailMockResult); expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` @@ -105,13 +108,17 @@ describe('send_email module', () => { }); test('handles unauthenticated email using secure host/port', async () => { - const sendEmailOptions = getSendEmailOptions(); + const sendEmailOptions = getSendEmailOptions({ + transport: { + host: 'example.com', + port: 1025, + secure: true, + }, + }); delete sendEmailOptions.transport.service; delete sendEmailOptions.transport.user; delete sendEmailOptions.transport.password; - sendEmailOptions.transport.host = 'example.com'; - sendEmailOptions.transport.port = 1025; - sendEmailOptions.transport.secure = true; + const result = await sendEmail(mockLogger, sendEmailOptions); expect(result).toBe(sendMailMockResult); expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` @@ -154,19 +161,22 @@ describe('send_email module', () => { }); }); -function getSendEmailOptions(): any { +function getSendEmailOptions({ content = {}, routing = {}, transport = {} } = {}) { return { content: { + ...content, message: 'a message', subject: 'a subject', }, routing: { + ...routing, from: 'fred@example.com', to: ['jim@example.com'], cc: ['bob@example.com', 'robert@example.com'], bcc: [], }, transport: { + ...transport, service: 'whatever', user: 'elastic', password: 'changeme', diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index ffbf7485a8b0b..869db34f034ae 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -43,13 +43,13 @@ export interface Content { } // send an email -export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { +export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { const { transport, routing, content } = options; const { service, host, port, secure, user, password } = transport; const { from, to, cc, bcc } = routing; const { subject, message } = content; - const transportConfig: Record = {}; + const transportConfig: Record = {}; if (user != null && password != null) { transportConfig.auth = { diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts index e5521558bc2da..e54c8179ae7b4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -22,7 +22,7 @@ const postPagerdutyMock = postPagerduty as jest.Mock; const ACTION_TYPE_ID = '.pagerduty'; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index f4d69a4a39e40..0c8802060164d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -68,9 +68,8 @@ const ParamsSchema = schema.object( { validate: validateParams } ); -function validateParams(paramsObject: any): string | void { - const params: ActionParamsType = paramsObject; - const { timestamp } = params; +function validateParams(paramsObject: unknown): string | void { + const { timestamp } = paramsObject as ActionParamsType; if (timestamp != null) { try { const date = Date.parse(timestamp); @@ -218,11 +217,23 @@ async function executor( const AcknowledgeOrResolve = new Set([EVENT_ACTION_ACKNOWLEDGE, EVENT_ACTION_RESOLVE]); -function getBodyForEventAction(actionId: string, params: ActionParamsType): any { +function getBodyForEventAction(actionId: string, params: ActionParamsType): unknown { const eventAction = params.eventAction || EVENT_ACTION_TRIGGER; const dedupKey = params.dedupKey || `action:${actionId}`; - const data: any = { + const data: { + event_action: ActionParamsType['eventAction']; + dedup_key: string; + payload?: { + summary: string; + source: string; + severity: string; + timestamp?: string; + component?: string; + group?: string; + class?: string; + }; + } = { event_action: eventAction, dedup_key: dedupKey, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts index bb806f8ae36fc..3ce01c59596f6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -91,7 +91,7 @@ describe('execute()', () => { await actionType.executor({ actionId, services: { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }, params: { message: 'message text here', level: 'info' }, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts index fb296089e9ec5..9166f53cf757e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/action_handlers.ts @@ -57,14 +57,14 @@ export const handleCreateIncident = async ({ comments && Array.isArray(comments) && comments.length > 0 && - mapping.get('comments').actionType !== 'nothing' + mapping.get('comments')?.actionType !== 'nothing' ) { comments = transformComments(comments, params, ['informationAdded']); res.comments = [ ...(await createComments( serviceNow, res.incidentId, - mapping.get('comments').target, + mapping.get('comments')!.target, comments )), ]; @@ -103,11 +103,11 @@ export const handleUpdateIncident = async ({ comments && Array.isArray(comments) && comments.length > 0 && - mapping.get('comments').actionType !== 'nothing' + mapping.get('comments')?.actionType !== 'nothing' ) { comments = transformComments(comments, params, ['informationAdded']); res.comments = [ - ...(await createComments(serviceNow, incidentId, mapping.get('comments').target, comments)), + ...(await createComments(serviceNow, incidentId, mapping.get('comments')!.target, comments)), ]; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts index 750fda93b60d6..0a26996ea8d69 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts @@ -39,7 +39,7 @@ export const buildMap = (mapping: MapEntry[]): Mapping => { }, new Map()); }; -export const mapParams = (params: any, mapping: Mapping) => { +export const mapParams = (params: Record, mapping: Mapping) => { return Object.keys(params).reduce((prev: KeyAny, curr: string): KeyAny => { const field = mapping.get(curr); if (field) { @@ -61,11 +61,11 @@ export const prepareFieldsForTransformation = ({ defaultPipes = ['informationCreated'], }: PrepareFieldsForTransformArgs): PipedField[] => { return Object.keys(params.incident) - .filter(p => mapping.get(p).actionType !== 'nothing') + .filter(p => mapping.get(p)!.actionType !== 'nothing') .map(p => ({ key: p, - value: params.incident[p], - actionType: mapping.get(p).actionType, + value: params.incident[p] as string, + actionType: mapping.get(p)!.actionType, pipes: [...defaultPipes], })) .map(p => ({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts index 1a23354e6490d..08b837cf8d0a5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts @@ -22,7 +22,7 @@ jest.mock('./action_handlers'); const handleIncidentMock = handleIncident as jest.Mock; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index a63c2fd3a6ceb..5066190d4fe56 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -85,7 +85,7 @@ async function serviceNowExecutor( const { comments, incidentId, ...restParams } = params; const mapping = buildMap(configurationMapping); - const incident = mapParams(restParams, mapping); + const incident = mapParams((restParams as unknown) as Record, mapping); const serviceNow = new ServiceNow({ url: apiUrl, username, password }); const handlerInput = { diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts index cc07a0b90330d..ed9cfe67a19a1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/lib/index.ts @@ -49,14 +49,14 @@ class ServiceNow { }: { url: string; method?: Method; - data?: any; + data?: unknown; }): Promise { const res = await this.axios(url, { method, data }); this._throwIfNotAlive(res.status, res.headers['content-type']); return res; } - private _patch({ url, data }: { url: string; data: any }): Promise { + private _patch({ url, data }: { url: string; data: unknown }): Promise { return this._request({ url, method: 'patch', diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index 71b05be8f3e4d..c5ef282aeffa7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -30,10 +30,10 @@ export type CasesConfigurationType = TypeOf; export type MapEntry = TypeOf; export type Comment = TypeOf; -export type Mapping = Map; +export type Mapping = Map>; export interface Params extends ExecutorParams { - incident: Record; + incident: Record; } export interface CreateHandlerArguments { serviceNow: ServiceNow; @@ -66,7 +66,7 @@ export interface AppendFieldArgs { } export interface KeyAny { - [index: string]: string; + [index: string]: unknown; } export interface AppendInformationFieldArgs { diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index 49b0b84e9dbb5..22c4b63474fdc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; +import { + ActionType, + Services, + ActionTypeExecutorOptions, + ActionTypeExecutorResult, +} from '../types'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { validateParams, validateSecrets } from '../lib'; import { getActionType } from './slack'; @@ -13,7 +18,7 @@ import { actionsConfigMock } from '../actions_config.mock'; const ACTION_TYPE_ID = '.slack'; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; @@ -21,7 +26,7 @@ let actionType: ActionType; beforeAll(() => { actionType = getActionType({ - async executor(options: ActionTypeExecutorOptions): Promise {}, + async executor() {}, configurationUtilities: actionsConfigMock.create(), }); }); @@ -117,7 +122,7 @@ describe('validateActionTypeSecrets()', () => { describe('execute()', () => { beforeAll(() => { - async function mockSlackExecutor(options: ActionTypeExecutorOptions): Promise { + async function mockSlackExecutor(options: ActionTypeExecutorOptions) { const { params } = options; const { message } = params; if (message == null) throw new Error('message property required in parameter'); @@ -130,7 +135,9 @@ describe('execute()', () => { return { text: `slack mockExecutor success: ${message}`, - }; + actionId: '', + status: 'ok', + } as ActionTypeExecutorResult; } actionType = getActionType({ @@ -148,10 +155,12 @@ describe('execute()', () => { params: { message: 'this invocation should succeed' }, }); expect(response).toMatchInlineSnapshot(` -Object { - "text": "slack mockExecutor success: this invocation should succeed", -} -`); + Object { + "actionId": "", + "status": "ok", + "text": "slack mockExecutor success: this invocation should succeed", + } + `); }); test('calls the mock executor with failure', async () => { diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index e51ef3f67bd65..edf3e485f3c57 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -156,7 +156,7 @@ async function slackExecutor( return successResult(actionId, result); } -function successResult(actionId: string, data: any): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { return { status: 'ok', data, actionId }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 03658b3b1dd85..1beaf92f3f48b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -22,7 +22,7 @@ const axiosRequestMock = axios.request as jest.Mock; const ACTION_TYPE_ID = '.webhook'; const services: Services = { - callCluster: async (path: string, opts: any) => {}, + callCluster: async (path: string, opts: unknown) => {}, savedObjectsClient: savedObjectsClientMock.create(), }; @@ -44,7 +44,7 @@ describe('actionType', () => { describe('secrets validation', () => { test('succeeds when secrets is valid', () => { - const secrets: Record = { + const secrets: Record = { user: 'bob', password: 'supersecret', }; @@ -60,20 +60,18 @@ describe('secrets validation', () => { }); test('succeeds when basic authentication credentials are omitted', () => { - expect(() => { - validateSecrets(actionType, {}).toEqual({}); - }); + expect(validateSecrets(actionType, {})).toEqual({ password: null, user: null }); }); }); describe('config validation', () => { - const defaultValues: Record = { + const defaultValues: Record = { headers: null, method: 'post', }; test('config validation passes when only required fields are provided', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', }; expect(validateConfig(actionType, config)).toEqual({ @@ -84,7 +82,7 @@ describe('config validation', () => { test('config validation passes when valid methods are provided', () => { ['post', 'put'].forEach(method => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', method, }; @@ -96,7 +94,7 @@ describe('config validation', () => { }); test('should validate and throw error when method on config is invalid', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', method: 'https', }; @@ -110,7 +108,7 @@ describe('config validation', () => { }); test('config validation passes when a url is specified', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', }; expect(validateConfig(actionType, config)).toEqual({ @@ -120,6 +118,8 @@ describe('config validation', () => { }); test('config validation passes when valid headers are provided', () => { + // any for testing + // eslint-disable-next-line @typescript-eslint/no-explicit-any const config: Record = { url: 'http://mylisteningserver:9200/endpoint', headers: { @@ -133,7 +133,7 @@ describe('config validation', () => { }); test('should validate and throw error when headers on config is invalid', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', headers: 'application/json', }; @@ -147,6 +147,8 @@ describe('config validation', () => { }); test('config validation passes when kibana config whitelists the url', () => { + // any for testing + // eslint-disable-next-line @typescript-eslint/no-explicit-any const config: Record = { url: 'http://mylisteningserver.com:9200/endpoint', headers: { @@ -171,6 +173,8 @@ describe('config validation', () => { }, }); + // any for testing + // eslint-disable-next-line @typescript-eslint/no-explicit-any const config: Record = { url: 'http://mylisteningserver.com:9200/endpoint', headers: { @@ -188,12 +192,12 @@ describe('config validation', () => { describe('params validation', () => { test('param validation passes when no fields are provided as none are required', () => { - const params: Record = {}; + const params: Record = {}; expect(validateParams(actionType, params)).toEqual({}); }); test('params validation passes when a valid body is provided', () => { - const params: Record = { + const params: Record = { body: 'count: {{ctx.payload.hits.total}}', }; expect(validateParams(actionType, params)).toEqual({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index 6173edc2df15a..0191ff1d9dbc1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -160,7 +160,7 @@ export async function executor( } // Action Executor Result w/ internationalisation -function successResult(actionId: string, data: any): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { return { status: 'ok', data, actionId }; } diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 51e87dbd75b48..161a6c31d4e59 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -7,7 +7,7 @@ import { configSchema } from './config'; describe('config validation', () => { test('action defaults', () => { - const config: Record = {}; + const config: Record = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { "enabled": true, @@ -23,7 +23,7 @@ describe('config validation', () => { }); test('action with preconfigured actions', () => { - const config: Record = { + const config: Record = { preconfigured: [ { id: 'my-slack1', diff --git a/x-pack/plugins/actions/server/constants/plugin.ts b/x-pack/plugins/actions/server/constants/plugin.ts index 68082ccaa1399..7d20eb6990247 100644 --- a/x-pack/plugins/actions/server/constants/plugin.ts +++ b/x-pack/plugins/actions/server/constants/plugin.ts @@ -9,6 +9,7 @@ import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/const export const PLUGIN = { ID: 'actions', MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => i18n.translate('xpack.actions.appName', { defaultMessage: 'Actions', diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 4a9ddf412b7cc..ac324587c5f4a 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from '../../../../src/core/server'; +import { SavedObjectsClientContract, KibanaRequest } from '../../../../src/core/server'; import { TaskManagerStartContract } from '../../task_manager/server'; import { GetBasePathFunction, @@ -15,7 +15,7 @@ import { interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; - getScopedSavedObjectsClient: (request: any) => SavedObjectsClientContract; + getScopedSavedObjectsClient: (request: KibanaRequest) => SavedObjectsClientContract; getBasePath: GetBasePathFunction; isESOUsingEphemeralEncryptionKey: boolean; actionTypeRegistry: ActionTypeRegistryContract; @@ -24,7 +24,7 @@ interface CreateExecuteFunctionOptions { export interface ExecuteOptions { id: string; - params: Record; + params: Record; spaceId: string; apiKey: string | null; } @@ -52,7 +52,7 @@ export function createExecuteFunction({ // Since we're using API keys and accessing elasticsearch can only be done // via a request, we're faking one with the proper authorization headers. - const fakeRequest: any = { + const fakeRequest: unknown = { headers: requestHeaders, getBasePath: () => getBasePath(spaceId), path: '/', @@ -67,7 +67,7 @@ export function createExecuteFunction({ }, }; - const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest); + const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest as KibanaRequest); const actionTypeId = await getActionTypeId(id); actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index a33fb8830a930..ac574decdba71 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -32,7 +32,7 @@ export interface ActionExecutorContext { export interface ExecuteOptions { actionId: string; request: KibanaRequest; - params: Record; + params: Record; } export type ActionExecutorContract = PublicMethodsOf; @@ -93,9 +93,9 @@ export class ActionExecutor { actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); const actionType = actionTypeRegistry.get(actionTypeId); - let validatedParams: Record; - let validatedConfig: Record; - let validatedSecrets: Record; + let validatedParams: Record; + let validatedConfig: Record; + let validatedSecrets: Record; try { validatedParams = validateParams(actionType, params); @@ -174,8 +174,8 @@ function actionErrorToMessage(result: ActionTypeExecutorResult): string { interface ActionInfo { actionTypeId: string; name: string; - config: any; - secrets: any; + config: unknown; + secrets: unknown; } async function getActionInfo( diff --git a/x-pack/plugins/actions/server/lib/executor_error.ts b/x-pack/plugins/actions/server/lib/executor_error.ts index 56ccbf14e6a45..c25033b2a27a2 100644 --- a/x-pack/plugins/actions/server/lib/executor_error.ts +++ b/x-pack/plugins/actions/server/lib/executor_error.ts @@ -5,9 +5,9 @@ */ export class ExecutorError extends Error { - readonly data?: any; + readonly data?: unknown; readonly retry: boolean | Date; - constructor(message?: string, data?: any, retry: boolean | Date = false) { + constructor(message?: string, data?: unknown, retry: boolean | Date = false) { super(message); this.data = data; this.retry = retry; diff --git a/x-pack/plugins/actions/server/lib/license_state.test.ts b/x-pack/plugins/actions/server/lib/license_state.test.ts index eb10e69a444e8..0a474ec3ae3ea 100644 --- a/x-pack/plugins/actions/server/lib/license_state.test.ts +++ b/x-pack/plugins/actions/server/lib/license_state.test.ts @@ -5,16 +5,16 @@ */ import { ActionType } from '../types'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; import { LicenseState, ILicenseState } from './license_state'; import { licensingMock } from '../../../licensing/server/mocks'; import { ILicense } from '../../../licensing/server'; describe('checkLicense()', () => { - let getRawLicense: any; + const getRawLicense = jest.fn(); beforeEach(() => { - getRawLicense = jest.fn(); + jest.resetAllMocks(); }); describe('status is LICENSE_STATUS_INVALID', () => { @@ -53,7 +53,7 @@ describe('checkLicense()', () => { }); describe('isLicenseValidForActionType', () => { - let license: BehaviorSubject; + let license: Subject; let licenseState: ILicenseState; const fooActionType: ActionType = { id: 'foo', @@ -63,7 +63,7 @@ describe('isLicenseValidForActionType', () => { }; beforeEach(() => { - license = new BehaviorSubject(null as any); + license = new Subject(); licenseState = new LicenseState(license); }); @@ -75,7 +75,7 @@ describe('isLicenseValidForActionType', () => { }); test('should return false when license not available', () => { - license.next({ isAvailable: false } as any); + license.next(createUnavailableLicense()); expect(licenseState.isLicenseValidForActionType(fooActionType)).toEqual({ isValid: false, reason: 'unavailable', @@ -114,7 +114,7 @@ describe('isLicenseValidForActionType', () => { }); describe('ensureLicenseForActionType()', () => { - let license: BehaviorSubject; + let license: Subject; let licenseState: ILicenseState; const fooActionType: ActionType = { id: 'foo', @@ -124,7 +124,7 @@ describe('ensureLicenseForActionType()', () => { }; beforeEach(() => { - license = new BehaviorSubject(null as any); + license = new Subject(); licenseState = new LicenseState(license); }); @@ -137,7 +137,7 @@ describe('ensureLicenseForActionType()', () => { }); test('should throw when license not available', () => { - license.next({ isAvailable: false } as any); + license.next(createUnavailableLicense()); expect(() => licenseState.ensureLicenseForActionType(fooActionType) ).toThrowErrorMatchingInlineSnapshot( @@ -175,3 +175,9 @@ describe('ensureLicenseForActionType()', () => { licenseState.ensureLicenseForActionType(fooActionType); }); }); + +function createUnavailableLicense() { + const unavailableLicense = licensingMock.createLicenseMock(); + unavailableLicense.isAvailable = false; + return unavailableLicense; +} diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index e2a6128aea203..08c5b90edbcb7 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -6,7 +6,7 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; -import { Logger, CoreStart } from '../../../../../src/core/server'; +import { Logger, CoreStart, KibanaRequest } from '../../../../../src/core/server'; import { RunContext } from '../../../task_manager/server'; import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; import { ActionTypeDisabledError } from './errors'; @@ -60,7 +60,7 @@ export class TaskRunnerFactory { return { async run() { - const { spaceId, actionTaskParamsId } = taskInstance.params; + const { spaceId, actionTaskParamsId } = taskInstance.params as Record; const namespace = spaceIdToNamespace(spaceId); const { @@ -78,7 +78,7 @@ export class TaskRunnerFactory { // Since we're using API keys and accessing elasticsearch can only be done // via a request, we're faking one with the proper authorization headers. - const fakeRequest: any = { + const fakeRequest = ({ headers: requestHeaders, getBasePath: () => getBasePath(spaceId), path: '/', @@ -91,7 +91,7 @@ export class TaskRunnerFactory { url: '/', }, }, - }; + } as unknown) as KibanaRequest; let executorResult: ActionTypeExecutorResult; try { diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts index b7d408985ed9f..1ccd25664374d 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts @@ -49,7 +49,7 @@ test('should validate when there are no individual validators', () => { }); test('should validate when validators return incoming value', () => { - const selfValidator = { validate: (value: any) => value }; + const selfValidator = { validate: (value: Record) => value }; const actionType: ActionType = { id: 'foo', name: 'bar', @@ -76,8 +76,8 @@ test('should validate when validators return incoming value', () => { }); test('should validate when validators return different values', () => { - const returnedValue: any = { something: { shaped: 'differently' } }; - const selfValidator = { validate: (value: any) => returnedValue }; + const returnedValue = { something: { shaped: 'differently' } }; + const selfValidator = { validate: () => returnedValue }; const actionType: ActionType = { id: 'foo', name: 'bar', @@ -105,7 +105,7 @@ test('should validate when validators return different values', () => { test('should throw with expected error when validators fail', () => { const erroringValidator = { - validate: (value: any) => { + validate: () => { throw new Error('test error'); }, }; diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.ts index 2306a726229e3..021c460f4c815 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.ts @@ -7,15 +7,15 @@ import Boom from 'boom'; import { ActionType } from '../types'; -export function validateParams(actionType: ActionType, value: any) { +export function validateParams(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'params', value); } -export function validateConfig(actionType: ActionType, value: any) { +export function validateConfig(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'config', value); } -export function validateSecrets(actionType: ActionType, value: any) { +export function validateSecrets(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'secrets', value); } @@ -24,33 +24,40 @@ type ValidKeys = 'params' | 'config' | 'secrets'; function validateWithSchema( actionType: ActionType, key: ValidKeys, - value: any -): Record { - if (actionType.validate == null) return value; - - let name; - try { - switch (key) { - case 'params': - name = 'action params'; - if (actionType.validate.params == null) return value; - return actionType.validate.params.validate(value); - - case 'config': - name = 'action type config'; - if (actionType.validate.config == null) return value; - return actionType.validate.config.validate(value); - - case 'secrets': - name = 'action type secrets'; - if (actionType.validate.secrets == null) return value; - return actionType.validate.secrets.validate(value); + value: unknown +): Record { + if (actionType.validate) { + let name; + try { + switch (key) { + case 'params': + name = 'action params'; + if (actionType.validate.params) { + return actionType.validate.params.validate(value); + } + break; + case 'config': + name = 'action type config'; + if (actionType.validate.config) { + return actionType.validate.config.validate(value); + } + + break; + case 'secrets': + name = 'action type secrets'; + if (actionType.validate.secrets) { + return actionType.validate.secrets.validate(value); + } + break; + default: + // should never happen, but left here for future-proofing + throw new Error(`invalid actionType validate key: ${key}`); + } + } catch (err) { + // we can't really i18n this yet, since the err.message isn't i18n'd itself + throw Boom.badRequest(`error validating ${name}: ${err.message}`); } - } catch (err) { - // we can't really i18n this yet, since the err.message isn't i18n'd itself - throw Boom.badRequest(`error validating ${name}: ${err.message}`); } - // should never happen, but left here for future-proofing - throw new Error(`invalid actionType validate key: ${key}`); + return value as Record; } diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index fa5b2f9399a4d..2b334953063d1 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginInitializerContext, RequestHandlerContext } from '../../../../src/core/server'; import { coreMock, httpServerMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; @@ -72,7 +72,9 @@ describe('Actions Plugin', () => { }); it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => { - await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, pluginsSetup); expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true); expect(context.logger.get().warn).toHaveBeenCalledWith( 'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.' @@ -81,7 +83,9 @@ describe('Actions Plugin', () => { describe('routeHandlerContext.getActionsClient()', () => { it('should not throw error when ESO plugin not using a generated key', async () => { - await plugin.setup(coreSetup, { + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, { ...pluginsSetup, encryptedSavedObjects: { ...pluginsSetup.encryptedSavedObjects, @@ -93,8 +97,8 @@ describe('Actions Plugin', () => { const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0]; expect(handler[0]).toEqual('actions'); - const actionsContextHandler = (await handler[1]( - { + const actionsContextHandler = ((await handler[1]( + ({ core: { savedObjects: { client: {}, @@ -103,32 +107,34 @@ describe('Actions Plugin', () => { adminClient: jest.fn(), }, }, - } as any, + } as unknown) as RequestHandlerContext, httpServerMock.createKibanaRequest(), httpServerMock.createResponseFactory() - )) as any; - actionsContextHandler.getActionsClient(); + )) as unknown) as RequestHandlerContext['actions']; + actionsContextHandler!.getActionsClient(); }); it('should throw error when ESO plugin using a generated key', async () => { - await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, pluginsSetup); expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1); const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0]; expect(handler[0]).toEqual('actions'); - const actionsContextHandler = (await handler[1]( - { + const actionsContextHandler = ((await handler[1]( + ({ core: { savedObjects: { client: {}, }, }, - } as any, + } as unknown) as RequestHandlerContext, httpServerMock.createKibanaRequest(), httpServerMock.createResponseFactory() - )) as any; - expect(() => actionsContextHandler.getActionsClient()).toThrowErrorMatchingInlineSnapshot( + )) as unknown) as RequestHandlerContext['actions']; + expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot( `"Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"` ); }); @@ -144,13 +150,17 @@ describe('Actions Plugin', () => { }; beforeEach(async () => { - setup = await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setup = await plugin.setup(coreSetup as any, pluginsSetup); }); it('should throw error when license type is invalid', async () => { expect(() => setup.registerType({ ...sampleActionType, + // we're faking an invalid value, this requires stripping the typing + // eslint-disable-next-line @typescript-eslint/no-explicit-any minimumLicenseRequired: 'foo' as any, }) ).toThrowErrorMatchingInlineSnapshot(`"\\"foo\\" is not a valid license type"`); @@ -211,7 +221,9 @@ describe('Actions Plugin', () => { describe('getActionsClientWithRequest()', () => { it('should not throw error when ESO plugin not using a generated key', async () => { - await plugin.setup(coreSetup, { + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, { ...pluginsSetup, encryptedSavedObjects: { ...pluginsSetup.encryptedSavedObjects, @@ -224,7 +236,9 @@ describe('Actions Plugin', () => { }); it('should throw error when ESO plugin using generated key', async () => { - await plugin.setup(coreSetup, pluginsSetup); + // coreMock.createSetup doesn't support Plugin generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await plugin.setup(coreSetup as any, pluginsSetup); const pluginStart = plugin.start(coreStart, pluginsStart); expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index a8ab3bbb2fad2..280c14ca8c058 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -117,7 +117,10 @@ export class ActionsPlugin implements Plugin, Plugi this.preconfiguredActions = []; } - public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { + public async setup( + core: CoreSetup, + plugins: ActionsPluginsSetup + ): Promise { this.licenseState = new LicenseState(plugins.licensing.license$); this.isESOUsingEphemeralEncryptionKey = plugins.encryptedSavedObjects.usingEphemeralEncryptionKey; @@ -182,7 +185,7 @@ export class ActionsPlugin implements Plugin, Plugi const usageCollection = plugins.usageCollection; if (usageCollection) { - core.getStartServices().then(async ([, startPlugins]: [CoreStart, any, any]) => { + core.getStartServices().then(async ([, startPlugins]) => { registerActionsUsageCollector(usageCollection, startPlugins.taskManager); initializeActionsTelemetry( @@ -299,7 +302,7 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( defaultKibanaIndex: string - ): IContextProvider, 'actions'> => { + ): IContextProvider, 'actions'> => { const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, preconfiguredActions } = this; return async function actionsRouteHandlerContext(context, request) { diff --git a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts index ec525adb8eab6..06f65ff23106c 100644 --- a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts @@ -7,12 +7,17 @@ import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { identity } from 'lodash'; import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { ActionType } from '../../common'; +import { ActionsClientMock, actionsClientMock } from '../actions_client.mock'; export function mockHandlerArguments( - { actionsClient, listTypes: listTypesRes = [] }: any, - req: any, + { + actionsClient = actionsClientMock.create(), + listTypes: listTypesRes = [], + }: { actionsClient?: ActionsClientMock; listTypes?: ActionType[] }, + req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { const listTypes = jest.fn(() => listTypesRes); return [ ({ @@ -31,7 +36,7 @@ export function mockHandlerArguments( }, }, } as unknown) as RequestHandlerContext, - req as KibanaRequest, + req as KibanaRequest, mockResponseFactory(res), ]; } diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts index 22cf0dd7f8ace..da926f28c8edc 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -8,6 +8,7 @@ import { mockRouter, RouterMock } from '../../../../../src/core/server/http/rout import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -40,10 +41,11 @@ describe('createActionRoute', () => { name: 'My name', actionTypeId: 'abc', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - create: jest.fn().mockResolvedValueOnce(createResult), - }; + + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce(createResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -89,16 +91,16 @@ describe('createActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - const actionsClient = { - create: jest.fn().mockResolvedValueOnce({ - id: '1', - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }); - const [context, req, res] = mockHandlerArguments(actionsClient, {}); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); await handler(context, req, res); @@ -117,16 +119,16 @@ describe('createActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - const actionsClient = { - create: jest.fn().mockResolvedValueOnce({ - id: '1', - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }); - const [context, req, res] = mockHandlerArguments(actionsClient, {}); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); @@ -141,9 +143,8 @@ describe('createActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - const actionsClient = { - create: jest.fn().mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok', 'forbidden']); diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index 0cd3143dc48d1..7f239bda41a60 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -36,9 +36,9 @@ export const createActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any>, + req: KibanaRequest>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { diff --git a/x-pack/plugins/actions/server/routes/delete.test.ts b/x-pack/plugins/actions/server/routes/delete.test.ts index 6fb526628cb02..d3b0aace93065 100644 --- a/x-pack/plugins/actions/server/routes/delete.test.ts +++ b/x-pack/plugins/actions/server/routes/delete.test.ts @@ -8,6 +8,7 @@ import { mockRouter, RouterMock } from '../../../../../src/core/server/http/rout import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../mocks'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -35,9 +36,8 @@ describe('deleteActionRoute', () => { } `); - const actionsClient = { - delete: jest.fn().mockResolvedValueOnce({}), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -71,13 +71,15 @@ describe('deleteActionRoute', () => { const [, handler] = router.delete.mock.calls[0]; - const actionsClient = { - delete: jest.fn().mockResolvedValueOnce({}), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(actionsClient, { - params: { id: '1' }, - }); + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + } + ); await handler(context, req, res); @@ -96,13 +98,15 @@ describe('deleteActionRoute', () => { const [, handler] = router.delete.mock.calls[0]; - const actionsClient = { - delete: jest.fn().mockResolvedValueOnce({}), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(actionsClient, { - id: '1', - }); + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + id: '1', + } + ); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index ffd1f0faabbab..b76aa27fa5d9b 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -37,9 +37,9 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts index 52d0aa706e703..79d3c1dfb8d22 100644 --- a/x-pack/plugins/actions/server/routes/execute.ts +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -43,9 +43,9 @@ export const executeActionRoute = ( }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, TypeOf, any>, + req: KibanaRequest, unknown, TypeOf>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); const { params } = req.body; const { id } = req.params; diff --git a/x-pack/plugins/actions/server/routes/get.test.ts b/x-pack/plugins/actions/server/routes/get.test.ts index f4e834a5b767c..20414f8f88004 100644 --- a/x-pack/plugins/actions/server/routes/get.test.ts +++ b/x-pack/plugins/actions/server/routes/get.test.ts @@ -9,6 +9,7 @@ import { mockRouter, RouterMock } from '../../../../../src/core/server/http/rout import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -41,10 +42,11 @@ describe('getActionRoute', () => { actionTypeId: '2', name: 'action name', config: {}, + isPreconfigured: false, }; - const actionsClient = { - get: jest.fn().mockResolvedValueOnce(getResult), - }; + + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce(getResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -60,6 +62,7 @@ describe('getActionRoute', () => { "actionTypeId": "2", "config": Object {}, "id": "1", + "isPreconfigured": false, "name": "action name", }, } @@ -81,17 +84,17 @@ describe('getActionRoute', () => { const [, handler] = router.get.mock.calls[0]; - const actionsClient = { - get: jest.fn().mockResolvedValueOnce({ - id: '1', - actionTypeId: '2', - name: 'action name', - config: {}, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }); const [context, req, res] = mockHandlerArguments( - actionsClient, + { actionsClient }, { params: { id: '1' }, }, @@ -115,17 +118,17 @@ describe('getActionRoute', () => { const [, handler] = router.get.mock.calls[0]; - const actionsClient = { - get: jest.fn().mockResolvedValueOnce({ - id: '1', - actionTypeId: '2', - name: 'action name', - config: {}, - }), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }); const [context, req, res] = mockHandlerArguments( - actionsClient, + { actionsClient }, { params: { id: '1' }, }, diff --git a/x-pack/plugins/actions/server/routes/get.ts b/x-pack/plugins/actions/server/routes/get.ts index cd29e54556b02..30e4289ed30c8 100644 --- a/x-pack/plugins/actions/server/routes/get.ts +++ b/x-pack/plugins/actions/server/routes/get.ts @@ -32,9 +32,9 @@ export const getActionRoute = (router: IRouter, licenseState: ILicenseState) => }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/get_all.test.ts b/x-pack/plugins/actions/server/routes/get_all.test.ts index 6499427b8c1a5..0ea47fc8b6e52 100644 --- a/x-pack/plugins/actions/server/routes/get_all.test.ts +++ b/x-pack/plugins/actions/server/routes/get_all.test.ts @@ -9,6 +9,7 @@ import { mockRouter, RouterMock } from '../../../../../src/core/server/http/rout import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -36,9 +37,8 @@ describe('getAllActionRoute', () => { } `); - const actionsClient = { - getAll: jest.fn().mockResolvedValueOnce([]), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.mockResolvedValueOnce([]); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); @@ -72,9 +72,8 @@ describe('getAllActionRoute', () => { } `); - const actionsClient = { - getAll: jest.fn().mockResolvedValueOnce([]), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.mockResolvedValueOnce([]); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); @@ -104,9 +103,8 @@ describe('getAllActionRoute', () => { } `); - const actionsClient = { - getAll: jest.fn().mockResolvedValueOnce([]), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.mockResolvedValueOnce([]); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts index c70a13bc01c9f..f6745427f74c7 100644 --- a/x-pack/plugins/actions/server/routes/get_all.ts +++ b/x-pack/plugins/actions/server/routes/get_all.ts @@ -25,9 +25,9 @@ export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/list_action_types.test.ts b/x-pack/plugins/actions/server/routes/list_action_types.test.ts index 76fb636a75be7..786780987355c 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.test.ts +++ b/x-pack/plugins/actions/server/routes/list_action_types.test.ts @@ -9,6 +9,7 @@ import { mockRouter, RouterMock } from '../../../../../src/core/server/http/rout import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { LicenseType } from '../../../../plugins/licensing/server'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -41,6 +42,9 @@ describe('listActionTypesRoute', () => { id: '1', name: 'name', enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, }, ]; @@ -51,7 +55,10 @@ describe('listActionTypesRoute', () => { "body": Array [ Object { "enabled": true, + "enabledInConfig": true, + "enabledInLicense": true, "id": "1", + "minimumLicenseRequired": "gold", "name": "name", }, ], @@ -87,6 +94,9 @@ describe('listActionTypesRoute', () => { id: '1', name: 'name', enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, }, ]; @@ -129,6 +139,9 @@ describe('listActionTypesRoute', () => { id: '1', name: 'name', enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, }, ]; diff --git a/x-pack/plugins/actions/server/routes/list_action_types.ts b/x-pack/plugins/actions/server/routes/list_action_types.ts index 71dcbd2e19bf7..50bc7405f4d6b 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.ts +++ b/x-pack/plugins/actions/server/routes/list_action_types.ts @@ -25,9 +25,9 @@ export const listActionTypesRoute = (router: IRouter, licenseState: ILicenseStat }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/update.test.ts b/x-pack/plugins/actions/server/routes/update.test.ts index 161fb4398af1d..0a3ce494e0ecc 100644 --- a/x-pack/plugins/actions/server/routes/update.test.ts +++ b/x-pack/plugins/actions/server/routes/update.test.ts @@ -8,6 +8,7 @@ import { mockRouter, RouterMock } from '../../../../../src/core/server/http/rout import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; jest.mock('../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -40,11 +41,11 @@ describe('updateActionRoute', () => { actionTypeId: 'my-action-type-id', name: 'My name', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - update: jest.fn().mockResolvedValueOnce(updateResult), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -97,11 +98,11 @@ describe('updateActionRoute', () => { actionTypeId: 'my-action-type-id', name: 'My name', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - update: jest.fn().mockResolvedValueOnce(updateResult), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -140,11 +141,11 @@ describe('updateActionRoute', () => { actionTypeId: 'my-action-type-id', name: 'My name', config: { foo: true }, + isPreconfigured: false, }; - const actionsClient = { - update: jest.fn().mockResolvedValueOnce(updateResult), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); const [context, req, res] = mockHandlerArguments( { actionsClient }, @@ -174,9 +175,8 @@ describe('updateActionRoute', () => { const [, handler] = router.put.mock.calls[0]; - const actionsClient = { - update: jest.fn().mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')), - }; + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ actionsClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts index 263df678f293d..45ced77be922e 100644 --- a/x-pack/plugins/actions/server/routes/update.ts +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -39,9 +39,9 @@ export const updateActionRoute = (router: IRouter, licenseState: ILicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, TypeOf, any>, + req: KibanaRequest, unknown, TypeOf>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 088398b40830e..9dcfdb81f5ebb 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -4,20 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, SavedObjectAttributes } from '../../../../src/core/server'; +import { + SavedObjectsClientContract, + SavedObjectAttributes, + KibanaRequest, +} from '../../../../src/core/server'; import { ActionTypeRegistry } from './action_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { ActionsClient } from './actions_client'; import { LicenseType } from '../../licensing/common/types'; export type WithoutQueryAndParams = Pick>; -export type GetServicesFunction = (request: any) => Services; +export type GetServicesFunction = (request: KibanaRequest) => Services; export type ActionTypeRegistryContract = PublicMethodsOf; export type GetBasePathFunction = (spaceId?: string) => string; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; export interface Services { - callCluster(path: string, opts: any): Promise; + callCluster(path: string, opts: unknown): Promise; savedObjectsClient: SavedObjectsClientContract; } @@ -45,8 +49,12 @@ export interface ActionsConfigType { export interface ActionTypeExecutorOptions { actionId: string; services: Services; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any config: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any secrets: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; } @@ -54,11 +62,15 @@ export interface ActionResult { id: string; actionTypeId: string; name: string; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any config?: Record; isPreconfigured: boolean; } export interface PreConfiguredAction extends ActionResult { + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any secrets: Record; } @@ -72,6 +84,8 @@ export interface ActionTypeExecutorResult { status: 'ok' | 'error'; message?: string; serviceMessage?: string; + // This will have to remain `any` until we can extend Action Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; retry?: null | boolean | Date; } @@ -82,7 +96,7 @@ export type ExecutorType = ( ) => Promise; interface ValidatorType { - validate(value: any): any; + validate(value: unknown): Record; } export type ActionTypeCreator = (config?: ActionsConfigType) => ActionType; @@ -108,6 +122,13 @@ export interface RawAction extends SavedObjectAttributes { export interface ActionTaskParams extends SavedObjectAttributes { actionId: string; + // Saved Objects won't allow us to enforce unknown rather than any + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; apiKey?: string; } + +export interface ActionTaskExecutorParams { + spaceId: string; + actionTaskParamsId: string; +} diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index eabb38e61d17d..6996ba629f8da 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -55,6 +55,8 @@ export async function getTotalCount(callCluster: APICaller, kibanaIndex: string) 0 ), countByType: Object.keys(searchResult.aggregations.byActionTypeId.value.types).reduce( + // ES DSL aggregations are returned as `any` by callCluster + // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, [key.replace('.', '__')]: searchResult.aggregations.byActionTypeId.value.types[key], diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerting/common/alert.ts index 8f28c8fbaed7f..b410b6aa0187e 100644 --- a/x-pack/plugins/alerting/common/alert.ts +++ b/x-pack/plugins/alerting/common/alert.ts @@ -28,6 +28,8 @@ export interface Alert { consumer: string; schedule: IntervalSchedule; actions: AlertAction[]; + // This will have to remain `any` until we can extend Alert Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; scheduledTaskId?: string; createdBy: string | null; diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerting/server/alert_type_registry.test.ts index b51286281571e..f9df390242cd4 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.test.ts @@ -231,7 +231,7 @@ function alertTypeWithVariables(id: string, context: string, state: string): Ale name: `${id}-name`, actionGroups: [], defaultActionGroupId: id, - executor: (params: any): any => {}, + async executor() {}, }; if (!context && !state) { diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerting/server/alert_type_registry.ts index a2be43f9dacbd..55e39b6a817db 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.ts @@ -77,7 +77,7 @@ export class AlertTypeRegistry { } } -function normalizedActionVariables(actionVariables: any) { +function normalizedActionVariables(actionVariables: AlertType['actionVariables']) { return { context: actionVariables?.context ?? [], state: actionVariables?.state ?? [], diff --git a/x-pack/plugins/alerting/server/alerts_client.mock.ts b/x-pack/plugins/alerting/server/alerts_client.mock.ts index 3189fa214d5f7..1848b3432ae5a 100644 --- a/x-pack/plugins/alerting/server/alerts_client.mock.ts +++ b/x-pack/plugins/alerting/server/alerts_client.mock.ts @@ -7,9 +7,10 @@ import { AlertsClient } from './alerts_client'; type Schema = PublicMethodsOf; +export type AlertsClientMock = jest.Mocked; const createAlertsClientMock = () => { - const mocked: jest.Mocked = { + const mocked: AlertsClientMock = { create: jest.fn(), get: jest.fn(), getAlertState: jest.fn(), @@ -27,6 +28,8 @@ const createAlertsClientMock = () => { return mocked; }; -export const alertsClientMock = { +export const alertsClientMock: { + create: () => AlertsClientMock; +} = { create: createAlertsClientMock, }; diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client.test.ts index a9ff5ee8ecdc6..6c7b93aa64003 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client.test.ts @@ -5,7 +5,7 @@ */ import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; -import { AlertsClient } from './alerts_client'; +import { AlertsClient, CreateOptions } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; @@ -45,6 +45,7 @@ beforeEach(() => { }); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any (global as any).Date = class Date { constructor() { return mockedDate; @@ -54,7 +55,7 @@ const mockedDate = new Date('2019-02-12T21:01:22.479Z'); } }; -function getMockData(overwrites: Record = {}) { +function getMockData(overwrites: Record = {}): CreateOptions['data'] { return { enabled: true, name: 'abc', diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 3e4c26d3444c9..ff501055ba9fe 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -24,6 +24,7 @@ import { IntervalSchedule, SanitizedAlert, AlertTaskState, + RawAlertAction, } from './types'; import { validateAlertTypeParams } from './lib'; import { @@ -83,7 +84,7 @@ export interface FindResult { data: SanitizedAlert[]; } -interface CreateOptions { +export interface CreateOptions { data: Omit< Alert, | 'id' @@ -109,7 +110,7 @@ interface UpdateOptions { tags: string[]; schedule: IntervalSchedule; actions: NormalizedAlertAction[]; - params: Record; + params: Record; throttle: string | null; }; } @@ -172,7 +173,7 @@ export class AlertsClient { createdBy: username, updatedBy: username, createdAt: new Date().toISOString(), - params: validatedAlertTypeParams, + params: validatedAlertTypeParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], }; @@ -337,7 +338,7 @@ export class AlertsClient { ...attributes, ...data, ...apiKeyAttributes, - params: validatedAlertTypeParams, + params: validatedAlertTypeParams as RawAlert['params'], actions, updatedBy: username, }, @@ -667,7 +668,7 @@ export class AlertsClient { private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { - const actionMap = new Map(); + const actionMap = new Map(); // map preconfigured actions for (const alertAction of alertActions) { const action = this.preconfiguredActions.find( @@ -712,8 +713,8 @@ export class AlertsClient { // if action is a save object, than actionTypeId should be under attributes property // if action is a preconfigured, than actionTypeId is the action property const actionTypeId = actionIds.find(actionId => actionId === id) - ? actionMapValue.attributes.actionTypeId - : actionMapValue.actionTypeId; + ? (actionMapValue as SavedObject>).attributes.actionTypeId + : (actionMapValue as RawAlertAction).actionTypeId; return { ...alertAction, actionRef, diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index 951d18a33b35f..e5aa0a674eccf 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -11,16 +11,13 @@ import { taskManagerMock } from '../../../plugins/task_manager/server/task_manag import { KibanaRequest } from '../../../../src/core/server'; import { loggingServiceMock, savedObjectsClientMock } from '../../../../src/core/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { AuthenticatedUser } from '../../../plugins/security/public'; +import { securityMock } from '../../../plugins/security/server/mocks'; jest.mock('./alerts_client'); const savedObjectsClient = savedObjectsClientMock.create(); -const securityPluginSetup = { - authc: { - grantAPIKeyAsInternalUser: jest.fn(), - getCurrentUser: jest.fn(), - }, -}; +const securityPluginSetup = securityMock.createSetup(); const alertsClientFactoryParams: jest.Mocked = { logger: loggingServiceMock.create().get(), taskManager: taskManagerMock.start(), @@ -30,7 +27,7 @@ const alertsClientFactoryParams: jest.Mocked = { encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(), preconfiguredActions: [], }; -const fakeRequest: Request = { +const fakeRequest = ({ headers: {}, getBasePath: () => '', path: '/', @@ -44,7 +41,7 @@ const fakeRequest: Request = { }, }, getSavedObjectsClient: () => savedObjectsClient, -} as any; +} as unknown) as Request; beforeEach(() => { jest.resetAllMocks(); @@ -86,12 +83,14 @@ test('getUserName() returns a name when security is enabled', async () => { const factory = new AlertsClientFactory(); factory.initialize({ ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, + securityPluginSetup, }); factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient); const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; - securityPluginSetup.authc.getCurrentUser.mockReturnValueOnce({ username: 'bob' }); + securityPluginSetup.authc.getCurrentUser.mockReturnValueOnce(({ + username: 'bob', + } as unknown) as AuthenticatedUser); const userNameResult = await constructorCall.getUserName(); expect(userNameResult).toEqual('bob'); }); @@ -121,7 +120,7 @@ test('createAPIKey() returns an API key when security is enabled', async () => { const factory = new AlertsClientFactory(); factory.initialize({ ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, + securityPluginSetup, }); factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient); const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; @@ -129,11 +128,12 @@ test('createAPIKey() returns an API key when security is enabled', async () => { securityPluginSetup.authc.grantAPIKeyAsInternalUser.mockResolvedValueOnce({ api_key: '123', id: 'abc', + name: '', }); const createAPIKeyResult = await constructorCall.createAPIKey(); expect(createAPIKeyResult).toEqual({ apiKeysEnabled: true, - result: { api_key: '123', id: 'abc' }, + result: { api_key: '123', id: 'abc', name: '' }, }); }); @@ -141,7 +141,7 @@ test('createAPIKey() throws when security plugin createAPIKey throws an error', const factory = new AlertsClientFactory(); factory.initialize({ ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, + securityPluginSetup, }); factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient); const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; diff --git a/x-pack/plugins/alerting/server/constants/plugin.ts b/x-pack/plugins/alerting/server/constants/plugin.ts index 173aa50013b40..9c276ed1d75de 100644 --- a/x-pack/plugins/alerting/server/constants/plugin.ts +++ b/x-pack/plugins/alerting/server/constants/plugin.ts @@ -9,6 +9,8 @@ import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/const export const PLUGIN = { ID: 'alerting', MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + // all plugins seem to use getI18nName with any + // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => i18n.translate('xpack.alerting.appName', { defaultMessage: 'Alerting', diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerting/server/lib/license_state.test.ts index 9bbd619dc4868..cbab98a6311dd 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.test.ts @@ -9,10 +9,10 @@ import { LicenseState } from './license_state'; import { licensingMock } from '../../../../plugins/licensing/server/mocks'; describe('license_state', () => { - let getRawLicense: any; + const getRawLicense = jest.fn(); beforeEach(() => { - getRawLicense = jest.fn(); + jest.resetAllMocks(); }); describe('status is LICENSE_STATUS_INVALID', () => { diff --git a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts b/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts index 248d896c06ac2..b6dcb522f0925 100644 --- a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts +++ b/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts @@ -5,15 +5,15 @@ */ import Boom from 'boom'; -import { AlertType } from '../types'; +import { AlertType, AlertExecutorOptions } from '../types'; -export function validateAlertTypeParams>( +export function validateAlertTypeParams( alertType: AlertType, - params: T -): T { + params: Record +): AlertExecutorOptions['params'] { const validator = alertType.validate && alertType.validate.params; if (!validator) { - return params; + return params as AlertExecutorOptions['params']; } try { diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 74a1f2349180e..267e68930a5d7 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertingPlugin } from './plugin'; +import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../../plugins/licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; +import { KibanaRequest, CoreSetup } from 'kibana/server'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -20,19 +21,19 @@ describe('Alerting Plugin', () => { const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); await plugin.setup( - { + ({ ...coreSetup, http: { ...coreSetup.http, route: jest.fn(), }, - } as any, - { + } as unknown) as CoreSetup, + ({ licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), eventLog: eventLogServiceMock.create(), - } as any + } as unknown) as AlertingPluginsSetup ); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); @@ -58,34 +59,34 @@ describe('Alerting Plugin', () => { const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); await plugin.setup( - { + ({ ...coreSetup, http: { ...coreSetup.http, route: jest.fn(), }, - } as any, - { + } as unknown) as CoreSetup, + ({ licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), eventLog: eventLogServiceMock.create(), - } as any + } as unknown) as AlertingPluginsSetup ); const startContract = plugin.start( - coreMock.createStart() as any, - { + coreMock.createStart() as ReturnType, + ({ actions: { execute: jest.fn(), getActionsClientWithRequest: jest.fn(), }, - } as any + } as unknown) as AlertingPluginsStart ); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(() => - startContract.getAlertsClientWithRequest({} as any) + startContract.getAlertsClientWithRequest({} as KibanaRequest) ).toThrowErrorMatchingInlineSnapshot( `"Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"` ); @@ -101,33 +102,33 @@ describe('Alerting Plugin', () => { usingEphemeralEncryptionKey: false, }; await plugin.setup( - { + ({ ...coreSetup, http: { ...coreSetup.http, route: jest.fn(), }, - } as any, - { + } as unknown) as CoreSetup, + ({ licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), eventLog: eventLogServiceMock.create(), - } as any + } as unknown) as AlertingPluginsSetup ); const startContract = plugin.start( - coreMock.createStart() as any, - { + coreMock.createStart() as ReturnType, + ({ actions: { execute: jest.fn(), getActionsClientWithRequest: jest.fn(), }, spaces: () => null, - } as any + } as unknown) as AlertingPluginsStart ); - const fakeRequest = { + const fakeRequest = ({ headers: {}, getBasePath: () => '', path: '/', @@ -141,8 +142,8 @@ describe('Alerting Plugin', () => { }, }, getSavedObjectsClient: jest.fn(), - }; - await startContract.getAlertsClientWithRequest(fakeRequest as any); + } as unknown) as KibanaRequest; + await startContract.getAlertsClientWithRequest(fakeRequest); }); }); }); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index ad39d09bd6d3d..35ebafce9dc67 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -117,7 +117,10 @@ export class AlertingPlugin { .toPromise(); } - public async setup(core: CoreSetup, plugins: AlertingPluginsSetup): Promise { + public async setup( + core: CoreSetup, + plugins: AlertingPluginsSetup + ): Promise { this.licenseState = new LicenseState(plugins.licensing.license$); this.spaces = plugins.spaces?.spacesService; this.security = plugins.security; @@ -157,7 +160,7 @@ export class AlertingPlugin { const usageCollection = plugins.usageCollection; if (usageCollection) { - core.getStartServices().then(async ([, startPlugins]: [CoreStart, any, any]) => { + core.getStartServices().then(async ([, startPlugins]) => { registerAlertsUsageCollector(usageCollection, startPlugins.taskManager); initializeAlertingTelemetry( @@ -246,7 +249,7 @@ export class AlertingPlugin { } private createRouteHandlerContext = (): IContextProvider< - RequestHandler, + RequestHandler, 'alerting' > => { const { alertTypeRegistry, alertsClientFactory } = this; diff --git a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts index 5a1d680eb06f3..6fb3df8446f5c 100644 --- a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts @@ -4,16 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IClusterClient, +} from 'kibana/server'; import { identity } from 'lodash'; import { httpServerMock } from '../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../alerts_client.mock'; +import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock'; +import { AlertType } from '../../common'; +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; export function mockHandlerArguments( - { alertsClient, listTypes: listTypesRes = [], elasticsearch }: any, - req: any, + { + alertsClient = alertsClientMock.create(), + listTypes: listTypesRes = [], + elasticsearch = elasticsearchServiceMock.createSetup(), + }: { + alertsClient?: AlertsClientMock; + listTypes?: AlertType[]; + elasticsearch?: jest.Mocked<{ + adminClient: jest.Mocked; + }>; + }, + req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { const listTypes = jest.fn(() => listTypesRes); return [ ({ @@ -25,7 +42,7 @@ export function mockHandlerArguments( }, }, } as unknown) as RequestHandlerContext, - req as KibanaRequest, + req as KibanaRequest, mockResponseFactory(res), ]; } diff --git a/x-pack/plugins/alerting/server/routes/create.test.ts b/x-pack/plugins/alerting/server/routes/create.test.ts index c6a0da2bd9191..c179915e9b10d 100644 --- a/x-pack/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/create.test.ts @@ -142,7 +142,7 @@ describe('createAlertRoute', () => { alertsClient.create.mockResolvedValueOnce(createResult); - const [context, req, res] = mockHandlerArguments(alertsClient, {}); + const [context, req, res] = mockHandlerArguments({ alertsClient }, {}); await handler(context, req, res); @@ -163,7 +163,7 @@ describe('createAlertRoute', () => { alertsClient.create.mockResolvedValueOnce(createResult); - const [context, req, res] = mockHandlerArguments(alertsClient, {}); + const [context, req, res] = mockHandlerArguments({ alertsClient }, {}); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); diff --git a/x-pack/plugins/alerting/server/routes/create.ts b/x-pack/plugins/alerting/server/routes/create.ts index f08460ffcb453..0c038b6490483 100644 --- a/x-pack/plugins/alerting/server/routes/create.ts +++ b/x-pack/plugins/alerting/server/routes/create.ts @@ -54,9 +54,9 @@ export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any>, + req: KibanaRequest>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { diff --git a/x-pack/plugins/alerting/server/routes/delete.test.ts b/x-pack/plugins/alerting/server/routes/delete.test.ts index 36bb485817c15..a8f4d78f58552 100644 --- a/x-pack/plugins/alerting/server/routes/delete.test.ts +++ b/x-pack/plugins/alerting/server/routes/delete.test.ts @@ -74,9 +74,12 @@ describe('deleteAlertRoute', () => { alertsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(alertsClient, { - params: { id: '1' }, - }); + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + } + ); await handler(context, req, res); @@ -97,9 +100,12 @@ describe('deleteAlertRoute', () => { alertsClient.delete.mockResolvedValueOnce({}); - const [context, req, res] = mockHandlerArguments(alertsClient, { - id: '1', - }); + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + id: '1', + } + ); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); diff --git a/x-pack/plugins/alerting/server/routes/delete.ts b/x-pack/plugins/alerting/server/routes/delete.ts index 8d77c9b395e59..7f6600b1ec48e 100644 --- a/x-pack/plugins/alerting/server/routes/delete.ts +++ b/x-pack/plugins/alerting/server/routes/delete.ts @@ -33,9 +33,9 @@ export const deleteAlertRoute = (router: IRouter, licenseState: LicenseState) => }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/disable.ts b/x-pack/plugins/alerting/server/routes/disable.ts index fcc7116a697b1..c7e7b1001f82d 100644 --- a/x-pack/plugins/alerting/server/routes/disable.ts +++ b/x-pack/plugins/alerting/server/routes/disable.ts @@ -33,9 +33,9 @@ export const disableAlertRoute = (router: IRouter, licenseState: LicenseState) = }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/enable.ts b/x-pack/plugins/alerting/server/routes/enable.ts index 2283ae4a4c765..3ed4fb0739d3d 100644 --- a/x-pack/plugins/alerting/server/routes/enable.ts +++ b/x-pack/plugins/alerting/server/routes/enable.ts @@ -35,9 +35,9 @@ export const enableAlertRoute = (router: IRouter, licenseState: LicenseState) => handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/find.test.ts b/x-pack/plugins/alerting/server/routes/find.test.ts index 391d6df3f9931..0824ce196325c 100644 --- a/x-pack/plugins/alerting/server/routes/find.test.ts +++ b/x-pack/plugins/alerting/server/routes/find.test.ts @@ -108,13 +108,16 @@ describe('findAlertRoute', () => { data: [], }); - const [context, req, res] = mockHandlerArguments(alertsClient, { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }); + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + } + ); await handler(context, req, res); diff --git a/x-pack/plugins/alerting/server/routes/find.ts b/x-pack/plugins/alerting/server/routes/find.ts index 0787f5c6b5ad6..c723419a965c5 100644 --- a/x-pack/plugins/alerting/server/routes/find.ts +++ b/x-pack/plugins/alerting/server/routes/find.ts @@ -55,9 +55,9 @@ export const findAlertRoute = (router: IRouter, licenseState: LicenseState) => { }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any>, + req: KibanaRequest, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/get.test.ts b/x-pack/plugins/alerting/server/routes/get.test.ts index 4506700c6d9cc..22054bca07c3b 100644 --- a/x-pack/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/get.test.ts @@ -99,7 +99,7 @@ describe('getAlertRoute', () => { alertsClient.get.mockResolvedValueOnce(mockedAlert); const [context, req, res] = mockHandlerArguments( - alertsClient, + { alertsClient }, { params: { id: '1' }, }, @@ -126,7 +126,7 @@ describe('getAlertRoute', () => { alertsClient.get.mockResolvedValueOnce(mockedAlert); const [context, req, res] = mockHandlerArguments( - alertsClient, + { alertsClient }, { params: { id: '1' }, }, diff --git a/x-pack/plugins/alerting/server/routes/get.ts b/x-pack/plugins/alerting/server/routes/get.ts index 39fbe64b62182..6d652d1304f65 100644 --- a/x-pack/plugins/alerting/server/routes/get.ts +++ b/x-pack/plugins/alerting/server/routes/get.ts @@ -33,9 +33,9 @@ export const getAlertRoute = (router: IRouter, licenseState: LicenseState) => { }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.ts b/x-pack/plugins/alerting/server/routes/get_alert_state.ts index c5493c4abf57a..552bfea22a42b 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.ts +++ b/x-pack/plugins/alerting/server/routes/get_alert_state.ts @@ -33,9 +33,9 @@ export const getAlertStateRoute = (router: IRouter, licenseState: LicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index fa2358a1f181c..bfdbc95a7d2da 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -39,9 +39,9 @@ export function healthRoute( }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); try { const { diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts b/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts index 723fd86fca8b5..d5b7c807fe7da 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts @@ -47,6 +47,7 @@ describe('listAlertTypesRoute', () => { }, ], defaultActionGroupId: 'default', + actionVariables: [], }, ]; @@ -62,6 +63,7 @@ describe('listAlertTypesRoute', () => { "name": "Default", }, ], + "actionVariables": Array [], "defaultActionGroupId": "default", "id": "1", "name": "name", @@ -99,6 +101,14 @@ describe('listAlertTypesRoute', () => { id: '1', name: 'name', enabled: true, + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + actionVariables: [], }, ]; @@ -147,6 +157,7 @@ describe('listAlertTypesRoute', () => { }, ], defaultActionGroupId: 'default', + actionVariables: [], }, ]; diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.ts b/x-pack/plugins/alerting/server/routes/list_alert_types.ts index 455bc5e378b6d..7ab64cf932051 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.ts +++ b/x-pack/plugins/alerting/server/routes/list_alert_types.ts @@ -26,9 +26,9 @@ export const listAlertTypesRoute = (router: IRouter, licenseState: LicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/mute_all.ts b/x-pack/plugins/alerting/server/routes/mute_all.ts index 29ef7d6b6b03b..d1b4322bd1ccb 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.ts +++ b/x-pack/plugins/alerting/server/routes/mute_all.ts @@ -33,9 +33,9 @@ export const muteAllAlertRoute = (router: IRouter, licenseState: LicenseState) = }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.ts b/x-pack/plugins/alerting/server/routes/mute_instance.ts index 7a071b1535dc7..fbdda62836d74 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/mute_instance.ts @@ -34,9 +34,9 @@ export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseSta }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.ts b/x-pack/plugins/alerting/server/routes/unmute_all.ts index 81e28a81874cd..e09f2fe6b8b93 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_all.ts @@ -33,9 +33,9 @@ export const unmuteAllAlertRoute = (router: IRouter, licenseState: LicenseState) }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.ts b/x-pack/plugins/alerting/server/routes/unmute_instance.ts index de081ae7f1fcb..64ba22dc3ea0b 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_instance.ts @@ -34,9 +34,9 @@ export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: LicenseS }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/update.ts b/x-pack/plugins/alerting/server/routes/update.ts index 45f7b26b521d4..7f07749311598 100644 --- a/x-pack/plugins/alerting/server/routes/update.ts +++ b/x-pack/plugins/alerting/server/routes/update.ts @@ -56,9 +56,9 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, TypeOf, any>, + req: KibanaRequest, unknown, TypeOf>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.ts b/x-pack/plugins/alerting/server/routes/update_api_key.ts index f70d30f0bb5da..9d0c34fc1a015 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.ts +++ b/x-pack/plugins/alerting/server/routes/update_api_key.ts @@ -35,9 +35,9 @@ export const updateApiKeyRoute = (router: IRouter, licenseState: LicenseState) = handleDisabledApiKeysError( router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, any, any, any>, + req: KibanaRequest, unknown, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts index 9f5e3851aa29d..4be506b78493b 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts +++ b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts @@ -7,10 +7,17 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; -import { SanitizedAlert, AlertTaskState, alertParamsSchema, alertStateSchema } from '../../common'; +import { + SanitizedAlert, + AlertTaskState, + alertParamsSchema, + alertStateSchema, + AlertTaskParams, +} from '../../common'; export interface AlertTaskInstance extends ConcreteTaskInstance { state: AlertTaskState; + params: AlertTaskParams; } const enumerateErrorFields = (e: t.Errors) => diff --git a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts b/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts index 1c4d8a42d2830..f5914fdf01a16 100644 --- a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts @@ -7,6 +7,7 @@ import { getNextRunAt } from './get_next_run_at'; const mockedNow = new Date('2019-06-03T18:55:25.982Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any (global as any).Date = class Date extends global.Date { static now() { return mockedNow.getTime(); 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 1d4b12e96bc76..9c8cf4b1c968d 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -5,7 +5,7 @@ */ import { pick, mapValues, omit, without } from 'lodash'; -import { Logger, SavedObject } from '../../../../../src/core/server'; +import { Logger, SavedObject, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; import { createExecutionHandler } from './create_execution_handler'; @@ -93,7 +93,7 @@ export class TaskRunner { }, }; - return this.context.getServices(fakeRequest); + return this.context.getServices((fakeRequest as unknown) as KibanaRequest); } private getExecutionHandler( @@ -178,7 +178,7 @@ export class TaskRunner { }; eventLogger.startTiming(event); - let updatedAlertTypeState: void | Record; + let updatedAlertTypeState: void | Record; try { updatedAlertTypeState = await this.alertType.executor({ alertId, diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index e0cff58c4d40a..64f846d13c0bf 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -29,7 +29,7 @@ export function transformActionParams({ actionParams, state, }: TransformActionParamsOptions): AlertActionParams { - const result = cloneDeep(actionParams, (value: any) => { + const result = cloneDeep(actionParams, (value: unknown) => { if (!isString(value)) return; // when the list of variables we pass in here changes, diff --git a/x-pack/plugins/alerting/server/test_utils/index.ts b/x-pack/plugins/alerting/server/test_utils/index.ts index be9c5493ccf2b..e8089984a786a 100644 --- a/x-pack/plugins/alerting/server/test_utils/index.ts +++ b/x-pack/plugins/alerting/server/test_utils/index.ts @@ -5,7 +5,7 @@ */ interface Resolvable { - resolve: (arg?: T) => void; + resolve: (arg: T) => void; } /** @@ -13,12 +13,10 @@ interface Resolvable { * coordinating async tests. */ export function resolvable(): Promise & Resolvable { - let resolve: (arg?: T) => void; - const result = new Promise(r => { - resolve = r; - }) as any; - - result.resolve = (arg: T) => resolve(arg); - - return result; + let resolve: (arg: T) => void; + return Object.assign(new Promise(r => (resolve = r)), { + resolve(arg: T) { + return setTimeout(() => resolve(arg), 0); + }, + }); } diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 739a0d0aece24..bc98cae65b4e6 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -7,15 +7,22 @@ import { AlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; -import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../src/core/server'; +import { + SavedObjectAttributes, + SavedObjectsClientContract, + KibanaRequest, +} from '../../../../src/core/server'; import { Alert, AlertActionParams, ActionGroup } from '../common'; import { AlertsClient } from './alerts_client'; export * from '../common'; +// This will have to remain `any` until we can extend Alert Executors with generics +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type State = Record; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type Context = Record; export type WithoutQueryAndParams = Pick>; -export type GetServicesFunction = (request: any) => Services; +export type GetServicesFunction = (request: KibanaRequest) => Services; export type GetBasePathFunction = (spaceId?: string) => string; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; @@ -29,6 +36,8 @@ declare module 'src/core/server' { } export interface Services { + // This will have to remain `any` until we can extend Alert Services with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any callCluster(path: string, opts: any): Promise; savedObjectsClient: SavedObjectsClientContract; } @@ -42,6 +51,8 @@ export interface AlertExecutorOptions { startedAt: Date; previousStartedAt: Date | null; services: AlertServices; + // This will have to remain `any` until we can extend Alert Executors with generics + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; state: State; spaceId: string; @@ -61,7 +72,7 @@ export interface AlertType { id: string; name: string; validate?: { - params?: { validate: (object: any) => any }; + params?: { validate: (object: unknown) => AlertExecutorOptions['params'] }; }; actionGroups: ActionGroup[]; defaultActionGroupId: ActionGroup['id']; diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 9c710fa3b3b8e..2edf1c1061864 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -5,6 +5,7 @@ */ import { APICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; const alertTypeMetric = { scripted_metric: { @@ -246,6 +247,8 @@ export async function getTotalCountAggregations(callCluster: APICaller, kibanaIn return { count_total: totalAlertsCount, count_by_type: Object.keys(results.aggregations.byAlertTypeId.value.types).reduce( + // ES DSL aggregations are returned as `any` by callCluster + // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, [key.replace('.', '__')]: results.aggregations.byAlertTypeId.value.types[key], @@ -284,7 +287,7 @@ export async function getTotalCountAggregations(callCluster: APICaller, kibanaIn } export async function getTotalCountInUse(callCluster: APICaller, kibanaInex: string) { - const searchResult = await callCluster('search', { + const searchResult: SearchResponse = await callCluster('search', { index: kibanaInex, rest_total_hits_as_int: true, body: { @@ -305,6 +308,8 @@ export async function getTotalCountInUse(callCluster: APICaller, kibanaInex: str 0 ), countByType: Object.keys(searchResult.aggregations.byAlertTypeId.value.types).reduce( + // ES DSL aggregations are returned as `any` by callCluster + // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, [key.replace('.', '__')]: searchResult.aggregations.byAlertTypeId.value.types[key], diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts index 33d1e1897e943..c209894fb6e89 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.test.ts @@ -6,6 +6,7 @@ import { ParamsSchema, Params } from './alert_type_params'; import { runTests } from './lib/core_query_types.test'; +import { TypeOf } from '@kbn/config-schema'; const DefaultParams: Writable> = { index: 'index-name', @@ -21,6 +22,7 @@ const DefaultParams: Writable> = { describe('alertType Params validate()', () => { runTests(ParamsSchema, DefaultParams); + // eslint-disable-next-line @typescript-eslint/no-explicit-any let params: any; beforeEach(() => { params = { ...DefaultParams }; @@ -64,7 +66,7 @@ describe('alertType Params validate()', () => { return () => validate(); } - function validate(): any { + function validate(): TypeOf { return ParamsSchema.validate(params); } }); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts index f83d7fa07cd2a..4a822156ebd06 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type_params.ts @@ -30,12 +30,12 @@ export const ParamsSchema = schema.object( const betweenComparators = new Set(['between', 'notBetween']); // using direct type not allowed, circular reference, so body is typed to any -function validateParams(anyParams: any): string | undefined { +function validateParams(anyParams: unknown): string | undefined { // validate core query parts, return if it fails validation (returning string) const coreQueryValidated = validateCoreQueryBody(anyParams); if (coreQueryValidated) return coreQueryValidated; - const { thresholdComparator, threshold }: Params = anyParams; + const { thresholdComparator, threshold }: Params = anyParams as Params; if (betweenComparators.has(thresholdComparator) && threshold.length === 1) { return i18n.translate('xpack.alertingBuiltins.indexThreshold.invalidThreshold2ErrorMessage', { diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts index 109785b835bdf..6c9c3542aea03 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.test.ts @@ -19,7 +19,8 @@ const DefaultParams: Writable> = { timeWindowUnit: 'm', }; -export function runTests(schema: ObjectType, defaultTypeParams: Record): void { +export function runTests(schema: ObjectType, defaultTypeParams: Record): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any let params: any; describe('coreQueryTypes', () => { @@ -186,7 +187,7 @@ export function runTests(schema: ObjectType, defaultTypeParams: Record validate(); } - function validate(): any { + function validate(): unknown { return schema.validate(params); } } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts index 6e9c0072bf7b6..c8da61fb56d21 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/core_query_types.ts @@ -41,9 +41,15 @@ export type CoreQueryParams = TypeOf; // Meant to be used in a "subclass"'s schema body validator, so the // anyParams object is assumed to have been validated with the schema // above. -// Using direct type not allowed, circular reference, so body is typed to any. -export function validateCoreQueryBody(anyParams: any): string | undefined { - const { aggType, aggField, groupBy, termField, termSize }: CoreQueryParams = anyParams; +// Using direct type not allowed, circular reference, so body is typed to unknown. +export function validateCoreQueryBody(anyParams: unknown): string | undefined { + const { + aggType, + aggField, + groupBy, + termField, + termSize, + }: CoreQueryParams = anyParams as CoreQueryParams; if (aggType !== 'count' && !aggField) { return i18n.translate('xpack.alertingBuiltins.indexThreshold.aggTypeRequiredErrorMessage', { defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"', diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts index 1d9cc1c98bc01..9c4133be6f483 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchResponse } from 'elasticsearch'; import { DEFAULT_GROUPS } from '../index'; import { getDateRangeInfo } from './date_range_info'; import { Logger, CallCluster } from '../../../types'; @@ -35,6 +36,8 @@ export async function timeSeriesQuery( const dateRangeInfo = getDateRangeInfo({ dateStart, dateEnd, window, interval }); // core query + // Constructing a typesafe ES query in JS is problematic, use any escapehatch for now + // eslint-disable-next-line @typescript-eslint/no-explicit-any const esQuery: any = { index, body: { @@ -122,7 +125,7 @@ export async function timeSeriesQuery( }; } - let esResult: any; + let esResult: SearchResponse; const logPrefix = 'indexThreshold timeSeriesQuery: callCluster'; logger.debug(`${logPrefix} call: ${JSON.stringify(esQuery)}`); @@ -147,7 +150,7 @@ export async function timeSeriesQuery( function getResultFromEs( isCountAgg: boolean, isGroupAgg: boolean, - esResult: Record + esResult: SearchResponse ): TimeSeriesResult { const aggregations = esResult?.aggregations || {}; diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts index fcbd49b26ffd0..ec164122032cb 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.test.ts @@ -6,6 +6,7 @@ import { TimeSeriesQuerySchema, TimeSeriesQuery } from './time_series_types'; import { runTests } from './core_query_types.test'; +import { TypeOf } from '@kbn/config-schema'; const DefaultParams: Writable> = { index: 'index-name', @@ -19,6 +20,7 @@ const DefaultParams: Writable> = { describe('TimeSeriesParams validate()', () => { runTests(TimeSeriesQuerySchema, DefaultParams); + // eslint-disable-next-line @typescript-eslint/no-explicit-any let params: any; beforeEach(() => { params = { ...DefaultParams }; @@ -102,7 +104,7 @@ describe('TimeSeriesParams validate()', () => { return () => validate(); } - function validate(): any { + function validate(): TypeOf { return TimeSeriesQuerySchema.validate(params); } }); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts index abe5d562027eb..40e6f187ce18f 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts @@ -48,13 +48,13 @@ export const TimeSeriesQuerySchema = schema.object( } ); -// using direct type not allowed, circular reference, so body is typed to any -function validateBody(anyParams: any): string | undefined { +// using direct type not allowed, circular reference, so body is typed to unknown +function validateBody(anyParams: unknown): string | undefined { // validate core query parts, return if it fails validation (returning string) const coreQueryValidated = validateCoreQueryBody(anyParams); if (coreQueryValidated) return coreQueryValidated; - const { dateStart, dateEnd, interval }: TimeSeriesQuery = anyParams; + const { dateStart, dateEnd, interval } = anyParams as TimeSeriesQuery; // dates already validated in validateDate(), if provided const epochStart = dateStart ? Date.parse(dateStart) : undefined; diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts index 32d6409d9c9fb..5cc41671f6167 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts @@ -38,7 +38,7 @@ export function createFieldsRoute(service: Service, router: IRouter, baseRoute: ); async function handler( ctx: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory ): Promise { service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts index c08450448b44c..ebcf6b4f0e45a 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts @@ -18,6 +18,7 @@ import { KibanaResponseFactory, IScopedClusterClient, } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { Service } from '../../../types'; const bodySchema = schema.object({ @@ -40,7 +41,7 @@ export function createIndicesRoute(service: Service, router: IRouter, baseRoute: ); async function handler( ctx: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory ): Promise { const pattern = req.body.pattern; @@ -102,12 +103,14 @@ async function getIndicesFromPattern( }, }, }; - const response = await dataClient.callAsCurrentUser('search', params); - if (response.status === 404 || !response.aggregations) { + const response: SearchResponse = await dataClient.callAsCurrentUser('search', params); + // TODO: Investigate when the status field might appear here, type suggests it shouldn't ever happen + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((response as any).status === 404 || !response.aggregations) { return []; } - return response.aggregations.indices.buckets.map((bucket: any) => bucket.key); + return (response.aggregations as IndiciesAggregation).indices.buckets.map(bucket => bucket.key); } async function getAliasesFromPattern( @@ -137,3 +140,9 @@ async function getAliasesFromPattern( return result; } + +interface IndiciesAggregation { + indices: { + buckets: Array<{ key: string }>; + }; +} diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts index c8129c2428ee4..201c82060f386 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts @@ -30,7 +30,7 @@ export function createTimeSeriesQueryRoute(service: Service, router: IRouter, ba ); async function handler( ctx: RequestHandlerContext, - req: KibanaRequest, + req: KibanaRequest, res: KibanaResponseFactory ): Promise { service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); 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 986486902c3fa..470123ada48ea 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 @@ -42,6 +42,8 @@ describe('indexDocument', () => { }); describe('doesIlmPolicyExist', () => { + // ElasticsearchError can be a bit random in shape, we need an any here + // eslint-disable-next-line @typescript-eslint/no-explicit-any const notFoundError = new Error('Not found') as any; notFoundError.statusCode = 404; @@ -187,6 +189,8 @@ describe('createIndex', () => { }); test(`shouldn't throw when an error of type resource_already_exists_exception is thrown`, async () => { + // ElasticsearchError can be a bit random in shape, we need an any here + // eslint-disable-next-line @typescript-eslint/no-explicit-any const err = new Error('Already exists') as any; err.body = { error: { 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 409bb2d00e161..6d5c6b31a637c 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 @@ -5,6 +5,7 @@ */ import { reject, isUndefined } from 'lodash'; +import { SearchResponse, Client } from 'elasticsearch'; import { Logger, ClusterClient } from '../../../../../src/core/server'; import { IEvent } from '../types'; import { FindOptionsType } from '../event_log_client'; @@ -33,8 +34,8 @@ export class ClusterClientAdapter { this.clusterClientPromise = opts.clusterClientPromise; } - public async indexDocument(doc: any): Promise { - await this.callEs('index', doc); + public async indexDocument(doc: unknown): Promise { + await this.callEs>('index', doc); } public async doesIlmPolicyExist(policyName: string): Promise { @@ -51,7 +52,7 @@ export class ClusterClientAdapter { return true; } - public async createIlmPolicy(policyName: string, policy: any): Promise { + public async createIlmPolicy(policyName: string, policy: unknown): Promise { const request = { method: 'PUT', path: `_ilm/policy/${policyName}`, @@ -67,21 +68,27 @@ export class ClusterClientAdapter { public async doesIndexTemplateExist(name: string): Promise { let result; try { - result = await this.callEs('indices.existsTemplate', { name }); + result = await this.callEs>( + 'indices.existsTemplate', + { name } + ); } catch (err) { throw new Error(`error checking existance of index template: ${err.message}`); } return result as boolean; } - public async createIndexTemplate(name: string, template: any): Promise { + public async createIndexTemplate(name: string, template: unknown): Promise { const addTemplateParams = { name, create: true, body: template, }; try { - await this.callEs('indices.putTemplate', addTemplateParams); + await this.callEs>( + 'indices.putTemplate', + addTemplateParams + ); } catch (err) { // The error message doesn't have a type attribute we can look to guarantee it's due // to the template already existing (only long message) so we'll check ourselves to see @@ -97,16 +104,19 @@ export class ClusterClientAdapter { public async doesAliasExist(name: string): Promise { let result; try { - result = await this.callEs('indices.existsAlias', { name }); + result = await this.callEs>( + 'indices.existsAlias', + { name } + ); } catch (err) { throw new Error(`error checking existance of initial index: ${err.message}`); } return result as boolean; } - public async createIndex(name: string, body: any = {}): Promise { + public async createIndex(name: string, body: unknown = {}): Promise { try { - await this.callEs('indices.create', { + await this.callEs>('indices.create', { index: name, body, }); @@ -125,12 +135,12 @@ export class ClusterClientAdapter { ): Promise { try { const { - hits: { - hits, - total: { value: total }, - }, - } = await this.callEs('search', { + hits: { hits, total }, + }: SearchResponse = await this.callEs('search', { index, + // The SearchResponse type only supports total as an int, + // so we're forced to explicitly request that it return as an int + rest_total_hits_as_int: true, body: { size: perPage, from: (page - 1) * perPage, @@ -189,7 +199,7 @@ export class ClusterClientAdapter { page, per_page: perPage, total, - data: hits.map((hit: any) => hit._source) as IEvent[], + data: hits.map(hit => hit._source) as IEvent[], }; } catch (err) { throw new Error( @@ -198,13 +208,15 @@ export class ClusterClientAdapter { } } - private async callEs(operation: string, body?: any): Promise { + // We have a common problem typing ES-DSL Queries + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private async callEs(operation: string, body?: any) { try { this.debug(`callEs(${operation}) calls:`, body); const clusterClient = await this.clusterClientPromise; const result = await clusterClient.callAsInternalUser(operation, body); this.debug(`callEs(${operation}) result:`, result); - return result; + return result as ESQueryResult; } catch (err) { this.debug(`callEs(${operation}) error:`, { message: err.message, @@ -214,7 +226,7 @@ export class ClusterClientAdapter { } } - private debug(message: string, object?: any) { + private debug(message: string, object?: unknown) { const objectString = object == null ? '' : JSON.stringify(object); this.logger.debug(`esContext: ${message} ${objectString}`); } diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts index 982454e671008..a6af209d6d3a0 100644 --- a/x-pack/plugins/event_log/server/es/documents.ts +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -9,7 +9,7 @@ import mappings from '../../generated/mappings.json'; // returns the body of an index template used in an ES indices.putTemplate call export function getIndexTemplate(esNames: EsNames) { - const indexTemplateBody: any = { + const indexTemplateBody = { index_patterns: [esNames.indexPatternWithVersion], settings: { number_of_shards: 1, diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts index a8d75bc6c2e5a..a088799748c9d 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -41,7 +41,7 @@ describe('EventLogClientService', () => { function fakeRequest(): KibanaRequest { const savedObjectsClient = savedObjectsClientMock.create(); - return { + return ({ headers: {}, getBasePath: () => '', path: '/', @@ -55,5 +55,5 @@ function fakeRequest(): KibanaRequest { }, }, getSavedObjectsClient: () => savedObjectsClient, - } as any; + } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_logger.ts b/x-pack/plugins/event_log/server/event_logger.ts index f5149da069953..bcfd7bd45a6f5 100644 --- a/x-pack/plugins/event_log/server/event_logger.ts +++ b/x-pack/plugins/event_log/server/event_logger.ts @@ -168,7 +168,7 @@ function indexEventDoc(esContext: EsContext, doc: Doc): void { } // whew, the thing that actually writes the event log document! -async function indexLogEventDoc(esContext: EsContext, doc: any) { +async function indexLogEventDoc(esContext: EsContext, doc: unknown) { esContext.logger.debug(`writing to event log: ${JSON.stringify(doc)}`); await esContext.waitTillReady(); await esContext.esAdapter.indexDocument(doc); @@ -176,6 +176,6 @@ async function indexLogEventDoc(esContext: EsContext, doc: any) { } // TODO: write log entry to a bounded queue buffer -function writeLogEventDocOnError(esContext: EsContext, doc: any) { +function writeLogEventDocOnError(esContext: EsContext, doc: unknown) { esContext.logger.warn(`unable to write event doc: ${JSON.stringify(doc)}`); } diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts index d4dbb9064a1ba..6f1d92034c06f 100644 --- a/x-pack/plugins/event_log/server/lib/ready_signal.test.ts +++ b/x-pack/plugins/event_log/server/lib/ready_signal.test.ts @@ -16,11 +16,11 @@ describe('ReadySignal', () => { test('works as expected', async done => { let value = 41; - timeoutSet(100, () => { + timeoutSet(100, async () => { expect(value).toBe(41); }); - timeoutSet(250, () => readySignal.signal(42)); + timeoutSet(250, async () => readySignal.signal(42)); timeoutSet(400, async () => { expect(value).toBe(42); @@ -35,6 +35,6 @@ describe('ReadySignal', () => { }); }); -function timeoutSet(ms: number, fn: any) { +function timeoutSet(ms: number, fn: () => Promise): void { setTimeout(fn, ms); } diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index e5034f599f118..dd83b2cfb03b8 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -120,7 +120,7 @@ export class Plugin implements CorePlugin, + RequestHandler, 'eventLog' > => { return async (context, request) => { diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts index 19933649277aa..2d5e37e870b28 100644 --- a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -11,9 +11,9 @@ import { IEventLogClient } from '../types'; export function mockHandlerArguments( eventLogClient: IEventLogClient, - req: any, + req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { return [ ({ eventLog: { @@ -22,7 +22,7 @@ export function mockHandlerArguments( }, }, } as unknown) as RequestHandlerContext, - req as KibanaRequest, + req as KibanaRequest, mockResponseFactory(res), ]; } diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts index cb170e50fb447..f8e1c842ae436 100644 --- a/x-pack/plugins/event_log/server/routes/find.ts +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -31,9 +31,9 @@ export const findRoute = (router: IRouter) => { }, router.handleLegacyErrors(async function( context: RequestHandlerContext, - req: KibanaRequest, FindOptionsType, any, any>, + req: KibanaRequest, FindOptionsType, unknown>, res: KibanaResponseFactory - ): Promise> { + ): Promise { if (!context.eventLog) { return res.badRequest({ body: 'RouteHandlerContext is not registered for eventLog' }); } diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index f7962f7011f34..8e877f696a2fc 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -7,7 +7,7 @@ import { configSchema } from './config'; describe('config validation', () => { test('task manager defaults', () => { - const config: Record = {}; + const config: Record = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { "enabled": true, @@ -21,7 +21,7 @@ describe('config validation', () => { }); test('the ElastiSearch Tasks index cannot be used for task manager', () => { - const config: Record = { + const config: Record = { index: '.tasks', }; expect(() => { diff --git a/x-pack/plugins/task_manager/server/create_task_manager.ts b/x-pack/plugins/task_manager/server/create_task_manager.ts index 9ff97bbcc17e6..7ab6acba7976d 100644 --- a/x-pack/plugins/task_manager/server/create_task_manager.ts +++ b/x-pack/plugins/task_manager/server/create_task_manager.ts @@ -12,9 +12,10 @@ import { } from '../../../../src/core/server'; import { TaskManager } from './task_manager'; import { Logger } from './types'; +import { TaskManagerConfig } from './config'; export interface LegacyDeps { - config: any; + config: unknown; elasticsearch: Pick; savedObjectsRepository: ISavedObjectsRepository; savedObjectsSerializer: SavedObjectsSerializer; @@ -33,7 +34,7 @@ export function createTaskManager( ) { return new TaskManager({ taskManagerId: core.uuid.getInstanceUuid(), - config, + config: config as TaskManagerConfig, savedObjectsRepository, serializer: savedObjectsSerializer, callAsInternalUser, diff --git a/x-pack/plugins/task_manager/server/lib/middleware.test.ts b/x-pack/plugins/task_manager/server/lib/middleware.test.ts index 3aa39eb3db513..abf69e726262f 100644 --- a/x-pack/plugins/task_manager/server/lib/middleware.test.ts +++ b/x-pack/plugins/task_manager/server/lib/middleware.test.ts @@ -28,9 +28,9 @@ const getMockConcreteTaskInstance = () => { scheduledAt: Date; startedAt: Date | null; retryAt: Date | null; - state: any; + state: unknown; taskType: string; - params: any; + params: unknown; ownerId: string | null; } = { id: 'hy8o99o83', @@ -47,7 +47,7 @@ const getMockConcreteTaskInstance = () => { params: { abc: 'def' }, ownerId: null, }; - return concrete; + return (concrete as unknown) as ConcreteTaskInstance; }; const getMockRunContext = (runTask: ConcreteTaskInstance) => ({ taskInstance: runTask, @@ -95,7 +95,7 @@ describe('addMiddlewareToChain', () => { await middlewareChain .beforeSave({ taskInstance: getMockTaskInstance() }) - .then((saveOpts: any) => { + .then((saveOpts: unknown) => { expect(saveOpts).toMatchInlineSnapshot(` Object { "taskInstance": Object { diff --git a/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts b/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts index da64befe28673..650eb36347c86 100644 --- a/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts +++ b/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { RunContext } from '../task'; +import { RunContext, TaskDictionary, TaskDefinition } from '../task'; import { sanitizeTaskDefinitions } from './sanitize_task_definitions'; interface Opts { @@ -14,7 +14,7 @@ interface Opts { const getMockTaskDefinitions = (opts: Opts) => { const { numTasks } = opts; - const tasks: any = {}; + const tasks: Record = {}; for (let i = 0; i < numTasks; i++) { const type = `test_task_type_${i}`; @@ -35,7 +35,7 @@ const getMockTaskDefinitions = (opts: Opts) => { }, }; } - return tasks; + return (tasks as unknown) as TaskDictionary; }; describe('sanitizeTaskDefinitions', () => { diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index e837fcd9c0dec..a70fbdb18c30b 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -35,7 +35,7 @@ export class TaskManagerPlugin this.currentConfig = {} as TaskManagerConfig; } - public setup(core: CoreSetup, plugins: any): TaskManagerSetupContract { + public setup(core: CoreSetup, plugins: unknown): TaskManagerSetupContract { const logger = this.initContext.logger.get('taskManager'); const config$ = this.initContext.config.create(); return { diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts index 8f7cc47f936b2..4fd4da3d83a36 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts @@ -55,6 +55,7 @@ export const IdleTaskWithExpiredRunAt: MustCondition = }; // TODO: Fix query clauses to support this +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const InactiveTasks: BoolClauseWithAnyCondition = { bool: { must_not: [ diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index 48e87582ce3fe..e806a866ff0b7 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -28,7 +28,7 @@ type Require = Omit & Required Promise; +export type ElasticJs = (action: string, args: unknown) => Promise; /** * The run context is passed into a task's run function as its sole argument. @@ -61,12 +61,12 @@ export interface RunResult { * The state which will be passed to the next run of this task (if this is a * recurring task). See the RunContext type definition for more details. */ - state: Record; + state: Record; } export interface SuccessfulRunResult { runAt?: Date; - state?: Record; + state?: Record; } export interface FailedRunResult extends SuccessfulRunResult { @@ -237,6 +237,9 @@ export interface TaskInstance { * A task-specific set of parameters, used by the task's run function to tailor * its work. This is generally user-input, such as { sms: '333-444-2222' }. */ + // we allow any here as unknown will break current use in other plugins + // this can be fixed by supporting generics in the future + // eslint-disable-next-line @typescript-eslint/no-explicit-any params: Record; /** @@ -244,6 +247,9 @@ export interface TaskInstance { * run. If there was no previous run, or if the previous run did not return * any state, this will be the empy object: {} */ + // we allow any here as unknown will break current use in other plugins + // this can be fixed by supporting generics in the future + // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record; /** @@ -336,6 +342,9 @@ export interface ConcreteTaskInstance extends TaskInstance { * run. If there was no previous run, or if the previous run did not return * any state, this will be the empy object: {} */ + // we allow any here as unknown will break current use in other plugins + // this can be fixed by supporting generics in the future + // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record; /** @@ -343,3 +352,15 @@ export interface ConcreteTaskInstance extends TaskInstance { */ ownerId: string | null; } + +export type SerializedConcreteTaskInstance = Omit< + ConcreteTaskInstance, + 'state' | 'params' | 'scheduledAt' | 'startedAt' | 'retryAt' | 'runAt' +> & { + state: string; + params: string; + scheduledAt: string; + startedAt: string | null; + retryAt: string | null; + runAt: string; +}; diff --git a/x-pack/plugins/task_manager/server/task_events.ts b/x-pack/plugins/task_manager/server/task_events.ts index 063ac2499471f..b17a3636c1730 100644 --- a/x-pack/plugins/task_manager/server/task_events.ts +++ b/x-pack/plugins/task_manager/server/task_events.ts @@ -68,16 +68,18 @@ export function asTaskRunRequestEvent( } export function isTaskMarkRunningEvent( - taskEvent: TaskEvent + taskEvent: TaskEvent ): taskEvent is TaskMarkRunning { return taskEvent.type === TaskEventType.TASK_MARK_RUNNING; } -export function isTaskRunEvent(taskEvent: TaskEvent): taskEvent is TaskRun { +export function isTaskRunEvent(taskEvent: TaskEvent): taskEvent is TaskRun { return taskEvent.type === TaskEventType.TASK_RUN; } -export function isTaskClaimEvent(taskEvent: TaskEvent): taskEvent is TaskClaim { +export function isTaskClaimEvent(taskEvent: TaskEvent): taskEvent is TaskClaim { return taskEvent.type === TaskEventType.TASK_CLAIM; } -export function isTaskRunRequestEvent(taskEvent: TaskEvent): taskEvent is TaskRunRequest { +export function isTaskRunRequestEvent( + taskEvent: TaskEvent +): taskEvent is TaskRunRequest { return taskEvent.type === TaskEventType.TASK_RUN_REQUEST; } diff --git a/x-pack/plugins/task_manager/server/task_manager.test.ts b/x-pack/plugins/task_manager/server/task_manager.test.ts index 3d48ce18c9d6a..3f3b14a791f24 100644 --- a/x-pack/plugins/task_manager/server/task_manager.test.ts +++ b/x-pack/plugins/task_manager/server/task_manager.test.ts @@ -25,6 +25,7 @@ import { SavedObjectsSerializer, SavedObjectTypeRegistry } from '../../../../src import { mockLogger } from './test_utils'; import { asErr, asOk } from './lib/result_type'; import { ConcreteTaskInstance, TaskLifecycleResult, TaskStatus } from './task'; +import { Middleware } from './lib/middleware'; const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = new SavedObjectsSerializer(new SavedObjectTypeRegistry()); @@ -247,20 +248,20 @@ describe('TaskManager', () => { test('allows middleware registration before starting', () => { const client = new TaskManager(taskManagerOpts); - const middleware = { - beforeSave: async (saveOpts: any) => saveOpts, - beforeRun: async (runOpts: any) => runOpts, - beforeMarkRunning: async (runOpts: any) => runOpts, + const middleware: Middleware = { + beforeSave: jest.fn(async saveOpts => saveOpts), + beforeRun: jest.fn(async runOpts => runOpts), + beforeMarkRunning: jest.fn(async runOpts => runOpts), }; expect(() => client.addMiddleware(middleware)).not.toThrow(); }); test('disallows middleware registration after starting', async () => { const client = new TaskManager(taskManagerOpts); - const middleware = { - beforeSave: async (saveOpts: any) => saveOpts, - beforeRun: async (runOpts: any) => runOpts, - beforeMarkRunning: async (runOpts: any) => runOpts, + const middleware: Middleware = { + beforeSave: jest.fn(async saveOpts => saveOpts), + beforeRun: jest.fn(async runOpts => runOpts), + beforeMarkRunning: jest.fn(async runOpts => runOpts), }; client.start(); diff --git a/x-pack/plugins/task_manager/server/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts index a7c67d190e72e..24ceea0fe71ef 100644 --- a/x-pack/plugins/task_manager/server/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -43,6 +43,7 @@ import { TaskLifecycle, TaskLifecycleResult, TaskStatus, + ElasticJs, } from './task'; import { createTaskPoller, PollingError, PollingErrorType } from './task_poller'; import { TaskPool } from './task_pool'; @@ -129,7 +130,7 @@ export class TaskManager { this.store = new TaskStore({ serializer: opts.serializer, savedObjectsRepository: opts.savedObjectsRepository, - callCluster: opts.callAsInternalUser, + callCluster: (opts.callAsInternalUser as unknown) as ElasticJs, index: opts.config.index, maxAttempts: opts.config.max_attempts, definitions: this.definitions, @@ -273,7 +274,7 @@ export class TaskManager { */ public async schedule( taskInstance: TaskInstanceWithDeprecatedFields, - options?: any + options?: object ): Promise { await this.waitUntilStarted(); const { taskInstance: modifiedTask } = await this.middleware.beforeSave({ @@ -308,7 +309,7 @@ export class TaskManager { */ public async ensureScheduled( taskInstance: TaskInstanceWithId, - options?: any + options?: object ): Promise { try { return await this.schedule(taskInstance, options); diff --git a/x-pack/plugins/task_manager/server/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_runner.test.ts index fad3bf96905ae..07247dcb1da47 100644 --- a/x-pack/plugins/task_manager/server/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_runner.test.ts @@ -9,7 +9,7 @@ import sinon from 'sinon'; import { minutesFromNow } from './lib/intervals'; import { asOk, asErr } from './lib/result_type'; import { TaskEvent, asTaskRunEvent, asTaskMarkRunningEvent } from './task_events'; -import { ConcreteTaskInstance, TaskStatus } from './task'; +import { ConcreteTaskInstance, TaskStatus, TaskDictionary, TaskDefinition } from './task'; import { TaskManagerRunner } from './task_runner'; import { mockLogger } from './test_utils'; import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; @@ -906,8 +906,8 @@ describe('TaskManagerRunner', () => { interface TestOpts { instance?: Partial; - definitions?: any; - onTaskEvent?: (event: TaskEvent) => void; + definitions?: unknown; + onTaskEvent?: (event: TaskEvent) => void; } function testOpts(opts: TestOpts) { @@ -956,7 +956,7 @@ describe('TaskManagerRunner', () => { title: 'Bar!', createTaskRunner, }, - }), + }) as TaskDictionary, onTaskEvent: opts.onTaskEvent, }); @@ -970,7 +970,7 @@ describe('TaskManagerRunner', () => { }; } - async function testReturn(result: any, shouldBeValid: boolean) { + async function testReturn(result: unknown, shouldBeValid: boolean) { const { runner, logger } = testOpts({ definitions: { bar: { @@ -991,11 +991,11 @@ describe('TaskManagerRunner', () => { } } - function allowsReturnType(result: any) { + function allowsReturnType(result: unknown) { return testReturn(result, true); } - function disallowsReturnType(result: any) { + function disallowsReturnType(result: unknown) { return testReturn(result, false); } }); diff --git a/x-pack/plugins/task_manager/server/task_runner.ts b/x-pack/plugins/task_manager/server/task_runner.ts index ec1c40dc80731..7a9fa0c45e15f 100644 --- a/x-pack/plugins/task_manager/server/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_runner.ts @@ -409,7 +409,7 @@ export class TaskManagerRunner implements TaskRunner { attempts, addDuration, }: { - error: any; + error: Error; attempts: number; addDuration?: string; }): Date | null { diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 4ecefcb7984eb..6524ea212e7c5 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -15,11 +15,17 @@ import { TaskInstance, TaskStatus, TaskLifecycleResult, + SerializedConcreteTaskInstance, + ConcreteTaskInstance, } from './task'; import { StoreOpts, OwnershipClaimingOpts, TaskStore, SearchOpts } from './task_store'; -import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks'; -import { SavedObjectsSerializer, SavedObjectTypeRegistry } from '../../../../src/core/server'; -import { SavedObjectsErrorHelpers } from '../../../../src/core/server/saved_objects/service/lib/errors'; +import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; +import { + SavedObjectsSerializer, + SavedObjectTypeRegistry, + SavedObjectAttributes, + SavedObjectsErrorHelpers, +} from 'src/core/server'; import { asTaskClaimEvent, TaskEvent } from './task_events'; import { asOk, asErr } from './lib/result_type'; @@ -47,6 +53,7 @@ const serializer = new SavedObjectsSerializer(new SavedObjectTypeRegistry()); beforeEach(() => jest.resetAllMocks()); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any (global as any).Date = class Date { constructor() { return mockedDate; @@ -58,9 +65,9 @@ const mockedDate = new Date('2019-02-12T21:01:22.479Z'); describe('TaskStore', () => { describe('schedule', () => { - async function testSchedule(task: TaskInstance) { + async function testSchedule(task: unknown) { const callCluster = jest.fn(); - savedObjectsClient.create.mockImplementation(async (type: string, attributes: any) => ({ + savedObjectsClient.create.mockImplementation(async (type: string, attributes: unknown) => ({ id: 'testid', type, attributes, @@ -76,7 +83,7 @@ describe('TaskStore', () => { definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, }); - const result = await store.schedule(task); + const result = await store.schedule(task as TaskInstance); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); @@ -149,14 +156,16 @@ describe('TaskStore', () => { test('sets runAt to now if not specified', async () => { await testSchedule({ taskType: 'dernstraight', params: {}, state: {} }); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); - const attributes: any = savedObjectsClient.create.mock.calls[0][1]; + const attributes = savedObjectsClient.create.mock + .calls[0][1] as SerializedConcreteTaskInstance; expect(new Date(attributes.runAt as string).getTime()).toEqual(mockedDate.getTime()); }); test('ensures params and state are not null', async () => { - await testSchedule({ taskType: 'yawn' } as any); + await testSchedule({ taskType: 'yawn' }); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); - const attributes: any = savedObjectsClient.create.mock.calls[0][1]; + const attributes = savedObjectsClient.create.mock + .calls[0][1] as SerializedConcreteTaskInstance; expect(attributes.params).toEqual('{}'); expect(attributes.state).toEqual('{}'); }); @@ -169,8 +178,8 @@ describe('TaskStore', () => { }); describe('fetch', () => { - async function testFetch(opts?: SearchOpts, hits: any[] = []) { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits } })); + async function testFetch(opts?: SearchOpts, hits: unknown[] = []) { + const callCluster = sinon.spy(async (name: string, params?: unknown) => ({ hits: { hits } })); const store = new TaskStore({ index: 'tasky', taskManagerId: '', @@ -229,11 +238,11 @@ describe('TaskStore', () => { claimingOpts, }: { opts: Partial; - hits?: any[]; + hits?: unknown[]; claimingOpts: OwnershipClaimingOpts; }) { const versionConflicts = 2; - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: hits.length + versionConflicts, @@ -266,7 +275,7 @@ describe('TaskStore', () => { } test('it returns normally with no tasks when the index does not exist.', async () => { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ + const callCluster = sinon.spy(async (name: string, params?: unknown) => ({ total: 0, updated: 0, })); @@ -745,7 +754,7 @@ if (doc['task.runAt'].size()!=0) { }; savedObjectsClient.update.mockImplementation( - async (type: string, id: string, attributes: any) => { + async (type: string, id: string, attributes: SavedObjectAttributes) => { return { id, type, @@ -1017,7 +1026,7 @@ if (doc['task.runAt'].size()!=0) { test('emits an event when a task is succesfully claimed by id', async done => { const { taskManagerId, runAt, tasks } = generateTasks(); - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: tasks.length, @@ -1036,9 +1045,9 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'aaa')) + .pipe(filter((event: TaskEvent) => event.id === 'aaa')) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent) => { expect(event).toMatchObject( asTaskClaimEvent( 'aaa', @@ -1074,7 +1083,7 @@ if (doc['task.runAt'].size()!=0) { test('emits an event when a task is succesfully by scheduling', async done => { const { taskManagerId, runAt, tasks } = generateTasks(); - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: tasks.length, @@ -1093,9 +1102,9 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'bbb')) + .pipe(filter((event: TaskEvent) => event.id === 'bbb')) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent) => { expect(event).toMatchObject( asTaskClaimEvent( 'bbb', @@ -1131,7 +1140,7 @@ if (doc['task.runAt'].size()!=0) { test('emits an event when the store fails to claim a required task by id', async done => { const { taskManagerId, tasks } = generateTasks(); - const callCluster = sinon.spy(async (name: string, params?: any) => + const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { total: tasks.length, @@ -1150,9 +1159,9 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'ccc')) + .pipe(filter((event: TaskEvent) => event.id === 'ccc')) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent) => { expect(event).toMatchObject( asTaskClaimEvent('ccc', asErr(new Error(`failed to claim task 'ccc'`))) ); diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 0e487386eb04d..01299615c7d49 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -9,11 +9,11 @@ */ import apm from 'elastic-apm-node'; import { Subject, Observable } from 'rxjs'; -import { omit, difference } from 'lodash'; +import { omit, difference, defaults } from 'lodash'; +import { SearchResponse, UpdateDocumentByQueryResponse } from 'elasticsearch'; import { SavedObject, - SavedObjectAttributes, SavedObjectsSerializer, SavedObjectsRawDoc, ISavedObjectsRepository, @@ -29,6 +29,7 @@ import { TaskInstance, TaskLifecycle, TaskLifecycleResult, + SerializedConcreteTaskInstance, } from './task'; import { TaskClaim, asTaskClaimEvent } from './task_events'; @@ -71,7 +72,7 @@ export interface SearchOpts { query?: object; size?: number; seq_no_primary_term?: boolean; - search_after?: any[]; + search_after?: unknown[]; } export interface UpdateByQuerySearchOpts extends SearchOpts { @@ -166,7 +167,7 @@ export class TaskStore { ); } - const savedObject = await this.savedObjectsRepository.create( + const savedObject = await this.savedObjectsRepository.create( 'task', taskInstanceToAttributes(taskInstance), { id: taskInstance.id, refresh: false } @@ -314,17 +315,21 @@ export class TaskStore { * @returns {Promise} */ public async update(doc: ConcreteTaskInstance): Promise { - const updatedSavedObject = await this.savedObjectsRepository.update( - 'task', - doc.id, - taskInstanceToAttributes(doc), - { - refresh: false, - version: doc.version, - } - ); + const attributes = taskInstanceToAttributes(doc); + const updatedSavedObject = await this.savedObjectsRepository.update< + SerializedConcreteTaskInstance + >('task', doc.id, attributes, { + refresh: false, + version: doc.version, + }); - return savedObjectToConcreteTaskInstance(updatedSavedObject); + return savedObjectToConcreteTaskInstance( + // The SavedObjects update api forces a Partial on the `attributes` on the response, + // but actually returns the whole object that is passed to it, so as we know we're + // passing in the whole object, this is safe to do. + // This is far from ideal, but unless we change the SavedObjectsClient this is the best we can do + { ...updatedSavedObject, attributes: defaults(updatedSavedObject.attributes, attributes) } + ); } /** @@ -377,12 +382,12 @@ export class TaskStore { }, }); - const rawDocs = result.hits.hits; + const rawDocs = (result as SearchResponse).hits.hits; return { docs: (rawDocs as SavedObjectsRawDoc[]) .map(doc => this.serializer.rawToSavedObject(doc)) - .map(doc => omit(doc, 'namespace') as SavedObject) + .map(doc => omit(doc, 'namespace') as SavedObject) .map(savedObjectToConcreteTaskInstance), }; } @@ -404,7 +409,7 @@ export class TaskStore { }, }); - const { total, updated, version_conflicts } = result; + const { total, updated, version_conflicts } = result as UpdateDocumentByQueryResponse; return { total, updated, @@ -413,7 +418,7 @@ export class TaskStore { } } -function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes { +function taskInstanceToAttributes(doc: TaskInstance): SerializedConcreteTaskInstance { return { ...omit(doc, 'id', 'version'), params: JSON.stringify(doc.params || {}), @@ -428,8 +433,7 @@ function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes { } export function savedObjectToConcreteTaskInstance( - // TODO: define saved object type - savedObject: Omit, 'references'> + savedObject: Omit, 'references'> ): ConcreteTaskInstance { return { ...savedObject.attributes, @@ -437,8 +441,8 @@ export function savedObjectToConcreteTaskInstance( version: savedObject.version, scheduledAt: new Date(savedObject.attributes.scheduledAt), runAt: new Date(savedObject.attributes.runAt), - startedAt: savedObject.attributes.startedAt && new Date(savedObject.attributes.startedAt), - retryAt: savedObject.attributes.retryAt && new Date(savedObject.attributes.retryAt), + startedAt: savedObject.attributes.startedAt ? new Date(savedObject.attributes.startedAt) : null, + retryAt: savedObject.attributes.retryAt ? new Date(savedObject.attributes.retryAt) : null, state: parseJSONField(savedObject.attributes.state, 'state', savedObject.id), params: parseJSONField(savedObject.attributes.params, 'params', savedObject.id), }; diff --git a/x-pack/plugins/task_manager/server/test_utils/index.ts b/x-pack/plugins/task_manager/server/test_utils/index.ts index 719ccadbe33dd..3dfc53672b46f 100644 --- a/x-pack/plugins/task_manager/server/test_utils/index.ts +++ b/x-pack/plugins/task_manager/server/test_utils/index.ts @@ -33,11 +33,11 @@ interface Resolvable { */ export function resolvable(): PromiseLike & Resolvable { let resolve: () => void; - const result = new Promise(r => (resolve = r)) as any; - - result.resolve = () => nativeTimeout(resolve, 0); - - return result; + return Object.assign(new Promise(r => (resolve = r)), { + resolve() { + return nativeTimeout(resolve, 0); + }, + }); } /**