[8.18] Connectors: Filter inference connectors without existing endpoints (#217641) (#217758)

# Backport

This will backport the following commits from `main` to `8.18`:
- [Connectors: Filter inference connectors without existing endpoints
(#217641)](https://github.com/elastic/kibana/pull/217641)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Dima
Arnautov","email":"dmitrii.arnautov@elastic.co"},"sourceCommit":{"committedDate":"2025-04-09T21:15:38Z","message":"Connectors:
Filter inference connectors without existing endpoints (#217641)\n\n##
Summary\n\nUpdated the `getAll` method in the actions client to exclude
inference\nconnectors that lack inference endpoints.\n\n###
Checklist\n\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] The PR
description includes the appropriate Release Notes section,\nand the
correct `release_note:*` label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"70c817db229dd6a9ea0485511b58b134b0d18bf4","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix",":ml","v9.0.0","Feature:Actions/ConnectorTypes","Team:ML","backport:version","v8.18.0","v9.1.0","v8.19.0","Feature:Inference
UI"],"title":"Connectors: Filter inference connectors without existing
endpoints
","number":217641,"url":"https://github.com/elastic/kibana/pull/217641","mergeCommit":{"message":"Connectors:
Filter inference connectors without existing endpoints (#217641)\n\n##
Summary\n\nUpdated the `getAll` method in the actions client to exclude
inference\nconnectors that lack inference endpoints.\n\n###
Checklist\n\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] The PR
description includes the appropriate Release Notes section,\nand the
correct `release_note:*` label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"70c817db229dd6a9ea0485511b58b134b0d18bf4"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/217641","number":217641,"mergeCommit":{"message":"Connectors:
Filter inference connectors without existing endpoints (#217641)\n\n##
Summary\n\nUpdated the `getAll` method in the actions client to exclude
inference\nconnectors that lack inference endpoints.\n\n###
Checklist\n\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] The PR
description includes the appropriate Release Notes section,\nand the
correct `release_note:*` label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"70c817db229dd6a9ea0485511b58b134b0d18bf4"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
Co-authored-by: Quynh Nguyen <quynh.nguyen@elastic.co>
This commit is contained in:
Kibana Machine 2025-04-10 01:55:25 +02:00 committed by GitHub
parent 756177e7aa
commit 0159aab0ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 125 additions and 9 deletions

View file

@ -25,6 +25,7 @@ import { Logger } from '@kbn/logging';
import { eventLogClientMock } from '@kbn/event-log-plugin/server/event_log_client.mock';
import { 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');
@ -611,6 +612,85 @@ 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,
ephemeralExecutionEnqueuer,
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;
}