mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Response Ops][Connectors] Allow connectors to explicitly register which 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>
This commit is contained in:
parent
676be86d9d
commit
3c24511c16
86 changed files with 1157 additions and 514 deletions
|
@ -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}
|
||||
|
||||
|
@ -52,7 +58,8 @@ 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",
|
||||
|
@ -60,7 +67,8 @@ The API returns the following:
|
|||
"minimum_license_required": "basic",
|
||||
"enabled": true,
|
||||
"enabled_in_config": true,
|
||||
"enabled_in_license": true
|
||||
"enabled_in_license": true,
|
||||
"supported_feature_ids": ["alerting"]
|
||||
},
|
||||
...
|
||||
]
|
||||
|
@ -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.
|
|
@ -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 |
|
||||
|
|
|
@ -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
x-pack/plugins/actions/common/connector_feature_config.ts
Normal file
73
x-pack/plugins/actions/common/connector_feature_config.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface ActionType {
|
|||
enabledInConfig: boolean;
|
||||
enabledInLicense: boolean;
|
||||
minimumLicenseRequired: LicenseType;
|
||||
supportedFeatureIds: string[];
|
||||
}
|
||||
|
||||
export enum InvalidEmailReason {
|
||||
|
|
|
@ -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);
|
||||
|
@ -86,6 +87,7 @@ describe('register()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
};
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
|
@ -100,6 +102,7 @@ describe('register()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(() =>
|
||||
|
@ -107,6 +110,7 @@ describe('register()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
|
@ -114,12 +118,43 @@ describe('register()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
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);
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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');
|
||||
|
@ -176,6 +214,9 @@ describe('get()', () => {
|
|||
"id": "my-action-type",
|
||||
"minimumLicenseRequired": "basic",
|
||||
"name": "My action type",
|
||||
"supportedFeatureIds": Array [
|
||||
"alerting",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
@ -196,6 +237,7 @@ describe('list()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
const actionTypes = actionTypeRegistry.list();
|
||||
|
@ -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();
|
||||
|
@ -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'));
|
||||
|
@ -238,6 +315,7 @@ describe('isActionTypeEnabled', () => {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
|
@ -305,6 +383,7 @@ describe('ensureActionTypeEnabled', () => {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
|
@ -350,6 +429,7 @@ describe('isActionExecutable()', () => {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ import Boom from '@hapi/boom';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { RunContext, TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
|
||||
import { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||
import { ActionType as CommonActionType } from '../common';
|
||||
import { ActionType as CommonActionType, areValidFeatures } from '../common';
|
||||
import { ActionsConfigurationUtilities } from './actions_config';
|
||||
import {
|
||||
ExecutorError,
|
||||
|
@ -122,6 +122,31 @@ export class ActionTypeRegistry {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!actionType.supportedFeatureIds || actionType.supportedFeatureIds.length === 0) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.actions.actionTypeRegistry.register.missingSupportedFeatureIds', {
|
||||
defaultMessage:
|
||||
'At least one "supportedFeatureId" value must be supplied for connector type "{connectorTypeId}".',
|
||||
values: {
|
||||
connectorTypeId: actionType.id,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!areValidFeatures(actionType.supportedFeatureIds)) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.actions.actionTypeRegistry.register.invalidConnectorFeatureIds', {
|
||||
defaultMessage: 'Invalid feature ids "{ids}" for connector type "{connectorTypeId}".',
|
||||
values: {
|
||||
connectorTypeId: actionType.id,
|
||||
ids: actionType.supportedFeatureIds.join(','),
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.actionTypes.set(actionType.id, { ...actionType } as unknown as ActionType);
|
||||
this.taskManager.registerTaskDefinitions({
|
||||
[`actions:${actionType.id}`]: {
|
||||
|
@ -170,16 +195,21 @@ export class ActionTypeRegistry {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a list of registered action types [{ id, name, enabled }]
|
||||
* Returns a list of registered action types [{ id, name, enabled }], filtered by featureId if provided.
|
||||
*/
|
||||
public list(): CommonActionType[] {
|
||||
return Array.from(this.actionTypes).map(([actionTypeId, actionType]) => ({
|
||||
id: actionTypeId,
|
||||
name: actionType.name,
|
||||
minimumLicenseRequired: actionType.minimumLicenseRequired,
|
||||
enabled: this.isActionTypeEnabled(actionTypeId),
|
||||
enabledInConfig: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
|
||||
enabledInLicense: !!this.licenseState.isLicenseValidForActionType(actionType).isValid,
|
||||
}));
|
||||
public list(featureId?: string): CommonActionType[] {
|
||||
return Array.from(this.actionTypes)
|
||||
.filter(([_, actionType]) =>
|
||||
featureId ? actionType.supportedFeatureIds.includes(featureId) : true
|
||||
)
|
||||
.map(([actionTypeId, actionType]) => ({
|
||||
id: actionTypeId,
|
||||
name: actionType.name,
|
||||
minimumLicenseRequired: actionType.minimumLicenseRequired,
|
||||
enabled: this.isActionTypeEnabled(actionTypeId),
|
||||
enabledInConfig: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
|
||||
enabledInLicense: !!this.licenseState.isLicenseValidForActionType(actionType).isValid,
|
||||
supportedFeatureIds: actionType.supportedFeatureIds,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);
|
||||
|
@ -186,6 +187,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);
|
||||
|
@ -226,6 +228,7 @@ describe('create()', () => {
|
|||
id: savedObjectCreateResult.attributes.actionTypeId,
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);
|
||||
|
@ -264,6 +267,7 @@ describe('create()', () => {
|
|||
id: savedObjectCreateResult.attributes.actionTypeId,
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);
|
||||
|
@ -316,6 +320,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);
|
||||
|
@ -359,6 +364,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: schema.object({
|
||||
param1: schema.string(),
|
||||
|
@ -391,6 +397,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
connector: connectorValidator,
|
||||
},
|
||||
|
@ -430,6 +437,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
|
@ -562,6 +570,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);
|
||||
|
@ -596,6 +605,7 @@ describe('create()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
mockedLicenseState.ensureLicenseForActionType.mockImplementation(() => {
|
||||
|
@ -1675,6 +1685,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
|
@ -1778,6 +1789,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
|
@ -1850,6 +1862,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
|
@ -1915,6 +1928,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: schema.object({
|
||||
param1: schema.string(),
|
||||
|
@ -1949,6 +1963,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
connector: () => {
|
||||
return '[param1] is required';
|
||||
|
@ -1983,6 +1998,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
|
@ -2063,6 +2079,7 @@ describe('update()', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
mockedLicenseState.ensureLicenseForActionType.mockImplementation(() => {
|
||||
|
@ -2314,6 +2331,7 @@ describe('isActionTypeEnabled()', () => {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -669,8 +669,8 @@ export class ActionsClient {
|
|||
return this.ephemeralExecutionEnqueuer(this.unsecuredSavedObjectsClient, options);
|
||||
}
|
||||
|
||||
public async listTypes(): Promise<ActionType[]> {
|
||||
return this.actionTypeRegistry.list();
|
||||
public async listTypes(featureId?: string): Promise<ActionType[]> {
|
||||
return this.actionTypeRegistry.list(featureId);
|
||||
}
|
||||
|
||||
public isActionTypeEnabled(
|
||||
|
|
|
@ -11,14 +11,19 @@ import { schema, TypeOf } from '@kbn/config-schema';
|
|||
import nodemailerGetService from 'nodemailer/lib/well-known';
|
||||
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { withoutMustacheTemplate } from '../../common';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
AdditionalEmailServices,
|
||||
withoutMustacheTemplate,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../common';
|
||||
|
||||
import { sendEmail, JSON_TRANSPORT_SERVICE, SendEmailOptions, Transport } from './lib/send_email';
|
||||
import { portSchema } from './lib/schemas';
|
||||
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
|
||||
import { ActionsConfigurationUtilities } from '../actions_config';
|
||||
import { renderMustacheString, renderMustacheObject } from '../lib/mustache_renderer';
|
||||
import { AdditionalEmailServices } from '../../common';
|
||||
|
||||
export type EmailActionType = ActionType<
|
||||
ActionTypeConfigType,
|
||||
|
@ -213,6 +218,11 @@ export function getActionType(params: GetActionTypeParams): EmailActionType {
|
|||
name: i18n.translate('xpack.actions.builtin.emailTitle', {
|
||||
defaultMessage: 'Email',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(ConfigSchemaProps, {
|
||||
validate: curry(validateConfig)(configurationUtilities),
|
||||
|
|
|
@ -11,7 +11,13 @@ import { schema, TypeOf } from '@kbn/config-schema';
|
|||
import { Logger } from '@kbn/core/server';
|
||||
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
|
||||
import { renderMustacheObject } from '../lib/mustache_renderer';
|
||||
import { buildAlertHistoryDocument, AlertHistoryEsIndexConnectorId } from '../../common';
|
||||
import {
|
||||
buildAlertHistoryDocument,
|
||||
AlertHistoryEsIndexConnectorId,
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../common';
|
||||
import { ALERT_HISTORY_PREFIX } from '../../common/alert_history_schema';
|
||||
|
||||
export type ESIndexActionType = ActionType<ActionTypeConfigType, {}, ActionParamsType, unknown>;
|
||||
|
@ -60,6 +66,11 @@ export function getActionType({ logger }: { logger: Logger }): ESIndexActionType
|
|||
name: i18n.translate('xpack.actions.builtin.esIndexTitle', {
|
||||
defaultMessage: 'Index',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: ConfigSchema,
|
||||
params: ParamsSchema,
|
||||
|
|
|
@ -32,6 +32,12 @@ import {
|
|||
ExecutorSubActionGetIncidentParams,
|
||||
} from './types';
|
||||
import * as i18n from './translations';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../../common';
|
||||
|
||||
export type ActionParamsType = TypeOf<typeof ExecutorParamsSchema>;
|
||||
interface GetActionTypeParams {
|
||||
|
@ -64,6 +70,12 @@ export function getActionType(
|
|||
id: ActionTypeId,
|
||||
minimumLicenseRequired: 'gold',
|
||||
name: i18n.NAME,
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(ExternalIncidentServiceConfiguration, {
|
||||
validate: curry(validate.config)(configurationUtilities),
|
||||
|
|
|
@ -13,6 +13,11 @@ import { Logger } from '@kbn/core/server';
|
|||
import { postPagerduty } from './lib/post_pagerduty';
|
||||
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
|
||||
import { ActionsConfigurationUtilities } from '../actions_config';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../common';
|
||||
|
||||
// uses the PagerDuty Events API v2
|
||||
// https://v2.developer.pagerduty.com/docs/events-api-v2
|
||||
|
@ -142,6 +147,11 @@ export function getActionType({
|
|||
name: i18n.translate('xpack.actions.builtin.pagerdutyTitle', {
|
||||
defaultMessage: 'PagerDuty',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(configSchemaProps, {
|
||||
validate: curry(validateActionTypeConfig)(configurationUtilities),
|
||||
|
|
|
@ -30,6 +30,11 @@ import {
|
|||
ExecutorSubActionCommonFieldsParams,
|
||||
} from './types';
|
||||
import * as i18n from './translations';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../../common';
|
||||
|
||||
export type ActionParamsType = TypeOf<typeof ExecutorParamsSchema>;
|
||||
|
||||
|
@ -55,6 +60,11 @@ export function getActionType(
|
|||
id: ActionTypeId,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
name: i18n.NAME,
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(ExternalIncidentServiceConfiguration, {
|
||||
validate: curry(validate.config)(configurationUtilities),
|
||||
|
|
|
@ -12,6 +12,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
|
|||
import { Logger, LogMeta } from '@kbn/core/server';
|
||||
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
|
||||
import { withoutControlCharacters } from './lib/string_utils';
|
||||
import { AlertingConnectorFeatureId, UptimeConnectorFeatureId } from '../../common';
|
||||
|
||||
export type ServerLogActionType = ActionType<{}, {}, ActionParamsType>;
|
||||
export type ServerLogActionTypeExecutorOptions = ActionTypeExecutorOptions<
|
||||
|
@ -48,6 +49,7 @@ export function getActionType({ logger }: { logger: Logger }): ServerLogActionTy
|
|||
name: i18n.translate('xpack.actions.builtin.serverLogTitle', {
|
||||
defaultMessage: 'Server log',
|
||||
}),
|
||||
supportedFeatureIds: [AlertingConnectorFeatureId, UptimeConnectorFeatureId],
|
||||
validate: {
|
||||
params: ParamsSchema,
|
||||
},
|
||||
|
|
|
@ -55,6 +55,12 @@ import { throwIfSubActionIsNotSupported } from './utils';
|
|||
import { createExternalServiceITOM } from './service_itom';
|
||||
import { apiITOM } from './api_itom';
|
||||
import { createServiceWrapper } from './create_service_wrapper';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../../common';
|
||||
|
||||
export {
|
||||
ServiceNowITSMActionTypeId,
|
||||
|
@ -92,6 +98,12 @@ export function getServiceNowITSMActionType(
|
|||
id: ServiceNowITSMActionTypeId,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
name: i18n.SERVICENOW_ITSM,
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(ExternalIncidentServiceConfiguration, {
|
||||
validate: curry(validate.config)(configurationUtilities),
|
||||
|
@ -120,6 +132,11 @@ export function getServiceNowSIRActionType(
|
|||
id: ServiceNowSIRActionTypeId,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
name: i18n.SERVICENOW_SIR,
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(ExternalIncidentServiceConfiguration, {
|
||||
validate: curry(validate.config)(configurationUtilities),
|
||||
|
@ -148,6 +165,7 @@ export function getServiceNowITOMActionType(
|
|||
id: ServiceNowITOMActionTypeId,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
name: i18n.SERVICENOW_ITOM,
|
||||
supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId],
|
||||
validate: {
|
||||
config: schema.object(ExternalIncidentServiceConfigurationBase, {
|
||||
validate: curry(validate.config)(configurationUtilities),
|
||||
|
|
|
@ -26,6 +26,11 @@ import {
|
|||
} from '../types';
|
||||
import { ActionsConfigurationUtilities } from '../actions_config';
|
||||
import { getCustomAgents } from './lib/get_custom_agents';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../common';
|
||||
|
||||
export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>;
|
||||
export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions<
|
||||
|
@ -70,6 +75,11 @@ export function getActionType({
|
|||
name: i18n.translate('xpack.actions.builtin.slackTitle', {
|
||||
defaultMessage: 'Slack',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
secrets: schema.object(secretsSchemaProps, {
|
||||
validate: curry(validateActionTypeConfig)(configurationUtilities),
|
||||
|
|
|
@ -26,7 +26,11 @@ import {
|
|||
} from './schema';
|
||||
import { createExternalService } from './service';
|
||||
import { api } from './api';
|
||||
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../../common';
|
||||
interface GetActionTypeParams {
|
||||
logger: Logger;
|
||||
configurationUtilities: ActionsConfigurationUtilities;
|
||||
|
@ -51,6 +55,11 @@ export function getActionType(
|
|||
name: i18n.translate('xpack.actions.builtin.swimlaneTitle', {
|
||||
defaultMessage: 'Swimlane',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
CasesConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(SwimlaneServiceConfiguration, {
|
||||
validate: curry(validate.config)(configurationUtilities),
|
||||
|
|
|
@ -18,6 +18,11 @@ import { isOk, promiseResult, Result } from './lib/result_type';
|
|||
import { request } from './lib/axios_utils';
|
||||
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
|
||||
import { ActionsConfigurationUtilities } from '../actions_config';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../common';
|
||||
|
||||
export type TeamsActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>;
|
||||
export type TeamsActionTypeExecutorOptions = ActionTypeExecutorOptions<
|
||||
|
@ -58,6 +63,11 @@ export function getActionType({
|
|||
name: i18n.translate('xpack.actions.builtin.teamsTitle', {
|
||||
defaultMessage: 'Microsoft Teams',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
secrets: schema.object(secretsSchemaProps, {
|
||||
validate: curry(validateActionTypeConfig)(configurationUtilities),
|
||||
|
|
|
@ -19,6 +19,11 @@ import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from
|
|||
import { ActionsConfigurationUtilities } from '../actions_config';
|
||||
import { request } from './lib/axios_utils';
|
||||
import { renderMustacheString } from '../lib/mustache_renderer';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '../../common';
|
||||
|
||||
// config definition
|
||||
export enum WebhookMethods {
|
||||
|
@ -88,6 +93,11 @@ export function getActionType({
|
|||
name: i18n.translate('xpack.actions.builtin.webhookTitle', {
|
||||
defaultMessage: 'Webhook',
|
||||
}),
|
||||
supportedFeatureIds: [
|
||||
AlertingConnectorFeatureId,
|
||||
UptimeConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
],
|
||||
validate: {
|
||||
config: schema.object(configSchemaProps, {
|
||||
validate: curry(validateActionTypeConfig)(configurationUtilities),
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Logger } from '@kbn/core/server';
|
|||
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
|
||||
import { ActionsConfigurationUtilities } from '../actions_config';
|
||||
import { postXmatters } from './lib/post_xmatters';
|
||||
import { AlertingConnectorFeatureId } from '../../common';
|
||||
|
||||
export type XmattersActionType = ActionType<
|
||||
ActionTypeConfigType,
|
||||
|
@ -68,6 +69,7 @@ export function getActionType({
|
|||
name: i18n.translate('xpack.actions.builtin.xmattersTitle', {
|
||||
defaultMessage: 'xMatters',
|
||||
}),
|
||||
supportedFeatureIds: [AlertingConnectorFeatureId],
|
||||
validate: {
|
||||
config: schema.object(configSchemaProps, {
|
||||
validate: curry(validateActionTypeConfig)(configurationUtilities),
|
||||
|
|
|
@ -70,6 +70,7 @@ describe('findAndCleanupTasks', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
jest.requireMock('./cleanup_tasks').cleanupTasks.mockResolvedValue({
|
||||
|
|
|
@ -59,6 +59,7 @@ test('successfully executes', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
const actionSavedObject = {
|
||||
|
@ -183,6 +184,7 @@ test('successfully executes as a task', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
const actionSavedObject = {
|
||||
|
@ -233,6 +235,7 @@ test('provides empty config when config and / or secrets is empty', async () =>
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
const actionSavedObject = {
|
||||
|
@ -265,6 +268,7 @@ test('throws an error when config is invalid', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: schema.object({
|
||||
param1: schema.string(),
|
||||
|
@ -305,6 +309,7 @@ test('throws an error when connector is invalid', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
connector: () => {
|
||||
return 'error';
|
||||
|
@ -345,6 +350,7 @@ test('throws an error when params is invalid', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
|
@ -392,6 +398,7 @@ test('throws an error if actionType is not enabled', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
const actionSavedObject = {
|
||||
|
@ -427,6 +434,7 @@ test('should not throws an error if actionType is preconfigured', async () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
const actionSavedObject = {
|
||||
|
@ -745,6 +753,7 @@ function setupActionExecutorMock() {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
const actionSavedObject = {
|
||||
|
|
|
@ -12,6 +12,7 @@ const sampleActionType: ActionType = {
|
|||
id: 'test',
|
||||
name: 'test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor({ actionId }) {
|
||||
return { status: 'ok', actionId };
|
||||
},
|
||||
|
|
|
@ -61,6 +61,7 @@ describe('isLicenseValidForActionType', () => {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
|
@ -156,6 +157,7 @@ describe('ensureLicenseForActionType()', () => {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ test('should validate when there are no validators', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
};
|
||||
const testValue = { any: ['old', 'thing'] };
|
||||
|
@ -37,6 +38,7 @@ test('should validate when there are no individual validators', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
validate: {},
|
||||
};
|
||||
|
@ -63,6 +65,7 @@ test('should validate when validators return incoming value', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
validate: {
|
||||
params: selfValidator,
|
||||
|
@ -95,6 +98,7 @@ test('should validate when validators return different values', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
validate: {
|
||||
params: selfValidator,
|
||||
|
@ -130,6 +134,7 @@ test('should throw with expected error when validators fail', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
validate: {
|
||||
params: erroringValidator,
|
||||
|
@ -164,6 +169,7 @@ test('should work with @kbn/config-schema', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
validate: {
|
||||
params: testSchema,
|
||||
|
@ -188,6 +194,7 @@ describe('validateConnectors', () => {
|
|||
id: 'foo',
|
||||
name: 'bar',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
validate: {
|
||||
params: selfValidator,
|
||||
|
|
|
@ -145,6 +145,7 @@ describe('Actions Plugin', () => {
|
|||
id: 'test',
|
||||
name: 'test',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor(options) {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
|
@ -431,6 +432,7 @@ describe('Actions Plugin', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -453,6 +455,7 @@ describe('Actions Plugin', () => {
|
|||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -58,6 +59,9 @@ describe('connectorTypesRoute', () => {
|
|||
"id": "1",
|
||||
"minimum_license_required": "gold",
|
||||
"name": "name",
|
||||
"supported_feature_ids": Array [
|
||||
"alerting",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -71,6 +75,81 @@ describe('connectorTypesRoute', () => {
|
|||
enabled: true,
|
||||
enabled_in_config: true,
|
||||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'gold',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('passes feature_id if provided as query parameter', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
connectorTypesRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector_types"`);
|
||||
|
||||
const listTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
},
|
||||
];
|
||||
|
||||
const actionsClient = actionsClientMock.create();
|
||||
actionsClient.listTypes.mockResolvedValueOnce(listTypes);
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ actionsClient },
|
||||
{
|
||||
query: {
|
||||
feature_id: 'alerting',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Array [
|
||||
Object {
|
||||
"enabled": true,
|
||||
"enabled_in_config": true,
|
||||
"enabled_in_license": true,
|
||||
"id": "1",
|
||||
"minimum_license_required": "gold",
|
||||
"name": "name",
|
||||
"supported_feature_ids": Array [
|
||||
"alerting",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
|
||||
expect(actionsClient.listTypes).toHaveBeenCalledTimes(1);
|
||||
expect(actionsClient.listTypes.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"alerting",
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
enabled: true,
|
||||
enabled_in_config: true,
|
||||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'gold',
|
||||
},
|
||||
],
|
||||
|
@ -94,6 +173,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
},
|
||||
];
|
||||
|
@ -135,6 +215,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -6,18 +6,32 @@
|
|||
*/
|
||||
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState } from '../lib';
|
||||
import { ActionType, BASE_ACTION_API_PATH, RewriteResponseCase } from '../../common';
|
||||
import { ActionsRequestHandlerContext } from '../types';
|
||||
import { verifyAccessAndContext } from './verify_access_and_context';
|
||||
|
||||
const querySchema = schema.object({
|
||||
feature_id: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
const rewriteBodyRes: RewriteResponseCase<ActionType[]> = (results) => {
|
||||
return results.map(({ enabledInConfig, enabledInLicense, minimumLicenseRequired, ...res }) => ({
|
||||
...res,
|
||||
enabled_in_config: enabledInConfig,
|
||||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
}));
|
||||
return results.map(
|
||||
({
|
||||
enabledInConfig,
|
||||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
...res
|
||||
}) => ({
|
||||
...res,
|
||||
enabled_in_config: enabledInConfig,
|
||||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const connectorTypesRoute = (
|
||||
|
@ -27,13 +41,15 @@ export const connectorTypesRoute = (
|
|||
router.get(
|
||||
{
|
||||
path: `${BASE_ACTION_API_PATH}/connector_types`,
|
||||
validate: {},
|
||||
validate: {
|
||||
query: querySchema,
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const actionsClient = (await context.actions).getActionsClient();
|
||||
return res.ok({
|
||||
body: rewriteBodyRes(await actionsClient.listTypes()),
|
||||
body: rewriteBodyRes(await actionsClient.listTypes(req.query?.feature_id)),
|
||||
});
|
||||
})
|
||||
)
|
||||
|
|
|
@ -49,6 +49,7 @@ describe('listActionTypesRoute', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -66,6 +67,9 @@ describe('listActionTypesRoute', () => {
|
|||
"id": "1",
|
||||
"minimumLicenseRequired": "gold",
|
||||
"name": "name",
|
||||
"supportedFeatureIds": Array [
|
||||
"alerting",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -94,6 +98,7 @@ describe('listActionTypesRoute', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -135,6 +140,7 @@ describe('listActionTypesRoute', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ describe('Executor', () => {
|
|||
id: '.test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
schema: {
|
||||
config: TestConfigSchema,
|
||||
secrets: TestSecretsSchema,
|
||||
|
|
|
@ -22,6 +22,7 @@ describe('Registration', () => {
|
|||
id: '.test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
schema: {
|
||||
config: TestConfigSchema,
|
||||
secrets: TestSecretsSchema,
|
||||
|
@ -51,6 +52,7 @@ describe('Registration', () => {
|
|||
id: connector.id,
|
||||
name: connector.name,
|
||||
minimumLicenseRequired: connector.minimumLicenseRequired,
|
||||
supportedFeatureIds: connector.supportedFeatureIds,
|
||||
validate: expect.anything(),
|
||||
executor: expect.anything(),
|
||||
});
|
||||
|
|
|
@ -51,6 +51,7 @@ export const register = <Config extends ActionTypeConfig, Secrets extends Action
|
|||
id: connector.id,
|
||||
name: connector.name,
|
||||
minimumLicenseRequired: connector.minimumLicenseRequired,
|
||||
supportedFeatureIds: connector.supportedFeatureIds,
|
||||
validate: validators,
|
||||
executor,
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@ export interface SubActionConnectorType<Config, Secrets> {
|
|||
id: string;
|
||||
name: string;
|
||||
minimumLicenseRequired: LicenseType;
|
||||
supportedFeatureIds: string[];
|
||||
schema: {
|
||||
config: Type<Config>;
|
||||
secrets: Type<Secrets>;
|
||||
|
|
|
@ -25,6 +25,7 @@ describe('Validators', () => {
|
|||
id: '.test',
|
||||
name: 'Test',
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
schema: {
|
||||
config: TestConfigSchema,
|
||||
secrets: TestSecretsSchema,
|
||||
|
|
|
@ -113,6 +113,7 @@ export interface ActionType<
|
|||
name: string;
|
||||
maxAttempts?: number;
|
||||
minimumLicenseRequired: LicenseType;
|
||||
supportedFeatureIds: string[];
|
||||
validate?: {
|
||||
params?: ValidatorType<Params>;
|
||||
config?: ValidatorType<Config>;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ConnectorTypes } from './api';
|
||||
import { CasesFeaturesAllRequired } from './ui/types';
|
||||
|
||||
export const DEFAULT_DATE_FORMAT = 'dateFormat' as const;
|
||||
|
@ -88,14 +87,6 @@ export const ACTION_URL = '/api/actions' as const;
|
|||
export const ACTION_TYPES_URL = `${ACTION_URL}/connector_types` as const;
|
||||
export const CONNECTORS_URL = `${ACTION_URL}/connectors` as const;
|
||||
|
||||
export const SUPPORTED_CONNECTORS = [
|
||||
`${ConnectorTypes.serviceNowITSM}`,
|
||||
`${ConnectorTypes.serviceNowSIR}`,
|
||||
`${ConnectorTypes.jira}`,
|
||||
`${ConnectorTypes.resilient}`,
|
||||
`${ConnectorTypes.swimlane}`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Alerts
|
||||
*/
|
||||
|
|
|
@ -70,6 +70,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: '.index',
|
||||
|
@ -78,6 +79,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: '.servicenow',
|
||||
|
@ -86,6 +88,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
{
|
||||
id: '.jira',
|
||||
|
@ -94,6 +97,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
{
|
||||
id: '.resilient',
|
||||
|
@ -102,6 +106,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
{
|
||||
id: '.servicenow-sir',
|
||||
|
@ -110,5 +115,6 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -553,20 +553,7 @@ describe('ConfigureCases', () => {
|
|||
expect(wrapper.find('[data-test-subj="add-connector-flyout"]').exists()).toBe(true);
|
||||
expect(getAddConnectorFlyoutMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
supportedActionTypes: [
|
||||
expect.objectContaining({
|
||||
id: '.servicenow',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: '.jira',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: '.resilient',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: '.servicenow-sir',
|
||||
}),
|
||||
],
|
||||
featureId: 'cases',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ import { EuiCallOut, EuiLink } from '@elastic/eui';
|
|||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ActionConnectorTableItem } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { SUPPORTED_CONNECTORS } from '../../../common/constants';
|
||||
import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { useGetActionTypes } from '../../containers/configure/use_action_types';
|
||||
import { useCaseConfigure } from '../../containers/configure/use_configure';
|
||||
|
@ -85,11 +85,6 @@ export const ConfigureCases: React.FC = React.memo(() => {
|
|||
refetch: refetchActionTypes,
|
||||
} = useGetActionTypes();
|
||||
|
||||
const supportedActionTypes = useMemo(
|
||||
() => actionTypes.filter((actionType) => SUPPORTED_CONNECTORS.includes(actionType.id)),
|
||||
[actionTypes]
|
||||
);
|
||||
|
||||
const onConnectorUpdated = useCallback(async () => {
|
||||
refetchConnectors();
|
||||
refetchActionTypes();
|
||||
|
@ -169,12 +164,12 @@ export const ConfigureCases: React.FC = React.memo(() => {
|
|||
addFlyoutVisible
|
||||
? triggersActionsUi.getAddConnectorFlyout({
|
||||
onClose: onCloseAddFlyout,
|
||||
supportedActionTypes,
|
||||
featureId: CasesConnectorFeatureId,
|
||||
onConnectorCreated: onConnectorUpdated,
|
||||
})
|
||||
: null,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[addFlyoutVisible, supportedActionTypes]
|
||||
[addFlyoutVisible]
|
||||
);
|
||||
|
||||
const ConnectorEditFlyout = useMemo(
|
||||
|
|
|
@ -152,6 +152,9 @@ describe('Case Configuration API', () => {
|
|||
expect(fetchMock).toHaveBeenCalledWith('/api/actions/connector_types', {
|
||||
method: 'GET',
|
||||
signal: abortCtrl.signal,
|
||||
query: {
|
||||
feature_id: 'cases',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import { getAllConnectorTypesUrl } from '../../../common/utils/connectors_api';
|
||||
import {
|
||||
ActionConnector,
|
||||
|
@ -93,7 +94,7 @@ export const patchCaseConfigure = async (
|
|||
export const fetchActionTypes = async ({ signal }: ApiProps): Promise<ActionTypeConnector[]> => {
|
||||
const response = await KibanaServices.get().http.fetch<ActionTypeConnector[]>(
|
||||
getAllConnectorTypesUrl(),
|
||||
{ method: 'GET', signal }
|
||||
{ method: 'GET', signal, query: { feature_id: CasesConnectorFeatureId } }
|
||||
);
|
||||
|
||||
return convertArrayToCamelCase(response) as ActionTypeConnector[];
|
||||
|
|
|
@ -25,6 +25,7 @@ describe('client', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
{
|
||||
id: '.servicenow',
|
||||
|
@ -33,6 +34,7 @@ describe('client', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
{
|
||||
id: '.unsupported',
|
||||
|
@ -41,6 +43,7 @@ describe('client', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: '.swimlane',
|
||||
|
@ -49,6 +52,7 @@ describe('client', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import { identity } from 'fp-ts/lib/function';
|
|||
import { SavedObject, SavedObjectsFindResponse, SavedObjectsUtils } from '@kbn/core/server';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { FindActionResult } from '@kbn/actions-plugin/server/types';
|
||||
import { ActionType } from '@kbn/actions-plugin/common';
|
||||
import { ActionType, CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import {
|
||||
CaseConfigurationsResponseRt,
|
||||
CaseConfigureResponseRt,
|
||||
|
@ -31,7 +31,7 @@ import {
|
|||
GetConfigureFindRequestRt,
|
||||
throwErrors,
|
||||
} from '../../../common/api';
|
||||
import { MAX_CONCURRENT_SEARCHES, SUPPORTED_CONNECTORS } from '../../../common/constants';
|
||||
import { MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
|
||||
import { createCaseError } from '../../common/error';
|
||||
import { CasesClientInternal } from '../client_internal';
|
||||
import { CasesClientArgs } from '../types';
|
||||
|
@ -222,8 +222,9 @@ function isConnectorSupported(
|
|||
actionTypes: Record<string, ActionType>
|
||||
): boolean {
|
||||
return (
|
||||
SUPPORTED_CONNECTORS.includes(action.actionTypeId) &&
|
||||
actionTypes[action.actionTypeId]?.enabledInLicense
|
||||
(actionTypes[action.actionTypeId]?.supportedFeatureIds ?? []).includes(
|
||||
CasesConnectorFeatureId
|
||||
) && actionTypes[action.actionTypeId]?.enabledInLicense
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/action_connect
|
|||
loadAllActions: jest.fn(),
|
||||
loadActionTypes: jest.fn(),
|
||||
}));
|
||||
const { loadActionTypes } = jest.requireMock(
|
||||
'@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api'
|
||||
);
|
||||
|
||||
jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/rule_api', () => ({
|
||||
loadAlertTypes: jest.fn(),
|
||||
|
@ -222,6 +225,18 @@ describe('alert_form', () => {
|
|||
mutedInstanceIds: [],
|
||||
} as unknown as Rule;
|
||||
|
||||
loadActionTypes.mockResolvedValue([
|
||||
{
|
||||
id: actionType.id,
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
const KibanaReactContext = createKibanaReactContext(Legacy.shims.kibanaServices);
|
||||
|
||||
const actionWrapper = mount(
|
||||
|
@ -238,16 +253,7 @@ describe('alert_form', () => {
|
|||
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
|
||||
}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
]}
|
||||
featureId="alerting"
|
||||
/>
|
||||
</KibanaReactContext.Provider>
|
||||
</I18nProvider>
|
||||
|
@ -265,7 +271,7 @@ describe('alert_form', () => {
|
|||
it('renders available action cards', async () => {
|
||||
const wrapperTwo = await setup();
|
||||
const actionOption = wrapperTwo.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
`[data-test-subj="${actionType.id}-alerting-ActionTypeSelectOption"]`
|
||||
);
|
||||
expect(actionOption.exists()).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -330,24 +330,6 @@ export const ML_GROUP_ID = 'security' as const;
|
|||
export const LEGACY_ML_GROUP_ID = 'siem' as const;
|
||||
export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID] as const;
|
||||
|
||||
/*
|
||||
Rule notifications options
|
||||
*/
|
||||
export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [
|
||||
'.email',
|
||||
'.index',
|
||||
'.jira',
|
||||
'.pagerduty',
|
||||
'.resilient',
|
||||
'.servicenow',
|
||||
'.servicenow-sir',
|
||||
'.servicenow-itom',
|
||||
'.slack',
|
||||
'.swimlane',
|
||||
'.teams',
|
||||
'.webhook',
|
||||
];
|
||||
|
||||
export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions' as const;
|
||||
export const NOTIFICATION_THROTTLE_RULE = 'rule' as const;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ export const ACTIONS_EDIT_TAB = '[data-test-subj="edit-rule-actions-tab"]';
|
|||
export const ACTIONS_THROTTLE_INPUT =
|
||||
'[data-test-subj="stepRuleActions"] [data-test-subj="select"]';
|
||||
|
||||
export const EMAIL_ACTION_BTN = '[data-test-subj=".email-ActionTypeSelectOption"]';
|
||||
export const EMAIL_ACTION_BTN = '[data-test-subj=".email-siem-ActionTypeSelectOption"]';
|
||||
|
||||
export const CREATE_ACTION_CONNECTOR_BTN = '[data-test-subj="createActionConnectorButton-0"]';
|
||||
|
||||
|
|
|
@ -8,11 +8,10 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { getSupportedActions, RuleActionsField } from '.';
|
||||
import { RuleActionsField } from '.';
|
||||
import { useForm, Form } from '../../../../shared_imports';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { useFormFieldMock } from '../../../../common/mock';
|
||||
import type { ActionType } from '@kbn/actions-plugin/common';
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
describe('RuleActionsField', () => {
|
||||
|
@ -46,11 +45,7 @@ describe('RuleActionsField', () => {
|
|||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<RuleActionsField
|
||||
field={field}
|
||||
messageVariables={messageVariables}
|
||||
hasErrorOnCreationCaseAction={false}
|
||||
/>
|
||||
<RuleActionsField field={field} messageVariables={messageVariables} />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
@ -58,40 +53,4 @@ describe('RuleActionsField', () => {
|
|||
|
||||
expect(wrapper.dive().find('ActionForm')).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('#getSupportedActions', () => {
|
||||
const actions: ActionType[] = [
|
||||
{
|
||||
id: '.jira',
|
||||
name: 'My Jira',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold',
|
||||
},
|
||||
{
|
||||
id: '.case',
|
||||
name: 'Cases',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
];
|
||||
|
||||
it('if we have an error on case action creation, we do not support case connector', () => {
|
||||
expect(getSupportedActions(actions, true)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"enabled": true,
|
||||
"enabledInConfig": false,
|
||||
"enabledInLicense": true,
|
||||
"id": ".jira",
|
||||
"minimumLicenseRequired": "gold",
|
||||
"name": "My Jira",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,18 +12,16 @@ import deepMerge from 'deepmerge';
|
|||
import ReactMarkdown from 'react-markdown';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import type { ActionType, ActionVariables } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { loadActionTypes } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type { ActionVariables } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type { RuleAction } from '@kbn/alerting-plugin/common';
|
||||
import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../common/constants';
|
||||
import { SecurityConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import type { FieldHook } from '../../../../shared_imports';
|
||||
import { useFormContext } from '../../../../shared_imports';
|
||||
import { convertArrayToCamelCase, useKibana } from '../../../../common/lib/kibana';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { FORM_ERRORS_TITLE } from './translations';
|
||||
|
||||
interface Props {
|
||||
field: FieldHook;
|
||||
hasErrorOnCreationCaseAction: boolean;
|
||||
messageVariables: ActionVariables;
|
||||
}
|
||||
|
||||
|
@ -58,29 +56,11 @@ const ContainerActions = styled.div.attrs(
|
|||
)}
|
||||
`;
|
||||
|
||||
export const getSupportedActions = (
|
||||
actionTypes: ActionType[],
|
||||
hasErrorOnCreationCaseAction: boolean
|
||||
): ActionType[] => {
|
||||
return actionTypes.filter((actionType) => {
|
||||
if (actionType.id === '.case' && hasErrorOnCreationCaseAction) {
|
||||
return false;
|
||||
}
|
||||
return NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.includes(actionType.id);
|
||||
});
|
||||
};
|
||||
|
||||
export const RuleActionsField: React.FC<Props> = ({
|
||||
field,
|
||||
hasErrorOnCreationCaseAction,
|
||||
messageVariables,
|
||||
}) => {
|
||||
export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) => {
|
||||
const [fieldErrors, setFieldErrors] = useState<string | null>(null);
|
||||
const [supportedActionTypes, setSupportedActionTypes] = useState<ActionType[] | undefined>();
|
||||
const form = useFormContext();
|
||||
const { isSubmitted, isSubmitting, isValid } = form;
|
||||
const {
|
||||
http,
|
||||
triggersActionsUi: { getActionForm },
|
||||
} = useKibana().services;
|
||||
|
||||
|
@ -141,7 +121,7 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
setActionIdByIndex,
|
||||
setActions: setAlertActionsProperty,
|
||||
setActionParamsProperty,
|
||||
actionTypes: supportedActionTypes,
|
||||
featureId: SecurityConnectorFeatureId,
|
||||
defaultActionMessage: DEFAULT_ACTION_MESSAGE,
|
||||
}),
|
||||
[
|
||||
|
@ -151,19 +131,9 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
setActionIdByIndex,
|
||||
setActionParamsProperty,
|
||||
setAlertActionsProperty,
|
||||
supportedActionTypes,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const actionTypes = convertArrayToCamelCase(await loadActionTypes({ http })) as ActionType[];
|
||||
const supportedTypes = getSupportedActions(actionTypes, hasErrorOnCreationCaseAction);
|
||||
setSupportedActionTypes(supportedTypes);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hasErrorOnCreationCaseAction]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting || !field.errors.length) {
|
||||
return setFieldErrors(null);
|
||||
|
@ -174,8 +144,6 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
}
|
||||
}, [isSubmitted, isSubmitting, field.isChangingValue, isValid, field.errors, setFieldErrors]);
|
||||
|
||||
if (!supportedActionTypes) return <></>;
|
||||
|
||||
return (
|
||||
<ContainerActions $caseIndexes={caseActionIndexes}>
|
||||
{fieldErrors ? (
|
||||
|
|
|
@ -69,7 +69,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
setForm,
|
||||
actionMessageParams,
|
||||
}) => {
|
||||
const [isLoadingCaseAction, hasErrorOnCreationCaseAction] = useManageCaseAction();
|
||||
const [isLoadingCaseAction] = useManageCaseAction();
|
||||
const {
|
||||
services: {
|
||||
application,
|
||||
|
@ -159,14 +159,13 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
component={RuleActionsField}
|
||||
componentProps={{
|
||||
messageVariables: actionMessageParams,
|
||||
hasErrorOnCreationCaseAction,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<UseField path="actions" component={GhostFormField} />
|
||||
),
|
||||
[throttle, actionMessageParams, hasErrorOnCreationCaseAction]
|
||||
[throttle, actionMessageParams]
|
||||
);
|
||||
// only display the actions dropdown if the user has "read" privileges for actions
|
||||
const displayActionsDropDown = useMemo(() => {
|
||||
|
|
|
@ -11,11 +11,7 @@ import { useDispatch } from 'react-redux';
|
|||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useFetcher } from '@kbn/observability-plugin/public';
|
||||
import { getConnectorsAction } from '../../state/alerts/alerts';
|
||||
import { fetchActionTypes } from '../../state/api/alerts';
|
||||
|
||||
import { ActionTypeId } from './types';
|
||||
|
||||
interface Props {
|
||||
focusInput: () => void;
|
||||
|
@ -26,18 +22,6 @@ interface KibanaDeps {
|
|||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export const ALLOWED_ACTION_TYPES: ActionTypeId[] = [
|
||||
'.slack',
|
||||
'.pagerduty',
|
||||
'.server-log',
|
||||
'.index',
|
||||
'.teams',
|
||||
'.servicenow',
|
||||
'.jira',
|
||||
'.webhook',
|
||||
'.email',
|
||||
];
|
||||
|
||||
export const AddConnectorFlyout = ({ focusInput, isDisabled }: Props) => {
|
||||
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
||||
const {
|
||||
|
@ -51,8 +35,6 @@ export const AddConnectorFlyout = ({ focusInput, isDisabled }: Props) => {
|
|||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { data: actionTypes } = useFetcher(() => fetchActionTypes(), []);
|
||||
|
||||
const ConnectorAddFlyout = useMemo(
|
||||
() =>
|
||||
getAddConnectorFlyout({
|
||||
|
@ -61,12 +43,10 @@ export const AddConnectorFlyout = ({ focusInput, isDisabled }: Props) => {
|
|||
setAddFlyoutVisibility(false);
|
||||
focusInput();
|
||||
},
|
||||
supportedActionTypes: (actionTypes ?? []).filter((actionType) =>
|
||||
ALLOWED_ACTION_TYPES.includes(actionType.id as ActionTypeId)
|
||||
),
|
||||
featureId: 'uptime',
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[actionTypes]
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -20,14 +20,15 @@ import { useSelector } from 'react-redux';
|
|||
import styled from 'styled-components';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { useFetcher } from '@kbn/observability-plugin/public';
|
||||
import { SettingsFormProps } from '../../pages/settings';
|
||||
import { connectorsSelector } from '../../state/alerts/alerts';
|
||||
import { AddConnectorFlyout, ALLOWED_ACTION_TYPES } from './add_connector_flyout';
|
||||
import { AddConnectorFlyout } from './add_connector_flyout';
|
||||
import { useGetUrlParams, useUrlParams } from '../../hooks';
|
||||
import { alertFormI18n } from './translations';
|
||||
import { useInitApp } from '../../hooks/use_init_app';
|
||||
import { ActionTypeId } from './types';
|
||||
import { DefaultEmail } from './default_email';
|
||||
import { fetchActionTypes } from '../../state/api/alerts';
|
||||
|
||||
type ConnectorOption = EuiComboBoxOptionOption<string>;
|
||||
|
||||
|
@ -63,6 +64,8 @@ export const AlertDefaultsForm: React.FC<SettingsFormProps> = ({
|
|||
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const { data: actionTypes } = useFetcher(() => fetchActionTypes(), []);
|
||||
|
||||
useInitApp();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -92,7 +95,7 @@ export const AlertDefaultsForm: React.FC<SettingsFormProps> = ({
|
|||
};
|
||||
|
||||
const options = (data ?? [])
|
||||
.filter((action) => ALLOWED_ACTION_TYPES.includes(action.actionTypeId as ActionTypeId))
|
||||
.filter((action) => (actionTypes ?? []).find((type) => type.id === action.actionTypeId))
|
||||
.map((connectorAction) => ({
|
||||
value: connectorAction.id,
|
||||
label: connectorAction.name,
|
||||
|
|
|
@ -22,6 +22,7 @@ describe('settings', () => {
|
|||
id: '.slack',
|
||||
minimumLicenseRequired: 'gold',
|
||||
name: 'Slack',
|
||||
supportedFeatureIds: ['uptime'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -152,20 +152,22 @@ export const disableAlertById = async ({ alertId }: { alertId: string }) => {
|
|||
};
|
||||
|
||||
export const fetchActionTypes = async (): Promise<ActionType[]> => {
|
||||
const response = (await apiService.get(API_URLS.CONNECTOR_TYPES)) as Array<
|
||||
AsApiContract<ActionType>
|
||||
>;
|
||||
const response = (await apiService.get(API_URLS.CONNECTOR_TYPES, {
|
||||
feature_id: 'uptime',
|
||||
})) as Array<AsApiContract<ActionType>>;
|
||||
return response.map<ActionType>(
|
||||
({
|
||||
enabled_in_config: enabledInConfig,
|
||||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
...res
|
||||
}: AsApiContract<ActionType>) => ({
|
||||
...res,
|
||||
enabledInConfig,
|
||||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1346,64 +1346,58 @@ Then this dependencies will be used to embed Actions form or register your own a
|
|||
2. Add Actions form to React component:
|
||||
|
||||
```
|
||||
import React, { useCallback } from 'react';
|
||||
import { ActionForm } from '../../../../../../../../../plugins/triggers_actions_ui/public';
|
||||
import { RuleAction } from '../../../../../../../../../plugins/triggers_actions_ui/public/types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { ActionForm } from '../../../../../../../../../plugins/triggers_actions_ui/public';
|
||||
import { RuleAction } from '../../../../../../../../../plugins/triggers_actions_ui/public/types';
|
||||
|
||||
const ALOWED_BY_PLUGIN_ACTION_TYPES = [
|
||||
{ id: '.email', name: 'Email', enabled: true },
|
||||
{ id: '.index', name: 'Index', enabled: false },
|
||||
{ id: '.example-action', name: 'Example Action', enabled: false },
|
||||
];
|
||||
|
||||
export const ComponentWithActionsForm: () => {
|
||||
const { http, triggersActionsUi, notifications } = useKibana().services;
|
||||
const actionTypeRegistry = triggersActionsUi.actionTypeRegistry;
|
||||
const initialAlert = ({
|
||||
name: 'test',
|
||||
params: {},
|
||||
consumer: 'alerts',
|
||||
alertTypeId: '.index-threshold',
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
export const ComponentWithActionsForm: () => {
|
||||
const { http, triggersActionsUi, notifications } = useKibana().services;
|
||||
const actionTypeRegistry = triggersActionsUi.actionTypeRegistry;
|
||||
const initialAlert = ({
|
||||
name: 'test',
|
||||
params: {},
|
||||
consumer: 'alerts',
|
||||
alertTypeId: '.index-threshold',
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'test',
|
||||
actionTypeId: '.index',
|
||||
params: {
|
||||
message: '',
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'test',
|
||||
actionTypeId: '.index',
|
||||
params: {
|
||||
message: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
tags: [],
|
||||
muteAll: false,
|
||||
enabled: false,
|
||||
mutedInstanceIds: [],
|
||||
} as unknown) as Alert;
|
||||
},
|
||||
],
|
||||
tags: [],
|
||||
muteAll: false,
|
||||
enabled: false,
|
||||
mutedInstanceIds: [],
|
||||
} as unknown) as Alert;
|
||||
|
||||
return (
|
||||
<ActionForm
|
||||
actions={initialAlert.actions}
|
||||
messageVariables={[ { name: 'testVar1', description: 'test var1' } ]}
|
||||
defaultActionGroupId={'default'}
|
||||
setActionIdByIndex={(id: string, index: number) => {
|
||||
initialAlert.actions[index].id = id;
|
||||
}}
|
||||
setRuleProperty={(_updatedActions: RuleAction[]) => {}}
|
||||
setActionParamsProperty={(key: string, value: any, index: number) =>
|
||||
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
|
||||
}
|
||||
http={http}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
defaultActionMessage={'Alert [{{ctx.metadata.name}}] has exceeded the threshold'}
|
||||
actionTypes={ALOWED_BY_PLUGIN_ACTION_TYPES}
|
||||
toastNotifications={notifications.toasts}
|
||||
consumer={initialAlert.consumer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<ActionForm
|
||||
actions={initialAlert.actions}
|
||||
messageVariables={[ { name: 'testVar1', description: 'test var1' } ]}
|
||||
defaultActionGroupId={'default'}
|
||||
setActionIdByIndex={(id: string, index: number) => {
|
||||
initialAlert.actions[index].id = id;
|
||||
}}
|
||||
setRuleProperty={(_updatedActions: RuleAction[]) => {}}
|
||||
setActionParamsProperty={(key: string, value: any, index: number) =>
|
||||
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
|
||||
}
|
||||
http={http}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
defaultActionMessage={'Alert [{{ctx.metadata.name}}] has exceeded the threshold'}
|
||||
featureId="alerting"
|
||||
toastNotifications={notifications.toasts}
|
||||
consumer={initialAlert.consumer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
ActionForm Props definition:
|
||||
|
@ -1420,7 +1414,7 @@ interface ActionAccordionFormProps {
|
|||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
toastNotifications: ToastsSetup;
|
||||
docLinks: DocLinksStart;
|
||||
actionTypes?: ActionType[];
|
||||
featureId: string;
|
||||
messageVariables?: ActionVariable[];
|
||||
defaultActionMessage?: string;
|
||||
capabilities: ApplicationStart['capabilities'];
|
||||
|
@ -1441,7 +1435,7 @@ interface ActionAccordionFormProps {
|
|||
| actionTypeRegistry | Registry for action types. |
|
||||
| toastNotifications | Toast messages Plugin Setup Contract. |
|
||||
| docLinks | Documentation links Plugin Start Contract. |
|
||||
| actionTypes | Optional property, which allows to define a list of available actions specific for a current plugin. |
|
||||
| featureId | Property that filters which action types are loaded when the flyout is opened. Each action type configures the feature ids it is available in during [server side registration](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions#action-types). |
|
||||
| messageVariables | Optional property, which allows to define a list of variables for action 'message' property. Set `useWithTripleBracesInTemplates` to true if you don't want the variable escaped when rendering. |
|
||||
| defaultActionMessage | Optional property, which allows to define a message value for action with 'message' property. |
|
||||
| capabilities | Kibana core's Capabilities ApplicationStart['capabilities']. |
|
||||
|
@ -1485,18 +1479,18 @@ import { ActionsConnectorsContextProvider, CreateConnectorFlyout } from '../../.
|
|||
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
||||
const onClose = useCallback(() => setAddFlyoutVisibility(false), []);
|
||||
|
||||
// load required dependancied
|
||||
// load required dependancies
|
||||
const { http, triggersActionsUi, notifications, application, docLinks } = useKibana().services;
|
||||
|
||||
const connector = {
|
||||
secrets: {},
|
||||
id: 'test',
|
||||
actionTypeId: '.index',
|
||||
actionType: 'Index',
|
||||
name: 'action-connector',
|
||||
referencedByCount: 0,
|
||||
config: {},
|
||||
};
|
||||
secrets: {},
|
||||
id: 'test',
|
||||
actionTypeId: '.index',
|
||||
actionType: 'Index',
|
||||
name: 'action-connector',
|
||||
referencedByCount: 0,
|
||||
config: {},
|
||||
};
|
||||
|
||||
// UI control item for open flyout
|
||||
<EuiButton
|
||||
|
@ -1512,18 +1506,11 @@ const connector = {
|
|||
</EuiButton>
|
||||
|
||||
// in render section of component
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={triggersActionsUi.actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
setAddFlyoutVisibility={setAddFlyoutVisibility}
|
||||
supportedActionTypes={[
|
||||
{
|
||||
id: '.index',
|
||||
enabled: true,
|
||||
name: 'Index',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={triggersActionsUi.actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
setAddFlyoutVisibility={setAddFlyoutVisibility}
|
||||
/>
|
||||
```
|
||||
|
||||
CreateConnectorFlyout Props definition:
|
||||
|
@ -1531,7 +1518,7 @@ CreateConnectorFlyout Props definition:
|
|||
export interface ConnectorAddFlyoutProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
onClose: () => void;
|
||||
supportedActionTypes?: ActionType[];
|
||||
featureId?: string;
|
||||
onConnectorCreated?: (connector: ActionConnector) => void;
|
||||
onTestConnector?: (connector: ActionConnector) => void;
|
||||
}
|
||||
|
@ -1541,7 +1528,7 @@ export interface ConnectorAddFlyoutProps {
|
|||
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| actionTypeRegistry | The action type registry. |
|
||||
| onClose | Called when closing the flyout |
|
||||
| supportedActionTypes | Optional property, that allows to define only specific action types list which is available for a current plugin. |
|
||||
| featureId | Optional property that filters which action types are loaded when the flyout is opened. Each action type configures the feature ids it is available in during [server side registration](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions#action-types). |
|
||||
| onConnectorCreated | Optional property. Function to be called after the creation of the connector. |
|
||||
| onTestConnector | Optional property. Function to be called when the user press the Save & Test button. |
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const http = httpServiceMock.createStartContract();
|
|||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('loadActionTypes', () => {
|
||||
test('should call get types API', async () => {
|
||||
test('should call list types API', async () => {
|
||||
const apiResponseValue = [
|
||||
{
|
||||
id: 'test',
|
||||
|
@ -22,6 +22,7 @@ describe('loadActionTypes', () => {
|
|||
enabled: true,
|
||||
enabled_in_config: true,
|
||||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'basic',
|
||||
},
|
||||
];
|
||||
|
@ -34,6 +35,7 @@ describe('loadActionTypes', () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
];
|
||||
|
@ -46,4 +48,44 @@ describe('loadActionTypes', () => {
|
|||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should call list types API with query parameter if specified', async () => {
|
||||
const apiResponseValue = [
|
||||
{
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
enabled_in_config: true,
|
||||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'basic',
|
||||
},
|
||||
];
|
||||
http.get.mockResolvedValueOnce(apiResponseValue);
|
||||
|
||||
const resolvedValue: ActionType[] = [
|
||||
{
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
];
|
||||
|
||||
const result = await loadActionTypes({ http, featureId: 'alerting' });
|
||||
expect(result).toEqual(resolvedValue);
|
||||
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/api/actions/connector_types",
|
||||
Object {
|
||||
"query": Object {
|
||||
"feature_id": "alerting",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,17 +18,34 @@ const rewriteBodyReq: RewriteRequestCase<ActionType> = ({
|
|||
enabled_in_config: enabledInConfig,
|
||||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
...res
|
||||
}: AsApiContract<ActionType>) => ({
|
||||
enabledInConfig,
|
||||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
...res,
|
||||
});
|
||||
|
||||
export async function loadActionTypes({ http }: { http: HttpSetup }): Promise<ActionType[]> {
|
||||
const res = await http.get<Parameters<typeof rewriteResponseRes>[0]>(
|
||||
`${BASE_ACTION_API_PATH}/connector_types`
|
||||
);
|
||||
export async function loadActionTypes({
|
||||
http,
|
||||
featureId,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
featureId?: string;
|
||||
}): Promise<ActionType[]> {
|
||||
const res = featureId
|
||||
? await http.get<Parameters<typeof rewriteResponseRes>[0]>(
|
||||
`${BASE_ACTION_API_PATH}/connector_types`,
|
||||
{
|
||||
query: {
|
||||
feature_id: featureId,
|
||||
},
|
||||
}
|
||||
)
|
||||
: await http.get<Parameters<typeof rewriteResponseRes>[0]>(
|
||||
`${BASE_ACTION_API_PATH}/connector_types`
|
||||
);
|
||||
return rewriteResponseRes(res);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ test('should sort enabled action types first', async () => {
|
|||
{
|
||||
id: '1',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'first',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
|
@ -21,6 +22,7 @@ test('should sort enabled action types first', async () => {
|
|||
{
|
||||
id: '2',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'second',
|
||||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
|
@ -29,6 +31,7 @@ test('should sort enabled action types first', async () => {
|
|||
{
|
||||
id: '3',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'third',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
|
@ -37,6 +40,7 @@ test('should sort enabled action types first', async () => {
|
|||
{
|
||||
id: '4',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'x-fourth',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
|
@ -55,6 +59,7 @@ test('should sort by name when all enabled', async () => {
|
|||
{
|
||||
id: '1',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'third',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
|
@ -63,6 +68,7 @@ test('should sort by name when all enabled', async () => {
|
|||
{
|
||||
id: '2',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'first',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
|
@ -71,6 +77,7 @@ test('should sort by name when all enabled', async () => {
|
|||
{
|
||||
id: '3',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'second',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
|
@ -79,6 +86,7 @@ test('should sort by name when all enabled', async () => {
|
|||
{
|
||||
id: '4',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'x-fourth',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('checkActionTypeEnabled', () => {
|
|||
const actionType: ActionType = {
|
||||
id: '1',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'my action',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
|
@ -40,6 +41,7 @@ describe('checkActionTypeEnabled', () => {
|
|||
const actionType: ActionType = {
|
||||
id: '1',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'my action',
|
||||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
|
@ -74,6 +76,7 @@ describe('checkActionTypeEnabled', () => {
|
|||
const actionType: ActionType = {
|
||||
id: '1',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'my action',
|
||||
enabled: false,
|
||||
enabledInConfig: false,
|
||||
|
@ -117,6 +120,7 @@ describe('checkActionFormActionTypeEnabled', () => {
|
|||
const actionType: ActionType = {
|
||||
id: '1',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'my action',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
|
@ -135,6 +139,7 @@ describe('checkActionFormActionTypeEnabled', () => {
|
|||
const actionType: ActionType = {
|
||||
id: 'disabled-by-config',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
name: 'my action',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
|
|
|
@ -24,6 +24,8 @@ jest.mock('../../lib/action_connector_api', () => ({
|
|||
loadAllActions: jest.fn(),
|
||||
loadActionTypes: jest.fn(),
|
||||
}));
|
||||
const { loadActionTypes } = jest.requireMock('../../lib/action_connector_api');
|
||||
|
||||
const setHasActionsWithBrokenConnector = jest.fn();
|
||||
describe('action_form', () => {
|
||||
const mockedActionParamsFields = lazy(async () => ({
|
||||
|
@ -234,6 +236,63 @@ describe('action_form', () => {
|
|||
mutedInstanceIds: [],
|
||||
} as unknown as Rule;
|
||||
|
||||
loadActionTypes.mockResolvedValue([
|
||||
{
|
||||
id: actionType.id,
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: '.index',
|
||||
name: 'Index',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'preconfigured',
|
||||
name: 'Preconfigured only',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'disabled-by-config',
|
||||
name: 'Disabled by config',
|
||||
enabled: false,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'disabled-by-license',
|
||||
name: 'Disabled by license',
|
||||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: '.jira',
|
||||
name: 'Disabled by action type',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
const defaultActionMessage = 'Alert [{{context.metadata.name}}] has exceeded the threshold';
|
||||
const wrapper = mountWithIntl(
|
||||
<ActionForm
|
||||
|
@ -246,6 +305,7 @@ describe('action_form', () => {
|
|||
state: [],
|
||||
context: [{ name: 'contextVar', description: 'context var1' }],
|
||||
}}
|
||||
featureId="alerting"
|
||||
defaultActionGroupId={'default'}
|
||||
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) => {
|
||||
const recoveryActionGroupId = customRecoveredActionGroup
|
||||
|
@ -275,56 +335,6 @@ describe('action_form', () => {
|
|||
}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
{
|
||||
id: '.index',
|
||||
name: 'Index',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
{
|
||||
id: 'preconfigured',
|
||||
name: 'Preconfigured only',
|
||||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
{
|
||||
id: 'disabled-by-config',
|
||||
name: 'Disabled by config',
|
||||
enabled: false,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold',
|
||||
},
|
||||
{
|
||||
id: 'disabled-by-license',
|
||||
name: 'Disabled by license',
|
||||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'gold',
|
||||
},
|
||||
{
|
||||
id: '.jira',
|
||||
name: 'Disabled by action type',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -340,35 +350,42 @@ describe('action_form', () => {
|
|||
it('renders available action cards', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
`[data-test-subj="${actionType.id}-alerting-ActionTypeSelectOption"]`
|
||||
);
|
||||
expect(actionOption.exists()).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find(`EuiToolTip [data-test-subj="${actionType.id}-ActionTypeSelectOption"]`)
|
||||
.find(`EuiToolTip [data-test-subj="${actionType.id}-alerting-ActionTypeSelectOption"]`)
|
||||
.exists()
|
||||
).toBeFalsy();
|
||||
expect(setHasActionsWithBrokenConnector).toHaveBeenLastCalledWith(false);
|
||||
expect(loadActionTypes).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
featureId: 'alerting',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render action types disabled by config', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="disabled-by-config-ActionTypeSelectOption"]'
|
||||
'[data-test-subj="disabled-by-config-alerting-ActionTypeSelectOption"]'
|
||||
);
|
||||
expect(actionOption.exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('render action types which is preconfigured only (disabled by config and with preconfigured connectors)', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]');
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="preconfigured-alerting-ActionTypeSelectOption"]'
|
||||
);
|
||||
expect(actionOption.exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders available action groups for the selected action type', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
`[data-test-subj="${actionType.id}-alerting-ActionTypeSelectOption"]`
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const actionGroupsSelect = wrapper.find(
|
||||
|
@ -403,7 +420,7 @@ describe('action_form', () => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const actionOption = wrapper.find(`[data-test-subj=".jira-ActionTypeSelectOption"]`);
|
||||
const actionOption = wrapper.find(`[data-test-subj=".jira-alerting-ActionTypeSelectOption"]`);
|
||||
actionOption.first().simulate('click');
|
||||
const actionGroupsSelect = wrapper.find(
|
||||
`[data-test-subj="addNewActionConnectorActionGroup-1"]`
|
||||
|
@ -440,7 +457,7 @@ describe('action_form', () => {
|
|||
],
|
||||
'iHaveRecovered'
|
||||
);
|
||||
const actionOption = wrapper.find(`[data-test-subj=".jira-ActionTypeSelectOption"]`);
|
||||
const actionOption = wrapper.find(`[data-test-subj=".jira-alerting-ActionTypeSelectOption"]`);
|
||||
actionOption.first().simulate('click');
|
||||
const actionGroupsSelect = wrapper.find(
|
||||
`[data-test-subj="addNewActionConnectorActionGroup-1"]`
|
||||
|
@ -466,7 +483,7 @@ describe('action_form', () => {
|
|||
it('renders available connectors for the selected action type', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
`[data-test-subj="${actionType.id}-alerting-ActionTypeSelectOption"]`
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const combobox = wrapper.find(`[data-test-subj="selectActionConnector-${actionType.id}-0"]`);
|
||||
|
@ -507,7 +524,9 @@ describe('action_form', () => {
|
|||
|
||||
it('renders only preconfigured connectors for the selected preconfigured action type', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]');
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="preconfigured-alerting-ActionTypeSelectOption"]'
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const combobox = wrapper.find('[data-test-subj="selectActionConnector-preconfigured-1"]');
|
||||
expect((combobox.first().props() as any).options).toMatchInlineSnapshot(`
|
||||
|
@ -528,7 +547,9 @@ describe('action_form', () => {
|
|||
|
||||
it('does not render "Add connector" button for preconfigured only action type', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]');
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="preconfigured-alerting-ActionTypeSelectOption"]'
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const preconfigPannel = wrapper.find('[data-test-subj="alertActionAccordion-default"]');
|
||||
const addNewConnectorButton = preconfigPannel.find(
|
||||
|
@ -540,12 +561,12 @@ describe('action_form', () => {
|
|||
it('renders action types disabled by license', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
'[data-test-subj="disabled-by-license-ActionTypeSelectOption"]'
|
||||
'[data-test-subj="disabled-by-license-alerting-ActionTypeSelectOption"]'
|
||||
);
|
||||
expect(actionOption.exists()).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('EuiToolTip [data-test-subj="disabled-by-license-ActionTypeSelectOption"]')
|
||||
.find('EuiToolTip [data-test-subj="disabled-by-license-alerting-ActionTypeSelectOption"]')
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
RuleAction,
|
||||
ActionTypeIndex,
|
||||
ActionConnector,
|
||||
ActionType,
|
||||
ActionVariables,
|
||||
ActionTypeRegistryContract,
|
||||
} from '../../../types';
|
||||
|
@ -35,7 +34,7 @@ import { ActionTypeForm } from './action_type_form';
|
|||
import { AddConnectorInline } from './connector_add_inline';
|
||||
import { actionTypeCompare } from '../../lib/action_type_compare';
|
||||
import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params';
|
||||
import { ConnectorAddModal } from '.';
|
||||
|
@ -56,7 +55,7 @@ export interface ActionAccordionFormProps {
|
|||
setActionGroupIdByIndex?: (group: string, index: number) => void;
|
||||
setActions: (actions: RuleAction[]) => void;
|
||||
setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void;
|
||||
actionTypes?: ActionType[];
|
||||
featureId: string;
|
||||
messageVariables?: ActionVariables;
|
||||
setHasActionsDisabled?: (value: boolean) => void;
|
||||
setHasActionsWithBrokenConnector?: (value: boolean) => void;
|
||||
|
@ -77,7 +76,7 @@ export const ActionForm = ({
|
|||
setActionGroupIdByIndex,
|
||||
setActions,
|
||||
setActionParamsProperty,
|
||||
actionTypes,
|
||||
featureId,
|
||||
messageVariables,
|
||||
actionGroups,
|
||||
defaultActionMessage,
|
||||
|
@ -112,8 +111,8 @@ export const ActionForm = ({
|
|||
(async () => {
|
||||
try {
|
||||
setIsLoadingActionTypes(true);
|
||||
const registeredActionTypes = (actionTypes ?? (await loadActionTypes({ http }))).sort(
|
||||
(a, b) => a.name.localeCompare(b.name)
|
||||
const registeredActionTypes = (await loadActionTypes({ http, featureId })).sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
);
|
||||
const index: ActionTypeIndex = {};
|
||||
for (const actionTypeItem of registeredActionTypes) {
|
||||
|
@ -232,11 +231,6 @@ export const ActionForm = ({
|
|||
const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured);
|
||||
actionTypeNodes = actionTypeRegistry
|
||||
.list()
|
||||
/**
|
||||
* TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
* If actionTypes are set, hidden connectors are filtered out. Otherwise, they are not.
|
||||
*/
|
||||
.filter(({ id }) => actionTypes ?? !DEFAULT_HIDDEN_ACTION_TYPES.includes(id))
|
||||
.filter((item) => actionTypesIndex[item.id])
|
||||
.filter((item) => !!item.actionParamsFields)
|
||||
.sort((a, b) =>
|
||||
|
@ -260,7 +254,7 @@ export const ActionForm = ({
|
|||
<EuiKeyPadMenuItem
|
||||
key={index}
|
||||
isDisabled={!checkEnabledResult.isEnabled}
|
||||
data-test-subj={`${item.id}-ActionTypeSelectOption`}
|
||||
data-test-subj={`${item.id}-${featureId}-ActionTypeSelectOption`}
|
||||
label={actionTypesIndex[item.id].name}
|
||||
onClick={() => addActionType(item)}
|
||||
>
|
||||
|
|
|
@ -246,6 +246,7 @@ function getActionTypeForm(
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
'.server-log': {
|
||||
id: '.server-log',
|
||||
|
@ -254,6 +255,7 @@ function getActionTypeForm(
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -6,13 +6,21 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { act } from '@testing-library/react';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ActionTypeMenu } from './action_type_menu';
|
||||
import { GenericValidationResult } from '../../../types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
jest.mock('../../lib/action_connector_api', () => ({
|
||||
...(jest.requireActual('../../lib/action_connector_api') as any),
|
||||
loadActionTypes: jest.fn(),
|
||||
}));
|
||||
const { loadActionTypes } = jest.requireMock('../../lib/action_connector_api');
|
||||
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
|
||||
|
@ -34,7 +42,7 @@ describe('connector_add_flyout', () => {
|
|||
};
|
||||
});
|
||||
|
||||
it('renders action type menu with proper EuiCards for registered action types', () => {
|
||||
it('renders action type menu with proper EuiCards for registered action types', async () => {
|
||||
const onActionTypeChange = jest.fn();
|
||||
const actionType = actionTypeRegistryMock.createMockActionTypeModel({
|
||||
id: 'my-action-type',
|
||||
|
@ -47,28 +55,33 @@ describe('connector_add_flyout', () => {
|
|||
actionConnectorFields: null,
|
||||
});
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<ActionTypeMenu
|
||||
onActionTypeChange={onActionTypeChange}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
]}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-test-subj="my-action-type-card"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`doesn't renders action types that are disabled via config`, () => {
|
||||
it(`doesn't renders action types that are disabled via config`, async () => {
|
||||
const onActionTypeChange = jest.fn();
|
||||
const actionType = actionTypeRegistryMock.createMockActionTypeModel({
|
||||
id: 'my-action-type',
|
||||
|
@ -81,28 +94,33 @@ describe('connector_add_flyout', () => {
|
|||
actionConnectorFields: null,
|
||||
});
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: false,
|
||||
name: 'Test',
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<ActionTypeMenu
|
||||
onActionTypeChange={onActionTypeChange}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: false,
|
||||
name: 'Test',
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold',
|
||||
},
|
||||
]}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-test-subj="my-action-type-card"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`renders action types as disabled when disabled by license`, () => {
|
||||
it(`renders action types as disabled when disabled by license`, async () => {
|
||||
const onActionTypeChange = jest.fn();
|
||||
const actionType = actionTypeRegistryMock.createMockActionTypeModel({
|
||||
id: 'my-action-type',
|
||||
|
@ -115,23 +133,28 @@ describe('connector_add_flyout', () => {
|
|||
actionConnectorFields: null,
|
||||
});
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: false,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<ActionTypeMenu
|
||||
onActionTypeChange={onActionTypeChange}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
enabled: false,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'gold',
|
||||
},
|
||||
]}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(wrapper.find('EuiToolTip [data-test-subj="my-action-type-card"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@ import { EuiFlexItem, EuiCard, EuiIcon, EuiFlexGrid, EuiSpacer } from '@elastic/
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants';
|
||||
import { ActionType, ActionTypeIndex, ActionTypeRegistryContract } from '../../../types';
|
||||
import { loadActionTypes } from '../../lib/action_connector_api';
|
||||
import { actionTypeCompare } from '../../lib/action_type_compare';
|
||||
|
@ -20,14 +19,14 @@ import { SectionLoading } from '../../components/section_loading';
|
|||
|
||||
interface Props {
|
||||
onActionTypeChange: (actionType: ActionType) => void;
|
||||
actionTypes?: ActionType[];
|
||||
featureId?: string;
|
||||
setHasActionsUpgradeableByTrial?: (value: boolean) => void;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
}
|
||||
|
||||
export const ActionTypeMenu = ({
|
||||
onActionTypeChange,
|
||||
actionTypes,
|
||||
featureId,
|
||||
setHasActionsUpgradeableByTrial,
|
||||
actionTypeRegistry,
|
||||
}: Props) => {
|
||||
|
@ -41,21 +40,10 @@ export const ActionTypeMenu = ({
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
/**
|
||||
* Hidden action types will be hidden only on Alerts & Actions.
|
||||
* actionTypes prop is not filtered. Thus, any consumer that provides it's own actionTypes
|
||||
* can use the hidden action types. For example, Cases or Detections of Security Solution.
|
||||
*
|
||||
* TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
* */
|
||||
let availableActionTypes = actionTypes;
|
||||
if (!availableActionTypes) {
|
||||
setLoadingActionTypes(true);
|
||||
availableActionTypes = (await loadActionTypes({ http })).filter(
|
||||
(actionType) => !DEFAULT_HIDDEN_ACTION_TYPES.includes(actionType.id)
|
||||
);
|
||||
setLoadingActionTypes(false);
|
||||
}
|
||||
setLoadingActionTypes(true);
|
||||
const availableActionTypes = await loadActionTypes({ http, featureId });
|
||||
setLoadingActionTypes(false);
|
||||
|
||||
const index: ActionTypeIndex = {};
|
||||
for (const actionTypeItem of availableActionTypes) {
|
||||
index[actionTypeItem.id] = actionTypeItem;
|
||||
|
|
|
@ -57,6 +57,7 @@ describe('connector_add_modal', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
|
|
|
@ -55,6 +55,7 @@ describe('connectors_selection', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -14,14 +15,17 @@ import {
|
|||
EuiText,
|
||||
EuiFlyoutHeader,
|
||||
IconType,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { getConnectorFeatureName } from '@kbn/actions-plugin/common';
|
||||
|
||||
const FlyoutHeaderComponent: React.FC<{
|
||||
icon?: IconType | null;
|
||||
actionTypeName?: string | null;
|
||||
actionTypeMessage?: string | null;
|
||||
}> = ({ icon, actionTypeName, actionTypeMessage }) => {
|
||||
featureIds?: string[] | null;
|
||||
}> = ({ icon, actionTypeName, actionTypeMessage, featureIds }) => {
|
||||
return (
|
||||
<EuiFlyoutHeader hasBorder data-test-subj="create-connector-flyout-header">
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
|
@ -47,6 +51,28 @@ const FlyoutHeaderComponent: React.FC<{
|
|||
<EuiText size="s" color="subdued">
|
||||
{actionTypeMessage}
|
||||
</EuiText>
|
||||
{featureIds && featureIds.length > 0 && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup
|
||||
data-test-subj="create-connector-flyout-header-availability"
|
||||
wrap
|
||||
responsive={false}
|
||||
gutterSize="xs"
|
||||
alignItems="center"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.addConnectorForm.flyoutHeaderAvailability"
|
||||
defaultMessage="Availability:"
|
||||
/>{' '}
|
||||
{featureIds.map((featureId: string) => (
|
||||
<EuiFlexItem grow={false} key={featureId}>
|
||||
<EuiBadge color="default">{getConnectorFeatureName(featureId)}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<EuiTitle size="s">
|
||||
|
|
|
@ -17,6 +17,13 @@ import {
|
|||
} from '../../../components/builtin_action_types/test_utils';
|
||||
import CreateConnectorFlyout from '.';
|
||||
|
||||
jest.mock('../../../lib/action_connector_api', () => ({
|
||||
...(jest.requireActual('../../../lib/action_connector_api') as any),
|
||||
loadActionTypes: jest.fn(),
|
||||
}));
|
||||
|
||||
const { loadActionTypes } = jest.requireMock('../../../lib/action_connector_api');
|
||||
|
||||
const createConnectorResponse = {
|
||||
connector_type_id: 'test',
|
||||
is_preconfigured: false,
|
||||
|
@ -37,7 +44,7 @@ describe('CreateConnectorFlyout', () => {
|
|||
actionConnectorFields: lazy(() => import('../connector_mock')),
|
||||
});
|
||||
|
||||
const actionTypes = [
|
||||
loadActionTypes.mockResolvedValue([
|
||||
{
|
||||
id: actionTypeModel.id,
|
||||
enabled: true,
|
||||
|
@ -45,8 +52,9 @@ describe('CreateConnectorFlyout', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'siem'],
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
|
||||
|
@ -67,7 +75,6 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
|
@ -78,96 +85,130 @@ describe('CreateConnectorFlyout', () => {
|
|||
expect(getByTestId('create-connector-flyout-footer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders action type menu on flyout open', () => {
|
||||
it('renders action type menu on flyout open', async () => {
|
||||
const { getByTestId } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByTestId(`${actionTypeModel.id}-card`)).toBeInTheDocument();
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(await getByTestId(`${actionTypeModel.id}-card`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Licensing', () => {
|
||||
it('renders banner with subscription links when gold features are disabled due to licensing', () => {
|
||||
it('renders banner with subscription links when gold features are disabled due to licensing', async () => {
|
||||
const disabledActionType = actionTypeRegistryMock.createMockActionTypeModel();
|
||||
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: actionTypeModel.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'siem'],
|
||||
},
|
||||
{
|
||||
id: disabledActionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
const { getByTestId } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={[
|
||||
...actionTypes,
|
||||
{
|
||||
id: disabledActionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'gold',
|
||||
},
|
||||
]}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(getByTestId('upgrade-your-license-callout')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render banner with subscription links when only platinum features are disabled due to licensing', () => {
|
||||
it('does not render banner with subscription links when only platinum features are disabled due to licensing', async () => {
|
||||
const disabledActionType = actionTypeRegistryMock.createMockActionTypeModel();
|
||||
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: actionTypeModel.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'siem'],
|
||||
},
|
||||
{
|
||||
id: disabledActionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'platinum',
|
||||
},
|
||||
]);
|
||||
const { queryByTestId } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={[
|
||||
...actionTypes,
|
||||
{
|
||||
id: disabledActionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
},
|
||||
]}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(queryByTestId('upgrade-your-license-callout')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('does not render banner with subscription links when only enterprise features are disabled due to licensing', () => {
|
||||
it('does not render banner with subscription links when only enterprise features are disabled due to licensing', async () => {
|
||||
const disabledActionType = actionTypeRegistryMock.createMockActionTypeModel();
|
||||
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: actionTypeModel.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'siem'],
|
||||
},
|
||||
{
|
||||
id: disabledActionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'enterprise',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
const { queryByTestId } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={[
|
||||
...actionTypes,
|
||||
{
|
||||
id: disabledActionType.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'enterprise',
|
||||
},
|
||||
]}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(queryByTestId('upgrade-your-license-callout')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -178,12 +219,13 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(queryByTestId('create-connector-flyout-header-icon')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -192,26 +234,53 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(getByText('Select a connector')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the feature id badges when the connector type is selected', async () => {
|
||||
const { getByTestId, getByText } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('test-connector-text-field')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(getByTestId('create-connector-flyout-header-availability')).toBeInTheDocument();
|
||||
expect(getByText('Alerting')).toBeInTheDocument();
|
||||
expect(getByText('Security Solution')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the icon when the connector type is selected', async () => {
|
||||
const { getByTestId, getByText, queryByText } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
});
|
||||
|
@ -233,11 +302,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
|
@ -286,24 +355,26 @@ describe('CreateConnectorFlyout', () => {
|
|||
});
|
||||
actionTypeRegistry.get.mockReturnValue(errorActionTypeModel);
|
||||
|
||||
loadActionTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: errorActionTypeModel.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
},
|
||||
]);
|
||||
|
||||
const { getByTestId, getByText } = appMockRenderer.render(
|
||||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={[
|
||||
{
|
||||
id: errorActionTypeModel.id,
|
||||
enabled: true,
|
||||
name: 'Test',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
},
|
||||
]}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${errorActionTypeModel.id}-card`));
|
||||
|
@ -338,11 +409,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
|
@ -401,11 +472,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(getByTestId('create-connector-flyout-cancel-btn')).toBeInTheDocument();
|
||||
expect(getByTestId('create-connector-flyout-save-test-btn')).toBeInTheDocument();
|
||||
|
@ -417,11 +488,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
|
@ -437,11 +508,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
|
@ -456,6 +527,8 @@ describe('CreateConnectorFlyout', () => {
|
|||
userEvent.click(getByTestId('create-connector-flyout-back-btn'));
|
||||
});
|
||||
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(getByTestId(`${actionTypeModel.id}-card`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -464,10 +537,10 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(queryByTestId('create-connector-flyout-save-test-btn')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -477,11 +550,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId('create-connector-flyout-cancel-btn'));
|
||||
|
@ -500,11 +573,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
expect(getByTestId('create-connector-flyout-cancel-btn')).not.toBeDisabled();
|
||||
expect(getByTestId('create-connector-flyout-save-test-btn')).toBeDisabled();
|
||||
|
@ -516,11 +589,11 @@ describe('CreateConnectorFlyout', () => {
|
|||
<CreateConnectorFlyout
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onClose={onClose}
|
||||
supportedActionTypes={actionTypes}
|
||||
onConnectorCreated={onConnectorCreated}
|
||||
onTestConnector={onTestConnector}
|
||||
/>
|
||||
);
|
||||
await act(() => Promise.resolve());
|
||||
|
||||
act(() => {
|
||||
userEvent.click(getByTestId(`${actionTypeModel.id}-card`));
|
||||
|
|
|
@ -21,20 +21,20 @@ import { useCreateConnector } from '../../../hooks/use_create_connector';
|
|||
import { ConnectorForm, ConnectorFormState } from '../connector_form';
|
||||
import { ConnectorFormSchema } from '../types';
|
||||
import { FlyoutHeader } from './header';
|
||||
import { FlyoutFooter } from './foooter';
|
||||
import { FlyoutFooter } from './footer';
|
||||
import { UpgradeLicenseCallOut } from './upgrade_license_callout';
|
||||
|
||||
export interface CreateConnectorFlyoutProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
onClose: () => void;
|
||||
supportedActionTypes?: ActionType[];
|
||||
featureId?: string;
|
||||
onConnectorCreated?: (connector: ActionConnector) => void;
|
||||
onTestConnector?: (connector: ActionConnector) => void;
|
||||
}
|
||||
|
||||
const CreateConnectorFlyoutComponent: React.FC<CreateConnectorFlyoutProps> = ({
|
||||
actionTypeRegistry,
|
||||
supportedActionTypes,
|
||||
featureId,
|
||||
onClose,
|
||||
onConnectorCreated,
|
||||
onTestConnector,
|
||||
|
@ -156,13 +156,14 @@ const CreateConnectorFlyoutComponent: React.FC<CreateConnectorFlyoutProps> = ({
|
|||
icon={actionTypeModel?.iconClass}
|
||||
actionTypeName={actionType?.name}
|
||||
actionTypeMessage={actionTypeModel?.selectMessage}
|
||||
featureIds={actionType?.supportedFeatureIds}
|
||||
/>
|
||||
<EuiFlyoutBody
|
||||
banner={!actionType && hasActionsUpgradeableByTrial ? <UpgradeLicenseCallOut /> : null}
|
||||
>
|
||||
{actionType == null ? (
|
||||
<ActionTypeMenu
|
||||
actionTypes={supportedActionTypes}
|
||||
featureId={featureId}
|
||||
onActionTypeChange={setActionType}
|
||||
setHasActionsUpgradeableByTrial={setHasActionsUpgradeableByTrial}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
|
|
|
@ -26,7 +26,7 @@ import { hasSaveActionsCapability } from '../../../lib/capabilities';
|
|||
import TestConnectorForm from '../test_connector_form';
|
||||
import { useExecuteConnector } from '../../../hooks/use_execute_connector';
|
||||
import { FlyoutHeader } from './header';
|
||||
import { FlyoutFooter } from './foooter';
|
||||
import { FlyoutFooter } from './footer';
|
||||
|
||||
export interface EditConnectorFlyoutProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { uniq } from 'lodash';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
|
@ -39,10 +40,12 @@ describe('actions_connectors_list component empty', () => {
|
|||
{
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
name: 'Test2',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
actionTypeRegistry.has.mockReturnValue(true);
|
||||
|
@ -140,11 +143,13 @@ describe('actions_connectors_list component with items', () => {
|
|||
id: 'test',
|
||||
name: 'Test',
|
||||
enabled: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
name: 'Test2',
|
||||
enabled: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -197,6 +202,13 @@ describe('actions_connectors_list component with items', () => {
|
|||
await setup();
|
||||
expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1);
|
||||
expect(wrapper.find('EuiTableRow')).toHaveLength(4);
|
||||
|
||||
const featureIdsBadges = wrapper.find(
|
||||
'EuiBadge[data-test-subj="connectorsTableCell-featureIds"]'
|
||||
);
|
||||
expect(featureIdsBadges).toHaveLength(5);
|
||||
|
||||
expect(uniq(featureIdsBadges.map((badge) => badge.text()))).toEqual(['Alerting', 'Cases']);
|
||||
});
|
||||
|
||||
it('renders table with preconfigured connectors', async () => {
|
||||
|
@ -272,10 +284,12 @@ describe('actions_connectors_list component empty with show only capability', ()
|
|||
{
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
name: 'Test2',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
const [
|
||||
|
@ -335,10 +349,12 @@ describe('actions_connectors_list with show only capability', () => {
|
|||
{
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
name: 'Test2',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
const [
|
||||
|
@ -406,6 +422,7 @@ describe('actions_connectors_list component with disabled items', () => {
|
|||
enabled: false,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
|
@ -413,6 +430,7 @@ describe('actions_connectors_list component with disabled items', () => {
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -486,6 +504,7 @@ describe('actions_connectors_list component with deprecated connectors', () => {
|
|||
enabled: false,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
|
@ -493,6 +512,7 @@ describe('actions_connectors_list component with deprecated connectors', () => {
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -21,12 +21,13 @@ import {
|
|||
EuiEmptyPrompt,
|
||||
Criteria,
|
||||
EuiButtonEmpty,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { omit } from 'lodash';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { withTheme, EuiTheme } from '@kbn/kibana-react-plugin/common';
|
||||
import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../common/constants';
|
||||
import { getConnectorFeatureName } from '@kbn/actions-plugin/common';
|
||||
import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api';
|
||||
import {
|
||||
hasDeleteActionsCapability,
|
||||
|
@ -130,23 +131,21 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
}, []);
|
||||
|
||||
const actionConnectorTableItems: ActionConnectorTableItem[] = actionTypesIndex
|
||||
? actions
|
||||
// TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
.filter((action) => !DEFAULT_HIDDEN_ACTION_TYPES.includes(action.actionTypeId))
|
||||
.map((action) => {
|
||||
return {
|
||||
...action,
|
||||
actionType: actionTypesIndex[action.actionTypeId]
|
||||
? actionTypesIndex[action.actionTypeId].name
|
||||
: action.actionTypeId,
|
||||
};
|
||||
})
|
||||
? actions.map((action) => {
|
||||
return {
|
||||
...action,
|
||||
actionType: actionTypesIndex[action.actionTypeId]
|
||||
? actionTypesIndex[action.actionTypeId].name
|
||||
: action.actionTypeId,
|
||||
featureIds: actionTypesIndex[action.actionTypeId]
|
||||
? actionTypesIndex[action.actionTypeId].supportedFeatureIds
|
||||
: [],
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
const actionTypesList: Array<{ value: string; name: string }> = actionTypesIndex
|
||||
? Object.values(actionTypesIndex)
|
||||
// TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
.filter((actionType) => !DEFAULT_HIDDEN_ACTION_TYPES.includes(actionType.id))
|
||||
.map((actionType) => ({
|
||||
value: actionType.id,
|
||||
name: `${actionType.name} (${getActionsCountByActionType(actions, actionType.id)})`,
|
||||
|
@ -257,6 +256,30 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
sortable: false,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'featureIds',
|
||||
name: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.featureIdsTitle',
|
||||
{
|
||||
defaultMessage: 'Availability',
|
||||
}
|
||||
),
|
||||
sortable: false,
|
||||
truncateText: true,
|
||||
render: (availability: string[]) => {
|
||||
return (
|
||||
<EuiFlexGroup wrap responsive={false} gutterSize="xs">
|
||||
{(availability ?? []).map((featureId: string) => (
|
||||
<EuiFlexItem grow={false} key={featureId}>
|
||||
<EuiBadge data-test-subj="connectorsTableCell-featureIds" color="default">
|
||||
{getConnectorFeatureName(featureId)}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
render: (item: ActionConnectorTableItem) => {
|
||||
|
|
|
@ -207,6 +207,7 @@ describe('rule_details', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -251,6 +252,7 @@ describe('rule_details', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
{
|
||||
id: '.email',
|
||||
|
@ -259,6 +261,7 @@ describe('rule_details', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -337,6 +340,7 @@ describe('rule_details', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
|
@ -466,6 +470,7 @@ describe('rule_details', () => {
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
];
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
|
|
|
@ -232,7 +232,7 @@ describe('rule_form', () => {
|
|||
describe('rule_form create rule', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
async function setup(enforceMinimum = false, schedule = '1m') {
|
||||
async function setup(enforceMinimum = false, schedule = '1m', featureId = 'alerting') {
|
||||
const mocks = coreMock.createSetup();
|
||||
const { useLoadRuleTypes } = jest.requireMock('../../hooks/use_load_rule_types');
|
||||
const ruleTypes: RuleType[] = [
|
||||
|
@ -333,6 +333,7 @@ describe('rule_form', () => {
|
|||
operation="create"
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
ruleTypeRegistry={ruleTypeRegistry}
|
||||
connectorFeatureId={featureId}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -387,7 +388,15 @@ describe('rule_form', () => {
|
|||
it('renders registered action types', async () => {
|
||||
await setup();
|
||||
const ruleTypeSelectOptions = wrapper.find(
|
||||
'[data-test-subj=".server-log-ActionTypeSelectOption"]'
|
||||
'[data-test-subj=".server-log-alerting-ActionTypeSelectOption"]'
|
||||
);
|
||||
expect(ruleTypeSelectOptions.exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders uses feature id to load action types', async () => {
|
||||
await setup(false, '1m', 'anotherFeature');
|
||||
const ruleTypeSelectOptions = wrapper.find(
|
||||
'[data-test-subj=".server-log-anotherFeature-ActionTypeSelectOption"]'
|
||||
);
|
||||
expect(ruleTypeSelectOptions.exists()).toBeFalsy();
|
||||
});
|
||||
|
|
|
@ -49,6 +49,7 @@ import {
|
|||
RecoveredActionGroup,
|
||||
isActionGroupDisabledForActionTypeId,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import { RuleReducerAction, InitialRule } from './rule_reducer';
|
||||
import {
|
||||
RuleTypeModel,
|
||||
|
@ -96,6 +97,7 @@ interface RuleFormProps<MetaData = Record<string, any>> {
|
|||
setHasActionsWithBrokenConnector?: (value: boolean) => void;
|
||||
metadata?: MetaData;
|
||||
filteredRuleTypes?: string[];
|
||||
connectorFeatureId?: string;
|
||||
}
|
||||
|
||||
export const RuleForm = ({
|
||||
|
@ -111,6 +113,7 @@ export const RuleForm = ({
|
|||
actionTypeRegistry,
|
||||
metadata,
|
||||
filteredRuleTypes: ruleTypeToFilter,
|
||||
connectorFeatureId = AlertingConnectorFeatureId,
|
||||
}: RuleFormProps) => {
|
||||
const {
|
||||
notifications: { toasts },
|
||||
|
@ -550,6 +553,7 @@ export const RuleForm = ({
|
|||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
messageVariables={selectedRuleType.actionVariables}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
featureId={connectorFeatureId}
|
||||
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) =>
|
||||
isActionGroupDisabledForActionType(selectedRuleType, actionGroupId, actionTypeId)
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
ALERTS_FEATURE_ID,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import {
|
||||
ActionType,
|
||||
Rule,
|
||||
|
@ -73,7 +74,6 @@ import { DeleteModalConfirmation } from '../../../components/delete_modal_confir
|
|||
import { EmptyPrompt } from '../../../components/prompts/empty_prompt';
|
||||
import { ALERT_STATUS_LICENSE_ERROR } from '../translations';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../common/constants';
|
||||
import './rules_list.scss';
|
||||
import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner';
|
||||
import { ManageLicenseModal } from './manage_license_modal';
|
||||
|
@ -325,13 +325,9 @@ export const RulesList = ({
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const result = await loadActionTypes({ http });
|
||||
const result = await loadActionTypes({ http, featureId: AlertingConnectorFeatureId });
|
||||
const sortedResult = result
|
||||
.filter(
|
||||
// TODO: Remove "DEFAULT_HIDDEN_ACTION_TYPES" when cases connector is available across Kibana.
|
||||
// Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
({ id }) => actionTypeRegistry.has(id) && !DEFAULT_HIDDEN_ACTION_TYPES.includes(id)
|
||||
)
|
||||
.filter(({ id }) => actionTypeRegistry.has(id))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
setActionTypes(sortedResult);
|
||||
} catch (e) {
|
||||
|
|
|
@ -10,7 +10,5 @@ export { AGGREGATION_TYPES, builtInAggregationTypes } from './aggregation_types'
|
|||
export { builtInGroupByTypes } from './group_by_types';
|
||||
|
||||
export const VIEW_LICENSE_OPTIONS_LINK = 'https://www.elastic.co/subscriptions';
|
||||
// TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
export const DEFAULT_HIDDEN_ACTION_TYPES = ['.case'];
|
||||
|
||||
export const PLUGIN_ID = 'triggersActions';
|
||||
|
|
|
@ -241,6 +241,7 @@ export type ActionConnectorWithoutId<
|
|||
|
||||
export type ActionConnectorTableItem = ActionConnector & {
|
||||
actionType: ActionType['name'];
|
||||
featureIds: ActionType['supportedFeatureIds'];
|
||||
};
|
||||
|
||||
type AsActionVariables<Keys extends string> = {
|
||||
|
|
|
@ -92,6 +92,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
id: 'test.not-enabled',
|
||||
name: 'Test: Not Enabled',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor() {
|
||||
return { status: 'ok', actionId: '' };
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@ export function defineActionTypes(
|
|||
id: 'test.noop',
|
||||
name: 'Test: Noop',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor() {
|
||||
return { status: 'ok', actionId: '' };
|
||||
},
|
||||
|
@ -32,6 +33,7 @@ export function defineActionTypes(
|
|||
id: 'test.throw',
|
||||
name: 'Test: Throw',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor() {
|
||||
throw new Error('this action is intended to fail');
|
||||
},
|
||||
|
@ -41,6 +43,7 @@ export function defineActionTypes(
|
|||
id: 'test.capped',
|
||||
name: 'Test: Capped',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor() {
|
||||
return { status: 'ok', actionId: '' };
|
||||
},
|
||||
|
@ -82,6 +85,7 @@ function getIndexRecordActionType() {
|
|||
id: 'test.index-record',
|
||||
name: 'Test: Index Record',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
config: configSchema,
|
||||
|
@ -122,6 +126,7 @@ function getDelayedActionType() {
|
|||
id: 'test.delayed',
|
||||
name: 'Test: Delayed',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
config: configSchema,
|
||||
|
@ -149,6 +154,7 @@ function getFailingActionType() {
|
|||
id: 'test.failing',
|
||||
name: 'Test: Failing',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
},
|
||||
|
@ -181,6 +187,7 @@ function getRateLimitedActionType() {
|
|||
id: 'test.rate-limit',
|
||||
name: 'Test: Rate Limit',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
maxAttempts: 2,
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
|
@ -217,6 +224,7 @@ function getNoAttemptsRateLimitedActionType() {
|
|||
id: 'test.no-attempts-rate-limit',
|
||||
name: 'Test: Rate Limit',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
maxAttempts: 0,
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
|
@ -256,6 +264,7 @@ function getAuthorizationActionType(core: CoreSetup<FixtureStartDeps>) {
|
|||
id: 'test.authorization',
|
||||
name: 'Test: Authorization',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
},
|
||||
|
@ -335,6 +344,7 @@ function getExcludedActionType() {
|
|||
id: 'test.excluded',
|
||||
name: 'Test: Excluded',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
async executor({ actionId }) {
|
||||
return { status: 'ok', actionId };
|
||||
},
|
||||
|
|
|
@ -83,6 +83,7 @@ export const getTestSubActionConnector = (
|
|||
id: '.test-sub-action-connector',
|
||||
name: 'Test: Sub action connector',
|
||||
minimumLicenseRequired: 'platinum' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
schema: { config: TestConfigSchema, secrets: TestSecretsSchema },
|
||||
Service: TestSubActionConnector,
|
||||
};
|
||||
|
@ -103,6 +104,7 @@ export const getTestSubActionConnectorWithoutSubActions = (
|
|||
id: '.test-sub-action-connector-without-sub-actions',
|
||||
name: 'Test: Sub action connector',
|
||||
minimumLicenseRequired: 'platinum' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
schema: { config: TestConfigSchema, secrets: TestSecretsSchema },
|
||||
Service: TestNoSubActions,
|
||||
};
|
||||
|
|
|
@ -150,7 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.setValue('ruleNameInput', alertName);
|
||||
await testSubjects.click('thresholdPopover');
|
||||
await testSubjects.setValue('alertThresholdInput', '3');
|
||||
await testSubjects.click('.index-ActionTypeSelectOption');
|
||||
await testSubjects.click('.index-alerting-ActionTypeSelectOption');
|
||||
|
||||
await monacoEditor.setCodeEditorValue(`{
|
||||
"rule_id": "{{ruleId}}",
|
||||
|
|
|
@ -134,7 +134,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await testSubjects.click('onThrottleInterval');
|
||||
await testSubjects.setValue('throttleInput', '10');
|
||||
|
||||
await testSubjects.click('.slack-ActionTypeSelectOption');
|
||||
await testSubjects.click('.slack-alerting-ActionTypeSelectOption');
|
||||
await testSubjects.click('addNewActionConnectorButton-.slack');
|
||||
const slackConnectorName = generateUniqueKey();
|
||||
await testSubjects.setValue('nameInput', slackConnectorName);
|
||||
|
@ -188,7 +188,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await defineAlwaysFiringAlert(alertName);
|
||||
|
||||
// create Slack connector and attach an action using it
|
||||
await testSubjects.click('.slack-ActionTypeSelectOption');
|
||||
await testSubjects.click('.slack-alerting-ActionTypeSelectOption');
|
||||
await testSubjects.click('addNewActionConnectorButton-.slack');
|
||||
const slackConnectorName = generateUniqueKey();
|
||||
await testSubjects.setValue('nameInput', slackConnectorName);
|
||||
|
@ -204,7 +204,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
).type('some text ');
|
||||
|
||||
await testSubjects.click('addAlertActionButton');
|
||||
await testSubjects.click('.slack-ActionTypeSelectOption');
|
||||
await testSubjects.click('.slack-alerting-ActionTypeSelectOption');
|
||||
await testSubjects.setValue('messageTextArea', 'test message ');
|
||||
await (
|
||||
await find.byCssSelector(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue