Ping the response-ops team whenever a new connector type is registered (#144736)

Similar to https://github.com/elastic/kibana/pull/144196 and
https://github.com/elastic/kibana/pull/144424.

In this PR, I'm making the Response Ops team get pinged whenever a new
connector type is added.

I also renamed two connector types in our test suite to keep the array
I'm asserting clean (from test connectors).
- `.test-sub-action-connector` -> `test.sub-action-connector`
- `.test-sub-action-connector-without-sub-actions` ->
`test.sub-action-connector-without-sub-actions`

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Mike Côté 2022-11-09 06:35:57 -05:00 committed by GitHub
parent a7fdac4564
commit 08eb63af67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 116 additions and 12 deletions

View file

@ -13,6 +13,7 @@ const createActionTypeRegistryMock = () => {
register: jest.fn(),
get: jest.fn(),
list: jest.fn(),
getAllTypes: jest.fn(),
ensureActionTypeEnabled: jest.fn(),
isActionTypeEnabled: jest.fn(),
isActionExecutable: jest.fn(),

View file

@ -474,3 +474,27 @@ describe('isActionExecutable()', () => {
});
});
});
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']);
});
});

View file

@ -221,4 +221,8 @@ export class ActionTypeRegistry {
public getUtils(): ActionsConfigurationUtilities {
return this.actionsConfigUtils;
}
public getAllTypes(): string[] {
return [...this.list().map(({ id }) => id)];
}
}

View file

@ -38,6 +38,7 @@ const createStartMock = () => {
const mock: jest.Mocked<PluginStartContract> = {
isActionTypeEnabled: jest.fn(),
isActionExecutable: jest.fn(),
getAllTypes: jest.fn(),
getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()),
getUnsecuredActionsClient: jest.fn().mockReturnValue(unsecuredActionsClientMock.create()),
getActionsAuthorizationWithRequest: jest

View file

@ -137,6 +137,8 @@ export interface PluginStartContract {
options?: { notifyUsage: boolean }
): boolean;
getAllTypes: ActionTypeRegistry['getAllTypes'];
getActionsClientWithRequest(request: KibanaRequest): Promise<PublicMethodsOf<ActionsClient>>;
getActionsAuthorizationWithRequest(request: KibanaRequest): PublicMethodsOf<ActionsAuthorization>;
@ -551,6 +553,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
) => {
return this.actionTypeRegistry!.isActionExecutable(actionId, actionTypeId, options);
},
getAllTypes: actionTypeRegistry!.getAllTypes.bind(actionTypeRegistry),
getActionsAuthorizationWithRequest(request: KibanaRequest) {
return instantiateAuthorization(request);
},

View file

@ -346,7 +346,7 @@ The actions framework exports the `registerSubActionConnectorType` to register s
```
plugins.actions.registerSubActionConnectorType({
id: '.test-sub-action-connector',
id: 'test.sub-action-connector',
name: 'Test: Sub action connector',
minimumLicenseRequired: 'platinum' as const,
schema: { config: TestConfigSchema, secrets: TestSecretsSchema },
@ -363,7 +363,7 @@ The sub actions framework allows custom validators during registration of the co
```typescript
plugins.actions.registerSubActionConnectorType({
id: '.test-sub-action-connector',
id: 'test.sub-action-connector',
name: 'Test: Sub action connector',
minimumLicenseRequired: 'platinum' as const,
schema: { config: TestConfigSchema, secrets: TestSecretsSchema },

View file

@ -46,8 +46,8 @@ const enabledActionTypes = [
'.slack',
'.webhook',
'.xmatters',
'.test-sub-action-connector',
'.test-sub-action-connector-without-sub-actions',
'test.sub-action-connector',
'test.sub-action-connector-without-sub-actions',
'test.authorization',
'test.failing',
'test.index-record',

View file

@ -427,4 +427,25 @@ export function defineRoutes(
}
}
);
router.get(
{
path: '/api/alerts_fixture/registered_connector_types',
validate: {},
},
async (
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> => {
try {
const [_, { actions }] = await core.getStartServices();
return res.ok({
body: actions.getAllTypes(),
});
} catch (e) {
return res.badRequest({ body: e });
}
}
);
}

View file

@ -80,7 +80,7 @@ export const getTestSubActionConnector = (
public async noData() {}
}
return {
id: '.test-sub-action-connector',
id: 'test.sub-action-connector',
name: 'Test: Sub action connector',
minimumLicenseRequired: 'platinum' as const,
supportedFeatureIds: ['alerting'],
@ -101,7 +101,7 @@ export const getTestSubActionConnectorWithoutSubActions = (
}
return {
id: '.test-sub-action-connector-without-sub-actions',
id: 'test.sub-action-connector-without-sub-actions',
name: 'Test: Sub action connector',
minimumLicenseRequired: 'platinum' as const,
supportedFeatureIds: ['alerting'],

View file

@ -18,7 +18,7 @@ const createSubActionConnector = async ({
supertest,
config,
secrets,
connectorTypeId = '.test-sub-action-connector',
connectorTypeId = 'test.sub-action-connector',
expectedHttpCode = 200,
}: {
supertest: SuperTest.SuperTest<SuperTest.Test>;
@ -94,7 +94,7 @@ export default function createActionTests({ getService }: FtrProviderContext) {
is_deprecated: false,
is_missing_secrets: false,
name: 'My sub connector',
connector_type_id: '.test-sub-action-connector',
connector_type_id: 'test.sub-action-connector',
config: {
url: 'https://example.com',
},
@ -247,7 +247,7 @@ export default function createActionTests({ getService }: FtrProviderContext) {
message: 'an error occurred while running the action',
retry: true,
connector_id: res.body.id,
service_message: `Sub action \"notRegistered\" is not registered. Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: .test-sub-action-connector`,
service_message: `Sub action \"notRegistered\" is not registered. Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: test.sub-action-connector`,
});
});
@ -267,7 +267,7 @@ export default function createActionTests({ getService }: FtrProviderContext) {
message: 'an error occurred while running the action',
retry: true,
connector_id: res.body.id,
service_message: `Method \"notAFunction\" does not exists in service. Sub action: \"notAFunction\". Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: .test-sub-action-connector`,
service_message: `Method \"notAFunction\" does not exists in service. Sub action: \"notAFunction\". Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: test.sub-action-connector`,
});
});
@ -287,14 +287,14 @@ export default function createActionTests({ getService }: FtrProviderContext) {
message: 'an error occurred while running the action',
retry: true,
connector_id: res.body.id,
service_message: `Method \"notExist\" does not exists in service. Sub action: \"notExist\". Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: .test-sub-action-connector`,
service_message: `Method \"notExist\" does not exists in service. Sub action: \"notExist\". Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: test.sub-action-connector`,
});
});
it('should return an error if there are no sub actions registered', async () => {
const res = await createSubActionConnector({
supertest,
connectorTypeId: '.test-sub-action-connector-without-sub-actions',
connectorTypeId: 'test.sub-action-connector-without-sub-actions',
});
objectRemover.add('default', res.body.id, 'action', 'actions');

View file

@ -0,0 +1,49 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function createRegisteredConnectorTypeTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
// This test is intended to fail when new connector types are registered.
// To resolve, add the new connector type ID to this list. This will trigger
// a CODEOWNERS review by Response Ops.
describe('check registered connector types', () => {
it('should list all registered connector types', async () => {
const registeredConnectorTypes = await supertest
.get('/api/alerts_fixture/registered_connector_types')
.expect(200)
.then((response) => response.body);
expect(
registeredConnectorTypes.filter(
(connectorType: string) => !connectorType.startsWith('test.')
)
).to.eql([
'.email',
'.index',
'.pagerduty',
'.swimlane',
'.server-log',
'.slack',
'.webhook',
'.cases-webhook',
'.xmatters',
'.servicenow',
'.servicenow-sir',
'.servicenow-itom',
'.jira',
'.resilient',
'.teams',
'.opsgenie',
]);
});
});
}

View file

@ -29,6 +29,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo
loadTestFile(require.resolve('./connector_types/stack/preconfigured_alert_history_connector'));
loadTestFile(require.resolve('./type_not_enabled'));
loadTestFile(require.resolve('./schedule_unsecured_action'));
loadTestFile(require.resolve('./check_registered_connector_types'));
// note that this test will destroy existing spaces
loadTestFile(require.resolve('./migrations'));