Make the actions plugin support generics (#71439)

* Initial attempt at making the actions plugin support generics

* Export WebhookMethods

* Fix typings for registry

* Usage of Record<string, unknown>

* Apply feedback from Gidi

* Cleanup

* Fix validate_with_schema

* Cleanup pt2

* Fix failing tests

* Add generics to ActionType for ActionTypeExecutorResult

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Mike Côté 2020-08-04 15:50:17 -04:00 committed by GitHub
parent 8e0e228dbf
commit 54e13ad6f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 466 additions and 247 deletions

View file

@ -41,7 +41,7 @@ beforeEach(() => {
};
});
const executor: ExecutorType = async (options) => {
const executor: ExecutorType<{}, {}, {}, void> = async (options) => {
return { status: 'ok', actionId: options.actionId };
};
@ -203,7 +203,9 @@ describe('isActionTypeEnabled', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'basic',
executor: async () => {},
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
};
beforeEach(() => {
@ -258,7 +260,9 @@ describe('ensureActionTypeEnabled', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'basic',
executor: async () => {},
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
};
beforeEach(() => {

View file

@ -8,9 +8,15 @@ import Boom from 'boom';
import { i18n } from '@kbn/i18n';
import { RunContext, TaskManagerSetupContract } from '../../task_manager/server';
import { ExecutorError, TaskRunnerFactory, ILicenseState } from './lib';
import { ActionType, PreConfiguredAction } from './types';
import { ActionType as CommonActionType } from '../common';
import { ActionsConfigurationUtilities } from './actions_config';
import {
ActionType,
PreConfiguredAction,
ActionTypeConfig,
ActionTypeSecrets,
ActionTypeParams,
} from './types';
export interface ActionTypeRegistryOpts {
taskManager: TaskManagerSetupContract;
@ -77,7 +83,12 @@ export class ActionTypeRegistry {
/**
* Registers an action type to the action type registry
*/
public register(actionType: ActionType) {
public register<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(actionType: ActionType<Config, Secrets, Params, ExecutorResultData>) {
if (this.has(actionType.id)) {
throw new Error(
i18n.translate(
@ -91,7 +102,7 @@ export class ActionTypeRegistry {
)
);
}
this.actionTypes.set(actionType.id, { ...actionType });
this.actionTypes.set(actionType.id, { ...actionType } as ActionType);
this.taskManager.registerTaskDefinitions({
[`actions:${actionType.id}`]: {
title: actionType.name,
@ -112,7 +123,12 @@ export class ActionTypeRegistry {
/**
* Returns an action type, throws if not registered
*/
public get(id: string): ActionType {
public get<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(id: string): ActionType<Config, Secrets, Params, ExecutorResultData> {
if (!this.has(id)) {
throw Boom.badRequest(
i18n.translate('xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage', {
@ -123,7 +139,7 @@ export class ActionTypeRegistry {
})
);
}
return this.actionTypes.get(id)!;
return this.actionTypes.get(id)! as ActionType<Config, Secrets, Params, ExecutorResultData>;
}
/**

View file

@ -39,7 +39,7 @@ let actionsClient: ActionsClient;
let mockedLicenseState: jest.Mocked<ILicenseState>;
let actionTypeRegistry: ActionTypeRegistry;
let actionTypeRegistryParams: ActionTypeRegistryOpts;
const executor: ExecutorType = async (options) => {
const executor: ExecutorType<{}, {}, {}, void> = async (options) => {
return { status: 'ok', actionId: options.actionId };
};

View file

@ -298,7 +298,7 @@ export class ActionsClient {
public async execute({
actionId,
params,
}: Omit<ExecuteOptions, 'request'>): Promise<ActionTypeExecutorResult> {
}: Omit<ExecuteOptions, 'request'>): Promise<ActionTypeExecutorResult<unknown>> {
await this.authorization.ensureAuthorized('execute');
return this.actionExecutor.execute({ actionId, params, request: this.request });
}

View file

@ -10,6 +10,10 @@ import { schema } from '@kbn/config-schema';
import { ActionTypeExecutorOptions, ActionTypeExecutorResult, ActionType } from '../../types';
import { ExecutorParamsSchema } from './schema';
import {
ExternalIncidentServiceConfiguration,
ExternalIncidentServiceSecretConfiguration,
} from './types';
import {
CreateExternalServiceArgs,
@ -23,6 +27,7 @@ import {
TransformFieldsArgs,
Comment,
ExecutorSubActionPushParams,
PushToServiceResponse,
} from './types';
import { transformers } from './transformers';
@ -63,14 +68,17 @@ export const createConnectorExecutor = ({
api,
createExternalService,
}: CreateExternalServiceBasicArgs) => async (
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> => {
execOptions: ActionTypeExecutorOptions<
ExternalIncidentServiceConfiguration,
ExternalIncidentServiceSecretConfiguration,
ExecutorParams
>
): Promise<ActionTypeExecutorResult<PushToServiceResponse | {}>> => {
const { actionId, config, params, secrets } = execOptions;
const { subAction, subActionParams } = params as ExecutorParams;
const { subAction, subActionParams } = params;
let data = {};
const res: Pick<ActionTypeExecutorResult, 'status'> &
Pick<ActionTypeExecutorResult, 'actionId'> = {
const res: ActionTypeExecutorResult<void> = {
status: 'ok',
actionId,
};

View file

@ -10,7 +10,6 @@ jest.mock('./lib/send_email', () => ({
import { Logger } from '../../../../../src/core/server';
import { ActionType, ActionTypeExecutorOptions } from '../types';
import { actionsConfigMock } from '../actions_config.mock';
import { validateConfig, validateSecrets, validateParams } from '../lib';
import { createActionTypeRegistry } from './index.test';
@ -21,6 +20,8 @@ import {
ActionTypeConfigType,
ActionTypeSecretsType,
getActionType,
EmailActionType,
EmailActionTypeExecutorOptions,
} from './email';
const sendEmailMock = sendEmail as jest.Mock;
@ -29,13 +30,17 @@ const ACTION_TYPE_ID = '.email';
const services = actionsMock.createServices();
let actionType: ActionType;
let actionType: EmailActionType;
let mockedLogger: jest.Mocked<Logger>;
beforeEach(() => {
jest.resetAllMocks();
const { actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get(ACTION_TYPE_ID);
actionType = actionTypeRegistry.get<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType
>(ACTION_TYPE_ID);
});
describe('actionTypeRegistry.get() works', () => {
@ -242,7 +247,7 @@ describe('execute()', () => {
};
const actionId = 'some-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: EmailActionTypeExecutorOptions = {
actionId,
config,
params,
@ -306,7 +311,7 @@ describe('execute()', () => {
};
const actionId = 'some-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: EmailActionTypeExecutorOptions = {
actionId,
config,
params,
@ -363,7 +368,7 @@ describe('execute()', () => {
};
const actionId = 'some-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: EmailActionTypeExecutorOptions = {
actionId,
config,
params,

View file

@ -15,6 +15,18 @@ import { Logger } from '../../../../../src/core/server';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
import { ActionsConfigurationUtilities } from '../actions_config';
export type EmailActionType = ActionType<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType,
unknown
>;
export type EmailActionTypeExecutorOptions = ActionTypeExecutorOptions<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType
>;
// config definition
export type ActionTypeConfigType = TypeOf<typeof ConfigSchema>;
@ -30,10 +42,9 @@ const ConfigSchema = schema.object(ConfigSchemaProps);
function validateConfig(
configurationUtilities: ActionsConfigurationUtilities,
configObject: unknown
configObject: ActionTypeConfigType
): string | void {
// avoids circular reference ...
const config = configObject as ActionTypeConfigType;
const config = configObject;
// 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.
@ -113,7 +124,7 @@ interface GetActionTypeParams {
}
// action type definition
export function getActionType(params: GetActionTypeParams): ActionType {
export function getActionType(params: GetActionTypeParams): EmailActionType {
const { logger, configurationUtilities } = params;
return {
id: '.email',
@ -136,12 +147,12 @@ export function getActionType(params: GetActionTypeParams): ActionType {
async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: EmailActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
const config = execOptions.config as ActionTypeConfigType;
const secrets = execOptions.secrets as ActionTypeSecretsType;
const params = execOptions.params as ActionParamsType;
const config = execOptions.config;
const secrets = execOptions.secrets;
const params = execOptions.params;
const transport: Transport = {};

View file

@ -8,21 +8,25 @@ jest.mock('./lib/send_email', () => ({
sendEmail: jest.fn(),
}));
import { ActionType, ActionTypeExecutorOptions } from '../types';
import { validateConfig, validateParams } from '../lib';
import { createActionTypeRegistry } from './index.test';
import { ActionParamsType, ActionTypeConfigType } from './es_index';
import { actionsMock } from '../mocks';
import {
ActionParamsType,
ActionTypeConfigType,
ESIndexActionType,
ESIndexActionTypeExecutorOptions,
} from './es_index';
const ACTION_TYPE_ID = '.index';
const services = actionsMock.createServices();
let actionType: ActionType;
let actionType: ESIndexActionType;
beforeAll(() => {
const { actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get(ACTION_TYPE_ID);
actionType = actionTypeRegistry.get<ActionTypeConfigType, {}, ActionParamsType>(ACTION_TYPE_ID);
});
beforeEach(() => {
@ -144,12 +148,12 @@ describe('params validation', () => {
describe('execute()', () => {
test('ensure parameters are as expected', async () => {
const secrets = {};
let config: Partial<ActionTypeConfigType>;
let config: ActionTypeConfigType;
let params: ActionParamsType;
let executorOptions: ActionTypeExecutorOptions;
let executorOptions: ESIndexActionTypeExecutorOptions;
// minimal params
config = { index: 'index-value', refresh: false };
config = { index: 'index-value', refresh: false, executionTimeField: null };
params = {
documents: [{ jim: 'bob' }],
};
@ -215,7 +219,7 @@ describe('execute()', () => {
`);
// minimal params
config = { index: 'index-value', executionTimeField: undefined, refresh: false };
config = { index: 'index-value', executionTimeField: null, refresh: false };
params = {
documents: [{ jim: 'bob' }],
};
@ -245,7 +249,7 @@ describe('execute()', () => {
`);
// multiple documents
config = { index: 'index-value', executionTimeField: undefined, refresh: false };
config = { index: 'index-value', executionTimeField: null, refresh: false };
params = {
documents: [{ a: 1 }, { b: 2 }],
};

View file

@ -11,6 +11,13 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { Logger } from '../../../../../src/core/server';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
export type ESIndexActionType = ActionType<ActionTypeConfigType, {}, ActionParamsType, unknown>;
export type ESIndexActionTypeExecutorOptions = ActionTypeExecutorOptions<
ActionTypeConfigType,
{},
ActionParamsType
>;
// config definition
export type ActionTypeConfigType = TypeOf<typeof ConfigSchema>;
@ -33,7 +40,7 @@ const ParamsSchema = schema.object({
});
// action type definition
export function getActionType({ logger }: { logger: Logger }): ActionType {
export function getActionType({ logger }: { logger: Logger }): ESIndexActionType {
return {
id: '.index',
minimumLicenseRequired: 'basic',
@ -52,11 +59,11 @@ export function getActionType({ logger }: { logger: Logger }): ActionType {
async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: ESIndexActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
const config = execOptions.config as ActionTypeConfigType;
const params = execOptions.params as ActionParamsType;
const config = execOptions.config;
const params = execOptions.params;
const services = execOptions.services;
const index = config.index;

View file

@ -8,14 +8,21 @@ jest.mock('./lib/post_pagerduty', () => ({
postPagerduty: jest.fn(),
}));
import { getActionType } from './pagerduty';
import { ActionType, Services, ActionTypeExecutorOptions } from '../types';
import { Services } from '../types';
import { validateConfig, validateSecrets, validateParams } from '../lib';
import { postPagerduty } from './lib/post_pagerduty';
import { createActionTypeRegistry } from './index.test';
import { Logger } from '../../../../../src/core/server';
import { actionsConfigMock } from '../actions_config.mock';
import { actionsMock } from '../mocks';
import {
ActionParamsType,
ActionTypeConfigType,
ActionTypeSecretsType,
getActionType,
PagerDutyActionType,
PagerDutyActionTypeExecutorOptions,
} from './pagerduty';
const postPagerdutyMock = postPagerduty as jest.Mock;
@ -23,12 +30,16 @@ const ACTION_TYPE_ID = '.pagerduty';
const services: Services = actionsMock.createServices();
let actionType: ActionType;
let actionType: PagerDutyActionType;
let mockedLogger: jest.Mocked<Logger>;
beforeAll(() => {
const { logger, actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get(ACTION_TYPE_ID);
actionType = actionTypeRegistry.get<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType
>(ACTION_TYPE_ID);
mockedLogger = logger;
});
@ -167,7 +178,7 @@ describe('execute()', () => {
test('should succeed with minimal valid params', async () => {
const secrets = { routingKey: 'super-secret' };
const config = {};
const config = { apiUrl: null };
const params = {};
postPagerdutyMock.mockImplementation(() => {
@ -175,7 +186,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -219,7 +230,7 @@ describe('execute()', () => {
const config = {
apiUrl: 'the-api-url',
};
const params = {
const params: ActionParamsType = {
eventAction: 'trigger',
dedupKey: 'a-dedup-key',
summary: 'the summary',
@ -236,7 +247,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -284,7 +295,7 @@ describe('execute()', () => {
const config = {
apiUrl: 'the-api-url',
};
const params = {
const params: ActionParamsType = {
eventAction: 'acknowledge',
dedupKey: 'a-dedup-key',
summary: 'the summary',
@ -301,7 +312,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -340,7 +351,7 @@ describe('execute()', () => {
const config = {
apiUrl: 'the-api-url',
};
const params = {
const params: ActionParamsType = {
eventAction: 'resolve',
dedupKey: 'a-dedup-key',
summary: 'the summary',
@ -357,7 +368,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -390,7 +401,7 @@ describe('execute()', () => {
test('should fail when sendPagerdury throws', async () => {
const secrets = { routingKey: 'super-secret' };
const config = {};
const config = { apiUrl: null };
const params = {};
postPagerdutyMock.mockImplementation(() => {
@ -398,7 +409,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -418,7 +429,7 @@ describe('execute()', () => {
test('should fail when sendPagerdury returns 429', async () => {
const secrets = { routingKey: 'super-secret' };
const config = {};
const config = { apiUrl: null };
const params = {};
postPagerdutyMock.mockImplementation(() => {
@ -426,7 +437,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -446,7 +457,7 @@ describe('execute()', () => {
test('should fail when sendPagerdury returns 501', async () => {
const secrets = { routingKey: 'super-secret' };
const config = {};
const config = { apiUrl: null };
const params = {};
postPagerdutyMock.mockImplementation(() => {
@ -454,7 +465,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,
@ -474,7 +485,7 @@ describe('execute()', () => {
test('should fail when sendPagerdury returns 418', async () => {
const secrets = { routingKey: 'super-secret' };
const config = {};
const config = { apiUrl: null };
const params = {};
postPagerdutyMock.mockImplementation(() => {
@ -482,7 +493,7 @@ describe('execute()', () => {
});
const actionId = 'some-action-id';
const executorOptions: ActionTypeExecutorOptions = {
const executorOptions: PagerDutyActionTypeExecutorOptions = {
actionId,
config,
params,

View file

@ -16,6 +16,18 @@ import { ActionsConfigurationUtilities } from '../actions_config';
// https://v2.developer.pagerduty.com/docs/events-api-v2
const PAGER_DUTY_API_URL = 'https://events.pagerduty.com/v2/enqueue';
export type PagerDutyActionType = ActionType<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType,
unknown
>;
export type PagerDutyActionTypeExecutorOptions = ActionTypeExecutorOptions<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType
>;
// config definition
export type ActionTypeConfigType = TypeOf<typeof ConfigSchema>;
@ -100,7 +112,7 @@ export function getActionType({
}: {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
}): ActionType {
}): PagerDutyActionType {
return {
id: '.pagerduty',
minimumLicenseRequired: 'gold',
@ -142,12 +154,12 @@ function getPagerDutyApiUrl(config: ActionTypeConfigType): string {
async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: PagerDutyActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
const config = execOptions.config as ActionTypeConfigType;
const secrets = execOptions.secrets as ActionTypeSecretsType;
const params = execOptions.params as ActionParamsType;
const config = execOptions.config;
const secrets = execOptions.secrets;
const params = execOptions.params;
const services = execOptions.services;
const apiUrl = getPagerDutyApiUrl(config);

View file

@ -4,20 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ActionType } from '../types';
import { validateParams } from '../lib';
import { Logger } from '../../../../../src/core/server';
import { createActionTypeRegistry } from './index.test';
import { actionsMock } from '../mocks';
import {
ActionParamsType,
ServerLogActionType,
ServerLogActionTypeExecutorOptions,
} from './server_log';
const ACTION_TYPE_ID = '.server-log';
let actionType: ActionType;
let actionType: ServerLogActionType;
let mockedLogger: jest.Mocked<Logger>;
beforeAll(() => {
const { logger, actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get(ACTION_TYPE_ID);
actionType = actionTypeRegistry.get<{}, {}, ActionParamsType>(ACTION_TYPE_ID);
mockedLogger = logger;
expect(actionType).toBeTruthy();
});
@ -88,13 +92,14 @@ describe('validateParams()', () => {
describe('execute()', () => {
test('calls the executor with proper params', async () => {
const actionId = 'some-id';
await actionType.executor({
const executorOptions: ServerLogActionTypeExecutorOptions = {
actionId,
services: actionsMock.createServices(),
params: { message: 'message text here', level: 'info' },
config: {},
secrets: {},
});
};
await actionType.executor(executorOptions);
expect(mockedLogger.info).toHaveBeenCalledWith('Server log: message text here');
});
});

View file

@ -12,6 +12,13 @@ import { Logger } from '../../../../../src/core/server';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
import { withoutControlCharacters } from './lib/string_utils';
export type ServerLogActionType = ActionType<{}, {}, ActionParamsType>;
export type ServerLogActionTypeExecutorOptions = ActionTypeExecutorOptions<
{},
{},
ActionParamsType
>;
// params definition
export type ActionParamsType = TypeOf<typeof ParamsSchema>;
@ -32,7 +39,7 @@ const ParamsSchema = schema.object({
});
// action type definition
export function getActionType({ logger }: { logger: Logger }): ActionType {
export function getActionType({ logger }: { logger: Logger }): ServerLogActionType {
return {
id: '.server-log',
minimumLicenseRequired: 'basic',
@ -50,10 +57,10 @@ export function getActionType({ logger }: { logger: Logger }): ActionType {
async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: ServerLogActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<void>> {
const actionId = execOptions.actionId;
const params = execOptions.params as ActionParamsType;
const params = execOptions.params;
const sanitizedMessage = withoutControlCharacters(params.message);
try {

View file

@ -17,9 +17,14 @@ import { ActionsConfigurationUtilities } from '../../actions_config';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types';
import { createExternalService } from './service';
import { api } from './api';
import { ExecutorParams, ExecutorSubActionPushParams } from './types';
import * as i18n from './translations';
import { Logger } from '../../../../../../src/core/server';
import {
ExecutorParams,
ExecutorSubActionPushParams,
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
} from './types';
// TODO: to remove, need to support Case
import { buildMap, mapParams } from '../case/utils';
@ -31,7 +36,14 @@ interface GetActionTypeParams {
}
// action type definition
export function getActionType(params: GetActionTypeParams): ActionType {
export function getActionType(
params: GetActionTypeParams
): ActionType<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
> {
const { logger, configurationUtilities } = params;
return {
id: '.servicenow',
@ -54,10 +66,14 @@ export function getActionType(params: GetActionTypeParams): ActionType {
async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: ActionTypeExecutorOptions<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams
>
): Promise<ActionTypeExecutorResult<PushToServiceResponse | {}>> {
const { actionId, config, params, secrets } = execOptions;
const { subAction, subActionParams } = params as ExecutorParams;
const { subAction, subActionParams } = params;
let data: PushToServiceResponse | null = null;
const externalService = createExternalService({
@ -81,9 +97,8 @@ async function executor(
const pushToServiceParams = subActionParams as ExecutorSubActionPushParams;
const { comments, externalId, ...restParams } = pushToServiceParams;
const mapping = config.incidentConfiguration
? buildMap(config.incidentConfiguration.mapping)
: null;
const incidentConfiguration = config.incidentConfiguration;
const mapping = incidentConfiguration ? buildMap(incidentConfiguration.mapping) : null;
const externalObject =
config.incidentConfiguration && mapping ? mapParams(restParams, mapping) : {};

View file

@ -4,14 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
ActionType,
Services,
ActionTypeExecutorOptions,
ActionTypeExecutorResult,
} from '../types';
import { Services, ActionTypeExecutorResult } from '../types';
import { validateParams, validateSecrets } from '../lib';
import { getActionType } from './slack';
import { getActionType, SlackActionType, SlackActionTypeExecutorOptions } from './slack';
import { actionsConfigMock } from '../actions_config.mock';
import { actionsMock } from '../mocks';
@ -19,11 +14,13 @@ const ACTION_TYPE_ID = '.slack';
const services: Services = actionsMock.createServices();
let actionType: ActionType;
let actionType: SlackActionType;
beforeAll(() => {
actionType = getActionType({
async executor() {},
async executor(options) {
return { status: 'ok', actionId: options.actionId };
},
configurationUtilities: actionsConfigMock.create(),
});
});
@ -119,7 +116,7 @@ describe('validateActionTypeSecrets()', () => {
describe('execute()', () => {
beforeAll(() => {
async function mockSlackExecutor(options: ActionTypeExecutorOptions) {
async function mockSlackExecutor(options: SlackActionTypeExecutorOptions) {
const { params } = options;
const { message } = params;
if (message == null) throw new Error('message property required in parameter');
@ -134,7 +131,7 @@ describe('execute()', () => {
text: `slack mockExecutor success: ${message}`,
actionId: '',
status: 'ok',
} as ActionTypeExecutorResult;
} as ActionTypeExecutorResult<void>;
}
actionType = getActionType({

View file

@ -21,6 +21,13 @@ import {
} from '../types';
import { ActionsConfigurationUtilities } from '../actions_config';
export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>;
export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions<
{},
ActionTypeSecretsType,
ActionParamsType
>;
// secrets definition
export type ActionTypeSecretsType = TypeOf<typeof SecretsSchema>;
@ -46,8 +53,8 @@ export function getActionType({
executor = slackExecutor,
}: {
configurationUtilities: ActionsConfigurationUtilities;
executor?: ExecutorType;
}): ActionType {
executor?: ExecutorType<{}, ActionTypeSecretsType, ActionParamsType, unknown>;
}): SlackActionType {
return {
id: '.slack',
minimumLicenseRequired: 'gold',
@ -92,11 +99,11 @@ function valdiateActionTypeConfig(
// action executor
async function slackExecutor(
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: SlackActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
const secrets = execOptions.secrets as ActionTypeSecretsType;
const params = execOptions.params as ActionParamsType;
const secrets = execOptions.secrets;
const params = execOptions.params;
let result: IncomingWebhookResult;
const { webhookUrl } = secrets;
@ -156,18 +163,21 @@ async function slackExecutor(
return successResult(actionId, result);
}
function successResult(actionId: string, data: unknown): ActionTypeExecutorResult {
function successResult(actionId: string, data: unknown): ActionTypeExecutorResult<unknown> {
return { status: 'ok', data, actionId };
}
function errorResult(actionId: string, message: string): ActionTypeExecutorResult {
function errorResult(actionId: string, message: string): ActionTypeExecutorResult<void> {
return {
status: 'error',
message,
actionId,
};
}
function serviceErrorResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult {
function serviceErrorResult(
actionId: string,
serviceMessage: string
): ActionTypeExecutorResult<void> {
const errMessage = i18n.translate('xpack.actions.builtin.slack.errorPostingErrorMessage', {
defaultMessage: 'error posting slack message',
});
@ -179,7 +189,7 @@ function serviceErrorResult(actionId: string, serviceMessage: string): ActionTyp
};
}
function retryResult(actionId: string, message: string): ActionTypeExecutorResult {
function retryResult(actionId: string, message: string): ActionTypeExecutorResult<void> {
const errMessage = i18n.translate(
'xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage',
{
@ -198,7 +208,7 @@ function retryResultSeconds(
actionId: string,
message: string,
retryAfter: number
): ActionTypeExecutorResult {
): ActionTypeExecutorResult<void> {
const retryEpoch = Date.now() + retryAfter * 1000;
const retry = new Date(retryEpoch);
const retryString = retry.toISOString();

View file

@ -8,14 +8,21 @@ jest.mock('axios', () => ({
request: jest.fn(),
}));
import { getActionType } from './webhook';
import { ActionType, Services } from '../types';
import { Services } from '../types';
import { validateConfig, validateSecrets, validateParams } from '../lib';
import { actionsConfigMock } from '../actions_config.mock';
import { createActionTypeRegistry } from './index.test';
import { Logger } from '../../../../../src/core/server';
import { actionsMock } from '../mocks';
import axios from 'axios';
import {
ActionParamsType,
ActionTypeConfigType,
ActionTypeSecretsType,
getActionType,
WebhookActionType,
WebhookMethods,
} from './webhook';
const axiosRequestMock = axios.request as jest.Mock;
@ -23,12 +30,16 @@ const ACTION_TYPE_ID = '.webhook';
const services: Services = actionsMock.createServices();
let actionType: ActionType;
let actionType: WebhookActionType;
let mockedLogger: jest.Mocked<Logger>;
beforeAll(() => {
const { logger, actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get(ACTION_TYPE_ID);
actionType = actionTypeRegistry.get<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType
>(ACTION_TYPE_ID);
mockedLogger = logger;
});
@ -235,16 +246,17 @@ describe('execute()', () => {
});
test('execute with username/password sends request with basic auth', async () => {
const config: ActionTypeConfigType = {
url: 'https://abc.def/my-webhook',
method: WebhookMethods.POST,
headers: {
aheader: 'a value',
},
};
await actionType.executor({
actionId: 'some-id',
services,
config: {
url: 'https://abc.def/my-webhook',
method: 'post',
headers: {
aheader: 'a value',
},
},
config,
secrets: { user: 'abc', password: '123' },
params: { body: 'some data' },
});
@ -266,17 +278,19 @@ describe('execute()', () => {
});
test('execute without username/password sends request without basic auth', async () => {
const config: ActionTypeConfigType = {
url: 'https://abc.def/my-webhook',
method: WebhookMethods.POST,
headers: {
aheader: 'a value',
},
};
const secrets: ActionTypeSecretsType = { user: null, password: null };
await actionType.executor({
actionId: 'some-id',
services,
config: {
url: 'https://abc.def/my-webhook',
method: 'post',
headers: {
aheader: 'a value',
},
},
secrets: {},
config,
secrets,
params: { body: 'some data' },
});

View file

@ -17,11 +17,23 @@ import { ActionsConfigurationUtilities } from '../actions_config';
import { Logger } from '../../../../../src/core/server';
// config definition
enum WebhookMethods {
export enum WebhookMethods {
POST = 'post',
PUT = 'put',
}
export type WebhookActionType = ActionType<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType,
unknown
>;
export type WebhookActionTypeExecutorOptions = ActionTypeExecutorOptions<
ActionTypeConfigType,
ActionTypeSecretsType,
ActionParamsType
>;
const HeadersSchema = schema.recordOf(schema.string(), schema.string());
const configSchemaProps = {
url: schema.string(),
@ -31,7 +43,7 @@ const configSchemaProps = {
headers: nullableType(HeadersSchema),
};
const ConfigSchema = schema.object(configSchemaProps);
type ActionTypeConfigType = TypeOf<typeof ConfigSchema>;
export type ActionTypeConfigType = TypeOf<typeof ConfigSchema>;
// secrets definition
export type ActionTypeSecretsType = TypeOf<typeof SecretsSchema>;
@ -51,7 +63,7 @@ const SecretsSchema = schema.object(secretSchemaProps, {
});
// params definition
type ActionParamsType = TypeOf<typeof ParamsSchema>;
export type ActionParamsType = TypeOf<typeof ParamsSchema>;
const ParamsSchema = schema.object({
body: schema.maybe(schema.string()),
});
@ -63,7 +75,7 @@ export function getActionType({
}: {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
}): ActionType {
}): WebhookActionType {
return {
id: '.webhook',
minimumLicenseRequired: 'gold',
@ -112,13 +124,13 @@ function validateActionTypeConfig(
// action executor
export async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult> {
execOptions: WebhookActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
const { method, url, headers = {} } = execOptions.config as ActionTypeConfigType;
const { body: data } = execOptions.params as ActionParamsType;
const { method, url, headers = {} } = execOptions.config;
const { body: data } = execOptions.params;
const secrets: ActionTypeSecretsType = execOptions.secrets as ActionTypeSecretsType;
const secrets: ActionTypeSecretsType = execOptions.secrets;
const basicAuth =
isString(secrets.user) && isString(secrets.password)
? { auth: { username: secrets.user, password: secrets.password } }
@ -172,11 +184,14 @@ export async function executor(
}
// Action Executor Result w/ internationalisation
function successResult(actionId: string, data: unknown): ActionTypeExecutorResult {
function successResult(actionId: string, data: unknown): ActionTypeExecutorResult<unknown> {
return { status: 'ok', data, actionId };
}
function errorResultInvalid(actionId: string, serviceMessage: string): ActionTypeExecutorResult {
function errorResultInvalid(
actionId: string,
serviceMessage: string
): ActionTypeExecutorResult<void> {
const errMessage = i18n.translate('xpack.actions.builtin.webhook.invalidResponseErrorMessage', {
defaultMessage: 'error calling webhook, invalid response',
});
@ -188,7 +203,7 @@ function errorResultInvalid(actionId: string, serviceMessage: string): ActionTyp
};
}
function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult {
function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult<void> {
const errMessage = i18n.translate('xpack.actions.builtin.webhook.unreachableErrorMessage', {
defaultMessage: 'error calling webhook, unexpected error',
});
@ -199,7 +214,7 @@ function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult
};
}
function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult {
function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult<void> {
const errMessage = i18n.translate(
'xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage',
{
@ -220,7 +235,7 @@ function retryResultSeconds(
serviceMessage: string,
retryAfter: number
): ActionTypeExecutorResult {
): ActionTypeExecutorResult<void> {
const retryEpoch = Date.now() + retryAfter * 1000;
const retry = new Date(retryEpoch);
const retryString = retry.toISOString();

View file

@ -59,7 +59,7 @@ export class ActionExecutor {
actionId,
params,
request,
}: ExecuteOptions): Promise<ActionTypeExecutorResult> {
}: ExecuteOptions): Promise<ActionTypeExecutorResult<unknown>> {
if (!this.isInitialized) {
throw new Error('ActionExecutor not initialized');
}
@ -125,7 +125,7 @@ export class ActionExecutor {
};
eventLogger.startTiming(event);
let rawResult: ActionTypeExecutorResult | null | undefined | void;
let rawResult: ActionTypeExecutorResult<unknown>;
try {
rawResult = await actionType.executor({
actionId,
@ -173,7 +173,7 @@ export class ActionExecutor {
}
}
function actionErrorToMessage(result: ActionTypeExecutorResult): string {
function actionErrorToMessage(result: ActionTypeExecutorResult<unknown>): string {
let message = result.message || 'unknown error running action';
if (result.serviceMessage) {

View file

@ -59,7 +59,9 @@ describe('isLicenseValidForActionType', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'gold',
executor: async () => {},
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
};
beforeEach(() => {
@ -120,7 +122,9 @@ describe('ensureLicenseForActionType()', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'gold',
executor: async () => {},
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
};
beforeEach(() => {

View file

@ -94,7 +94,7 @@ export class TaskRunnerFactory {
},
} as unknown) as KibanaRequest;
let executorResult: ActionTypeExecutorResult;
let executorResult: ActionTypeExecutorResult<unknown>;
try {
executorResult = await actionExecutor.execute({
params,

View file

@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { validateParams, validateConfig, validateSecrets } from './validate_with_schema';
import { ActionType, ExecutorType } from '../types';
const executor: ExecutorType = async (options) => {
const executor: ExecutorType<{}, {}, {}, void> = async (options) => {
return { status: 'ok', actionId: options.actionId };
};

View file

@ -5,24 +5,44 @@
*/
import Boom from 'boom';
import { ActionType } from '../types';
import { ActionType, ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types';
export function validateParams(actionType: ActionType, value: unknown) {
export function validateParams<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(actionType: ActionType<Config, Secrets, Params, ExecutorResultData>, value: unknown) {
return validateWithSchema(actionType, 'params', value);
}
export function validateConfig(actionType: ActionType, value: unknown) {
export function validateConfig<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(actionType: ActionType<Config, Secrets, Params, ExecutorResultData>, value: unknown) {
return validateWithSchema(actionType, 'config', value);
}
export function validateSecrets(actionType: ActionType, value: unknown) {
export function validateSecrets<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(actionType: ActionType<Config, Secrets, Params, ExecutorResultData>, value: unknown) {
return validateWithSchema(actionType, 'secrets', value);
}
type ValidKeys = 'params' | 'config' | 'secrets';
function validateWithSchema(
actionType: ActionType,
function validateWithSchema<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>,
key: ValidKeys,
value: unknown
): Record<string, unknown> {

View file

@ -125,7 +125,9 @@ describe('Actions Plugin', () => {
id: 'test',
name: 'test',
minimumLicenseRequired: 'basic',
async executor() {},
async executor(options) {
return { status: 'ok', actionId: options.actionId };
},
};
beforeEach(async () => {

View file

@ -33,13 +33,20 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../features/serve
import { SecurityPluginSetup } from '../../security/server';
import { ActionsConfig } from './config';
import { Services, ActionType, PreConfiguredAction } from './types';
import { ActionExecutor, TaskRunnerFactory, LicenseState, ILicenseState } from './lib';
import { ActionsClient } from './actions_client';
import { ActionTypeRegistry } from './action_type_registry';
import { createExecutionEnqueuerFunction } from './create_execute_function';
import { registerBuiltInActionTypes } from './builtin_action_types';
import { registerActionsUsageCollector } from './usage';
import {
Services,
ActionType,
PreConfiguredAction,
ActionTypeConfig,
ActionTypeSecrets,
ActionTypeParams,
} from './types';
import { getActionsConfigurationUtilities } from './actions_config';
@ -70,7 +77,13 @@ export const EVENT_LOG_ACTIONS = {
};
export interface PluginSetupContract {
registerType: (actionType: ActionType) => void;
registerType<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams
>(
actionType: ActionType<Config, Secrets, Params>
): void;
}
export interface PluginStartContract {
@ -219,7 +232,13 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
executeActionRoute(router, this.licenseState);
return {
registerType: (actionType: ActionType) => {
registerType: <
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams
>(
actionType: ActionType<Config, Secrets, Params>
) => {
if (!(actionType.minimumLicenseRequired in LICENSE_TYPE)) {
throw new Error(`"${actionType.minimumLicenseRequired}" is not a valid license type`);
}

View file

@ -71,7 +71,9 @@ describe('executeActionRoute', () => {
const router = httpServiceMock.createRouter();
const actionsClient = actionsClientMock.create();
actionsClient.execute.mockResolvedValueOnce((null as unknown) as ActionTypeExecutorResult);
actionsClient.execute.mockResolvedValueOnce(
(null as unknown) as ActionTypeExecutorResult<void>
);
const [context, req, res] = mockHandlerArguments(
{ actionsClient },

View file

@ -48,7 +48,7 @@ export const executeActionRoute = (router: IRouter, licenseState: ILicenseState)
const { params } = req.body;
const { id } = req.params;
try {
const body: ActionTypeExecutorResult = await actionsClient.execute({
const body: ActionTypeExecutorResult<unknown> = await actionsClient.execute({
params,
actionId: id,
});

View file

@ -21,6 +21,9 @@ 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 type ActionTypeConfig = Record<string, unknown>;
export type ActionTypeSecrets = Record<string, unknown>;
export type ActionTypeParams = Record<string, unknown>;
export interface Services {
callCluster: ILegacyScopedClusterClient['callAsCurrentUser'];
@ -49,32 +52,27 @@ export interface ActionsConfigType {
}
// the parameters passed to an action type executor function
export interface ActionTypeExecutorOptions {
export interface ActionTypeExecutorOptions<Config, Secrets, Params> {
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>;
config: Config;
secrets: Secrets;
params: Params;
}
export interface ActionResult {
export interface ActionResult<Config extends ActionTypeConfig = ActionTypeConfig> {
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>;
config?: Config;
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>;
export interface PreConfiguredAction<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets
> extends ActionResult<Config> {
secrets: Secrets;
}
export interface FindActionResult extends ActionResult {
@ -82,38 +80,45 @@ export interface FindActionResult extends ActionResult {
}
// the result returned from an action type executor function
export interface ActionTypeExecutorResult {
export interface ActionTypeExecutorResult<Data> {
actionId: string;
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;
data?: Data;
retry?: null | boolean | Date;
}
// signature of the action type executor function
export type ExecutorType = (
options: ActionTypeExecutorOptions
) => Promise<ActionTypeExecutorResult | null | undefined | void>;
export type ExecutorType<Config, Secrets, Params, ResultData> = (
options: ActionTypeExecutorOptions<Config, Secrets, Params>
) => Promise<ActionTypeExecutorResult<ResultData>>;
interface ValidatorType {
validate(value: unknown): Record<string, unknown>;
interface ValidatorType<Type> {
validate(value: unknown): Type;
}
export type ActionTypeCreator = (config?: ActionsConfigType) => ActionType;
export interface ActionType {
export interface ActionValidationService {
isWhitelistedHostname(hostname: string): boolean;
isWhitelistedUri(uri: string): boolean;
}
export interface ActionType<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
> {
id: string;
name: string;
maxAttempts?: number;
minimumLicenseRequired: LicenseType;
validate?: {
params?: ValidatorType;
config?: ValidatorType;
secrets?: ValidatorType;
params?: ValidatorType<Params>;
config?: ValidatorType<Config>;
secrets?: ValidatorType<Secrets>;
};
executor: ExecutorType;
executor: ExecutorType<Config, Secrets, Params, ExecutorResultData>;
}
export interface RawAction extends SavedObjectAttributes {

View file

@ -241,16 +241,15 @@ export const pushToService = async (
casePushParams: ServiceConnectorCaseParams,
signal: AbortSignal
): Promise<ServiceConnectorCaseResponse> => {
const response = await KibanaServices.get().http.fetch<ActionTypeExecutorResult>(
`${ACTION_URL}/action/${connectorId}/_execute`,
{
method: 'POST',
body: JSON.stringify({
params: { subAction: 'pushToService', subActionParams: casePushParams },
}),
signal,
}
);
const response = await KibanaServices.get().http.fetch<
ActionTypeExecutorResult<ReturnType<typeof decodeServiceConnectorCaseResponse>>
>(`${ACTION_URL}/action/${connectorId}/_execute`, {
method: 'POST',
body: JSON.stringify({
params: { subAction: 'pushToService', subActionParams: casePushParams },
}),
signal,
});
if (response.status === 'error') {
throw new Error(response.serviceMessage ?? response.message ?? i18n.ERROR_PUSH_TO_SERVICE);

View file

@ -5,16 +5,14 @@
*/
import { CoreSetup } from 'src/core/server';
import { schema } from '@kbn/config-schema';
import { schema, TypeOf } from '@kbn/config-schema';
import { FixtureStartDeps, FixtureSetupDeps } from './plugin';
import { ActionType, ActionTypeExecutorOptions } from '../../../../../../../plugins/actions/server';
import { ActionType } from '../../../../../../../plugins/actions/server';
export function defineActionTypes(
core: CoreSetup<FixtureStartDeps>,
{ actions }: Pick<FixtureSetupDeps, 'actions'>
) {
const clusterClient = core.elasticsearch.legacy.client;
// Action types
const noopActionType: ActionType = {
id: 'test.noop',
@ -32,24 +30,39 @@ export function defineActionTypes(
throw new Error('this action is intended to fail');
},
};
const indexRecordActionType: ActionType = {
actions.registerType(noopActionType);
actions.registerType(throwActionType);
actions.registerType(getIndexRecordActionType());
actions.registerType(getFailingActionType());
actions.registerType(getRateLimitedActionType());
actions.registerType(getAuthorizationActionType(core));
}
function getIndexRecordActionType() {
const paramsSchema = schema.object({
index: schema.string(),
reference: schema.string(),
message: schema.string(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const configSchema = schema.object({
unencrypted: schema.string(),
});
type ConfigType = TypeOf<typeof configSchema>;
const secretsSchema = schema.object({
encrypted: schema.string(),
});
type SecretsType = TypeOf<typeof secretsSchema>;
const result: ActionType<ConfigType, SecretsType, ParamsType> = {
id: 'test.index-record',
name: 'Test: Index Record',
minimumLicenseRequired: 'gold',
validate: {
params: schema.object({
index: schema.string(),
reference: schema.string(),
message: schema.string(),
}),
config: schema.object({
unencrypted: schema.string(),
}),
secrets: schema.object({
encrypted: schema.string(),
}),
params: paramsSchema,
config: configSchema,
secrets: secretsSchema,
},
async executor({ config, secrets, params, services, actionId }: ActionTypeExecutorOptions) {
async executor({ config, secrets, params, services, actionId }) {
await services.callCluster('index', {
index: params.index,
refresh: 'wait_for',
@ -64,17 +77,23 @@ export function defineActionTypes(
return { status: 'ok', actionId };
},
};
const failingActionType: ActionType = {
return result;
}
function getFailingActionType() {
const paramsSchema = schema.object({
index: schema.string(),
reference: schema.string(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const result: ActionType<{}, {}, ParamsType> = {
id: 'test.failing',
name: 'Test: Failing',
minimumLicenseRequired: 'gold',
validate: {
params: schema.object({
index: schema.string(),
reference: schema.string(),
}),
params: paramsSchema,
},
async executor({ config, secrets, params, services }: ActionTypeExecutorOptions) {
async executor({ config, secrets, params, services }) {
await services.callCluster('index', {
index: params.index,
refresh: 'wait_for',
@ -89,19 +108,25 @@ export function defineActionTypes(
throw new Error(`expected failure for ${params.index} ${params.reference}`);
},
};
const rateLimitedActionType: ActionType = {
return result;
}
function getRateLimitedActionType() {
const paramsSchema = schema.object({
index: schema.string(),
reference: schema.string(),
retryAt: schema.number(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const result: ActionType<{}, {}, ParamsType> = {
id: 'test.rate-limit',
name: 'Test: Rate Limit',
minimumLicenseRequired: 'gold',
maxAttempts: 2,
validate: {
params: schema.object({
index: schema.string(),
reference: schema.string(),
retryAt: schema.number(),
}),
params: paramsSchema,
},
async executor({ config, params, services }: ActionTypeExecutorOptions) {
async executor({ config, params, services }) {
await services.callCluster('index', {
index: params.index,
refresh: 'wait_for',
@ -119,20 +144,27 @@ export function defineActionTypes(
};
},
};
const authorizationActionType: ActionType = {
return result;
}
function getAuthorizationActionType(core: CoreSetup<FixtureStartDeps>) {
const clusterClient = core.elasticsearch.legacy.client;
const paramsSchema = schema.object({
callClusterAuthorizationIndex: schema.string(),
savedObjectsClientType: schema.string(),
savedObjectsClientId: schema.string(),
index: schema.string(),
reference: schema.string(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const result: ActionType<{}, {}, ParamsType> = {
id: 'test.authorization',
name: 'Test: Authorization',
minimumLicenseRequired: 'gold',
validate: {
params: schema.object({
callClusterAuthorizationIndex: schema.string(),
savedObjectsClientType: schema.string(),
savedObjectsClientId: schema.string(),
index: schema.string(),
reference: schema.string(),
}),
params: paramsSchema,
},
async executor({ params, services, actionId }: ActionTypeExecutorOptions) {
async executor({ params, services, actionId }) {
// Call cluster
let callClusterSuccess = false;
let callClusterError;
@ -200,10 +232,5 @@ export function defineActionTypes(
};
},
};
actions.registerType(noopActionType);
actions.registerType(throwActionType);
actions.registerType(indexRecordActionType);
actions.registerType(failingActionType);
actions.registerType(rateLimitedActionType);
actions.registerType(authorizationActionType);
return result;
}