mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Make action retries configurable (#147876)
Resolves: #146222 This PR makes maximum number of retries of an action configurable. Follows the same pattern we used in alerting plugin. `xpack.actions.run.maxAttempts` as a global settings and `xpack.actions.run.connectorTypeOverrides` to override the global settings for specific connector types.
This commit is contained in:
parent
3ac25e9b9d
commit
ffb1dc3e28
9 changed files with 506 additions and 415 deletions
|
@ -199,6 +199,22 @@ Specifies the time allowed for requests to external resources. Requests that tak
|
|||
+
|
||||
For example, `20m`, `24h`, `7d`, `1w`. Default: `60s`.
|
||||
|
||||
`xpack.actions.run.maxAttempts` {ess-icon}::
|
||||
Specifies the maximum number of times an action can be attempted to run. Can be minimum 1 and maximum 10.
|
||||
|
||||
`xpack.actions.run.connectorTypeOverrides` {ess-icon}::
|
||||
Overrides the configs under `xpack.actions.run` for the connector type with the given ID. List the connector type identifier and its settings in an array of objects.
|
||||
+
|
||||
For example:
|
||||
[source,yaml]
|
||||
--
|
||||
xpack.actions.run:
|
||||
maxAttempts: 1
|
||||
connectorTypeOverrides:
|
||||
- id: '.server-log'
|
||||
maxAttempts: 5
|
||||
--
|
||||
|
||||
[float]
|
||||
[[alert-settings]]
|
||||
==== Alerting settings
|
||||
|
|
|
@ -21,50 +21,51 @@ let mockedLicenseState: jest.Mocked<ILicenseState>;
|
|||
let mockedActionsConfig: jest.Mocked<ActionsConfigurationUtilities>;
|
||||
let actionTypeRegistryParams: ActionTypeRegistryOpts;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockedLicenseState = licenseStateMock.create();
|
||||
mockedActionsConfig = actionsConfigMock.create();
|
||||
actionTypeRegistryParams = {
|
||||
licensing: licensingMock.createSetup(),
|
||||
taskManager: mockTaskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOCanEncrypt: true }),
|
||||
inMemoryMetrics
|
||||
),
|
||||
actionsConfigUtils: mockedActionsConfig,
|
||||
licenseState: mockedLicenseState,
|
||||
preconfiguredActions: [
|
||||
{
|
||||
actionTypeId: 'foo',
|
||||
config: {},
|
||||
id: 'my-slack1',
|
||||
name: 'Slack #xyz',
|
||||
secrets: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
},
|
||||
],
|
||||
describe('actionTypeRegistry', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockedLicenseState = licenseStateMock.create();
|
||||
mockedActionsConfig = actionsConfigMock.create();
|
||||
actionTypeRegistryParams = {
|
||||
licensing: licensingMock.createSetup(),
|
||||
taskManager: mockTaskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOCanEncrypt: true }),
|
||||
inMemoryMetrics
|
||||
),
|
||||
actionsConfigUtils: mockedActionsConfig,
|
||||
licenseState: mockedLicenseState,
|
||||
preconfiguredActions: [
|
||||
{
|
||||
actionTypeId: 'foo',
|
||||
config: {},
|
||||
id: 'my-slack1',
|
||||
name: 'Slack #xyz',
|
||||
secrets: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const executor: ExecutorType<{}, {}, {}, void> = async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
};
|
||||
});
|
||||
|
||||
const executor: ExecutorType<{}, {}, {}, void> = async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
};
|
||||
|
||||
describe('register()', () => {
|
||||
test('able to register action types', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistry.has('my-action-type')).toEqual(true);
|
||||
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.registerTaskDefinitions.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
describe('register()', () => {
|
||||
test('able to register action types', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistry.has('my-action-type')).toEqual(true);
|
||||
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.registerTaskDefinitions.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"actions:my-action-type": Object {
|
||||
|
@ -76,157 +77,157 @@ describe('register()', () => {
|
|||
},
|
||||
]
|
||||
`);
|
||||
expect(actionTypeRegistryParams.licensing.featureUsage.register).toHaveBeenCalledWith(
|
||||
'Connector: My action type',
|
||||
'gold'
|
||||
);
|
||||
});
|
||||
|
||||
test('shallow clones the given action type', () => {
|
||||
const myType: ActionType = {
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
};
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(myType);
|
||||
myType.name = 'Changed';
|
||||
expect(actionTypeRegistry.get('my-action-type').name).toEqual('My action type');
|
||||
});
|
||||
|
||||
test('throws error if action type already registered', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
expect(actionTypeRegistryParams.licensing.featureUsage.register).toHaveBeenCalledWith(
|
||||
'Connector: My action type',
|
||||
'gold'
|
||||
);
|
||||
});
|
||||
expect(() =>
|
||||
|
||||
test('shallow clones the given action type', () => {
|
||||
const myType: ActionType = {
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
};
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(myType);
|
||||
myType.name = 'Changed';
|
||||
expect(actionTypeRegistry.get('my-action-type').name).toEqual('My action type');
|
||||
});
|
||||
|
||||
test('throws error if action type already registered', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Action type \\"my-action-type\\" is already registered."`
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(() =>
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Action type \\"my-action-type\\" is already registered."`
|
||||
);
|
||||
});
|
||||
|
||||
test('throws if empty supported feature ids provided', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
expect(() =>
|
||||
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: [],
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"At least one \\"supportedFeatureId\\" value must be supplied for connector type \\"my-action-type\\"."`
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
const registerTaskDefinitionsCall = mockTaskManager.registerTaskDefinitions.mock.calls[0][0];
|
||||
const getRetry = registerTaskDefinitionsCall['actions:my-action-type'].getRetry!;
|
||||
|
||||
test('throws if invalid feature ids provided', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
expect(() =>
|
||||
const retryTime = new Date();
|
||||
expect(getRetry(0, new Error())).toEqual(true);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, true))).toEqual(true);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, false))).toEqual(false);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, undefined))).toEqual(false);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, retryTime))).toEqual(retryTime);
|
||||
});
|
||||
|
||||
test('provides a getRetry function that handles errors based on maxAttempts', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['foo'],
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid feature ids \\"foo\\" for connector type \\"my-action-type\\"."`
|
||||
);
|
||||
maxAttempts: 2,
|
||||
});
|
||||
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
const registerTaskDefinitionsCall = mockTaskManager.registerTaskDefinitions.mock.calls[0][0];
|
||||
const getRetry = registerTaskDefinitionsCall['actions:my-action-type'].getRetry!;
|
||||
|
||||
expect(getRetry(1, new Error())).toEqual(true);
|
||||
expect(getRetry(3, new Error())).toEqual(false);
|
||||
});
|
||||
|
||||
test('registers gold+ action types to the licensing feature usage API', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistryParams.licensing.featureUsage.register).toHaveBeenCalledWith(
|
||||
'Connector: My action type',
|
||||
'gold'
|
||||
);
|
||||
});
|
||||
|
||||
test(`doesn't register basic action types to the licensing feature usage API`, () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistryParams.licensing.featureUsage.register).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
const registerTaskDefinitionsCall = mockTaskManager.registerTaskDefinitions.mock.calls[0][0];
|
||||
const getRetry = registerTaskDefinitionsCall['actions:my-action-type'].getRetry!;
|
||||
|
||||
const retryTime = new Date();
|
||||
expect(getRetry(0, new Error())).toEqual(false);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, true))).toEqual(true);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, false))).toEqual(false);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, undefined))).toEqual(false);
|
||||
expect(getRetry(0, new ExecutorError('my message', {}, retryTime))).toEqual(retryTime);
|
||||
});
|
||||
|
||||
test('provides a getRetry function that handles errors based on maxAttempts', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
maxAttempts: 2,
|
||||
});
|
||||
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
const registerTaskDefinitionsCall = mockTaskManager.registerTaskDefinitions.mock.calls[0][0];
|
||||
const getRetry = registerTaskDefinitionsCall['actions:my-action-type'].getRetry!;
|
||||
|
||||
expect(getRetry(1, new Error())).toEqual(true);
|
||||
expect(getRetry(2, new Error())).toEqual(false);
|
||||
});
|
||||
|
||||
test('registers gold+ action types to the licensing feature usage API', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'gold',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistryParams.licensing.featureUsage.register).toHaveBeenCalledWith(
|
||||
'Connector: My action type',
|
||||
'gold'
|
||||
);
|
||||
});
|
||||
|
||||
test(`doesn't register basic action types to the licensing feature usage API`, () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistryParams.licensing.featureUsage.register).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('get()', () => {
|
||||
test('returns action type', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
const actionType = actionTypeRegistry.get('my-action-type');
|
||||
expect(actionType).toMatchInlineSnapshot(`
|
||||
describe('get()', () => {
|
||||
test('returns action type', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
const actionType = actionTypeRegistry.get('my-action-type');
|
||||
expect(actionType).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": [Function],
|
||||
"id": "my-action-type",
|
||||
|
@ -237,255 +238,99 @@ describe('get()', () => {
|
|||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test(`throws an error when action type doesn't exist`, () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
expect(() => actionTypeRegistry.get('my-action-type')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Action type \\"my-action-type\\" is not registered."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('list()', () => {
|
||||
test('returns list of action types', () => {
|
||||
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,
|
||||
});
|
||||
const actionTypes = actionTypeRegistry.list();
|
||||
expect(actionTypes).toEqual([
|
||||
{
|
||||
|
||||
test(`throws an error when action type doesn't exist`, () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
expect(() => actionTypeRegistry.get('my-action-type')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Action type \\"my-action-type\\" is not registered."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('list()', () => {
|
||||
test('returns list of action types', () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalled();
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalled();
|
||||
});
|
||||
executor,
|
||||
});
|
||||
const actionTypes = actionTypeRegistry.list();
|
||||
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();
|
||||
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([
|
||||
{
|
||||
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',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
},
|
||||
]);
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalled();
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('has()', () => {
|
||||
test('returns false for unregistered action types', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
expect(actionTypeRegistry.has('my-action-type')).toEqual(false);
|
||||
});
|
||||
|
||||
test('returns true after registering an action type', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistry.has('my-action-type'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActionTypeEnabled', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
});
|
||||
|
||||
test('should call isActionTypeEnabled of the actions config', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo');
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
test('should call isActionExecutable of the actions config', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionExecutable('my-slack1', 'foo');
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
test('should return true when isActionTypeEnabled is false and isLicenseValidForActionType is true and it has preconfigured connectors', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(false);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
|
||||
expect(actionTypeRegistry.isActionExecutable('my-slack1', 'foo')).toEqual(true);
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage false by default', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo');
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: false,
|
||||
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();
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage true when specified', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo', { notifyUsage: true });
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: true,
|
||||
describe('has()', () => {
|
||||
test('returns false for unregistered action types', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
expect(actionTypeRegistry.has('my-action-type')).toEqual(false);
|
||||
});
|
||||
|
||||
test('returns true after registering an action type', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register({
|
||||
id: 'my-action-type',
|
||||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor,
|
||||
});
|
||||
expect(actionTypeRegistry.has('my-action-type'));
|
||||
});
|
||||
});
|
||||
|
||||
test('should return false when isActionTypeEnabled is false and isLicenseValidForActionType is true', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(false);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
expect(actionTypeRegistry.isActionTypeEnabled('foo')).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return false when isActionTypeEnabled is true and isLicenseValidForActionType is false', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(true);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({
|
||||
isValid: false,
|
||||
reason: 'invalid',
|
||||
});
|
||||
expect(actionTypeRegistry.isActionTypeEnabled('foo')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureActionTypeEnabled', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
});
|
||||
|
||||
test('should call ensureActionTypeEnabled of the action config', async () => {
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo');
|
||||
expect(mockedActionsConfig.ensureActionTypeEnabled).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
test('should call ensureLicenseForActionType on the license state', async () => {
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo');
|
||||
expect(mockedLicenseState.ensureLicenseForActionType).toHaveBeenCalledWith(fooActionType);
|
||||
});
|
||||
|
||||
test('should throw when ensureActionTypeEnabled throws', async () => {
|
||||
mockedActionsConfig.ensureActionTypeEnabled.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
expect(() =>
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo')
|
||||
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
});
|
||||
|
||||
test('should throw when ensureLicenseForActionType throws', async () => {
|
||||
mockedLicenseState.ensureLicenseForActionType.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
expect(() =>
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo')
|
||||
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActionExecutable()', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage false by default', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionExecutable('123', 'foo');
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage true when specified', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionExecutable('123', 'foo', { notifyUsage: true });
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllTypes()', () => {
|
||||
test('should return empty when notihing is registered', () => {
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
const result = registry.getAllTypes();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return list of registered type ids', () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
registry.register({
|
||||
describe('isActionTypeEnabled', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
|
@ -493,8 +338,165 @@ describe('getAllTypes()', () => {
|
|||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
});
|
||||
|
||||
test('should call isActionTypeEnabled of the actions config', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo');
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
test('should call isActionExecutable of the actions config', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionExecutable('my-slack1', 'foo');
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
test('should return true when isActionTypeEnabled is false and isLicenseValidForActionType is true and it has preconfigured connectors', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(false);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
|
||||
expect(actionTypeRegistry.isActionExecutable('my-slack1', 'foo')).toEqual(true);
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage false by default', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo');
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage true when specified', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo', { notifyUsage: true });
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return false when isActionTypeEnabled is false and isLicenseValidForActionType is true', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(false);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
expect(actionTypeRegistry.isActionTypeEnabled('foo')).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return false when isActionTypeEnabled is true and isLicenseValidForActionType is false', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(true);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({
|
||||
isValid: false,
|
||||
reason: 'invalid',
|
||||
});
|
||||
expect(actionTypeRegistry.isActionTypeEnabled('foo')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureActionTypeEnabled', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
});
|
||||
|
||||
test('should call ensureActionTypeEnabled of the action config', async () => {
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo');
|
||||
expect(mockedActionsConfig.ensureActionTypeEnabled).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
test('should call ensureLicenseForActionType on the license state', async () => {
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo');
|
||||
expect(mockedLicenseState.ensureLicenseForActionType).toHaveBeenCalledWith(fooActionType);
|
||||
});
|
||||
|
||||
test('should throw when ensureActionTypeEnabled throws', async () => {
|
||||
mockedActionsConfig.ensureActionTypeEnabled.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
expect(() =>
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo')
|
||||
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
});
|
||||
|
||||
test('should throw when ensureLicenseForActionType throws', async () => {
|
||||
mockedLicenseState.ensureLicenseForActionType.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
expect(() =>
|
||||
actionTypeRegistry.ensureActionTypeEnabled('foo')
|
||||
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActionExecutable()', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage false by default', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionExecutable('123', 'foo');
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage true when specified', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionExecutable('123', 'foo', { notifyUsage: true });
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalledWith(fooActionType, {
|
||||
notifyUsage: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllTypes()', () => {
|
||||
test('should return empty when notihing is registered', () => {
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
const result = registry.getAllTypes();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return list of registered type ids', () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
registry.register({
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
executor: async (options) => {
|
||||
return { status: 'ok', actionId: options.actionId };
|
||||
},
|
||||
});
|
||||
const result = registry.getAllTypes();
|
||||
expect(result).toEqual(['foo']);
|
||||
});
|
||||
const result = registry.getAllTypes();
|
||||
expect(result).toEqual(['foo']);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,8 +25,6 @@ import {
|
|||
ActionTypeParams,
|
||||
} from './types';
|
||||
|
||||
export const MAX_ATTEMPTS: number = 3;
|
||||
|
||||
export interface ActionTypeRegistryOpts {
|
||||
licensing: LicensingPluginSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
|
@ -149,20 +147,25 @@ export class ActionTypeRegistry {
|
|||
);
|
||||
}
|
||||
|
||||
const maxAttempts = this.actionsConfigUtils.getMaxAttempts({
|
||||
actionTypeId: actionType.id,
|
||||
actionTypeMaxAttempts: actionType.maxAttempts,
|
||||
});
|
||||
|
||||
this.actionTypes.set(actionType.id, { ...actionType } as unknown as ActionType);
|
||||
this.taskManager.registerTaskDefinitions({
|
||||
[`actions:${actionType.id}`]: {
|
||||
title: actionType.name,
|
||||
maxAttempts: actionType.maxAttempts || MAX_ATTEMPTS,
|
||||
maxAttempts,
|
||||
getRetry(attempts: number, error: unknown) {
|
||||
if (error instanceof ExecutorError) {
|
||||
return error.retry == null ? false : error.retry;
|
||||
}
|
||||
// Only retry other kinds of errors based on attempts
|
||||
return attempts < (actionType.maxAttempts ?? 0);
|
||||
return attempts < maxAttempts;
|
||||
},
|
||||
createTaskRunner: (context: RunContext) =>
|
||||
this.taskRunnerFactory.create(context, actionType.maxAttempts),
|
||||
this.taskRunnerFactory.create(context, maxAttempts),
|
||||
},
|
||||
});
|
||||
// No need to notify usage on basic action types
|
||||
|
|
|
@ -26,6 +26,7 @@ const createActionsConfigMock = () => {
|
|||
getCustomHostSettings: jest.fn().mockReturnValue(undefined),
|
||||
getMicrosoftGraphApiUrl: jest.fn().mockReturnValue(undefined),
|
||||
validateEmailAddresses: jest.fn().mockReturnValue(undefined),
|
||||
getMaxAttempts: jest.fn().mockReturnValue(3),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
|
|
@ -534,3 +534,38 @@ describe('validateEmailAddresses()', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMaxAttempts()', () => {
|
||||
test('returns the maxAttempts defined in config', () => {
|
||||
const acu = getActionsConfigurationUtilities({
|
||||
...defaultActionsConfig,
|
||||
run: { maxAttempts: 1 },
|
||||
});
|
||||
const maxAttempts = acu.getMaxAttempts({ actionTypeMaxAttempts: 2, actionTypeId: 'slack' });
|
||||
expect(maxAttempts).toEqual(1);
|
||||
});
|
||||
|
||||
test('returns the maxAttempts defined in config for the action type', () => {
|
||||
const acu = getActionsConfigurationUtilities({
|
||||
...defaultActionsConfig,
|
||||
run: { maxAttempts: 1, connectorTypeOverrides: [{ id: 'slack', maxAttempts: 4 }] },
|
||||
});
|
||||
const maxAttempts = acu.getMaxAttempts({ actionTypeMaxAttempts: 2, actionTypeId: 'slack' });
|
||||
expect(maxAttempts).toEqual(4);
|
||||
});
|
||||
|
||||
test('returns the maxAttempts passed by the action type', () => {
|
||||
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
|
||||
const maxAttempts = acu.getMaxAttempts({ actionTypeMaxAttempts: 2, actionTypeId: 'slack' });
|
||||
expect(maxAttempts).toEqual(2);
|
||||
});
|
||||
|
||||
test('returns the default maxAttempts', () => {
|
||||
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
|
||||
const maxAttempts = acu.getMaxAttempts({
|
||||
actionTypeMaxAttempts: undefined,
|
||||
actionTypeId: 'slack',
|
||||
});
|
||||
expect(maxAttempts).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,6 +28,8 @@ enum AllowListingField {
|
|||
hostname = 'hostname',
|
||||
}
|
||||
|
||||
export const DEFAULT_MAX_ATTEMPTS: number = 3;
|
||||
|
||||
export interface ActionsConfigurationUtilities {
|
||||
isHostnameAllowed: (hostname: string) => boolean;
|
||||
isUriAllowed: (uri: string) => boolean;
|
||||
|
@ -40,6 +42,13 @@ export interface ActionsConfigurationUtilities {
|
|||
getResponseSettings: () => ResponseSettings;
|
||||
getCustomHostSettings: (targetUrl: string) => CustomHostSettings | undefined;
|
||||
getMicrosoftGraphApiUrl: () => undefined | string;
|
||||
getMaxAttempts: ({
|
||||
actionTypeMaxAttempts,
|
||||
actionTypeId,
|
||||
}: {
|
||||
actionTypeMaxAttempts?: number;
|
||||
actionTypeId: string;
|
||||
}) => number;
|
||||
validateEmailAddresses(
|
||||
addresses: string[],
|
||||
options?: ValidateEmailAddressesOptions
|
||||
|
@ -194,5 +203,17 @@ export function getActionsConfigurationUtilities(
|
|||
getMicrosoftGraphApiUrl: () => getMicrosoftGraphApiUrlFromConfig(config),
|
||||
validateEmailAddresses: (addresses: string[], options: ValidateEmailAddressesOptions) =>
|
||||
validatedEmailCurried(addresses, options),
|
||||
getMaxAttempts: ({ actionTypeMaxAttempts, actionTypeId }) => {
|
||||
const connectorTypeConfig = config.run?.connectorTypeOverrides?.find(
|
||||
(connectorType) => actionTypeId === connectorType.id
|
||||
);
|
||||
|
||||
return (
|
||||
connectorTypeConfig?.maxAttempts ||
|
||||
config.run?.maxAttempts ||
|
||||
actionTypeMaxAttempts ||
|
||||
DEFAULT_MAX_ATTEMPTS
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ export enum EnabledActionTypes {
|
|||
Any = '*',
|
||||
}
|
||||
|
||||
const MAX_MAX_ATTEMPTS = 10;
|
||||
const MIN_MAX_ATTEMPTS = 1;
|
||||
|
||||
const preconfiguredActionSchema = schema.object({
|
||||
name: schema.string({ minLength: 1 }),
|
||||
actionTypeId: schema.string({ minLength: 1 }),
|
||||
|
@ -56,6 +59,11 @@ const customHostSettingsSchema = schema.object({
|
|||
|
||||
export type CustomHostSettings = TypeOf<typeof customHostSettingsSchema>;
|
||||
|
||||
const connectorTypeSchema = schema.object({
|
||||
id: schema.string(),
|
||||
maxAttempts: schema.maybe(schema.number({ min: MIN_MAX_ATTEMPTS, max: MAX_MAX_ATTEMPTS })),
|
||||
});
|
||||
|
||||
export const configSchema = schema.object({
|
||||
allowedHosts: schema.arrayOf(
|
||||
schema.oneOf([schema.string({ hostname: true }), schema.literal(AllowedHosts.Any)]),
|
||||
|
@ -117,6 +125,12 @@ export const configSchema = schema.object({
|
|||
domain_allowlist: schema.arrayOf(schema.string()),
|
||||
})
|
||||
),
|
||||
run: schema.maybe(
|
||||
schema.object({
|
||||
maxAttempts: schema.maybe(schema.number({ min: MIN_MAX_ATTEMPTS, max: MAX_MAX_ATTEMPTS })),
|
||||
connectorTypeOverrides: schema.maybe(schema.arrayOf(connectorTypeSchema)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type ActionsConfig = TypeOf<typeof configSchema>;
|
||||
|
|
|
@ -106,7 +106,7 @@ export class TaskRunnerFactory {
|
|||
// Throwing an executor error means we will attempt to retry the task
|
||||
// TM will treat a task as a failure if `attempts >= maxAttempts`
|
||||
// so we need to handle that here to avoid TM persisting the failed task
|
||||
const isRetryableBasedOnAttempts = taskInfo.attempts < (maxAttempts ?? 1);
|
||||
const isRetryableBasedOnAttempts = taskInfo.attempts < maxAttempts;
|
||||
const willRetryMessage = `and will retry`;
|
||||
const willNotRetryMessage = `and will not retry`;
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import {
|
|||
import { ActionsConfig, getValidatedConfig } from './config';
|
||||
import { resolveCustomHosts } from './lib/custom_host_settings';
|
||||
import { ActionsClient } from './actions_client';
|
||||
import { ActionTypeRegistry, MAX_ATTEMPTS } from './action_type_registry';
|
||||
import { ActionTypeRegistry } from './action_type_registry';
|
||||
import {
|
||||
createExecutionEnqueuerFunction,
|
||||
createEphemeralExecutionEnqueuerFunction,
|
||||
|
@ -360,7 +360,6 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>
|
||||
) => {
|
||||
ensureSufficientLicense(actionType);
|
||||
actionType.maxAttempts = actionType.maxAttempts ?? MAX_ATTEMPTS;
|
||||
actionTypeRegistry.register(actionType);
|
||||
},
|
||||
registerSubActionConnectorType: <
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue