Skip to content

Commit

Permalink
[Alerting UI] Reduced triggersActionsUi bundle size by making all act…
Browse files Browse the repository at this point in the history
…ion types UI validation messages translations asynchronous. (elastic#100525)

* [Alerting UI] Reduced triggersActionsUi bundle size by making all connectors validation messages translations asyncronus.

* changed validation logic to be async

* fixed action form

* fixed tests

* fixed tests

* fixed validation usage in security

* fixed due to comments

* fixed due to comments

* added spinner for the validation awaiting

* fixed typechecks

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
YulNaumenko and kibanamachine committed Jun 3, 2021
1 parent 88615e4 commit ec71a44
Show file tree
Hide file tree
Showing 80 changed files with 1,149 additions and 843 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const validateParams = (actionParams: CaseActionParams) => {
validationResult.errors.caseId.push(i18n.CASE_CONNECTOR_CASE_REQUIRED);
}

return validationResult;
return Promise.resolve(validationResult);
};

export function getActionType(): ActionTypeModel {
Expand All @@ -34,7 +34,7 @@ export function getActionType(): ActionTypeModel {
iconClass: 'securityAnalyticsApp',
selectMessage: i18n.CASE_CONNECTOR_DESC,
actionTypeTitle: i18n.CASE_CONNECTOR_TITLE,
validateConnector: () => ({ config: { errors: {} }, secrets: { errors: {} } }),
validateConnector: () => Promise.resolve({ config: { errors: {} }, secrets: { errors: {} } }),
validateParams,
actionConnectorFields: null,
actionParamsFields: lazy(() => import('./alert_fields')),
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ describe('alert_form', () => {
id: 'alert-action-type',
iconClass: '',
selectMessage: '',
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
return {};
validateConnector: (): Promise<ConnectorValidationResult<unknown, unknown>> => {
return Promise.resolve({});
},
validateParams: (): GenericValidationResult<unknown> => {
validateParams: (): Promise<GenericValidationResult<unknown>> => {
const validationResult = { errors: {} };
return validationResult;
return Promise.resolve(validationResult);
},
actionConnectorFields: null,
actionParamsFields: mockedActionParamsFields,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
...(defaultValues ?? stepActionsDefaultValue),
kibanaSiemAppUrl: kibanaAbsoluteUrl,
};

const schema = useMemo(() => getSchema({ actionTypeRegistry }), [actionTypeRegistry]);
const { form } = useForm<ActionsStepRule>({
defaultValue: initialState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ describe('stepRuleActions schema', () => {
const actionTypeRegistry = actionTypeRegistryMock.create();

describe('validateSingleAction', () => {
it('should validate single action', () => {
it('should validate single action', async () => {
(isUuid as jest.Mock).mockReturnValue(true);
(validateActionParams as jest.Mock).mockReturnValue([]);
(validateMustache as jest.Mock).mockReturnValue([]);

expect(
validateSingleAction(
await validateSingleAction(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
Expand All @@ -33,12 +33,12 @@ describe('stepRuleActions schema', () => {
).toHaveLength(0);
});

it('should validate single action with invalid mustache template', () => {
it('should validate single action with invalid mustache template', async () => {
(isUuid as jest.Mock).mockReturnValue(true);
(validateActionParams as jest.Mock).mockReturnValue([]);
(validateMustache as jest.Mock).mockReturnValue(['Message is not valid mustache template']);

const errors = validateSingleAction(
const errors = await validateSingleAction(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
Expand All @@ -54,12 +54,12 @@ describe('stepRuleActions schema', () => {
expect(errors[0]).toEqual('Message is not valid mustache template');
});

it('should validate single action with incorrect id', () => {
it('should validate single action with incorrect id', async () => {
(isUuid as jest.Mock).mockReturnValue(false);
(validateMustache as jest.Mock).mockReturnValue([]);
(validateActionParams as jest.Mock).mockReturnValue([]);

const errors = validateSingleAction(
const errors = await validateSingleAction(
{
id: '823d4',
group: 'default',
Expand All @@ -74,10 +74,10 @@ describe('stepRuleActions schema', () => {
});

describe('validateRuleActionsField', () => {
it('should validate rule actions field', () => {
it('should validate rule actions field', async () => {
const validator = validateRuleActionsField(actionTypeRegistry);

const result = validator({
const result = await validator({
path: '',
value: [],
form: {} as FormHook,
Expand All @@ -88,11 +88,11 @@ describe('stepRuleActions schema', () => {
expect(result).toEqual(undefined);
});

it('should validate incorrect rule actions field', () => {
it('should validate incorrect rule actions field', async () => {
(getActionTypeName as jest.Mock).mockReturnValue('Slack');
const validator = validateRuleActionsField(actionTypeRegistry);

const result = validator({
const result = await validator({
path: '',
value: [
{
Expand All @@ -117,7 +117,7 @@ describe('stepRuleActions schema', () => {
});
});

it('should validate multiple incorrect rule actions field', () => {
it('should validate multiple incorrect rule actions field', async () => {
(isUuid as jest.Mock).mockReturnValueOnce(false);
(getActionTypeName as jest.Mock).mockReturnValueOnce('Slack');
(isUuid as jest.Mock).mockReturnValueOnce(true);
Expand All @@ -126,7 +126,7 @@ describe('stepRuleActions schema', () => {
(validateMustache as jest.Mock).mockReturnValue(['Component is not valid mustache template']);
const validator = validateRuleActionsField(actionTypeRegistry);

const result = validator({
const result = await validator({
path: '',
value: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,46 @@ import {
AlertAction,
ActionTypeRegistryContract,
} from '../../../../../../triggers_actions_ui/public';
import { FormSchema, ValidationFunc, ERROR_CODE } from '../../../../shared_imports';
import {
FormSchema,
ValidationFunc,
ERROR_CODE,
ValidationError,
} from '../../../../shared_imports';
import { ActionsStepRule } from '../../../pages/detection_engine/rules/types';
import * as I18n from './translations';
import { isUuid, getActionTypeName, validateMustache, validateActionParams } from './utils';

export const validateSingleAction = (
export const validateSingleAction = async (
actionItem: AlertAction,
actionTypeRegistry: ActionTypeRegistryContract
): string[] => {
): Promise<string[]> => {
if (!isUuid(actionItem.id)) {
return [I18n.NO_CONNECTOR_SELECTED];
}

const actionParamsErrors = validateActionParams(actionItem, actionTypeRegistry);
const actionParamsErrors = await validateActionParams(actionItem, actionTypeRegistry);
const mustacheErrors = validateMustache(actionItem.params);

return [...actionParamsErrors, ...mustacheErrors];
};

export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryContract) => (
export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryContract) => async (
...data: Parameters<ValidationFunc>
): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => {
): Promise<ValidationError<ERROR_CODE> | void | undefined> => {
const [{ value, path }] = data as [{ value: AlertAction[]; path: string }];

const errors = value.reduce((acc, actionItem) => {
const errorsArray = validateSingleAction(actionItem, actionTypeRegistry);
const errors = [];
for (const actionItem of value) {
const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry);

if (errorsArray.length) {
const actionTypeName = getActionTypeName(actionItem.actionTypeId);
const errorsListItems = errorsArray.map((error) => `* ${error}\n`);

return [...acc, `\n**${actionTypeName}:**\n${errorsListItems.join('')}`];
errors.push(`\n**${actionTypeName}:**\n${errorsListItems.join('')}`);
}

return acc;
}, [] as string[]);
}

if (errors.length) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ describe('stepRuleActions utils', () => {
actionTypeRegistry.get.mockReturnValue(actionMock);
});

it('should validate action params', () => {
it('should validate action params', async () => {
validateParamsMock.mockReturnValue({ errors: [] });

expect(
validateActionParams(
await validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
Expand All @@ -79,13 +79,13 @@ describe('stepRuleActions utils', () => {
).toHaveLength(0);
});

it('should validate incorrect action params', () => {
it('should validate incorrect action params', async () => {
validateParamsMock.mockReturnValue({
errors: ['Message is required'],
});

expect(
validateActionParams(
await validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
Expand All @@ -97,7 +97,7 @@ describe('stepRuleActions utils', () => {
).toHaveLength(1);
});

it('should validate incorrect action params and filter error objects', () => {
it('should validate incorrect action params and filter error objects', async () => {
validateParamsMock.mockReturnValue({
errors: [
{
Expand All @@ -107,7 +107,7 @@ describe('stepRuleActions utils', () => {
});

expect(
validateActionParams(
await validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
Expand All @@ -119,13 +119,13 @@ describe('stepRuleActions utils', () => {
).toHaveLength(0);
});

it('should validate incorrect action params and filter duplicated errors', () => {
it('should validate incorrect action params and filter duplicated errors', async () => {
validateParamsMock.mockReturnValue({
errors: ['Message is required', 'Message is required', 'Message is required'],
});

expect(
validateActionParams(
await validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export const validateMustache = (params: AlertAction['params']) => {
return errors;
};

export const validateActionParams = (
export const validateActionParams = async (
actionItem: AlertAction,
actionTypeRegistry: ActionTypeRegistryContract
): string[] => {
const actionErrors = actionTypeRegistry
): Promise<string[]> => {
const actionErrors = await actionTypeRegistry
.get(actionItem.actionTypeId)
?.validateParams(actionItem.params);

Expand Down
32 changes: 16 additions & 16 deletions x-pack/plugins/triggers_actions_ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -888,10 +888,10 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Send to Server log',
}
),
validateConnector: (): ValidationResult => {
validateConnector: (): Promise<ValidationResult> => {
return { errors: {} };
},
validateParams: (actionParams: ServerLogActionParams): ValidationResult => {
validateParams: (actionParams: ServerLogActionParams): Promise<ValidationResult> => {
// validation of action params implementation
},
actionConnectorFields: null,
Expand Down Expand Up @@ -929,10 +929,10 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Send to email',
}
),
validateConnector: (action: EmailActionConnector): ValidationResult => {
validateConnector: (action: EmailActionConnector): Promise<ValidationResult> => {
// validation of connector properties implementation
},
validateParams: (actionParams: EmailActionParams): ValidationResult => {
validateParams: (actionParams: EmailActionParams): Promise<ValidationResult> => {
// validation of action params implementation
},
actionConnectorFields: EmailActionConnectorFields,
Expand Down Expand Up @@ -967,10 +967,10 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Send to Slack',
}
),
validateConnector: (action: SlackActionConnector): ValidationResult => {
validateConnector: (action: SlackActionConnector): Promise<ValidationResult> => {
// validation of connector properties implementation
},
validateParams: (actionParams: SlackActionParams): ValidationResult => {
validateParams: (actionParams: SlackActionParams): Promise<ValidationResult> => {
// validation of action params implementation
},
actionConnectorFields: SlackActionFields,
Expand Down Expand Up @@ -1000,12 +1000,12 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Index data into Elasticsearch.',
}
),
validateConnector: (): ValidationResult => {
validateConnector: (): Promise<ValidationResult> => {
return { errors: {} };
},
actionConnectorFields: IndexActionConnectorFields,
actionParamsFields: IndexParamsFields,
validateParams: (): ValidationResult => {
validateParams: (): Promise<ValidationResult> => {
return { errors: {} };
},
};
Expand Down Expand Up @@ -1046,10 +1046,10 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Send a request to a web service.',
}
),
validateConnector: (action: WebhookActionConnector): ValidationResult => {
validateConnector: (action: WebhookActionConnector): Promise<ValidationResult> => {
// validation of connector properties implementation
},
validateParams: (actionParams: WebhookActionParams): ValidationResult => {
validateParams: (actionParams: WebhookActionParams): Promise<ValidationResult> => {
// validation of action params implementation
},
actionConnectorFields: WebhookActionConnectorFields,
Expand Down Expand Up @@ -1086,10 +1086,10 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Send to PagerDuty',
}
),
validateConnector: (action: PagerDutyActionConnector): ValidationResult => {
validateConnector: (action: PagerDutyActionConnector): Promise<ValidationResult> => {
// validation of connector properties implementation
},
validateParams: (actionParams: PagerDutyActionParams): ValidationResult => {
validateParams: (actionParams: PagerDutyActionParams): Promise<ValidationResult> => {
// validation of action params implementation
},
actionConnectorFields: PagerDutyActionConnectorFields,
Expand All @@ -1113,8 +1113,8 @@ Each action type should be defined as an `ActionTypeModel` object with the follo
iconClass: IconType;
selectMessage: string;
actionTypeTitle?: string;
validateConnector: (connector: any) => ValidationResult;
validateParams: (actionParams: any) => ValidationResult;
validateConnector: (connector: any) => Promise<ValidationResult>;
validateParams: (actionParams: any) => Promise<ValidationResult>;
actionConnectorFields: React.FunctionComponent<any> | null;
actionParamsFields: React.LazyExoticComponent<ComponentType<ActionParamsProps<ActionParams>>>;
```
Expand Down Expand Up @@ -1186,7 +1186,7 @@ export function getActionType(): ActionTypeModel {
defaultMessage: 'Example Action',
}
),
validateConnector: (action: ExampleActionConnector): ValidationResult => {
validateConnector: (action: ExampleActionConnector): Promise<ValidationResult> => {
const validationResult = { errors: {} };
const errors = {
someConnectorField: new Array<string>(),
Expand All @@ -1204,7 +1204,7 @@ export function getActionType(): ActionTypeModel {
}
return validationResult;
},
validateParams: (actionParams: ExampleActionParams): ValidationResult => {
validateParams: (actionParams: ExampleActionParams): Promise<ValidationResult> => {
const validationResult = { errors: {} };
const errors = {
message: new Array<string>(),
Expand Down
Loading

0 comments on commit ec71a44

Please sign in to comment.