[alerting] removes usage of any throughout Alerting Services code (#64161)

This removes unneeded use of `any` throughout:
1. alerting
2. alerting_builtin
3. actions
4. task manager
5. event log

It also adds a linting rule that will prevent us from adding more `any` in the future unless an explicit exemption is made.
This commit is contained in:
Gidi Meir Morris 2020-04-24 17:04:36 +01:00 committed by GitHub
parent 4051c94568
commit a012ddf9df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
131 changed files with 838 additions and 540 deletions

View file

@ -739,6 +739,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
*/

View file

@ -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<LegacyPluginSpec> {
return new kibana.Plugin({
id: 'actions',
configPrefix: 'xpack.actions',
@ -29,5 +34,5 @@ export function actions(kibana: any) {
uiExports: {
mappings,
},
});
} as Legacy.PluginSpecOptions);
}

View file

@ -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<LegacyPluginSpec> {
return new kibana.Plugin({
id: 'alerting',
configPrefix: 'xpack.alerting',
@ -31,5 +36,5 @@ export function alerting(kibana: any) {
uiExports: {
mappings,
},
});
} as Legacy.PluginSpecOptions);
}

View file

@ -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<LegacyPluginSpec> {
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);
}

View file

@ -49,10 +49,10 @@ export function createLegacyApi(legacyTaskManager: Promise<TaskManager>): 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)),
};
}

View file

@ -7,7 +7,7 @@ import { SavedObject } from '../../../../../src/core/server';
export const migrations = {
task: {
'7.4.0': (doc: SavedObject<Record<any, any>>) => ({
'7.4.0': (doc: SavedObject<Record<string, unknown>>) => ({
...doc,
updated_at: new Date().toISOString(),
}),
@ -18,7 +18,7 @@ export const migrations = {
function moveIntervalIntoSchedule({
attributes: { interval, ...attributes },
...doc
}: SavedObject<Record<any, any>>) {
}: SavedObject<Record<string, unknown>>) {
return {
...doc,
attributes: {

View file

@ -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<string, any>;
isPreconfigured: boolean;
}

View file

@ -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;
}

View file

@ -7,9 +7,10 @@
import { ActionsClient } from './actions_client';
type ActionsClientContract = PublicMethodsOf<ActionsClient>;
export type ActionsClientMock = jest.Mocked<ActionsClientContract>;
const createActionsClientMock = () => {
const mocked: jest.Mocked<ActionsClientContract> = {
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,
};

View file

@ -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<string, any>,
config: result.attributes.config as Record<string, unknown>,
isPreconfigured: false,
};
}
@ -228,7 +228,7 @@ async function injectExtraFindData(
scopedClusterClient: IScopedClusterClient,
actionResults: ActionResult[]
): Promise<FindActionResult[]> {
const aggs: Record<string, any> = {};
const aggs: Record<string, unknown> = {};
for (const actionResult of actionResults) {
aggs[actionResult.id] = {
filter: {

View file

@ -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<string, any> = {
const config: Record<string, unknown> = {
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<string, any> = {
const baseConfig: Record<string, unknown> = {
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<string, any> = {
const secrets: Record<string, unknown> = {
user: 'bob',
password: 'supersecret',
};
@ -185,7 +185,7 @@ describe('secrets validation', () => {
});
test('secrets validation succeeds when secrets props are null/undefined', () => {
const secrets: Record<string, any> = {
const secrets: Record<string, unknown> = {
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<string, any> = {
const params: Record<string, unknown> = {
to: ['bob@example.com'],
subject: 'this is a test',
message: 'this is the message',

View file

@ -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;

View file

@ -43,7 +43,7 @@ describe('actionTypeRegistry.get() works', () => {
describe('config validation', () => {
test('config validation succeeds when config is valid', () => {
const config: Record<string, any> = {
const config: Record<string, unknown> = {
index: 'testing-123',
refresh: false,
};
@ -97,7 +97,7 @@ describe('config validation', () => {
});
test('config validation fails when config is not valid', () => {
const baseConfig: Record<string, any> = {
const baseConfig: Record<string, unknown> = {
indeX: 'bob',
};
@ -111,7 +111,7 @@ describe('config validation', () => {
describe('params validation', () => {
test('params validation succeeds when params is valid', () => {
const params: Record<string, any> = {
const params: Record<string, unknown> = {
documents: [{ rando: 'thing' }],
};
expect(validateParams(actionType, params)).toMatchInlineSnapshot(`

View file

@ -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);

View file

@ -9,7 +9,7 @@ import { Services } from '../../types';
interface PostPagerdutyOptions {
apiUrl: string;
data: any;
data: unknown;
headers: Record<string, string>;
services: Services;
}

View file

@ -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',

View file

@ -43,13 +43,13 @@ export interface Content {
}
// send an email
export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise<any> {
export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise<unknown> {
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<string, any> = {};
const transportConfig: Record<string, unknown> = {};
if (user != null && password != null) {
transportConfig.auth = {

View file

@ -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(),
};

View file

@ -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,
};

View file

@ -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' },

View file

@ -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)),
];
}

View file

@ -39,7 +39,7 @@ export const buildMap = (mapping: MapEntry[]): Mapping => {
}, new Map());
};
export const mapParams = (params: any, mapping: Mapping) => {
export const mapParams = (params: Record<string, unknown>, 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 => ({

View file

@ -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(),
};

View file

@ -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<string, unknown>, mapping);
const serviceNow = new ServiceNow({ url: apiUrl, username, password });
const handlerInput = {

View file

@ -49,14 +49,14 @@ class ServiceNow {
}: {
url: string;
method?: Method;
data?: any;
data?: unknown;
}): Promise<AxiosResponse> {
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<AxiosResponse> {
private _patch({ url, data }: { url: string; data: unknown }): Promise<AxiosResponse> {
return this._request({
url,
method: 'patch',

View file

@ -30,10 +30,10 @@ export type CasesConfigurationType = TypeOf<typeof CasesConfigurationSchema>;
export type MapEntry = TypeOf<typeof MapEntrySchema>;
export type Comment = TypeOf<typeof CommentSchema>;
export type Mapping = Map<string, any>;
export type Mapping = Map<string, Omit<MapEntry, 'source'>>;
export interface Params extends ExecutorParams {
incident: Record<string, any>;
incident: Record<string, unknown>;
}
export interface CreateHandlerArguments {
serviceNow: ServiceNow;
@ -66,7 +66,7 @@ export interface AppendFieldArgs {
}
export interface KeyAny {
[index: string]: string;
[index: string]: unknown;
}
export interface AppendInformationFieldArgs {

View file

@ -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<any> {},
async executor() {},
configurationUtilities: actionsConfigMock.create(),
});
});
@ -117,7 +122,7 @@ describe('validateActionTypeSecrets()', () => {
describe('execute()', () => {
beforeAll(() => {
async function mockSlackExecutor(options: ActionTypeExecutorOptions): Promise<any> {
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 () => {

View file

@ -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 };
}

View file

@ -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<string, any> = {
const secrets: Record<string, string> = {
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<string, any> = {
const defaultValues: Record<string, string | null> = {
headers: null,
method: 'post',
};
test('config validation passes when only required fields are provided', () => {
const config: Record<string, any> = {
const config: Record<string, string> = {
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<string, any> = {
const config: Record<string, string> = {
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<string, any> = {
const config: Record<string, string> = {
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<string, any> = {
const config: Record<string, string> = {
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<string, any> = {
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<string, any> = {
const config: Record<string, string> = {
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<string, any> = {
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<string, any> = {
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<string, any> = {};
const params: Record<string, string> = {};
expect(validateParams(actionType, params)).toEqual({});
});
test('params validation passes when a valid body is provided', () => {
const params: Record<string, any> = {
const params: Record<string, string> = {
body: 'count: {{ctx.payload.hits.total}}',
};
expect(validateParams(actionType, params)).toEqual({

View file

@ -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 };
}

View file

@ -7,7 +7,7 @@ import { configSchema } from './config';
describe('config validation', () => {
test('action defaults', () => {
const config: Record<string, any> = {};
const config: Record<string, unknown> = {};
expect(configSchema.validate(config)).toMatchInlineSnapshot(`
Object {
"enabled": true,
@ -23,7 +23,7 @@ describe('config validation', () => {
});
test('action with preconfigured actions', () => {
const config: Record<string, any> = {
const config: Record<string, unknown> = {
preconfigured: [
{
id: 'my-slack1',

View file

@ -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',

View file

@ -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<string, any>;
params: Record<string, unknown>;
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);

View file

@ -32,7 +32,7 @@ export interface ActionExecutorContext {
export interface ExecuteOptions {
actionId: string;
request: KibanaRequest;
params: Record<string, any>;
params: Record<string, unknown>;
}
export type ActionExecutorContract = PublicMethodsOf<ActionExecutor>;
@ -93,9 +93,9 @@ export class ActionExecutor {
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
const actionType = actionTypeRegistry.get(actionTypeId);
let validatedParams: Record<string, any>;
let validatedConfig: Record<string, any>;
let validatedSecrets: Record<string, any>;
let validatedParams: Record<string, unknown>;
let validatedConfig: Record<string, unknown>;
let validatedSecrets: Record<string, unknown>;
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(

View file

@ -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;

View file

@ -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<ILicense>;
let license: Subject<ILicense>;
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<ILicense>;
let license: Subject<ILicense>;
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;
}

View file

@ -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<string, string>;
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 {

View file

@ -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<string, unknown>) => 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');
},
};

View file

@ -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<string, any> {
if (actionType.validate == null) return value;
value: unknown
): Record<string, unknown> {
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);
}
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);
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<string, unknown>;
}

View file

@ -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);

View file

@ -117,7 +117,10 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
this.preconfiguredActions = [];
}
public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise<PluginSetupContract> {
public async setup(
core: CoreSetup<ActionsPluginsStart>,
plugins: ActionsPluginsSetup
): Promise<PluginSetupContract> {
this.licenseState = new LicenseState(plugins.licensing.license$);
this.isESOUsingEphemeralEncryptionKey =
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
@ -182,7 +185,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, 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<Promise<PluginSetupContract>, Plugi
private createRouteHandlerContext = (
defaultKibanaIndex: string
): IContextProvider<RequestHandler<any, any, any>, 'actions'> => {
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'actions'> => {
const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, preconfiguredActions } = this;
return async function actionsRouteHandlerContext(context, request) {

View file

@ -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<MethodKeysOf<KibanaResponseFactory>>
): [RequestHandlerContext, KibanaRequest<any, any, any, any>, KibanaResponseFactory] {
): [RequestHandlerContext, KibanaRequest<unknown, unknown, unknown>, KibanaResponseFactory] {
const listTypes = jest.fn(() => listTypesRes);
return [
({
@ -31,7 +36,7 @@ export function mockHandlerArguments(
},
},
} as unknown) as RequestHandlerContext,
req as KibanaRequest<any, any, any, any>,
req as KibanaRequest<unknown, unknown, unknown>,
mockResponseFactory(res),
];
}

View file

@ -8,6 +8,7 @@ import { httpServiceMock } from 'src/core/server/mocks';
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']);

View file

@ -36,9 +36,9 @@ export const createActionRoute = (router: IRouter, licenseState: ILicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, TypeOf<typeof bodySchema>, any>,
req: KibanaRequest<unknown, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.actions) {

View file

@ -8,6 +8,7 @@ import { httpServiceMock } from 'src/core/server/mocks';
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]`);

View file

@ -37,9 +37,9 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -43,9 +43,9 @@ export const executeActionRoute = (
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, TypeOf<typeof bodySchema>, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
const { params } = req.body;
const { id } = req.params;

View file

@ -9,6 +9,7 @@ import { httpServiceMock } from 'src/core/server/mocks';
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' },
},

View file

@ -32,9 +32,9 @@ export const getActionRoute = (router: IRouter, licenseState: ILicenseState) =>
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -9,6 +9,7 @@ import { httpServiceMock } from 'src/core/server/mocks';
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']);

View file

@ -25,9 +25,9 @@ export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -9,6 +9,7 @@ import { httpServiceMock } from 'src/core/server/mocks';
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,
},
];

View file

@ -25,9 +25,9 @@ export const listActionTypesRoute = (router: IRouter, licenseState: ILicenseStat
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -8,6 +8,7 @@ import { httpServiceMock } from 'src/core/server/mocks';
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',

View file

@ -39,9 +39,9 @@ export const updateActionRoute = (router: IRouter, licenseState: ILicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, TypeOf<typeof bodySchema>, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.actions) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' });

View file

@ -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<T> = Pick<T, Exclude<keyof T, 'query' | 'params'>>;
export type GetServicesFunction = (request: any) => Services;
export type GetServicesFunction = (request: KibanaRequest) => Services;
export type ActionTypeRegistryContract = PublicMethodsOf<ActionTypeRegistry>;
export type GetBasePathFunction = (spaceId?: string) => string;
export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined;
export interface Services {
callCluster(path: string, opts: any): Promise<any>;
callCluster(path: string, opts: unknown): Promise<unknown>;
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<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
secrets: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params: Record<string, any>;
}
@ -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<string, any>;
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<string, any>;
}
@ -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<ActionTypeExecutorResult | null | undefined | void>;
interface ValidatorType {
validate<T>(value: any): any;
validate(value: unknown): Record<string, unknown>;
}
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<string, any>;
apiKey?: string;
}
export interface ActionTaskExecutorParams {
spaceId: string;
actionTaskParamsId: string;
}

View file

@ -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],

View file

@ -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<string, any>;
scheduledTaskId?: string;
createdBy: string | null;

View file

@ -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) {

View file

@ -77,7 +77,7 @@ export class AlertTypeRegistry {
}
}
function normalizedActionVariables(actionVariables: any) {
function normalizedActionVariables(actionVariables: AlertType['actionVariables']) {
return {
context: actionVariables?.context ?? [],
state: actionVariables?.state ?? [],

View file

@ -7,9 +7,10 @@
import { AlertsClient } from './alerts_client';
type Schema = PublicMethodsOf<AlertsClient>;
export type AlertsClientMock = jest.Mocked<Schema>;
const createAlertsClientMock = () => {
const mocked: jest.Mocked<Schema> = {
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,
};

View file

@ -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<string, any> = {}) {
function getMockData(overwrites: Record<string, unknown> = {}): CreateOptions['data'] {
return {
enabled: true,
name: 'abc',

View file

@ -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<string, any>;
params: Record<string, unknown>;
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<string, any>();
const actionMap = new Map<string, unknown>();
// 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<Record<string, string>>).attributes.actionTypeId
: (actionMapValue as RawAlertAction).actionTypeId;
return {
...alertAction,
actionRef,

View file

@ -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<AlertsClientFactoryOpts> = {
logger: loggingServiceMock.create().get(),
taskManager: taskManagerMock.start(),
@ -30,7 +27,7 @@ const alertsClientFactoryParams: jest.Mocked<AlertsClientFactoryOpts> = {
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];

View file

@ -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',

View file

@ -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', () => {

View file

@ -5,15 +5,15 @@
*/
import Boom from 'boom';
import { AlertType } from '../types';
import { AlertType, AlertExecutorOptions } from '../types';
export function validateAlertTypeParams<T extends Record<string, any>>(
export function validateAlertTypeParams(
alertType: AlertType,
params: T
): T {
params: Record<string, unknown>
): AlertExecutorOptions['params'] {
const validator = alertType.validate && alertType.validate.params;
if (!validator) {
return params;
return params as AlertExecutorOptions['params'];
}
try {

View file

@ -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<AlertingPluginsStart, unknown>,
({
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<AlertingPluginsStart, unknown>,
({
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<typeof coreMock.createStart>,
({
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<AlertingPluginsStart, unknown>,
({
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<typeof coreMock.createStart>,
({
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);
});
});
});

View file

@ -117,7 +117,10 @@ export class AlertingPlugin {
.toPromise();
}
public async setup(core: CoreSetup, plugins: AlertingPluginsSetup): Promise<PluginSetupContract> {
public async setup(
core: CoreSetup<AlertingPluginsStart, unknown>,
plugins: AlertingPluginsSetup
): Promise<PluginSetupContract> {
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<any, any, any>,
RequestHandler<unknown, unknown, unknown>,
'alerting'
> => {
const { alertTypeRegistry, alertsClientFactory } = this;

View file

@ -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<IClusterClient>;
}>;
},
req: unknown,
res?: Array<MethodKeysOf<KibanaResponseFactory>>
): [RequestHandlerContext, KibanaRequest<any, any, any, any>, KibanaResponseFactory] {
): [RequestHandlerContext, KibanaRequest<unknown, unknown, unknown>, KibanaResponseFactory] {
const listTypes = jest.fn(() => listTypesRes);
return [
({
@ -25,7 +42,7 @@ export function mockHandlerArguments(
},
},
} as unknown) as RequestHandlerContext,
req as KibanaRequest<any, any, any, any>,
req as KibanaRequest<unknown, unknown, unknown>,
mockResponseFactory(res),
];
}

View file

@ -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]`);

View file

@ -54,9 +54,9 @@ export const createAlertRoute = (router: IRouter, licenseState: LicenseState) =>
handleDisabledApiKeysError(
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, TypeOf<typeof bodySchema>, any>,
req: KibanaRequest<unknown, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {

View file

@ -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]`);

View file

@ -33,9 +33,9 @@ export const deleteAlertRoute = (router: IRouter, licenseState: LicenseState) =>
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -33,9 +33,9 @@ export const disableAlertRoute = (router: IRouter, licenseState: LicenseState) =
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -35,9 +35,9 @@ export const enableAlertRoute = (router: IRouter, licenseState: LicenseState) =>
handleDisabledApiKeysError(
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -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);

View file

@ -55,9 +55,9 @@ export const findAlertRoute = (router: IRouter, licenseState: LicenseState) => {
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, TypeOf<typeof querySchema>, any, any>,
req: KibanaRequest<unknown, TypeOf<typeof querySchema>, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -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' },
},

View file

@ -33,9 +33,9 @@ export const getAlertRoute = (router: IRouter, licenseState: LicenseState) => {
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -33,9 +33,9 @@ export const getAlertStateRoute = (router: IRouter, licenseState: LicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -39,9 +39,9 @@ export function healthRoute(
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
try {
const {

View file

@ -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: [],
},
];

View file

@ -26,9 +26,9 @@ export const listAlertTypesRoute = (router: IRouter, licenseState: LicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
req: KibanaRequest<unknown, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -33,9 +33,9 @@ export const muteAllAlertRoute = (router: IRouter, licenseState: LicenseState) =
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -34,9 +34,9 @@ export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseSta
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -33,9 +33,9 @@ export const unmuteAllAlertRoute = (router: IRouter, licenseState: LicenseState)
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -34,9 +34,9 @@ export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: LicenseS
},
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -56,9 +56,9 @@ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) =>
handleDisabledApiKeysError(
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, TypeOf<typeof bodySchema>, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, TypeOf<typeof bodySchema>>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -35,9 +35,9 @@ export const updateApiKeyRoute = (router: IRouter, licenseState: LicenseState) =
handleDisabledApiKeysError(
router.handleLegacyErrors(async function(
context: RequestHandlerContext,
req: KibanaRequest<TypeOf<typeof paramSchema>, any, any, any>,
req: KibanaRequest<TypeOf<typeof paramSchema>, unknown, unknown>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse> {
verifyApiAccess(licenseState);
if (!context.alerting) {
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });

View file

@ -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) =>

View file

@ -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();

View file

@ -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<string, any>;
let updatedAlertTypeState: void | Record<string, unknown>;
try {
updatedAlertTypeState = await this.alertType.executor({
alertId,

View file

@ -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,

View file

@ -5,7 +5,7 @@
*/
interface Resolvable<T> {
resolve: (arg?: T) => void;
resolve: (arg: T) => void;
}
/**
@ -13,12 +13,10 @@ interface Resolvable<T> {
* coordinating async tests.
*/
export function resolvable<T>(): Promise<T> & Resolvable<T> {
let resolve: (arg?: T) => void;
const result = new Promise<T>(r => {
resolve = r;
}) as any;
result.resolve = (arg: T) => resolve(arg);
return result;
let resolve: (arg: T) => void;
return Object.assign(new Promise<T>(r => (resolve = r)), {
resolve(arg: T) {
return setTimeout(() => resolve(arg), 0);
},
});
}

View file

@ -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<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Context = Record<string, any>;
export type WithoutQueryAndParams<T> = Pick<T, Exclude<keyof T, 'query' | 'params'>>;
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<any>;
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<string, any>;
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'];

View file

@ -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<unknown> = 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],

View file

@ -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<Partial<Params>> = {
index: 'index-name',
@ -21,6 +22,7 @@ const DefaultParams: Writable<Partial<Params>> = {
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<typeof ParamsSchema> {
return ParamsSchema.validate(params);
}
});

View file

@ -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', {

View file

@ -19,7 +19,8 @@ const DefaultParams: Writable<Partial<CoreQueryParams>> = {
timeWindowUnit: 'm',
};
export function runTests(schema: ObjectType, defaultTypeParams: Record<string, any>): void {
export function runTests(schema: ObjectType, defaultTypeParams: Record<string, unknown>): 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<string, a
return () => validate();
}
function validate(): any {
function validate(): unknown {
return schema.validate(params);
}
}

Some files were not shown because too many files have changed in this diff Show more