Skip to content

Commit

Permalink
[Response Ops][Connectors] Allow connectors to explicitly register wh…
Browse files Browse the repository at this point in the history
…ich features they will be available in (#136331)

* Adding feature config to connector type and checking for validity on registration

* Updating actions APIs to filter by feature id if provided

* Fixing types

* Renaming allowedFeatureIds to featureConfig

* Adding siem feature config. Returning feature config to client. Showing availability in connector list

* Fixing types

* Showing availability in create connector flyout header

* Passing feature id into action form used by rule creators.

* Renaming some stuff

* Finishing triggers_actions_uis. Starting cases

* Fixing cases

* fixing types

* Fixing types and adding uptime feature

* Cleanup

* fixing tests

* Updating README

* Filtering action type filter on rule list

* Update x-pack/plugins/actions/common/connector_feature_config.ts

Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co>

* Fixing tests

* Renaming featureConfig to supportedFeatureIds

* PR feedback

* fixing i18n

* Updating docs

Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 25, 2022
1 parent 676be86 commit 3c24511
Show file tree
Hide file tree
Showing 86 changed files with 1,159 additions and 516 deletions.
13 changes: 11 additions & 2 deletions docs/api/actions-and-connectors/list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ run this API.
`space_id`::
(Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used.

[[list-connector-types-api-query-params]]
=== {api-query-parms-title}

`feature_id`::
(Optional, string) Filters list of connector types to those that support the feature id.

[[list-connector-types-api-codes]]
==== {api-response-codes-title}

Expand All @@ -52,15 +58,17 @@ The API returns the following:
"minimum_license_required": "gold", <3>
"enabled": false, <4>
"enabled_in_config": true, <5>
"enabled_in_license": true <6>
"enabled_in_license": true, <6>
"supported_feature_ids": ["alerting"] <7>
},
{
"id": ".index",
"name": "Index",
"minimum_license_required": "basic",
"enabled": true,
"enabled_in_config": true,
"enabled_in_license": true
"enabled_in_license": true,
"supported_feature_ids": ["alerting"]
},
...
]
Expand All @@ -71,3 +79,4 @@ The API returns the following:
<4> `enabled` - Specifies if the connector type is enabled or disabled in {kib}.
<5> `enabled_in_config` - Specifies if the connector type is enabled or enabled in the {kib} `.yml` file.
<6> `enabled_in_license` - Specifies if the connector type is enabled or disabled in the license.
<7> `supported_feature_ids` - Specifies which Kibana features this connector type supports.
1 change: 1 addition & 0 deletions x-pack/plugins/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ The following table describes the properties of the `options` object.
| name | A user-friendly name for the action type. These will be displayed in dropdowns when chosing action types. | string |
| maxAttempts | The maximum number of times this action will attempt to run when scheduled. | number |
| minimumLicenseRequired | The license required to use the action type. | string |
| supportedFeatureIds | List of IDs of the features that this action type is available in. Allowed values are `alerting`, `siem`, `uptime`, `cases`. See `x-pack/plugins/actions/common/connector_feature_config.ts` for the most up to date list. | string[] |
| validate.params | When developing an action type, it needs to accept parameters to know what to do with the action. (Example `to`, `from`, `subject`, `body` of an email). See the current built-in email action type for an example of the state-of-the-art validation. <p>Technically, the value of this property should have a property named `validate()` which is a function that takes a params object to validate and returns a sanitized version of that object to pass to the execution function. Validation errors should be thrown from the `validate()` function and will be available as an error message | schema / validation function |
| validate.config | Similar to params, a config may be required when creating an action (for example `host` and `port` for an email server). | schema / validation function |
| validate.secrets | Similar to params, a secrets object may be required when creating an action (for example `user` and `password` for an email server). | schema / validation function |
Expand Down
37 changes: 37 additions & 0 deletions x-pack/plugins/actions/common/connector_feature_config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { areValidFeatures, getConnectorFeatureName } from './connector_feature_config';

describe('areValidFeatures', () => {
it('returns true when all inputs are valid features', () => {
expect(areValidFeatures(['alerting', 'cases'])).toBeTruthy();
});

it('returns true when only one input and it is a valid feature', () => {
expect(areValidFeatures(['alerting'])).toBeTruthy();
expect(areValidFeatures(['cases'])).toBeTruthy();
});

it('returns false when one item in input is invalid', () => {
expect(areValidFeatures(['alerting', 'nope'])).toBeFalsy();
});

it('returns false when all items in input are invalid', () => {
expect(areValidFeatures(['alerts', 'nope'])).toBeFalsy();
});
});

describe('getConnectorFeatureName', () => {
it('returns the feature name for valid feature ids', () => {
expect(getConnectorFeatureName('siem')).toEqual('Security Solution');
});

it('returns the id for invalid feature ids', () => {
expect(getConnectorFeatureName('foo')).toEqual('foo');
});
});
73 changes: 73 additions & 0 deletions x-pack/plugins/actions/common/connector_feature_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';

interface ConnectorFeatureConfig {
/**
* Unique identifier for this feature.
*/
id: string;

/**
* Display name for this feature.
* This will be displayed to end-users, so a translatable string is advised for i18n.
*/
name: string;
}

export const AlertingConnectorFeatureId = 'alerting';
export const CasesConnectorFeatureId = 'cases';
export const UptimeConnectorFeatureId = 'uptime';
export const SecurityConnectorFeatureId = 'siem';

export const AlertingConnectorFeature: ConnectorFeatureConfig = {
id: AlertingConnectorFeatureId,
name: i18n.translate('xpack.actions.availableConnectorFeatures.alerting', {
defaultMessage: 'Alerting',
}),
};

export const CasesConnectorFeature: ConnectorFeatureConfig = {
id: CasesConnectorFeatureId,
name: i18n.translate('xpack.actions.availableConnectorFeatures.cases', {
defaultMessage: 'Cases',
}),
};

export const UptimeConnectorFeature: ConnectorFeatureConfig = {
id: UptimeConnectorFeatureId,
name: i18n.translate('xpack.actions.availableConnectorFeatures.uptime', {
defaultMessage: 'Uptime',
}),
};

export const SecuritySolutionFeature: ConnectorFeatureConfig = {
id: SecurityConnectorFeatureId,
name: i18n.translate('xpack.actions.availableConnectorFeatures.securitySolution', {
defaultMessage: 'Security Solution',
}),
};

const AllAvailableConnectorFeatures: ConnectorFeatureConfig[] = [
AlertingConnectorFeature,
CasesConnectorFeature,
UptimeConnectorFeature,
SecuritySolutionFeature,
];

export function areValidFeatures(ids: string[]) {
return ids.every(
(id: string) =>
!!AllAvailableConnectorFeatures.find((config: ConnectorFeatureConfig) => config.id === id)
);
}

export function getConnectorFeatureName(id: string) {
const featureConfig = AllAvailableConnectorFeatures.find((config) => config.id === id);
return featureConfig ? featureConfig.name : id;
}
1 change: 1 addition & 0 deletions x-pack/plugins/actions/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './rewrite_request_case';
export * from './mustache_template';
export * from './validate_email_addresses';
export * from './servicenow_config';
export * from './connector_feature_config';

export const BASE_ACTION_API_PATH = '/api/actions';
export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions';
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/actions/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ActionType {
enabledInConfig: boolean;
enabledInLicense: boolean;
minimumLicenseRequired: LicenseType;
supportedFeatureIds: string[];
}

export enum InvalidEmailReason {
Expand Down
80 changes: 80 additions & 0 deletions x-pack/plugins/actions/server/action_type_registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('register()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'gold',
supportedFeatureIds: ['alerting'],
executor,
});
expect(actionTypeRegistry.has('my-action-type')).toEqual(true);
Expand Down Expand Up @@ -86,6 +87,7 @@ describe('register()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
};
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
Expand All @@ -100,26 +102,59 @@ describe('register()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
expect(() =>
actionTypeRegistry.register({
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
})
).toThrowErrorMatchingInlineSnapshot(
`"Action type \\"my-action-type\\" is already registered."`
);
});

test('throws if empty supported feature ids provided', () => {
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
expect(() =>
actionTypeRegistry.register({
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: [],
executor,
})
).toThrowErrorMatchingInlineSnapshot(
`"At least one \\"supportedFeatureId\\" value must be supplied for connector type \\"my-action-type\\"."`
);
});

test('throws if invalid feature ids provided', () => {
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
expect(() =>
actionTypeRegistry.register({
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['foo'],
executor,
})
).toThrowErrorMatchingInlineSnapshot(
`"Invalid feature ids \\"foo\\" for connector type \\"my-action-type\\"."`
);
});

test('provides a getRetry function that handles ExecutorError', () => {
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
actionTypeRegistry.register({
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
Expand All @@ -140,6 +175,7 @@ describe('register()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'gold',
supportedFeatureIds: ['alerting'],
executor,
});
expect(actionTypeRegistryParams.licensing.featureUsage.register).toHaveBeenCalledWith(
Expand All @@ -154,6 +190,7 @@ describe('register()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
expect(actionTypeRegistryParams.licensing.featureUsage.register).not.toHaveBeenCalled();
Expand All @@ -167,6 +204,7 @@ describe('get()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
const actionType = actionTypeRegistry.get('my-action-type');
Expand All @@ -176,6 +214,9 @@ describe('get()', () => {
"id": "my-action-type",
"minimumLicenseRequired": "basic",
"name": "My action type",
"supportedFeatureIds": Array [
"alerting",
],
}
`);
});
Expand All @@ -196,6 +237,7 @@ describe('list()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
const actionTypes = actionTypeRegistry.list();
Expand All @@ -207,6 +249,40 @@ describe('list()', () => {
enabledInConfig: true,
enabledInLicense: true,
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
},
]);
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalled();
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalled();
});

test('returns list of connector types filtered by feature id if provided', () => {
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
actionTypeRegistry.register({
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
actionTypeRegistry.register({
id: 'another-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['cases'],
executor,
});
const actionTypes = actionTypeRegistry.list('alerting');
expect(actionTypes).toEqual([
{
id: 'my-action-type',
name: 'My action type',
enabled: true,
enabledInConfig: true,
enabledInLicense: true,
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
},
]);
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalled();
Expand All @@ -226,6 +302,7 @@ describe('has()', () => {
id: 'my-action-type',
name: 'My action type',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor,
});
expect(actionTypeRegistry.has('my-action-type'));
Expand All @@ -238,6 +315,7 @@ describe('isActionTypeEnabled', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
Expand Down Expand Up @@ -305,6 +383,7 @@ describe('ensureActionTypeEnabled', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
Expand Down Expand Up @@ -350,6 +429,7 @@ describe('isActionExecutable()', () => {
id: 'foo',
name: 'Foo',
minimumLicenseRequired: 'basic',
supportedFeatureIds: ['alerting'],
executor: async (options) => {
return { status: 'ok', actionId: options.actionId };
},
Expand Down
Loading

0 comments on commit 3c24511

Please sign in to comment.