diff --git a/dev_docs/key_concepts/feature_privileges.mdx b/dev_docs/key_concepts/feature_privileges.mdx
index 7666ca1e82399..87f650133be25 100644
--- a/dev_docs/key_concepts/feature_privileges.mdx
+++ b/dev_docs/key_concepts/feature_privileges.mdx
@@ -179,8 +179,10 @@ public setup(core: CoreSetup, deps: FeatureControlExampleDeps) {
{
path: '/internal/my_plugin/sensitive_action',
validate: false,
- options: {
- tags: ['access:my_closed_example_api'],
+ security: {
+ authz: {
+ requiredPrivileges: ['my_closed_example_api']
+ }
},
},
async (context, request, response) => {
@@ -193,8 +195,11 @@ public setup(core: CoreSetup, deps: FeatureControlExampleDeps) {
);
}
```
+
+ For more information on the `security.authz` object and API authorization, please refer to our guide on
+
-Notice, we've added an `options.tags` property for the API route that returns sensitive information. This tag is then used in the privileges object as follow
+Notice, we've added a `security.authz.requiredPrivileges` property for the API route that returns sensitive information. This added configuration is then used in the privileges object as follow
```ts
{
@@ -347,7 +352,6 @@ A deep dive into every option for the Kibana Feature configuration and what they
}
```
-
### FeatureKibanaPrivileges Interface
#### excludeFromBasePrivileges (optional)
diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc
index 9587674b59e61..b4334b7c7ea80 100644
--- a/docs/user/reporting/automating-report-generation.asciidoc
+++ b/docs/user/reporting/automating-report-generation.asciidoc
@@ -16,11 +16,9 @@ To create the POST URL for PDF reports:
. Open the dashboard, visualization, or **Canvas** workpad you want to view as a report.
-. From the toolbar, click *Share > PDF Reports*, then choose an option:
+* If you are using *Dashboard* or *Visualize Library*, from the toolbar, click *Share > Export*, select the PDF option then click *Copy POST URL*.
-* If you are using *Dashboard* or *Visulize Library*, click *Copy POST URL*.
-
-* If you are using *Canvas*, click *Advanced options > Copy POST URL*.
+* If you are using *Canvas*, from the toolbar, click *Share > PDF Reports*, then click *Advanced options > Copy POST URL*.
To create the POST URL for CSV reports:
@@ -28,7 +26,7 @@ To create the POST URL for CSV reports:
. Open the saved search you want to share.
-. In the toolbar, click *Share > CSV Reports > Copy POST URL*.
+. In the toolbar, click *Share > Export > Copy POST URL*.
[float]
[[use-watcher]]
diff --git a/packages/kbn-cli-dev-mode/src/watcher.ts b/packages/kbn-cli-dev-mode/src/watcher.ts
index 3c9763e0543aa..6dc11371d9582 100644
--- a/packages/kbn-cli-dev-mode/src/watcher.ts
+++ b/packages/kbn-cli-dev-mode/src/watcher.ts
@@ -26,7 +26,8 @@ const packageMatcher = makeMatcher([
/**
* Any code that is outside of a package must match this in order to trigger a restart
*/
-const nonPackageMatcher = makeMatcher(['config/**/*.yml']);
+const nonPackageMatcher = makeMatcher(['config/**/*.yml', 'plugins/**/server/**/*']);
+const staticFileMatcher = makeMatcher(['plugins/**/kibana.json']);
export interface Options {
enabled: boolean;
@@ -87,6 +88,10 @@ export class Watcher {
if (result.type === 'non-package') {
return nonPackageMatcher(result.repoRel) && fire(result.repoRel);
}
+
+ if (result.type === 'static') {
+ return staticFileMatcher(result.repoRel) && fire(result.repoRel);
+ }
}
},
{
diff --git a/renovate.json b/renovate.json
index 307f2c86a08ee..08c842400d671 100644
--- a/renovate.json
+++ b/renovate.json
@@ -137,15 +137,11 @@
"groupName": "@elastic/appex-qa dependencies",
"matchDepNames": [
"cheerio",
- "@types/enzyme",
"@types/faker",
"@types/pixelmatch",
"@types/pngjs",
"@types/supertest",
- "@wojtekmaj/enzyme-adapter-react-17",
"babel-plugin-istanbul",
- "enzyme",
- "enzyme-to-json",
"faker",
"nyc",
"oboe",
@@ -175,10 +171,13 @@
"matchDepNames": [
"@elastic/filesaver",
"@elastic/numeral",
+ "@wojtekmaj/enzyme-adapter-react-17",
"base64-js",
"blurhash",
"classnames",
"deep-freeze-strict",
+ "enzyme",
+ "enzyme-to-json",
"fflate",
"history",
"lz-string",
@@ -188,6 +187,7 @@
"@types/base64-js",
"@types/classnames",
"@types/deep-freeze-strict",
+ "@types/enzyme",
"@types/history",
"@types/lz-string",
"@types/styled-components"
diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts
index 7a64ada1bfff9..186360c03e805 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/run.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts
@@ -51,7 +51,7 @@ export async function runDockerGenerator(
*/
if (flags.baseImage === 'wolfi')
baseImageName =
- 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:26caa6beaee2bbf739a82e91a35173892dfe888d0a744b9e46cdc19a90d8656f';
+ 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:32099b99697d9da842c1ccacdbef1beee05a68cddb817e858d7656df45ed4c93';
let imageFlavor = '';
if (flags.baseImage === 'ubi') imageFlavor += `-ubi`;
diff --git a/src/plugins/data_views/common/constants.ts b/src/plugins/data_views/common/constants.ts
index b1e68fd44745c..4b1cf465efcc9 100644
--- a/src/plugins/data_views/common/constants.ts
+++ b/src/plugins/data_views/common/constants.ts
@@ -79,3 +79,12 @@ export const EXISTING_INDICES_PATH = '/internal/data_views/_existing_indices';
export const DATA_VIEWS_FIELDS_EXCLUDED_TIERS = 'data_views:fields_excluded_data_tiers';
export const DEFAULT_DATA_VIEW_ID = 'defaultIndex';
+
+/**
+ * Valid `failureReason` attribute values for `has_es_data` API error responses
+ */
+export enum HasEsDataFailureReason {
+ localDataTimeout = 'local_data_timeout',
+ remoteDataTimeout = 'remote_data_timeout',
+ unknown = 'unknown',
+}
diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts
index 2b5ca664d56fc..d359489681a2e 100644
--- a/src/plugins/data_views/common/index.ts
+++ b/src/plugins/data_views/common/index.ts
@@ -13,6 +13,7 @@ export {
META_FIELDS,
DATA_VIEW_SAVED_OBJECT_TYPE,
MAX_DATA_VIEW_FIELD_DESCRIPTION_LENGTH,
+ HasEsDataFailureReason,
} from './constants';
export { LATEST_VERSION } from './content_management/v1/constants';
diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts
index 45f747691fc4b..8c229a01d0477 100644
--- a/src/plugins/data_views/common/types.ts
+++ b/src/plugins/data_views/common/types.ts
@@ -571,4 +571,5 @@ export interface ClientConfigType {
scriptedFieldsEnabled?: boolean;
dataTiersExcludedForFields?: string;
fieldListCachingEnabled?: boolean;
+ hasEsDataTimeout: number;
}
diff --git a/src/plugins/data_views/public/services/has_data.test.ts b/src/plugins/data_views/public/services/has_data.test.ts
index fc032ee44bc41..1171cd677b64f 100644
--- a/src/plugins/data_views/public/services/has_data.test.ts
+++ b/src/plugins/data_views/public/services/has_data.test.ts
@@ -10,6 +10,7 @@
import { coreMock } from '@kbn/core/public/mocks';
import { HasData } from './has_data';
+import { HttpFetchError } from '@kbn/core-http-browser-internal/src/http_fetch_error';
describe('when calling hasData service', () => {
describe('hasDataView', () => {
@@ -170,6 +171,78 @@ describe('when calling hasData service', () => {
expect(await response).toBe(false);
});
+
+ it('should return true and show an error toast when checking for remote cluster data times out', async () => {
+ const coreStart = coreMock.createStart();
+ const http = coreStart.http;
+
+ // Mock getIndices
+ const spy = jest.spyOn(http, 'get').mockImplementation(() =>
+ Promise.reject(
+ new HttpFetchError(
+ 'Timeout while checking for Elasticsearch data',
+ 'TimeoutError',
+ new Request(''),
+ undefined,
+ {
+ statusCode: 504,
+ message: 'Timeout while checking for Elasticsearch data',
+ attributes: {
+ failureReason: 'remote_data_timeout',
+ },
+ }
+ )
+ )
+ );
+ const hasData = new HasData();
+ const hasDataService = hasData.start(coreStart, true);
+ const response = hasDataService.hasESData();
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(await response).toBe(true);
+ expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledTimes(1);
+ expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
+ title: 'Remote cluster timeout',
+ text: 'Checking for data on remote clusters timed out. One or more remote clusters may be unavailable.',
+ });
+ });
+
+ it('should return true and not show an error toast when checking for remote cluster data times out, but onRemoteDataTimeout is overridden', async () => {
+ const coreStart = coreMock.createStart();
+ const http = coreStart.http;
+
+ // Mock getIndices
+ const responseBody = {
+ statusCode: 504,
+ message: 'Timeout while checking for Elasticsearch data',
+ attributes: {
+ failureReason: 'remote_data_timeout',
+ },
+ };
+ const spy = jest
+ .spyOn(http, 'get')
+ .mockImplementation(() =>
+ Promise.reject(
+ new HttpFetchError(
+ 'Timeout while checking for Elasticsearch data',
+ 'TimeoutError',
+ new Request(''),
+ undefined,
+ responseBody
+ )
+ )
+ );
+ const hasData = new HasData();
+ const hasDataService = hasData.start(coreStart, true);
+ const onRemoteDataTimeout = jest.fn();
+ const response = hasDataService.hasESData({ onRemoteDataTimeout });
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(await response).toBe(true);
+ expect(coreStart.notifications.toasts.addDanger).not.toHaveBeenCalled();
+ expect(onRemoteDataTimeout).toHaveBeenCalledTimes(1);
+ expect(onRemoteDataTimeout).toHaveBeenCalledWith(responseBody);
+ });
});
describe('resolve/cluster not available', () => {
diff --git a/src/plugins/data_views/public/services/has_data.ts b/src/plugins/data_views/public/services/has_data.ts
index aad546c446cf3..bcf80ca337460 100644
--- a/src/plugins/data_views/public/services/has_data.ts
+++ b/src/plugins/data_views/public/services/has_data.ts
@@ -8,10 +8,22 @@
*/
import { CoreStart, HttpStart } from '@kbn/core/public';
-import { DEFAULT_ASSETS_TO_IGNORE } from '../../common';
+import { IHttpFetchError, ResponseErrorBody, isHttpFetchError } from '@kbn/core-http-browser';
+import { isObject } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import { DEFAULT_ASSETS_TO_IGNORE, HasEsDataFailureReason } from '../../common';
import { HasDataViewsResponse, IndicesViaSearchResponse } from '..';
import { IndicesResponse, IndicesResponseModified } from '../types';
+export interface HasEsDataParams {
+ /**
+ * Callback to handle the case where checking for remote data times out.
+ * If not provided, the default behavior is to show a toast notification.
+ * @param body The error response body
+ */
+ onRemoteDataTimeout?: (body: ResponseErrorBody) => void;
+}
+
export class HasData {
private removeAliases = (source: IndicesResponseModified): boolean => !source.item.indices;
@@ -38,28 +50,55 @@ export class HasData {
return hasLocalESData;
};
- const hasESDataViaResolveCluster = async () => {
+ const hasESDataViaResolveCluster = async (
+ onRemoteDataTimeout: (body: ResponseErrorBody) => void
+ ) => {
try {
const { hasEsData } = await http.get<{ hasEsData: boolean }>(
'/internal/data_views/has_es_data',
- {
- version: '1',
- }
+ { version: '1' }
);
+
return hasEsData;
} catch (e) {
+ if (
+ this.isResponseError(e) &&
+ e.body?.statusCode === 504 &&
+ e.body?.attributes?.failureReason === HasEsDataFailureReason.remoteDataTimeout
+ ) {
+ onRemoteDataTimeout(e.body);
+
+ // In the case of a remote cluster timeout,
+ // we can't be sure if there is data or not,
+ // so just assume there is
+ return true;
+ }
+
// fallback to previous implementation
return hasESDataViaResolveIndex();
}
};
+ const showRemoteDataTimeoutToast = () =>
+ core.notifications.toasts.addDanger({
+ title: i18n.translate('dataViews.hasData.remoteDataTimeoutTitle', {
+ defaultMessage: 'Remote cluster timeout',
+ }),
+ text: i18n.translate('dataViews.hasData.remoteDataTimeoutText', {
+ defaultMessage:
+ 'Checking for data on remote clusters timed out. One or more remote clusters may be unavailable.',
+ }),
+ });
+
return {
/**
* Check to see if ES data exists
*/
- hasESData: async (): Promise => {
+ hasESData: async ({
+ onRemoteDataTimeout = showRemoteDataTimeoutToast,
+ }: HasEsDataParams = {}): Promise => {
if (callResolveCluster) {
- return hasESDataViaResolveCluster();
+ return hasESDataViaResolveCluster(onRemoteDataTimeout);
}
return hasESDataViaResolveIndex();
},
@@ -82,6 +121,9 @@ export class HasData {
// ES Data
+ private isResponseError = (e: Error): e is IHttpFetchError =>
+ isHttpFetchError(e) && isObject(e.body) && 'message' in e.body && 'statusCode' in e.body;
+
private responseToItemArray = (response: IndicesResponse): IndicesResponseModified[] => {
const { indices = [], aliases = [] } = response;
const source: IndicesResponseModified[] = [];
diff --git a/src/plugins/data_views/server/index.ts b/src/plugins/data_views/server/index.ts
index d72a50d20e31c..143bea2ba5d51 100644
--- a/src/plugins/data_views/server/index.ts
+++ b/src/plugins/data_views/server/index.ts
@@ -47,7 +47,6 @@ const configSchema = schema.object({
schema.boolean({ defaultValue: false }),
schema.never()
),
-
dataTiersExcludedForFields: schema.conditional(
schema.contextRef('serverless'),
true,
@@ -60,6 +59,7 @@ const configSchema = schema.object({
schema.boolean({ defaultValue: false }),
schema.boolean({ defaultValue: true })
),
+ hasEsDataTimeout: schema.number({ defaultValue: 5000 }),
});
type ConfigType = TypeOf;
diff --git a/src/plugins/data_views/server/plugin.ts b/src/plugins/data_views/server/plugin.ts
index 8decac6c36b1f..9e79da893949a 100644
--- a/src/plugins/data_views/server/plugin.ts
+++ b/src/plugins/data_views/server/plugin.ts
@@ -63,9 +63,11 @@ export class DataViewsServerPlugin
registerRoutes({
http: core.http,
+ logger: this.logger,
getStartServices: core.getStartServices,
isRollupsEnabled: () => this.rollupsEnabled,
dataViewRestCounter,
+ hasEsDataTimeout: config.hasEsDataTimeout,
});
expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices }));
diff --git a/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.test.ts b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.test.ts
new file mode 100644
index 0000000000000..7ca07d25bf773
--- /dev/null
+++ b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.test.ts
@@ -0,0 +1,319 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import type { MockedKeys } from '@kbn/utility-types-jest';
+import { IKibanaResponse, Logger, RequestHandlerContext } from '@kbn/core/server';
+import { httpServerMock } from '@kbn/core/server/mocks';
+import { createHandler, crossClusterPatterns, patterns } from './has_es_data';
+import { loggerMock } from '@kbn/logging-mocks';
+
+const mockEsDataTimeout = 5000;
+
+describe('has_es_data route', () => {
+ let mockLogger: MockedKeys;
+
+ beforeEach(() => {
+ mockLogger = loggerMock.create();
+ });
+
+ it('should return hasEsData: true if there are matching local indices', async () => {
+ const mockESClient = {
+ indices: {
+ resolveCluster: jest.fn().mockResolvedValue({
+ local: { matching_indices: true },
+ }),
+ },
+ };
+ const mockContext = {
+ core: {
+ elasticsearch: { client: { asCurrentUser: mockESClient } },
+ },
+ } as unknown as RequestHandlerContext;
+ const mockRequest = httpServerMock.createKibanaRequest();
+ const mockResponse = httpServerMock.createResponseFactory();
+ jest
+ .spyOn(mockResponse, 'ok')
+ .mockImplementation((params) => params as unknown as IKibanaResponse);
+ const handler = createHandler(mockLogger, mockEsDataTimeout);
+ const response = await handler(mockContext, mockRequest, mockResponse);
+ expect(mockESClient.indices.resolveCluster).toBeCalledTimes(1);
+ expect(mockESClient.indices.resolveCluster).toBeCalledWith(
+ {
+ name: patterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockResponse.ok).toBeCalledTimes(1);
+ expect(mockResponse.ok).toBeCalledWith({ body: { hasEsData: true } });
+ expect(response).toEqual({ body: { hasEsData: true } });
+ });
+
+ it('should return hasEsData: true if there are no matching local indices but matching remote indices', async () => {
+ const mockESClient = {
+ indices: {
+ resolveCluster: jest
+ .fn()
+ .mockImplementation(({ name }) =>
+ name === patterns
+ ? { local: { matching_indices: false } }
+ : name === crossClusterPatterns
+ ? { remote: { matching_indices: true } }
+ : {}
+ ),
+ },
+ };
+ const mockContext = {
+ core: {
+ elasticsearch: { client: { asCurrentUser: mockESClient } },
+ },
+ } as unknown as RequestHandlerContext;
+ const mockRequest = httpServerMock.createKibanaRequest();
+ const mockResponse = httpServerMock.createResponseFactory();
+ jest
+ .spyOn(mockResponse, 'ok')
+ .mockImplementation((params) => params as unknown as IKibanaResponse);
+ const handler = createHandler(mockLogger, mockEsDataTimeout);
+ const response = await handler(mockContext, mockRequest, mockResponse);
+ expect(mockESClient.indices.resolveCluster).toBeCalledTimes(2);
+ expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith(
+ 1,
+ {
+ name: patterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith(
+ 2,
+ {
+ name: crossClusterPatterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockResponse.ok).toBeCalledTimes(1);
+ expect(mockResponse.ok).toBeCalledWith({ body: { hasEsData: true } });
+ expect(response).toEqual({ body: { hasEsData: true } });
+ });
+
+ it('should return hasEsData: false if there are no matching local or remote indices', async () => {
+ const mockESClient = {
+ indices: {
+ resolveCluster: jest.fn().mockResolvedValue({
+ local: { matching_indices: false },
+ remote: { matching_indices: false },
+ }),
+ },
+ };
+ const mockContext = {
+ core: {
+ elasticsearch: { client: { asCurrentUser: mockESClient } },
+ },
+ } as unknown as RequestHandlerContext;
+ const mockRequest = httpServerMock.createKibanaRequest();
+ const mockResponse = httpServerMock.createResponseFactory();
+ jest
+ .spyOn(mockResponse, 'ok')
+ .mockImplementation((params) => params as unknown as IKibanaResponse);
+ const handler = createHandler(mockLogger, mockEsDataTimeout);
+ const response = await handler(mockContext, mockRequest, mockResponse);
+ expect(mockESClient.indices.resolveCluster).toBeCalledTimes(2);
+ expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith(
+ 1,
+ {
+ name: patterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith(
+ 2,
+ {
+ name: crossClusterPatterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockResponse.ok).toBeCalledTimes(1);
+ expect(mockResponse.ok).toBeCalledWith({ body: { hasEsData: false } });
+ expect(response).toEqual({ body: { hasEsData: false } });
+ });
+
+ it('should return a 504 response and log a warning if the local data request times out', async () => {
+ const mockESClient = {
+ indices: {
+ resolveCluster: jest.fn().mockRejectedValue({ name: 'TimeoutError' }),
+ },
+ };
+ const mockContext = {
+ core: {
+ elasticsearch: { client: { asCurrentUser: mockESClient } },
+ },
+ } as unknown as RequestHandlerContext;
+ const mockRequest = httpServerMock.createKibanaRequest();
+ const mockResponse = httpServerMock.createResponseFactory();
+ jest
+ .spyOn(mockResponse, 'customError')
+ .mockImplementation((params) => params as unknown as IKibanaResponse);
+ const handler = createHandler(mockLogger, mockEsDataTimeout);
+ const response = await handler(mockContext, mockRequest, mockResponse);
+ expect(mockESClient.indices.resolveCluster).toBeCalledTimes(1);
+ expect(mockESClient.indices.resolveCluster).toBeCalledWith(
+ {
+ name: patterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockResponse.customError).toBeCalledTimes(1);
+ expect(mockResponse.customError).toBeCalledWith({
+ statusCode: 504,
+ body: {
+ message: 'Timeout while checking for Elasticsearch data',
+ attributes: { failureReason: 'local_data_timeout' },
+ },
+ });
+ expect(response).toEqual({
+ statusCode: 504,
+ body: {
+ message: 'Timeout while checking for Elasticsearch data',
+ attributes: { failureReason: 'local_data_timeout' },
+ },
+ });
+ expect(mockLogger.warn).toBeCalledTimes(1);
+ expect(mockLogger.warn).toBeCalledWith(
+ 'Timeout while checking for Elasticsearch data: local_data_timeout. Current timeout value is 5000ms. ' +
+ 'Use "data_views.hasEsDataTimeout" in kibana.yml to change it, or set to 0 to disable timeouts.'
+ );
+ });
+
+ it('should return a 504 response and log a warning if the remote data request times out', async () => {
+ const mockESClient = {
+ indices: {
+ resolveCluster: jest.fn().mockImplementation(({ name }) => {
+ if (name === patterns) {
+ return { local: { matching_indices: false } };
+ }
+
+ if (name === crossClusterPatterns) {
+ // eslint-disable-next-line no-throw-literal
+ throw { name: 'TimeoutError' };
+ }
+
+ return {};
+ }),
+ },
+ };
+ const mockContext = {
+ core: {
+ elasticsearch: { client: { asCurrentUser: mockESClient } },
+ },
+ } as unknown as RequestHandlerContext;
+ const mockRequest = httpServerMock.createKibanaRequest();
+ const mockResponse = httpServerMock.createResponseFactory();
+ jest
+ .spyOn(mockResponse, 'customError')
+ .mockImplementation((params) => params as unknown as IKibanaResponse);
+ const handler = createHandler(mockLogger, mockEsDataTimeout);
+ const response = await handler(mockContext, mockRequest, mockResponse);
+ expect(mockESClient.indices.resolveCluster).toBeCalledTimes(2);
+ expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith(
+ 1,
+ {
+ name: patterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith(
+ 2,
+ {
+ name: crossClusterPatterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockResponse.customError).toBeCalledTimes(1);
+ expect(mockResponse.customError).toBeCalledWith({
+ statusCode: 504,
+ body: {
+ message: 'Timeout while checking for Elasticsearch data',
+ attributes: { failureReason: 'remote_data_timeout' },
+ },
+ });
+ expect(response).toEqual({
+ statusCode: 504,
+ body: {
+ message: 'Timeout while checking for Elasticsearch data',
+ attributes: { failureReason: 'remote_data_timeout' },
+ },
+ });
+ expect(mockLogger.warn).toBeCalledTimes(1);
+ expect(mockLogger.warn).toBeCalledWith(
+ 'Timeout while checking for Elasticsearch data: remote_data_timeout. Current timeout value is 5000ms. ' +
+ 'Use "data_views.hasEsDataTimeout" in kibana.yml to change it, or set to 0 to disable timeouts.'
+ );
+ });
+
+ it('should return a 500 response and log an error if the request fails for an unknown reason', async () => {
+ const someError = new Error('Some error');
+ const mockESClient = {
+ indices: {
+ resolveCluster: jest.fn().mockRejectedValue(someError),
+ },
+ };
+ const mockContext = {
+ core: {
+ elasticsearch: { client: { asCurrentUser: mockESClient } },
+ },
+ } as unknown as RequestHandlerContext;
+ const mockRequest = httpServerMock.createKibanaRequest();
+ const mockResponse = httpServerMock.createResponseFactory();
+ jest
+ .spyOn(mockResponse, 'customError')
+ .mockImplementation((params) => params as unknown as IKibanaResponse);
+ const handler = createHandler(mockLogger, mockEsDataTimeout);
+ const response = await handler(mockContext, mockRequest, mockResponse);
+ expect(mockESClient.indices.resolveCluster).toBeCalledTimes(1);
+ expect(mockESClient.indices.resolveCluster).toBeCalledWith(
+ {
+ name: patterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: mockEsDataTimeout }
+ );
+ expect(mockResponse.customError).toBeCalledTimes(1);
+ expect(mockResponse.customError).toBeCalledWith({
+ statusCode: 500,
+ body: {
+ message: 'Error while checking for Elasticsearch data',
+ attributes: { failureReason: 'unknown' },
+ },
+ });
+ expect(response).toEqual({
+ statusCode: 500,
+ body: {
+ message: 'Error while checking for Elasticsearch data',
+ attributes: { failureReason: 'unknown' },
+ },
+ });
+ expect(mockLogger.error).toBeCalledTimes(1);
+ expect(mockLogger.error).toBeCalledWith(someError);
+ });
+});
diff --git a/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts
index 4f3fb9f19a6ff..72b2e508ba529 100644
--- a/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts
+++ b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts
@@ -7,34 +7,124 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { IRouter, RequestHandlerContext } from '@kbn/core/server';
-import type { VersionedRoute } from '@kbn/core-http-server';
+import type { ElasticsearchClient, IRouter, Logger, RequestHandlerContext } from '@kbn/core/server';
+import type { KibanaResponseFactory, VersionedRoute } from '@kbn/core-http-server';
import { schema } from '@kbn/config-schema';
-import { DEFAULT_ASSETS_TO_IGNORE } from '../../../common';
+import { DEFAULT_ASSETS_TO_IGNORE, HasEsDataFailureReason } from '../../../common';
type Handler = Parameters['addVersion']>[1];
-const patterns = ['*', '-.*'].concat(
+export const patterns = ['*', '-.*'].concat(
DEFAULT_ASSETS_TO_IGNORE.DATA_STREAMS_TO_IGNORE.map((ds) => `-${ds}`)
);
-const crossClusterPatterns = patterns.map((ds) => `*:${ds}`);
+export const crossClusterPatterns = patterns.map((ds) => `*:${ds}`);
-export const handler: Handler = async (ctx: RequestHandlerContext, req, res) => {
- const core = await ctx.core;
- const elasticsearchClient = core.elasticsearch.client.asCurrentUser;
- const response = await elasticsearchClient.indices.resolveCluster({
- name: patterns.concat(crossClusterPatterns),
- allow_no_indices: true,
- ignore_unavailable: true,
- });
+export const createHandler =
+ (parentLogger: Logger, hasEsDataTimeout: number): Handler =>
+ async (ctx, _, res) => {
+ const logger = parentLogger.get('hasEsData');
+ const core = await ctx.core;
+ const elasticsearchClient = core.elasticsearch.client.asCurrentUser;
+ const commonParams: Omit = {
+ elasticsearchClient,
+ logger,
+ res,
+ hasEsDataTimeout,
+ };
- const hasEsData = !!Object.values(response).find((cluster) => cluster.matching_indices);
+ const localDataResponse = await hasEsData({
+ ...commonParams,
+ matchPatterns: patterns,
+ timeoutReason: HasEsDataFailureReason.localDataTimeout,
+ });
- return res.ok({ body: { hasEsData } });
+ if (localDataResponse) {
+ return localDataResponse;
+ }
+
+ const remoteDataResponse = await hasEsData({
+ ...commonParams,
+ matchPatterns: crossClusterPatterns,
+ timeoutReason: HasEsDataFailureReason.remoteDataTimeout,
+ });
+
+ if (remoteDataResponse) {
+ return remoteDataResponse;
+ }
+
+ return res.ok({ body: { hasEsData: false } });
+ };
+
+interface HasEsDataParams {
+ elasticsearchClient: ElasticsearchClient;
+ logger: Logger;
+ res: KibanaResponseFactory;
+ matchPatterns: string[];
+ hasEsDataTimeout: number;
+ timeoutReason: HasEsDataFailureReason;
+}
+
+const timeoutMessage = 'Timeout while checking for Elasticsearch data';
+const errorMessage = 'Error while checking for Elasticsearch data';
+
+const hasEsData = async ({
+ elasticsearchClient,
+ logger,
+ res,
+ matchPatterns,
+ hasEsDataTimeout,
+ timeoutReason,
+}: HasEsDataParams) => {
+ try {
+ const response = await elasticsearchClient.indices.resolveCluster(
+ {
+ name: matchPatterns,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ },
+ { requestTimeout: hasEsDataTimeout === 0 ? undefined : hasEsDataTimeout }
+ );
+
+ const hasData = Object.values(response).some((cluster) => cluster.matching_indices);
+
+ if (hasData) {
+ return res.ok({ body: { hasEsData: true } });
+ }
+ } catch (e) {
+ if (e.name === 'TimeoutError') {
+ const warningMessage =
+ `${timeoutMessage}: ${timeoutReason}. Current timeout value is ${hasEsDataTimeout}ms. ` +
+ `Use "data_views.hasEsDataTimeout" in kibana.yml to change it, or set to 0 to disable timeouts.`;
+
+ logger.warn(warningMessage);
+
+ return res.customError({
+ statusCode: 504,
+ body: {
+ message: timeoutMessage,
+ attributes: { failureReason: timeoutReason },
+ },
+ });
+ }
+
+ logger.error(e);
+
+ return res.customError({
+ statusCode: 500,
+ body: {
+ message: errorMessage,
+ attributes: { failureReason: HasEsDataFailureReason.unknown },
+ },
+ });
+ }
};
-export const registerHasEsDataRoute = (router: IRouter): void => {
+export const registerHasEsDataRoute = (
+ router: IRouter,
+ logger: Logger,
+ hasEsDataTimeout: number
+): void => {
router.versioned
.get({
path: '/internal/data_views/has_es_data',
@@ -51,9 +141,18 @@ export const registerHasEsDataRoute = (router: IRouter): void => {
hasEsData: schema.boolean(),
}),
},
+ 400: {
+ body: () =>
+ schema.object({
+ message: schema.string(),
+ attributes: schema.object({
+ failureReason: schema.string(),
+ }),
+ }),
+ },
},
},
},
- handler
+ createHandler(logger, hasEsDataTimeout)
);
};
diff --git a/src/plugins/data_views/server/routes.ts b/src/plugins/data_views/server/routes.ts
index e5803423d819e..9e8501f928f14 100644
--- a/src/plugins/data_views/server/routes.ts
+++ b/src/plugins/data_views/server/routes.ts
@@ -7,8 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { HttpServiceSetup, StartServicesAccessor } from '@kbn/core/server';
-import { UsageCounter } from '@kbn/usage-collection-plugin/server';
+import type { HttpServiceSetup, Logger, StartServicesAccessor } from '@kbn/core/server';
+import type { UsageCounter } from '@kbn/usage-collection-plugin/server';
import { routes } from './rest_api_routes/public';
import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from './types';
@@ -20,19 +20,23 @@ import { registerFields } from './rest_api_routes/internal/fields';
interface RegisterRoutesArgs {
http: HttpServiceSetup;
+ logger: Logger;
getStartServices: StartServicesAccessor<
DataViewsServerPluginStartDependencies,
DataViewsServerPluginStart
>;
isRollupsEnabled: () => boolean;
dataViewRestCounter?: UsageCounter;
+ hasEsDataTimeout: number;
}
export function registerRoutes({
http,
+ logger,
getStartServices,
- dataViewRestCounter,
isRollupsEnabled,
+ dataViewRestCounter,
+ hasEsDataTimeout,
}: RegisterRoutesArgs) {
const router = http.createRouter();
@@ -42,5 +46,5 @@ export function registerRoutes({
registerFieldForWildcard(router, getStartServices, isRollupsEnabled);
registerFields(router, getStartServices, isRollupsEnabled);
registerHasDataViewsRoute(router);
- registerHasEsDataRoute(router);
+ registerHasEsDataRoute(router, logger, hasEsDataTimeout);
}
diff --git a/src/plugins/data_views/tsconfig.json b/src/plugins/data_views/tsconfig.json
index 312de968d6408..45992b3548f8e 100644
--- a/src/plugins/data_views/tsconfig.json
+++ b/src/plugins/data_views/tsconfig.json
@@ -34,6 +34,9 @@
"@kbn/core-saved-objects-server",
"@kbn/logging",
"@kbn/crypto-browser",
+ "@kbn/core-http-browser",
+ "@kbn/core-http-browser-internal",
+ "@kbn/logging-mocks",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/packages/ai-infra/inference-common/index.ts b/x-pack/packages/ai-infra/inference-common/index.ts
index 2791896c801ef..4b5ef3a5cfda1 100644
--- a/x-pack/packages/ai-infra/inference-common/index.ts
+++ b/x-pack/packages/ai-infra/inference-common/index.ts
@@ -34,6 +34,9 @@ export {
type ChatCompleteStreamResponse,
type ChatCompleteResponse,
type ChatCompletionTokenCount,
+ type BoundChatCompleteAPI,
+ type BoundChatCompleteOptions,
+ type UnboundChatCompleteOptions,
withoutTokenCountEvents,
withoutChunkEvents,
isChatCompletionMessageEvent,
@@ -59,6 +62,9 @@ export {
type OutputUpdateEvent,
type Output,
type OutputEvent,
+ type BoundOutputAPI,
+ type BoundOutputOptions,
+ type UnboundOutputOptions,
isOutputCompleteEvent,
isOutputUpdateEvent,
isOutputEvent,
diff --git a/x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts b/x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts
new file mode 100644
index 0000000000000..083620ed99a93
--- /dev/null
+++ b/x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 type { ChatCompleteOptions, ChatCompleteCompositeResponse } from './api';
+import type { ToolOptions } from './tools';
+
+/**
+ * Static options used to call the {@link BoundChatCompleteAPI}
+ */
+export type BoundChatCompleteOptions<
+ TToolOptions extends ToolOptions = ToolOptions,
+ TStream extends boolean = false
+> = Pick, 'connectorId' | 'functionCalling'>;
+
+/**
+ * Options used to call the {@link BoundChatCompleteAPI}
+ */
+export type UnboundChatCompleteOptions<
+ TToolOptions extends ToolOptions = ToolOptions,
+ TStream extends boolean = false
+> = Omit, 'connectorId' | 'functionCalling'>;
+
+/**
+ * Version of {@link ChatCompleteAPI} that got pre-bound to a set of static parameters
+ */
+export type BoundChatCompleteAPI = <
+ TToolOptions extends ToolOptions = ToolOptions,
+ TStream extends boolean = false
+>(
+ options: UnboundChatCompleteOptions
+) => ChatCompleteCompositeResponse;
diff --git a/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts b/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts
index ca69f39b273e5..3daa898ab2e1a 100644
--- a/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts
+++ b/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts
@@ -13,6 +13,11 @@ export type {
ChatCompleteStreamResponse,
ChatCompleteResponse,
} from './api';
+export type {
+ BoundChatCompleteAPI,
+ BoundChatCompleteOptions,
+ UnboundChatCompleteOptions,
+} from './bound_api';
export {
ChatCompletionEventType,
type ChatCompletionMessageEvent,
diff --git a/x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts b/x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts
new file mode 100644
index 0000000000000..967dac20c0568
--- /dev/null
+++ b/x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 type { OutputOptions, OutputCompositeResponse } from './api';
+import type { ToolSchema } from '../chat_complete/tool_schema';
+
+/**
+ * Static options used to call the {@link BoundOutputAPI}
+ */
+export type BoundOutputOptions<
+ TId extends string = string,
+ TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined,
+ TStream extends boolean = false
+> = Pick, 'connectorId' | 'functionCalling'>;
+
+/**
+ * Options used to call the {@link BoundOutputAPI}
+ */
+export type UnboundOutputOptions<
+ TId extends string = string,
+ TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined,
+ TStream extends boolean = false
+> = Omit, 'connectorId' | 'functionCalling'>;
+
+/**
+ * Version of {@link OutputAPI} that got pre-bound to a set of static parameters
+ */
+export type BoundOutputAPI = <
+ TId extends string = string,
+ TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined,
+ TStream extends boolean = false
+>(
+ options: UnboundOutputOptions
+) => OutputCompositeResponse;
diff --git a/x-pack/packages/ai-infra/inference-common/src/output/index.ts b/x-pack/packages/ai-infra/inference-common/src/output/index.ts
index a3039005b2f7c..d4e17967b50f5 100644
--- a/x-pack/packages/ai-infra/inference-common/src/output/index.ts
+++ b/x-pack/packages/ai-infra/inference-common/src/output/index.ts
@@ -12,6 +12,7 @@ export type {
OutputResponse,
OutputStreamResponse,
} from './api';
+export type { BoundOutputAPI, BoundOutputOptions, UnboundOutputOptions } from './bound_api';
export {
OutputEventType,
type OutputCompleteEvent,
diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
index 70635a2f8c362..226064204bc2a 100644
--- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
@@ -91,7 +91,8 @@ describe('AllCases', () => {
jest.clearAllMocks();
});
- describe('empty table', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/162852
+ describe.skip('empty table', () => {
beforeEach(() => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx
index 2b009f05f3bb1..c73e35fe1397d 100644
--- a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx
+++ b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx
@@ -9,7 +9,8 @@ import moment from 'moment';
import { METRIC_TYPE_VALUES, MetricTypes } from '../../../common/rest_types';
import { getDataUsageMetricsFiltersFromUrlParams } from './use_charts_url_params';
-describe('#getDataUsageMetricsFiltersFromUrlParams', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/200888
+describe.skip('#getDataUsageMetricsFiltersFromUrlParams', () => {
const getMetricTypesAsArray = (): MetricTypes[] => {
return [...METRIC_TYPE_VALUES];
};
diff --git a/x-pack/plugins/data_usage/server/config.ts b/x-pack/plugins/data_usage/server/config.ts
index 7dd664f35288b..c6721592b6aac 100644
--- a/x-pack/plugins/data_usage/server/config.ts
+++ b/x-pack/plugins/data_usage/server/config.ts
@@ -20,7 +20,6 @@ export const configSchema = schema.object({
schema.object({
certificate: schema.maybe(schema.string()),
key: schema.maybe(schema.string()),
- ca: schema.maybe(schema.string()),
})
),
})
diff --git a/x-pack/plugins/data_usage/server/services/autoops_api.ts b/x-pack/plugins/data_usage/server/services/autoops_api.ts
index c1b96a973d9d7..582cd7ab33046 100644
--- a/x-pack/plugins/data_usage/server/services/autoops_api.ts
+++ b/x-pack/plugins/data_usage/server/services/autoops_api.ts
@@ -52,12 +52,23 @@ export class AutoOpsAPIService {
throw new AutoOpsError(AUTO_OPS_MISSING_CONFIG_ERROR);
}
+ if (!autoopsConfig.api?.url) {
+ this.logger.error(`[AutoOps API] Missing API URL in the configuration.`, errorMetadata);
+ throw new AutoOpsError('Missing API URL in AutoOps configuration.');
+ }
+
+ if (!autoopsConfig.api?.tls?.certificate || !autoopsConfig.api?.tls?.key) {
+ this.logger.error(
+ `[AutoOps API] Missing required TLS certificate or key in the configuration.`,
+ errorMetadata
+ );
+ throw new AutoOpsError('Missing required TLS certificate or key in AutoOps configuration.');
+ }
+
this.logger.debug(
- `[AutoOps API] Creating autoops agent with TLS cert: ${
- autoopsConfig?.api?.tls?.certificate ? '[REDACTED]' : 'undefined'
- } and TLS key: ${autoopsConfig?.api?.tls?.key ? '[REDACTED]' : 'undefined'}
- and TLS ca: ${autoopsConfig?.api?.tls?.ca ? '[REDACTED]' : 'undefined'}`
+ `[AutoOps API] Creating autoops agent with request URL: ${autoopsConfig.api.url} and TLS cert: [REDACTED] and TLS key: [REDACTED]`
);
+
const controller = new AbortController();
const tlsConfig = this.createTlsConfig(autoopsConfig);
const cloudSetup = appContextService.getCloud();
@@ -169,7 +180,6 @@ export class AutoOpsAPIService {
enabled: true,
certificate: autoopsConfig?.api?.tls?.certificate,
key: autoopsConfig?.api?.tls?.key,
- certificateAuthorities: autoopsConfig?.api?.tls?.ca,
})
);
}
@@ -187,7 +197,6 @@ export class AutoOpsAPIService {
...requestConfig.httpsAgent.options,
cert: requestConfig.httpsAgent.options.cert ? 'REDACTED' : undefined,
key: requestConfig.httpsAgent.options.key ? 'REDACTED' : undefined,
- ca: requestConfig.httpsAgent.options.ca ? 'REDACTED' : undefined,
},
},
});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx
index 109e9c73bd778..1d6d6750e06e6 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx
@@ -129,7 +129,8 @@ describe('stepStepSelectAgentPolicy', () => {
});
});
- describe('with multiple agent policies', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/197985
+ describe.skip('with multiple agent policies', () => {
beforeEach(() => {
testRenderer = createFleetTestRendererMock();
useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: true });
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx
index d70ed67247207..deb8402af5bea 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx
@@ -24,16 +24,14 @@ import { isAgentUpgradeable, ExperimentalFeaturesService } from '../../../../ser
import { AgentHealth } from '../../components';
import type { Pagination } from '../../../../hooks';
-import { useAgentVersion, useGetListOutputsForPolicies } from '../../../../hooks';
+import { useAgentVersion } from '../../../../hooks';
import { useLink, useAuthz } from '../../../../hooks';
import { AgentPolicySummaryLine } from '../../../../components';
import { Tags } from '../../components/tags';
-import type { AgentMetrics, OutputsForAgentPolicy } from '../../../../../../../common/types';
+import type { AgentMetrics } from '../../../../../../../common/types';
import { formatAgentCPU, formatAgentMemory } from '../../services/agent_metrics';
-import { AgentPolicyOutputsSummary } from './agent_policy_outputs_summary';
-
import { AgentUpgradeStatus } from './agent_upgrade_status';
import { EmptyPrompt } from './empty_prompt';
@@ -45,8 +43,6 @@ const AGENTS_TABLE_FIELDS = {
METRICS: 'metrics',
VERSION: 'local_metadata.elastic.agent.version',
LAST_CHECKIN: 'last_checkin',
- OUTPUT_INTEGRATION: 'output_integrations',
- OUTPUT_MONITORING: 'output_monitoring',
};
function safeMetadata(val: any) {
@@ -128,14 +124,6 @@ export const AgentListTable: React.FC = (props: Props) => {
: [];
}, [agents, isAgentSelectable, showUpgradeable, totalAgents]);
- // get the policyIds of the agents shown on the page
- const policyIds = useMemo(() => {
- return agentsShown.map((agent) => agent?.policy_id ?? '');
- }, [agentsShown]);
- const allOutputs = useGetListOutputsForPolicies({
- ids: policyIds,
- });
-
const noItemsMessage =
isLoading && isCurrentRequestIncremented ? (
= (props: Props) => {
render: (lastCheckin: string) =>
lastCheckin ? : undefined,
},
- {
- field: AGENTS_TABLE_FIELDS.OUTPUT_INTEGRATION,
- sortable: true,
- truncateText: true,
- name: i18n.translate('xpack.fleet.agentList.integrationsOutputTitle', {
- defaultMessage: 'Output for integrations',
- }),
- width: '180px',
- render: (outputs: OutputsForAgentPolicy[], agent: Agent) => {
- if (!agent?.policy_id) return null;
-
- const outputsForPolicy = allOutputs?.data?.items.find(
- (item) => item.agentPolicyId === agent?.policy_id
- );
- return ;
- },
- },
- {
- field: AGENTS_TABLE_FIELDS.OUTPUT_MONITORING,
- sortable: true,
- truncateText: true,
- name: i18n.translate('xpack.fleet.agentList.monitoringOutputTitle', {
- defaultMessage: 'Output for monitoring',
- }),
- width: '180px',
- render: (outputs: OutputsForAgentPolicy[], agent: Agent) => {
- if (!agent?.policy_id) return null;
-
- const outputsForPolicy = allOutputs?.data?.items.find(
- (item) => item.agentPolicyId === agent?.policy_id
- );
- return ;
- },
- },
{
field: AGENTS_TABLE_FIELDS.VERSION,
sortable: true,
diff --git a/x-pack/plugins/inference/README.md b/x-pack/plugins/inference/README.md
index 935ae31bd6bc6..bba5b4cdcfc27 100644
--- a/x-pack/plugins/inference/README.md
+++ b/x-pack/plugins/inference/README.md
@@ -77,6 +77,25 @@ class MyPlugin {
}
```
+### Binding common parameters
+
+It is also possible to bind a client to its configuration parameters, to avoid passing connectorId
+to every call, for example, using the `bindTo` parameter when creating the client.
+
+```ts
+const inferenceClient = myStartDeps.inference.getClient({
+ request,
+ bindTo: {
+ connectorId: 'my-connector-id',
+ functionCalling: 'simulated',
+ }
+});
+
+const chatResponse = inferenceClient.chatComplete({
+ messages: [{ role: MessageRole.User, content: 'Do something' }],
+});
+```
+
## APIs
### `chatComplete` API:
diff --git a/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.test.ts b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.test.ts
new file mode 100644
index 0000000000000..039fd0410d254
--- /dev/null
+++ b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.test.ts
@@ -0,0 +1,126 @@
+/*
+ * 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 {
+ BoundChatCompleteOptions,
+ ChatCompleteAPI,
+ MessageRole,
+ UnboundChatCompleteOptions,
+} from '@kbn/inference-common';
+import { bindChatComplete } from './bind_chat_complete';
+
+describe('bindChatComplete', () => {
+ let chatComplete: ChatCompleteAPI & jest.MockedFn;
+
+ beforeEach(() => {
+ chatComplete = jest.fn();
+ });
+
+ it('calls chatComplete with both bound and unbound params', async () => {
+ const bound: BoundChatCompleteOptions = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ };
+
+ const unbound: UnboundChatCompleteOptions = {
+ messages: [{ role: MessageRole.User, content: 'hello there' }],
+ };
+
+ const boundApi = bindChatComplete(chatComplete, bound);
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ ...bound,
+ ...unbound,
+ });
+ });
+
+ it('forwards the response from chatComplete', async () => {
+ const expectedReturnValue = Symbol('something');
+ chatComplete.mockResolvedValue(expectedReturnValue as any);
+
+ const boundApi = bindChatComplete(chatComplete, { connectorId: 'my-connector' });
+
+ const result = await boundApi({
+ messages: [{ role: MessageRole.User, content: 'hello there' }],
+ });
+
+ expect(result).toEqual(expectedReturnValue);
+ });
+
+ it('only passes the expected parameters from the bound param object', async () => {
+ const bound = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ foo: 'bar',
+ } as BoundChatCompleteOptions;
+
+ const unbound: UnboundChatCompleteOptions = {
+ messages: [{ role: MessageRole.User, content: 'hello there' }],
+ };
+
+ const boundApi = bindChatComplete(chatComplete, bound);
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ messages: unbound.messages,
+ });
+ });
+
+ it('ignores mutations of the bound parameters after binding', async () => {
+ const bound: BoundChatCompleteOptions = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ };
+
+ const unbound: UnboundChatCompleteOptions = {
+ messages: [{ role: MessageRole.User, content: 'hello there' }],
+ };
+
+ const boundApi = bindChatComplete(chatComplete, bound);
+
+ bound.connectorId = 'some-other-id';
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ messages: unbound.messages,
+ });
+ });
+
+ it('does not allow overriding bound parameters with the unbound object', async () => {
+ const bound: BoundChatCompleteOptions = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ };
+
+ const unbound = {
+ messages: [{ role: MessageRole.User, content: 'hello there' }],
+ connectorId: 'overridden',
+ } as UnboundChatCompleteOptions;
+
+ const boundApi = bindChatComplete(chatComplete, bound);
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ messages: unbound.messages,
+ });
+ });
+});
diff --git a/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.ts b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.ts
new file mode 100644
index 0000000000000..3030dee641223
--- /dev/null
+++ b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 type {
+ ChatCompleteAPI,
+ ChatCompleteOptions,
+ BoundChatCompleteAPI,
+ BoundChatCompleteOptions,
+ UnboundChatCompleteOptions,
+ ToolOptions,
+} from '@kbn/inference-common';
+
+/**
+ * Bind chatComplete to the provided parameters,
+ * returning a bound version of the API.
+ */
+export function bindChatComplete(
+ chatComplete: ChatCompleteAPI,
+ boundParams: BoundChatCompleteOptions
+): BoundChatCompleteAPI;
+export function bindChatComplete(
+ chatComplete: ChatCompleteAPI,
+ boundParams: BoundChatCompleteOptions
+) {
+ const { connectorId, functionCalling } = boundParams;
+ return (unboundParams: UnboundChatCompleteOptions) => {
+ const params: ChatCompleteOptions = {
+ ...unboundParams,
+ connectorId,
+ functionCalling,
+ };
+ return chatComplete(params);
+ };
+}
diff --git a/x-pack/plugins/inference/common/chat_complete/index.ts b/x-pack/plugins/inference/common/chat_complete/index.ts
new file mode 100644
index 0000000000000..9eaa850fc8195
--- /dev/null
+++ b/x-pack/plugins/inference/common/chat_complete/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export { bindChatComplete } from './bind_chat_complete';
diff --git a/x-pack/plugins/inference/common/index.ts b/x-pack/plugins/inference/common/index.ts
index 19b24d53a389a..79433cbc71a68 100644
--- a/x-pack/plugins/inference/common/index.ts
+++ b/x-pack/plugins/inference/common/index.ts
@@ -12,6 +12,6 @@ export {
export { generateFakeToolCallId } from './utils/generate_fake_tool_call_id';
-export { createOutputApi } from './create_output_api';
+export { createOutputApi } from './output';
export type { ChatCompleteRequestBody, GetConnectorsResponseBody } from './http_apis';
diff --git a/x-pack/plugins/inference/common/output/bind_output.test.ts b/x-pack/plugins/inference/common/output/bind_output.test.ts
new file mode 100644
index 0000000000000..65741acbd8a3e
--- /dev/null
+++ b/x-pack/plugins/inference/common/output/bind_output.test.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { BoundOutputOptions, OutputAPI, UnboundOutputOptions } from '@kbn/inference-common';
+import { bindOutput } from './bind_output';
+
+describe('createScopedOutputAPI', () => {
+ let chatComplete: OutputAPI & jest.MockedFn;
+
+ beforeEach(() => {
+ chatComplete = jest.fn();
+ });
+
+ it('calls chatComplete with both bound and unbound params', async () => {
+ const bound: BoundOutputOptions = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ };
+
+ const unbound: UnboundOutputOptions = {
+ id: 'foo',
+ input: 'hello there',
+ };
+
+ const boundApi = bindOutput(chatComplete, bound);
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ ...bound,
+ ...unbound,
+ });
+ });
+
+ it('forwards the response from chatComplete', async () => {
+ const expectedReturnValue = Symbol('something');
+ chatComplete.mockResolvedValue(expectedReturnValue as any);
+
+ const boundApi = bindOutput(chatComplete, { connectorId: 'my-connector' });
+
+ const result = await boundApi({
+ id: 'foo',
+ input: 'hello there',
+ });
+
+ expect(result).toEqual(expectedReturnValue);
+ });
+
+ it('only passes the expected parameters from the bound param object', async () => {
+ const bound = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ foo: 'bar',
+ } as BoundOutputOptions;
+
+ const unbound: UnboundOutputOptions = {
+ id: 'foo',
+ input: 'hello there',
+ };
+
+ const boundApi = bindOutput(chatComplete, bound);
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ id: 'foo',
+ input: 'hello there',
+ });
+ });
+
+ it('ignores mutations of the bound parameters after binding', async () => {
+ const bound: BoundOutputOptions = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ };
+
+ const unbound: UnboundOutputOptions = {
+ id: 'foo',
+ input: 'hello there',
+ };
+
+ const boundApi = bindOutput(chatComplete, bound);
+
+ bound.connectorId = 'some-other-id';
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ id: 'foo',
+ input: 'hello there',
+ });
+ });
+
+ it('does not allow overriding bound parameters with the unbound object', async () => {
+ const bound: BoundOutputOptions = {
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ };
+
+ const unbound = {
+ id: 'foo',
+ input: 'hello there',
+ connectorId: 'overridden',
+ } as UnboundOutputOptions;
+
+ const boundApi = bindOutput(chatComplete, bound);
+
+ await boundApi({ ...unbound });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: 'some-id',
+ functionCalling: 'native',
+ id: 'foo',
+ input: 'hello there',
+ });
+ });
+});
diff --git a/x-pack/plugins/inference/common/output/bind_output.ts b/x-pack/plugins/inference/common/output/bind_output.ts
new file mode 100644
index 0000000000000..45ac434d5ffd6
--- /dev/null
+++ b/x-pack/plugins/inference/common/output/bind_output.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 type {
+ OutputAPI,
+ OutputOptions,
+ BoundOutputAPI,
+ BoundOutputOptions,
+ UnboundOutputOptions,
+ ToolSchema,
+} from '@kbn/inference-common';
+
+/**
+ * Bind output to the provided parameters,
+ * returning a bound version of the API.
+ */
+export function bindOutput(
+ chatComplete: OutputAPI,
+ boundParams: BoundOutputOptions
+): BoundOutputAPI;
+export function bindOutput(chatComplete: OutputAPI, boundParams: BoundOutputOptions) {
+ const { connectorId, functionCalling } = boundParams;
+ return (unboundParams: UnboundOutputOptions) => {
+ const params: OutputOptions = {
+ ...unboundParams,
+ connectorId,
+ functionCalling,
+ };
+ return chatComplete(params);
+ };
+}
diff --git a/x-pack/plugins/inference/common/create_output_api.test.ts b/x-pack/plugins/inference/common/output/create_output_api.test.ts
similarity index 100%
rename from x-pack/plugins/inference/common/create_output_api.test.ts
rename to x-pack/plugins/inference/common/output/create_output_api.test.ts
diff --git a/x-pack/plugins/inference/common/create_output_api.ts b/x-pack/plugins/inference/common/output/create_output_api.ts
similarity index 97%
rename from x-pack/plugins/inference/common/create_output_api.ts
rename to x-pack/plugins/inference/common/output/create_output_api.ts
index e5dd2eeda2cbd..d263f733bf4ee 100644
--- a/x-pack/plugins/inference/common/create_output_api.ts
+++ b/x-pack/plugins/inference/common/output/create_output_api.ts
@@ -16,7 +16,7 @@ import {
withoutTokenCountEvents,
} from '@kbn/inference-common';
import { isObservable, map } from 'rxjs';
-import { ensureMultiTurn } from './utils/ensure_multi_turn';
+import { ensureMultiTurn } from '../utils/ensure_multi_turn';
export function createOutputApi(chatCompleteApi: ChatCompleteAPI): OutputAPI;
export function createOutputApi(chatCompleteApi: ChatCompleteAPI) {
diff --git a/x-pack/plugins/inference/common/output/index.ts b/x-pack/plugins/inference/common/output/index.ts
new file mode 100644
index 0000000000000..4c6f053d6ed85
--- /dev/null
+++ b/x-pack/plugins/inference/common/output/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export { createOutputApi } from './create_output_api';
+export { bindOutput } from './bind_output';
diff --git a/x-pack/plugins/inference/public/plugin.tsx b/x-pack/plugins/inference/public/plugin.tsx
index f1023bc9c2546..614c2107c0a06 100644
--- a/x-pack/plugins/inference/public/plugin.tsx
+++ b/x-pack/plugins/inference/public/plugin.tsx
@@ -7,7 +7,7 @@
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import type { Logger } from '@kbn/logging';
-import { createOutputApi } from '../common/create_output_api';
+import { createOutputApi } from '../common/output';
import type { GetConnectorsResponseBody } from '../common/http_apis';
import { createChatCompleteApi } from './chat_complete';
import type {
diff --git a/x-pack/plugins/inference/scripts/util/kibana_client.ts b/x-pack/plugins/inference/scripts/util/kibana_client.ts
index ad6c21cf4b248..ef6f1c4fdcdce 100644
--- a/x-pack/plugins/inference/scripts/util/kibana_client.ts
+++ b/x-pack/plugins/inference/scripts/util/kibana_client.ts
@@ -28,7 +28,7 @@ import {
} from '@kbn/inference-common';
import type { ChatCompleteRequestBody } from '../../common/http_apis';
import type { InferenceConnector } from '../../common/connectors';
-import { createOutputApi } from '../../common/create_output_api';
+import { createOutputApi } from '../../common/output/create_output_api';
import { eventSourceStreamIntoObservable } from '../../server/util/event_source_stream_into_observable';
// eslint-disable-next-line spaced-comment
diff --git a/x-pack/plugins/inference/server/chat_complete/api.ts b/x-pack/plugins/inference/server/chat_complete/api.ts
index cf325e72ddf3a..13b1c8d87270c 100644
--- a/x-pack/plugins/inference/server/chat_complete/api.ts
+++ b/x-pack/plugins/inference/server/chat_complete/api.ts
@@ -16,14 +16,14 @@ import {
type ToolOptions,
ChatCompleteOptions,
} from '@kbn/inference-common';
-import type { InferenceStartDependencies } from '../types';
+import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
import { getConnectorById } from '../util/get_connector_by_id';
import { getInferenceAdapter } from './adapters';
import { createInferenceExecutor, chunksIntoMessage, streamToResponse } from './utils';
interface CreateChatCompleteApiOptions {
request: KibanaRequest;
- actions: InferenceStartDependencies['actions'];
+ actions: ActionsPluginStart;
logger: Logger;
}
diff --git a/x-pack/plugins/inference/server/index.ts b/x-pack/plugins/inference/server/index.ts
index 60ce870020feb..128e90a58308d 100644
--- a/x-pack/plugins/inference/server/index.ts
+++ b/x-pack/plugins/inference/server/index.ts
@@ -15,7 +15,7 @@ import type {
} from './types';
import { InferencePlugin } from './plugin';
-export type { InferenceClient } from './types';
+export type { InferenceClient, BoundInferenceClient } from './inference_client';
export type { InferenceServerSetup, InferenceServerStart };
export { naturalLanguageToEsql } from './tasks/nl_to_esql';
diff --git a/x-pack/plugins/inference/server/inference_client/bind_client.ts b/x-pack/plugins/inference/server/inference_client/bind_client.ts
new file mode 100644
index 0000000000000..4600ed1364ed3
--- /dev/null
+++ b/x-pack/plugins/inference/server/inference_client/bind_client.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 type { BoundChatCompleteOptions } from '@kbn/inference-common';
+import { bindChatComplete } from '../../common/chat_complete';
+import { bindOutput } from '../../common/output';
+import type { InferenceClient, BoundInferenceClient } from './types';
+
+export const bindClient = (
+ unboundClient: InferenceClient,
+ boundParams: BoundChatCompleteOptions
+): BoundInferenceClient => {
+ return {
+ ...unboundClient,
+ chatComplete: bindChatComplete(unboundClient.chatComplete, boundParams),
+ output: bindOutput(unboundClient.output, boundParams),
+ };
+};
diff --git a/x-pack/plugins/inference/server/inference_client/create_client.test.ts b/x-pack/plugins/inference/server/inference_client/create_client.test.ts
new file mode 100644
index 0000000000000..98f5502cdfa55
--- /dev/null
+++ b/x-pack/plugins/inference/server/inference_client/create_client.test.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { createClient } from './create_client';
+import { loggerMock, type MockedLogger } from '@kbn/logging-mocks';
+import { httpServerMock } from '@kbn/core/server/mocks';
+import { actionsMock } from '@kbn/actions-plugin/server/mocks';
+
+jest.mock('./inference_client');
+jest.mock('./bind_client');
+import { createInferenceClient } from './inference_client';
+import { bindClient } from './bind_client';
+
+const bindClientMock = bindClient as jest.MockedFn;
+const createInferenceClientMock = createInferenceClient as jest.MockedFn<
+ typeof createInferenceClient
+>;
+
+describe('createClient', () => {
+ let logger: MockedLogger;
+ let actions: ReturnType;
+ let request: ReturnType;
+
+ beforeEach(() => {
+ logger = loggerMock.create();
+ actions = actionsMock.createStart();
+ request = httpServerMock.createKibanaRequest();
+ });
+
+ afterEach(() => {
+ bindClientMock.mockReset();
+ createInferenceClientMock.mockReset();
+ });
+
+ describe('when `bindTo` is not specified', () => {
+ it('calls createInferenceClient and return the client', () => {
+ const expectedResult = Symbol('expected') as any;
+ createInferenceClientMock.mockReturnValue(expectedResult);
+
+ const result = createClient({
+ request,
+ actions,
+ logger,
+ });
+
+ expect(createInferenceClientMock).toHaveBeenCalledTimes(1);
+ expect(createInferenceClientMock).toHaveBeenCalledWith({ request, actions, logger });
+
+ expect(bindClientMock).not.toHaveBeenCalled();
+
+ expect(result).toBe(expectedResult);
+ });
+
+ it('return a client with the expected type', async () => {
+ createInferenceClientMock.mockReturnValue({
+ chatComplete: jest.fn(),
+ } as any);
+
+ const client = createClient({
+ request,
+ actions,
+ logger,
+ });
+
+ // type check on client.chatComplete
+ await client.chatComplete({
+ messages: [],
+ connectorId: '.foo',
+ });
+ });
+ });
+
+ describe('when `bindTo` is specified', () => {
+ it('calls createInferenceClient and bindClient and forward the expected value', () => {
+ const createInferenceResult = Symbol('createInferenceResult') as any;
+ createInferenceClientMock.mockReturnValue(createInferenceResult);
+
+ const bindClientResult = Symbol('bindClientResult') as any;
+ bindClientMock.mockReturnValue(bindClientResult);
+
+ const result = createClient({
+ request,
+ actions,
+ logger,
+ bindTo: {
+ connectorId: '.my-connector',
+ },
+ });
+
+ expect(createInferenceClientMock).toHaveBeenCalledTimes(1);
+ expect(createInferenceClientMock).toHaveBeenCalledWith({
+ request,
+ actions,
+ logger,
+ });
+
+ expect(bindClientMock).toHaveBeenCalledTimes(1);
+ expect(bindClientMock).toHaveBeenCalledWith(createInferenceResult, {
+ connectorId: '.my-connector',
+ });
+
+ expect(result).toBe(bindClientResult);
+ });
+
+ it('return a client with the expected type', async () => {
+ bindClientMock.mockReturnValue({
+ chatComplete: jest.fn(),
+ } as any);
+
+ const client = createClient({
+ request,
+ actions,
+ logger,
+ bindTo: {
+ connectorId: '.foo',
+ },
+ });
+
+ // type check on client.chatComplete
+ await client.chatComplete({
+ messages: [],
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/inference/server/inference_client/create_client.ts b/x-pack/plugins/inference/server/inference_client/create_client.ts
new file mode 100644
index 0000000000000..3507dd7fef8a8
--- /dev/null
+++ b/x-pack/plugins/inference/server/inference_client/create_client.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 type { Logger } from '@kbn/logging';
+import type { KibanaRequest } from '@kbn/core-http-server';
+import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
+import type { BoundChatCompleteOptions } from '@kbn/inference-common';
+import type { BoundInferenceClient, InferenceClient } from './types';
+import { createInferenceClient } from './inference_client';
+import { bindClient } from './bind_client';
+
+interface UnboundOptions {
+ request: KibanaRequest;
+ actions: ActionsPluginStart;
+ logger: Logger;
+}
+
+interface BoundOptions extends UnboundOptions {
+ bindTo: BoundChatCompleteOptions;
+}
+
+export function createClient(options: UnboundOptions): InferenceClient;
+export function createClient(options: BoundOptions): BoundInferenceClient;
+export function createClient(
+ options: UnboundOptions | BoundOptions
+): BoundInferenceClient | InferenceClient {
+ const { actions, request, logger } = options;
+ const client = createInferenceClient({ request, actions, logger });
+ if ('bindTo' in options) {
+ return bindClient(client, options.bindTo);
+ } else {
+ return client;
+ }
+}
diff --git a/x-pack/plugins/inference/server/inference_client/index.ts b/x-pack/plugins/inference/server/inference_client/index.ts
index 03da0e3da200f..9d56ebe7ff61a 100644
--- a/x-pack/plugins/inference/server/inference_client/index.ts
+++ b/x-pack/plugins/inference/server/inference_client/index.ts
@@ -5,28 +5,5 @@
* 2.0.
*/
-import type { Logger } from '@kbn/logging';
-import type { KibanaRequest } from '@kbn/core-http-server';
-import type { InferenceClient, InferenceStartDependencies } from '../types';
-import { createChatCompleteApi } from '../chat_complete';
-import { createOutputApi } from '../../common/create_output_api';
-import { getConnectorById } from '../util/get_connector_by_id';
-
-export function createInferenceClient({
- request,
- actions,
- logger,
-}: { request: KibanaRequest; logger: Logger } & Pick<
- InferenceStartDependencies,
- 'actions'
->): InferenceClient {
- const chatComplete = createChatCompleteApi({ request, actions, logger });
- return {
- chatComplete,
- output: createOutputApi(chatComplete),
- getConnectorById: async (connectorId: string) => {
- const actionsClient = await actions.getActionsClientWithRequest(request);
- return await getConnectorById({ connectorId, actionsClient });
- },
- };
-}
+export { createClient } from './create_client';
+export type { InferenceClient, BoundInferenceClient } from './types';
diff --git a/x-pack/plugins/inference/server/inference_client/inference_client.ts b/x-pack/plugins/inference/server/inference_client/inference_client.ts
new file mode 100644
index 0000000000000..f4c64ebdcce54
--- /dev/null
+++ b/x-pack/plugins/inference/server/inference_client/inference_client.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 type { Logger } from '@kbn/logging';
+import type { KibanaRequest } from '@kbn/core-http-server';
+import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
+import type { InferenceClient } from './types';
+import { createChatCompleteApi } from '../chat_complete';
+import { createOutputApi } from '../../common/output/create_output_api';
+import { getConnectorById } from '../util/get_connector_by_id';
+
+export function createInferenceClient({
+ request,
+ actions,
+ logger,
+}: {
+ request: KibanaRequest;
+ logger: Logger;
+ actions: ActionsPluginStart;
+}): InferenceClient {
+ const chatComplete = createChatCompleteApi({ request, actions, logger });
+ return {
+ chatComplete,
+ output: createOutputApi(chatComplete),
+ getConnectorById: async (connectorId: string) => {
+ const actionsClient = await actions.getActionsClientWithRequest(request);
+ return await getConnectorById({ connectorId, actionsClient });
+ },
+ };
+}
diff --git a/x-pack/plugins/inference/server/inference_client/types.ts b/x-pack/plugins/inference/server/inference_client/types.ts
new file mode 100644
index 0000000000000..193ce83f6d7b6
--- /dev/null
+++ b/x-pack/plugins/inference/server/inference_client/types.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 type {
+ BoundChatCompleteAPI,
+ ChatCompleteAPI,
+ BoundOutputAPI,
+ OutputAPI,
+} from '@kbn/inference-common';
+import type { InferenceConnector } from '../../common/connectors';
+
+/**
+ * An inference client, scoped to a request, that can be used to interact with LLMs.
+ */
+export interface InferenceClient {
+ /**
+ * `chatComplete` requests the LLM to generate a response to
+ * a prompt or conversation, which might be plain text
+ * or a tool call, or a combination of both.
+ */
+ chatComplete: ChatCompleteAPI;
+ /**
+ * `output` asks the LLM to generate a structured (JSON)
+ * response based on a schema and a prompt or conversation.
+ */
+ output: OutputAPI;
+ /**
+ * `getConnectorById` returns an inference connector by id.
+ * Non-inference connectors will throw an error.
+ */
+ getConnectorById: (id: string) => Promise;
+}
+
+/**
+ * A version of the {@link InferenceClient} that is pre-bound to a set of parameters.
+ */
+export interface BoundInferenceClient {
+ /**
+ * `chatComplete` requests the LLM to generate a response to
+ * a prompt or conversation, which might be plain text
+ * or a tool call, or a combination of both.
+ */
+ chatComplete: BoundChatCompleteAPI;
+ /**
+ * `output` asks the LLM to generate a structured (JSON)
+ * response based on a schema and a prompt or conversation.
+ */
+ output: BoundOutputAPI;
+ /**
+ * `getConnectorById` returns an inference connector by id.
+ * Non-inference connectors will throw an error.
+ */
+ getConnectorById: (id: string) => Promise;
+}
diff --git a/x-pack/plugins/inference/server/plugin.ts b/x-pack/plugins/inference/server/plugin.ts
index 2b1a7be0a165c..0f7090f483339 100644
--- a/x-pack/plugins/inference/server/plugin.ts
+++ b/x-pack/plugins/inference/server/plugin.ts
@@ -7,10 +7,16 @@
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
-import { createInferenceClient } from './inference_client';
+import {
+ type BoundInferenceClient,
+ createClient as createInferenceClient,
+ type InferenceClient,
+} from './inference_client';
import { registerRoutes } from './routes';
import type { InferenceConfig } from './config';
-import type {
+import {
+ InferenceBoundClientCreateOptions,
+ InferenceClientCreateOptions,
InferenceServerSetup,
InferenceServerStart,
InferenceSetupDependencies,
@@ -48,12 +54,12 @@ export class InferencePlugin
start(core: CoreStart, pluginsStart: InferenceStartDependencies): InferenceServerStart {
return {
- getClient: ({ request }) => {
+ getClient: (options: T) => {
return createInferenceClient({
- request,
+ ...options,
actions: pluginsStart.actions,
logger: this.logger.get('client'),
- });
+ }) as T extends InferenceBoundClientCreateOptions ? BoundInferenceClient : InferenceClient;
},
};
}
diff --git a/x-pack/plugins/inference/server/routes/chat_complete.ts b/x-pack/plugins/inference/server/routes/chat_complete.ts
index e4e078e58c15a..b363c88352994 100644
--- a/x-pack/plugins/inference/server/routes/chat_complete.ts
+++ b/x-pack/plugins/inference/server/routes/chat_complete.ts
@@ -15,7 +15,7 @@ import type {
} from '@kbn/core/server';
import { MessageRole, ToolCall, ToolChoiceType } from '@kbn/inference-common';
import type { ChatCompleteRequestBody } from '../../common/http_apis';
-import { createInferenceClient } from '../inference_client';
+import { createClient as createInferenceClient } from '../inference_client';
import { InferenceServerStart, InferenceStartDependencies } from '../types';
import { observableIntoEventSourceStream } from '../util/observable_into_event_source_stream';
diff --git a/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts b/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts
index ce45d9a15e4b3..db3ac3b493481 100644
--- a/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts
+++ b/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts
@@ -14,7 +14,7 @@ import type {
ToolOptions,
OutputCompleteEvent,
} from '@kbn/inference-common';
-import type { InferenceClient } from '../../types';
+import type { InferenceClient } from '../../inference_client';
export type NlToEsqlTaskEvent =
| OutputCompleteEvent<
diff --git a/x-pack/plugins/inference/server/types.ts b/x-pack/plugins/inference/server/types.ts
index f538448372e36..8d6d1413f306a 100644
--- a/x-pack/plugins/inference/server/types.ts
+++ b/x-pack/plugins/inference/server/types.ts
@@ -10,8 +10,8 @@ import type {
PluginSetupContract as ActionsPluginSetup,
} from '@kbn/actions-plugin/server';
import type { KibanaRequest } from '@kbn/core-http-server';
-import { ChatCompleteAPI, OutputAPI } from '@kbn/inference-common';
-import { InferenceConnector } from '../common/connectors';
+import type { BoundChatCompleteOptions } from '@kbn/inference-common';
+import type { InferenceClient, BoundInferenceClient } from './inference_client';
/* eslint-disable @typescript-eslint/no-empty-interface*/
@@ -23,37 +23,74 @@ export interface InferenceStartDependencies {
actions: ActionsPluginStart;
}
+/**
+ * Setup contract of the inference plugin.
+ */
export interface InferenceServerSetup {}
-export interface InferenceClient {
- /**
- * `chatComplete` requests the LLM to generate a response to
- * a prompt or conversation, which might be plain text
- * or a tool call, or a combination of both.
- */
- chatComplete: ChatCompleteAPI;
+/**
+ * Options to create an inference client using the {@link InferenceServerStart.getClient} API.
+ */
+export interface InferenceUnboundClientCreateOptions {
/**
- * `output` asks the LLM to generate a structured (JSON)
- * response based on a schema and a prompt or conversation.
+ * The request to scope the client to.
*/
- output: OutputAPI;
+ request: KibanaRequest;
+}
+
+/**
+ * Options to create a bound inference client using the {@link InferenceServerStart.getClient} API.
+ */
+export interface InferenceBoundClientCreateOptions extends InferenceUnboundClientCreateOptions {
/**
- * `getConnectorById` returns an inference connector by id.
- * Non-inference connectors will throw an error.
+ * The parameters to bind the client to.
*/
- getConnectorById: (id: string) => Promise;
+ bindTo: BoundChatCompleteOptions;
}
-interface InferenceClientCreateOptions {
- request: KibanaRequest;
-}
+/**
+ * Options to create an inference client using the {@link InferenceServerStart.getClient} API.
+ */
+export type InferenceClientCreateOptions =
+ | InferenceUnboundClientCreateOptions
+ | InferenceBoundClientCreateOptions;
+/**
+ * Start contract of the inference plugin, exposing APIs to interact with LLMs.
+ */
export interface InferenceServerStart {
/**
- * Creates an inference client, scoped to a request.
+ * Creates an {@link InferenceClient}, scoped to a request.
+ *
+ * @example
+ * ```ts
+ * const inferenceClient = myStartDeps.inference.getClient({ request });
+ *
+ * const chatResponse = inferenceClient.chatComplete({
+ * connectorId: 'my-connector-id',
+ * messages: [{ role: MessageRole.User, content: 'Do something' }],
+ * });
+ * ```
+ *
+ * It is also possible to bind a client to its configuration parameters, to avoid passing connectorId
+ * to every call, for example. Defining the `bindTo` parameter will return a {@link BoundInferenceClient}
+ *
+ * @example
+ * ```ts
+ * const inferenceClient = myStartDeps.inference.getClient({
+ * request,
+ * bindTo: {
+ * connectorId: 'my-connector-id',
+ * functionCalling: 'simulated',
+ * }
+ * });
*
- * @param options {@link InferenceClientCreateOptions}
- * @returns {@link InferenceClient}
+ * const chatResponse = inferenceClient.chatComplete({
+ * messages: [{ role: MessageRole.User, content: 'Do something' }],
+ * });
+ * ```
*/
- getClient: (options: InferenceClientCreateOptions) => InferenceClient;
+ getClient: (
+ options: T
+ ) => T extends InferenceBoundClientCreateOptions ? BoundInferenceClient : InferenceClient;
}
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts
index 64779bb2ba27e..b108cb8985b80 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts
@@ -9,7 +9,8 @@ import { closeAllToasts } from '../../tasks/toasts';
import { login, ROLE } from '../../tasks/login';
import { loadPage } from '../../tasks/common';
-describe('When defining a kibana role for Endpoint security access', { tags: '@ess' }, () => {
+// FLAKY: https://github.com/elastic/kibana/issues/200967
+describe.skip('When defining a kibana role for Endpoint security access', { tags: '@ess' }, () => {
const getAllSubFeatureRows = (): Cypress.Chainable> => {
return cy
.get('#featurePrivilegeControls_siem')
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts
index 424b3fc954c57..d2a86e7899aee 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts
@@ -23,7 +23,8 @@ import {
setSecuritySolutionEndpointGroupPrivilege,
} from '../../screens/stack_management/role_page';
-describe(
+// Failing: See https://github.com/elastic/kibana/issues/200962
+describe.skip(
'When defining a kibana role for Endpoint security access with space awareness enabled',
{
// TODO:PR Remove `'@skipInServerlessMKI` once PR merges to `main`
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts
index 72e87fde6ca2f..8f0b3586066fa 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts
@@ -174,9 +174,11 @@ describe('multiLineStringDiffAlgorithm', () => {
const result = multiLineStringDiffAlgorithm(mockVersions);
const endTime = performance.now();
- // If the regex merge in this function takes over 500ms, this test fails
+ // If the regex merge in this function takes over 1 sec, this test fails
// Performance measurements: https://github.com/elastic/kibana/pull/199388
- expect(endTime - startTime).toBeLessThan(500);
+ // NOTE: despite the fact that this test runs in ~50ms locally, on CI it
+ // runs slower and can be flaky even with a 500ms threshold.
+ expect(endTime - startTime).toBeLessThan(1000);
expect(result).toEqual(
expect.objectContaining({
diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts
index 96f2ff4b00f7f..d466abfd552ea 100644
--- a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts
+++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts
@@ -15,8 +15,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const ui = getService('observabilityAIAssistantUI');
const testSubjects = getService('testSubjects');
- describe('ai assistant management privileges', () => {
- describe('all privileges', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/191707
+ describe.skip('ai assistant management privileges', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/191707
+ describe.skip('all privileges', () => {
before(async () => {
await createAndLoginUserWithCustomRole(getPageObjects, getService, {
// we need all these privileges to view and modify Obs AI Assistant settings view
diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts
index a7447353e805a..60d858206d68e 100644
--- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts
@@ -47,7 +47,8 @@ export default function ({ getService }: FtrProviderContext) {
const UNREGISTERED_TASK_TYPE_ID = 'ce7e1250-3322-11eb-94c1-db6995e83f6b';
const REMOVED_TASK_TYPE_ID = 'be7e1250-3322-11eb-94c1-db6995e83f6a';
- describe('not registered task types', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/200154
+ describe.skip('not registered task types', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/task_manager_removed_types');
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts
index e3402e0c6b80e..22e9a6f04b6e4 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts
@@ -8,7 +8,7 @@
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
- describe('@ess @serverless SecuritySolution Saved Objects', () => {
+ describe('@ess @serverless @serverlessQA SecuritySolution Saved Objects', () => {
loadTestFile(require.resolve('./notes'));
loadTestFile(require.resolve('./pinned_events'));
loadTestFile(require.resolve('./timeline'));
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts
index 0d14c693ea828..b337faad85f07 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts
@@ -8,7 +8,7 @@
import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
export default function ({ loadTestFile }: FtrProviderContextWithSpaces) {
- describe('@ess @serverless SecuritySolution Timeline', () => {
+ describe('@ess @serverless @serverlessQA SecuritySolution Timeline', () => {
loadTestFile(require.resolve('./events'));
loadTestFile(require.resolve('./timeline_details'));
loadTestFile(require.resolve('./timeline'));
diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts
index 97a30d0f340f9..fa0714aa61544 100644
--- a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
+import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
import { services as apmServices } from './apm_api_integration/common/services';
import { services as datasetQualityServices } from './dataset_quality_api_integration/common/services';
@@ -32,6 +32,5 @@ export default createTestConfig({
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
- `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});
diff --git a/x-pack/test_serverless/api_integration/test_suites/search/config.ts b/x-pack/test_serverless/api_integration/test_suites/search/config.ts
index 9f02dc98b88c3..4db3e86bb9787 100644
--- a/x-pack/test_serverless/api_integration/test_suites/search/config.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/search/config.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
+import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
export default createTestConfig({
@@ -28,6 +28,5 @@ export default createTestConfig({
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
- `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});
diff --git a/x-pack/test_serverless/api_integration/test_suites/security/config.ts b/x-pack/test_serverless/api_integration/test_suites/security/config.ts
index 52b933a22b086..511ec3176ef6f 100644
--- a/x-pack/test_serverless/api_integration/test_suites/security/config.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/security/config.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
+import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
export default createTestConfig({
@@ -32,6 +32,5 @@ export default createTestConfig({
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
- `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});
diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.ts b/x-pack/test_serverless/functional/test_suites/observability/config.ts
index 9fffd5623f0a3..41093df640976 100644
--- a/x-pack/test_serverless/functional/test_suites/observability/config.ts
+++ b/x-pack/test_serverless/functional/test_suites/observability/config.ts
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
+import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
export default createTestConfig({
@@ -25,6 +25,5 @@ export default createTestConfig({
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
- `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});
diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts
index aef26951908d0..5c52828a11659 100644
--- a/x-pack/test_serverless/functional/test_suites/search/config.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/config.ts
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
+import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
export default createTestConfig({
@@ -28,7 +28,6 @@ export default createTestConfig({
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
- `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
apps: {
serverlessElasticsearch: {
diff --git a/x-pack/test_serverless/functional/test_suites/security/config.ts b/x-pack/test_serverless/functional/test_suites/security/config.ts
index 1693a07b0e844..6bf456e5f6d55 100644
--- a/x-pack/test_serverless/functional/test_suites/security/config.ts
+++ b/x-pack/test_serverless/functional/test_suites/security/config.ts
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
+import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
export default createTestConfig({
@@ -25,6 +25,5 @@ export default createTestConfig({
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
- `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});