Connectors: Filter inference connectors without existing endpoints (#217641)

## Summary

Updated the `getAll` method in the actions client to exclude inference
connectors that lack inference endpoints.

### Checklist


- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Dima Arnautov 2025-04-09 23:15:38 +02:00 committed by GitHub
parent b28cc66c3e
commit 70c817db22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 124 additions and 9 deletions

View file

@ -25,6 +25,7 @@ import type { Logger } from '@kbn/logging';
import { eventLogClientMock } from '@kbn/event-log-plugin/server/event_log_client.mock';
import type { ActionTypeRegistry } from '../../../../action_type_registry';
import { getAllUnsecured } from './get_all';
import type { InferenceInferenceEndpointInfo } from '@elastic/elasticsearch/lib/api/types';
jest.mock('@kbn/core-saved-objects-utils-server', () => {
const actual = jest.requireActual('@kbn/core-saved-objects-utils-server');
@ -585,6 +586,84 @@ describe('getAll()', () => {
expect(logger.warn).not.toHaveBeenCalled();
});
test('filters out inference connectors without endpoints', async () => {
unsecuredSavedObjectsClient.find.mockResolvedValueOnce({
total: 1,
per_page: 10,
page: 1,
saved_objects: [],
});
scopedClusterClient.asInternalUser.search.mockResponse(
// @ts-expect-error not full search response
{
aggregations: {
testPreconfigured01: { doc_count: 2 },
testPreconfigured02: { doc_count: 2 },
},
}
);
scopedClusterClient.asInternalUser.inference.get.mockResolvedValueOnce({
endpoints: [{ inference_id: '2' } as InferenceInferenceEndpointInfo],
});
actionsClient = new ActionsClient({
logger,
actionTypeRegistry,
unsecuredSavedObjectsClient,
scopedClusterClient,
kibanaIndices,
actionExecutor,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
inMemoryConnectors: [
{
id: 'testPreconfigured01',
actionTypeId: '.inference',
name: 'test1',
config: {
inferenceId: '1',
},
secrets: {},
isDeprecated: false,
isMissingSecrets: false,
isPreconfigured: false,
isSystemAction: true,
},
{
id: 'testPreconfigured02',
actionTypeId: '.inference',
name: 'test2',
config: {
inferenceId: '2',
},
secrets: {},
isDeprecated: false,
isMissingSecrets: false,
isPreconfigured: false,
isSystemAction: true,
},
],
connectorTokenClient: connectorTokenClientMock.create(),
getEventLogClient,
});
const result = await actionsClient.getAll({ includeSystemActions: true });
expect(result).toEqual([
{
actionTypeId: '.inference',
id: 'testPreconfigured02',
isDeprecated: false,
isPreconfigured: false,
isSystemAction: true,
name: 'test2',
referencedByCount: 2,
},
]);
});
});
describe('getAllSystemConnectors()', () => {

View file

@ -113,15 +113,17 @@ async function getAllHelper({
const mergedResult = [
...savedObjectsActions,
...inMemoryConnectors.map((inMemoryConnector) => ({
id: inMemoryConnector.id,
actionTypeId: inMemoryConnector.actionTypeId,
name: inMemoryConnector.name,
isPreconfigured: inMemoryConnector.isPreconfigured,
isDeprecated: isConnectorDeprecated(inMemoryConnector),
isSystemAction: inMemoryConnector.isSystemAction,
...(inMemoryConnector.exposeConfig ? { config: inMemoryConnector.config } : {}),
})),
...(await filterInferenceConnectors(esClient, inMemoryConnectors)).map((connector) => {
return {
id: connector.id,
actionTypeId: connector.actionTypeId,
name: connector.name,
isPreconfigured: connector.isPreconfigured,
isDeprecated: isConnectorDeprecated(connector),
isSystemAction: connector.isSystemAction,
...(connector.exposeConfig ? { config: connector.config } : {}),
};
}),
].sort((a, b) => a.name.localeCompare(b.name));
const connectors = await injectExtraFindData({
@ -238,3 +240,37 @@ async function injectExtraFindData({
referencedByCount: aggregationResult.aggregations[connector.id].doc_count,
}));
}
/**
* Filters out inference connectors that do not have an endpoint.
* It requires a connector config in order to retrieve the inference id.
*
* @param esClient
* @param connectors
* @returns
*/
export async function filterInferenceConnectors(
esClient: ElasticsearchClient,
connectors: InMemoryConnector[]
): Promise<InMemoryConnector[]> {
let result = connectors;
if (result.some((connector) => connector.actionTypeId === '.inference')) {
try {
// Get all inference endpoints to filter out inference connector without endpoints
const inferenceEndpoints = await esClient.inference.get();
result = result.filter((connector) => {
if (connector.actionTypeId !== '.inference') return true;
const inferenceEndpoint = inferenceEndpoints.endpoints.find(
(endpoint) => endpoint.inference_id === connector.config?.inferenceId
);
return inferenceEndpoint !== undefined;
});
} catch (e) {
// If we can't get the inference endpoints, we just return all connectors
}
}
return result;
}