test
diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts
index 8609eef92a268..91c3903a279d0 100644
--- a/src/dev/license_checker/config.ts
+++ b/src/dev/license_checker/config.ts
@@ -87,7 +87,7 @@ export const LICENSE_OVERRIDES = {
'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts
'@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint
'@elastic/ems-client@8.5.3': ['Elastic License 2.0'],
- '@elastic/eui@97.3.0': ['Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0'],
+ '@elastic/eui@97.3.1': ['Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0'],
'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry
'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary
'@bufbuild/protobuf@1.2.1': ['Apache-2.0'], // license (Apache-2.0 AND BSD-3-Clause)
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap
index ef2699747a6b5..81e3d2122ebfa 100644
--- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap
@@ -23,7 +23,7 @@ exports[`Intro component renders correctly 1`] = `
class="euiFlexGroup emotion-euiFlexGroup-wrap-l-flexStart-stretch-row-euiPageHeaderContent__rightSideItems"
>
{
collectionActions.handleChange = jest.fn();
const wrapper = mountWithIntl();
- const operatorInput = findTestSubject(wrapper, 'colorRuleOperator');
+ const operatorInput = findTestSubject(wrapper, 'colorRuleOperator').find('input');
operatorInput.simulate('keyDown', { key: keys.ARROW_DOWN });
operatorInput.simulate('keyDown', { key: keys.ARROW_DOWN });
operatorInput.simulate('keyDown', { key: keys.ENTER });
diff --git a/x-pack/packages/kbn-langchain/server/tracers/telemetry/index.ts b/x-pack/packages/kbn-langchain/server/tracers/telemetry/index.ts
new file mode 100644
index 0000000000000..079c0e9a33087
--- /dev/null
+++ b/x-pack/packages/kbn-langchain/server/tracers/telemetry/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 { TelemetryTracer } from './telemetry_tracer';
diff --git a/x-pack/packages/kbn-langchain/server/tracers/telemetry/telemetry_tracer.test.ts b/x-pack/packages/kbn-langchain/server/tracers/telemetry/telemetry_tracer.test.ts
new file mode 100644
index 0000000000000..bca293ba9957e
--- /dev/null
+++ b/x-pack/packages/kbn-langchain/server/tracers/telemetry/telemetry_tracer.test.ts
@@ -0,0 +1,204 @@
+/*
+ * 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 { AnalyticsServiceSetup, Logger } from '@kbn/core/server';
+import { TelemetryTracer, TelemetryParams } from './telemetry_tracer';
+import { Run } from 'langsmith/schemas';
+import { loggerMock } from '@kbn/logging-mocks';
+
+const mockRun = {
+ inputs: {
+ responseLanguage: 'English',
+ conversationId: 'db8f74c5-7dca-43a3-b592-d56f219dffab',
+ llmType: 'openai',
+ isStream: false,
+ isOssModel: false,
+ },
+ outputs: {
+ input:
+ 'Generate an ESQL query to find documents with `host.name` that contains my favorite color',
+ lastNode: 'agent',
+ steps: [
+ {
+ action: {
+ tool: 'KnowledgeBaseRetrievalTool',
+ toolInput: {
+ query: "user's favorite color",
+ },
+ },
+ observation:
+ '"[{\\"pageContent\\":\\"favorite color is blue\\",\\"metadata\\":{\\"source\\":\\"conversation\\",\\"required\\":false,\\"kbResource\\":\\"user\\"}},{\\"pageContent\\":\\"favorite food is pizza\\",\\"metadata\\":{\\"source\\":\\"conversation\\",\\"required\\":false,\\"kbResource\\":\\"user\\"}}]"',
+ },
+ {
+ action: {
+ tool: 'NaturalLanguageESQLTool',
+ toolInput: {
+ question: 'Generate an ESQL query to find documents with host.name that contains blue',
+ },
+ },
+ observation:
+ '"To find documents with `host.name` that contains \\"blue\\", you can use the `LIKE` operator with wildcards. Here is the ES|QL query:\\n\\n```esql\\nFROM your_index\\n| WHERE host.name LIKE \\"*blue*\\"\\n```\\n\\nReplace `your_index` with the actual name of your index. This query will filter documents where the `host.name` field contains the substring \\"blue\\"."',
+ },
+ {
+ action: {
+ tool: 'KnowledgeBaseRetrievalTool',
+ toolInput: {
+ query: "user's favorite food",
+ },
+ },
+ observation:
+ '"[{\\"pageContent\\":\\"favorite color is blue\\",\\"metadata\\":{\\"source\\":\\"conversation\\",\\"required\\":false,\\"kbResource\\":\\"user\\"}},{\\"pageContent\\":\\"favorite food is pizza\\",\\"metadata\\":{\\"source\\":\\"conversation\\",\\"required\\":false,\\"kbResource\\":\\"user\\"}}]"',
+ },
+ {
+ action: {
+ tool: 'CustomIndexTool',
+ toolInput: {
+ query: 'query about index',
+ },
+ },
+ observation: '"Wow this is totally cool."',
+ },
+ {
+ action: {
+ tool: 'CustomIndexTool',
+ toolInput: {
+ query: 'query about index',
+ },
+ },
+ observation: '"Wow this is totally cool."',
+ },
+ {
+ action: {
+ tool: 'CustomIndexTool',
+ toolInput: {
+ query: 'query about index',
+ },
+ },
+ observation: '"Wow this is totally cool."',
+ },
+ ],
+ hasRespondStep: false,
+ agentOutcome: {
+ returnValues: {
+ output:
+ 'To find documents with `host.name` that contains your favorite color "blue", you can use the `LIKE` operator with wildcards. Here is the ES|QL query:\n\n```esql\nFROM your_index\n| WHERE host.name LIKE "*blue*"\n```\n\nReplace `your_index` with the actual name of your index. This query will filter documents where the `host.name` field contains the substring "blue".',
+ },
+ log: 'To find documents with `host.name` that contains your favorite color "blue", you can use the `LIKE` operator with wildcards. Here is the ES|QL query:\n\n```esql\nFROM your_index\n| WHERE host.name LIKE "*blue*"\n```\n\nReplace `your_index` with the actual name of your index. This query will filter documents where the `host.name` field contains the substring "blue".',
+ },
+ messages: [],
+ chatTitle: 'Welcome',
+ llmType: 'openai',
+ isStream: false,
+ isOssModel: false,
+ conversation: {
+ timestamp: '2024-11-07T17:37:07.400Z',
+ createdAt: '2024-11-07T17:37:07.400Z',
+ users: [
+ {
+ id: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
+ name: 'elastic',
+ },
+ ],
+ title: 'Welcome',
+ category: 'assistant',
+ apiConfig: {
+ connectorId: 'my-gpt4o-ai',
+ actionTypeId: '.gen-ai',
+ },
+ isDefault: true,
+ messages: [
+ {
+ timestamp: '2024-11-07T22:47:45.994Z',
+ content:
+ 'Generate an ESQL query to find documents with `host.name` that contains my favorite color',
+ role: 'user',
+ },
+ ],
+ updatedAt: '2024-11-08T17:01:21.958Z',
+ replacements: {},
+ namespace: 'default',
+ id: 'db8f74c5-7dca-43a3-b592-d56f219dffab',
+ },
+ conversationId: 'db8f74c5-7dca-43a3-b592-d56f219dffab',
+ responseLanguage: 'English',
+ },
+ end_time: 1731085297190,
+ start_time: 1731085289113,
+} as unknown as Run;
+const elasticTools = [
+ 'AlertCountsTool',
+ 'NaturalLanguageESQLTool',
+ 'KnowledgeBaseRetrievalTool',
+ 'KnowledgeBaseWriteTool',
+ 'OpenAndAcknowledgedAlertsTool',
+ 'SecurityLabsKnowledgeBaseTool',
+];
+const mockLogger = loggerMock.create();
+
+describe('TelemetryTracer', () => {
+ let telemetry: AnalyticsServiceSetup;
+ let logger: Logger;
+ let telemetryParams: TelemetryParams;
+ let telemetryTracer: TelemetryTracer;
+ const reportEvent = jest.fn();
+ beforeEach(() => {
+ telemetry = {
+ reportEvent,
+ } as unknown as AnalyticsServiceSetup;
+ logger = mockLogger;
+ telemetryParams = {
+ eventType: 'INVOKE_AI_SUCCESS',
+ assistantStreamingEnabled: true,
+ actionTypeId: '.gen-ai',
+ isEnabledKnowledgeBase: true,
+ model: 'test_model',
+ };
+ telemetryTracer = new TelemetryTracer(
+ {
+ elasticTools,
+ telemetry,
+ telemetryParams,
+ totalTools: 9,
+ },
+ logger
+ );
+ });
+
+ it('should initialize correctly', () => {
+ expect(telemetryTracer.name).toBe('telemetry_tracer');
+ expect(telemetryTracer.elasticTools).toEqual(elasticTools);
+ expect(telemetryTracer.telemetry).toBe(telemetry);
+ expect(telemetryTracer.telemetryParams).toBe(telemetryParams);
+ expect(telemetryTracer.totalTools).toBe(9);
+ });
+
+ it('should not log and report event on chain end if parent_run_id exists', async () => {
+ await telemetryTracer.onChainEnd({ ...mockRun, parent_run_id: '123' });
+
+ expect(logger.get().debug).not.toHaveBeenCalled();
+ expect(telemetry.reportEvent).not.toHaveBeenCalled();
+ });
+
+ it('should log and report event on chain end', async () => {
+ await telemetryTracer.onChainEnd(mockRun);
+
+ expect(logger.get().debug).toHaveBeenCalledWith(expect.any(Function));
+ expect(telemetry.reportEvent).toHaveBeenCalledWith('INVOKE_AI_SUCCESS', {
+ assistantStreamingEnabled: true,
+ actionTypeId: '.gen-ai',
+ isEnabledKnowledgeBase: true,
+ model: 'test_model',
+ isOssModel: false,
+ durationMs: 8077,
+ toolsInvoked: {
+ KnowledgeBaseRetrievalTool: 2,
+ NaturalLanguageESQLTool: 1,
+ CustomTool: 3,
+ },
+ });
+ });
+});
diff --git a/x-pack/packages/kbn-langchain/server/tracers/telemetry/telemetry_tracer.ts b/x-pack/packages/kbn-langchain/server/tracers/telemetry/telemetry_tracer.ts
new file mode 100644
index 0000000000000..7031e638c1fa4
--- /dev/null
+++ b/x-pack/packages/kbn-langchain/server/tracers/telemetry/telemetry_tracer.ts
@@ -0,0 +1,94 @@
+/*
+ * 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 { BaseCallbackHandlerInput } from '@langchain/core/callbacks/base';
+import type { Run } from 'langsmith/schemas';
+import { BaseTracer } from '@langchain/core/tracers/base';
+import { AnalyticsServiceSetup, Logger } from '@kbn/core/server';
+
+export interface TelemetryParams {
+ assistantStreamingEnabled: boolean;
+ actionTypeId: string;
+ isEnabledKnowledgeBase: boolean;
+ eventType: string;
+ model?: string;
+}
+export interface LangChainTracerFields extends BaseCallbackHandlerInput {
+ elasticTools: string[];
+ telemetry: AnalyticsServiceSetup;
+ telemetryParams: TelemetryParams;
+ totalTools: number;
+}
+interface ToolRunStep {
+ action: {
+ tool: string;
+ };
+}
+/**
+ * TelemetryTracer is a tracer that uses event based telemetry to track LangChain events.
+ */
+export class TelemetryTracer extends BaseTracer implements LangChainTracerFields {
+ name = 'telemetry_tracer';
+ logger: Logger;
+ elasticTools: string[];
+ telemetry: AnalyticsServiceSetup;
+ telemetryParams: TelemetryParams;
+ totalTools: number;
+ constructor(fields: LangChainTracerFields, logger: Logger) {
+ super(fields);
+ this.logger = logger.get('telemetryTracer');
+ this.elasticTools = fields.elasticTools;
+ this.telemetry = fields.telemetry;
+ this.telemetryParams = fields.telemetryParams;
+ this.totalTools = fields.totalTools;
+ }
+
+ async onChainEnd(run: Run): Promise {
+ if (!run.parent_run_id) {
+ const { eventType, ...telemetryParams } = this.telemetryParams;
+ const toolsInvoked =
+ run?.outputs && run?.outputs.steps.length
+ ? run.outputs.steps.reduce((acc: { [k: string]: number }, event: ToolRunStep | never) => {
+ if ('action' in event && event?.action?.tool) {
+ if (this.elasticTools.includes(event.action.tool)) {
+ return {
+ ...acc,
+ ...(event.action.tool in acc
+ ? { [event.action.tool]: acc[event.action.tool] + 1 }
+ : { [event.action.tool]: 1 }),
+ };
+ } else {
+ // Custom tool names are user data, so we strip them out
+ return {
+ ...acc,
+ ...('CustomTool' in acc
+ ? { CustomTool: acc.CustomTool + 1 }
+ : { CustomTool: 1 }),
+ };
+ }
+ }
+ return acc;
+ }, {})
+ : {};
+ const telemetryValue = {
+ ...telemetryParams,
+ durationMs: (run.end_time ?? 0) - (run.start_time ?? 0),
+ toolsInvoked,
+ ...(telemetryParams.actionTypeId === '.gen-ai'
+ ? { isOssModel: run.inputs.isOssModel }
+ : {}),
+ };
+ this.logger.debug(
+ () => `Invoke ${eventType} telemetry:\n${JSON.stringify(telemetryValue, null, 2)}`
+ );
+ this.telemetry.reportEvent(eventType, telemetryValue);
+ }
+ }
+
+ // everything below is required for type only
+ protected async persistRun(_run: Run): Promise {}
+}
diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts
index 09bb5b291ef9a..77a1e37df965f 100644
--- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts
+++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts
@@ -6,7 +6,12 @@
*/
import { v4 as uuidv4 } from 'uuid';
-import { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server';
+import {
+ AnalyticsServiceSetup,
+ AuthenticatedUser,
+ ElasticsearchClient,
+ Logger,
+} from '@kbn/core/server';
import {
DocumentEntryCreateFields,
@@ -15,6 +20,10 @@ import {
KnowledgeBaseEntryUpdateProps,
Metadata,
} from '@kbn/elastic-assistant-common';
+import {
+ CREATE_KNOWLEDGE_BASE_ENTRY_ERROR_EVENT,
+ CREATE_KNOWLEDGE_BASE_ENTRY_SUCCESS_EVENT,
+} from '../../lib/telemetry/event_based_telemetry';
import { getKnowledgeBaseEntry } from './get_knowledge_base_entry';
import { CreateKnowledgeBaseEntrySchema, UpdateKnowledgeBaseEntrySchema } from './types';
@@ -27,6 +36,7 @@ export interface CreateKnowledgeBaseEntryParams {
knowledgeBaseEntry: KnowledgeBaseEntryCreateProps | LegacyKnowledgeBaseEntryCreateProps;
global?: boolean;
isV2?: boolean;
+ telemetry: AnalyticsServiceSetup;
}
export const createKnowledgeBaseEntry = async ({
@@ -38,6 +48,7 @@ export const createKnowledgeBaseEntry = async ({
logger,
global = false,
isV2 = false,
+ telemetry,
}: CreateKnowledgeBaseEntryParams): Promise => {
const createdAt = new Date().toISOString();
const body = isV2
@@ -55,6 +66,12 @@ export const createKnowledgeBaseEntry = async ({
entry: knowledgeBaseEntry as unknown as TransformToLegacyCreateSchemaProps['entry'],
global,
});
+ const telemetryPayload = {
+ entryType: body.type,
+ required: body.required ?? false,
+ sharing: body.users.length ? 'private' : 'global',
+ ...(body.type === 'document' ? { source: body.source } : {}),
+ };
try {
const response = await esClient.create({
body,
@@ -63,17 +80,24 @@ export const createKnowledgeBaseEntry = async ({
refresh: 'wait_for',
});
- return await getKnowledgeBaseEntry({
+ const newKnowledgeBaseEntry = await getKnowledgeBaseEntry({
esClient,
knowledgeBaseIndex,
id: response._id,
logger,
user,
});
+
+ telemetry.reportEvent(CREATE_KNOWLEDGE_BASE_ENTRY_SUCCESS_EVENT.eventType, telemetryPayload);
+ return newKnowledgeBaseEntry;
} catch (err) {
logger.error(
`Error creating Knowledge Base Entry: ${err} with kbResource: ${knowledgeBaseEntry.name}`
);
+ telemetry.reportEvent(CREATE_KNOWLEDGE_BASE_ENTRY_ERROR_EVENT.eventType, {
+ ...telemetryPayload,
+ errorMessage: err.message ?? 'Unknown error',
+ });
throw err;
}
};
diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts
index 333fbb796ddd9..50e124321fe6c 100644
--- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts
+++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts
@@ -25,7 +25,7 @@ import {
import pRetry from 'p-retry';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { StructuredTool } from '@langchain/core/tools';
-import { ElasticsearchClient } from '@kbn/core/server';
+import { AnalyticsServiceSetup, ElasticsearchClient } from '@kbn/core/server';
import { IndexPatternsFetcher } from '@kbn/data-views-plugin/server';
import { map } from 'lodash';
import { AIAssistantDataClient, AIAssistantDataClientParams } from '..';
@@ -459,6 +459,8 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
filter: docsCreated.map((c) => `_id:${c}`).join(' OR '),
})
: undefined;
+ // Intentionally no telemetry here - this path only used to install security docs
+ // Plans to make this function private in a different PR so no user entry ever is created in this path
this.options.logger.debug(`created: ${created?.data.hits.hits.length ?? '0'}`);
this.options.logger.debug(() => `errors: ${JSON.stringify(errors, null, 2)}`);
@@ -686,10 +688,12 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
*/
public createKnowledgeBaseEntry = async ({
knowledgeBaseEntry,
+ telemetry,
global = false,
}: {
knowledgeBaseEntry: KnowledgeBaseEntryCreateProps | LegacyKnowledgeBaseEntryCreateProps;
global?: boolean;
+ telemetry: AnalyticsServiceSetup;
}): Promise => {
const authenticatedUser = this.options.currentUser;
@@ -716,6 +720,7 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
user: authenticatedUser,
knowledgeBaseEntry,
global,
+ telemetry,
isV2: this.options.v2KnowledgeBaseEnabled,
});
};
diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts
index 3e573aff2f4c8..da560dfae72dd 100644
--- a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts
+++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts
@@ -15,6 +15,8 @@ import { ExecuteConnectorRequestBody, Message, Replacements } from '@kbn/elastic
import { StreamResponseWithHeaders } from '@kbn/ml-response-stream/server';
import { PublicMethodsOf } from '@kbn/utility-types';
import type { InferenceServerStart } from '@kbn/inference-plugin/server';
+import { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
+import { TelemetryParams } from '@kbn/langchain/server/tracers/telemetry/telemetry_tracer';
import { ResponseBody } from '../types';
import type { AssistantTool } from '../../../types';
import { AIAssistantKnowledgeBaseDataClient } from '../../../ai_assistant_data_clients/knowledge_base';
@@ -55,6 +57,8 @@ export interface AgentExecutorParams {
response?: KibanaResponseFactory;
size?: number;
systemPrompt?: string;
+ telemetry: AnalyticsServiceSetup;
+ telemetryParams?: TelemetryParams;
traceOptions?: TraceOptions;
responseLanguage?: string;
}
diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts
index d1b3514b15b73..0126692b5b6a5 100644
--- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts
+++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts
@@ -7,6 +7,7 @@
import agent, { Span } from 'elastic-apm-node';
import type { Logger } from '@kbn/logging';
+import { TelemetryTracer } from '@kbn/langchain/server/tracers/telemetry';
import { streamFactory, StreamResponseWithHeaders } from '@kbn/ml-response-stream/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import type { KibanaRequest } from '@kbn/core-http-server';
@@ -26,6 +27,7 @@ interface StreamGraphParams {
logger: Logger;
onLlmResponse?: OnLlmResponse;
request: KibanaRequest;
+ telemetryTracer?: TelemetryTracer;
traceOptions?: TraceOptions;
}
@@ -38,6 +40,7 @@ interface StreamGraphParams {
* @param logger
* @param onLlmResponse
* @param request
+ * @param telemetryTracer
* @param traceOptions
*/
export const streamGraph = async ({
@@ -47,6 +50,7 @@ export const streamGraph = async ({
logger,
onLlmResponse,
request,
+ telemetryTracer,
traceOptions,
}: StreamGraphParams): Promise => {
let streamingSpan: Span | undefined;
@@ -84,7 +88,11 @@ export const streamGraph = async ({
const stream = await assistantGraph.streamEvents(
inputs,
{
- callbacks: [apmTracer, ...(traceOptions?.tracers ?? [])],
+ callbacks: [
+ apmTracer,
+ ...(traceOptions?.tracers ?? []),
+ ...(telemetryTracer ? [telemetryTracer] : []),
+ ],
runName: DEFAULT_ASSISTANT_GRAPH_ID,
tags: traceOptions?.tags ?? [],
version: 'v2',
@@ -120,7 +128,11 @@ export const streamGraph = async ({
let finalMessage = '';
let conversationId: string | undefined;
const stream = assistantGraph.streamEvents(inputs, {
- callbacks: [apmTracer, ...(traceOptions?.tracers ?? [])],
+ callbacks: [
+ apmTracer,
+ ...(traceOptions?.tracers ?? []),
+ ...(telemetryTracer ? [telemetryTracer] : []),
+ ],
runName: DEFAULT_ASSISTANT_GRAPH_ID,
streamMode: 'values',
tags: traceOptions?.tags ?? [],
@@ -187,6 +199,7 @@ interface InvokeGraphParams {
assistantGraph: DefaultAssistantGraph;
inputs: GraphInputs;
onLlmResponse?: OnLlmResponse;
+ telemetryTracer?: TelemetryTracer;
traceOptions?: TraceOptions;
}
interface InvokeGraphResponse {
@@ -202,6 +215,7 @@ interface InvokeGraphResponse {
* @param assistantGraph
* @param inputs
* @param onLlmResponse
+ * @param telemetryTracer
* @param traceOptions
*/
export const invokeGraph = async ({
@@ -209,6 +223,7 @@ export const invokeGraph = async ({
assistantGraph,
inputs,
onLlmResponse,
+ telemetryTracer,
traceOptions,
}: InvokeGraphParams): Promise => {
return withAssistantSpan(DEFAULT_ASSISTANT_GRAPH_ID, async (span) => {
@@ -222,7 +237,11 @@ export const invokeGraph = async ({
span.addLabels({ evaluationId: traceOptions?.evaluationId });
}
const r = await assistantGraph.invoke(inputs, {
- callbacks: [apmTracer, ...(traceOptions?.tracers ?? [])],
+ callbacks: [
+ apmTracer,
+ ...(traceOptions?.tracers ?? []),
+ ...(telemetryTracer ? [telemetryTracer] : []),
+ ],
runName: DEFAULT_ASSISTANT_GRAPH_ID,
tags: traceOptions?.tags ?? [],
});
diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts
index 4f043c681f8df..f55006e452cd0 100644
--- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts
+++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts
@@ -13,6 +13,7 @@ import {
createToolCallingAgent,
} from 'langchain/agents';
import { APMTracer } from '@kbn/langchain/server/tracers/apm';
+import { TelemetryTracer } from '@kbn/langchain/server/tracers/telemetry';
import { getLlmClass } from '../../../../routes/utils';
import { EsAnonymizationFieldsSchema } from '../../../../ai_assistant_data_clients/anonymization_fields/types';
import { AssistantToolParams } from '../../../../types';
@@ -44,6 +45,8 @@ export const callAssistantGraph: AgentExecutor = async ({
request,
size,
systemPrompt,
+ telemetry,
+ telemetryParams,
traceOptions,
responseLanguage = 'English',
}) => {
@@ -107,6 +110,7 @@ export const callAssistantGraph: AgentExecutor = async ({
replacements,
request,
size,
+ telemetry,
};
const tools: StructuredTool[] = assistantTools.flatMap(
@@ -150,7 +154,17 @@ export const callAssistantGraph: AgentExecutor = async ({
});
const apmTracer = new APMTracer({ projectName: traceOptions?.projectName ?? 'default' }, logger);
-
+ const telemetryTracer = telemetryParams
+ ? new TelemetryTracer(
+ {
+ elasticTools: assistantTools.map(({ name }) => name),
+ totalTools: tools.length,
+ telemetry,
+ telemetryParams,
+ },
+ logger
+ )
+ : undefined;
const assistantGraph = getDefaultAssistantGraph({
agentRunnable,
dataClients,
@@ -177,6 +191,7 @@ export const callAssistantGraph: AgentExecutor = async ({
logger,
onLlmResponse,
request,
+ telemetryTracer,
traceOptions,
});
}
@@ -186,6 +201,7 @@ export const callAssistantGraph: AgentExecutor = async ({
assistantGraph,
inputs,
onLlmResponse,
+ telemetryTracer,
traceOptions,
});
diff --git a/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts b/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts
index 5ff5ff894dffe..1087703ba13a4 100644
--- a/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts
+++ b/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts
@@ -76,7 +76,16 @@ export const INVOKE_ASSISTANT_SUCCESS_EVENT: EventTypeOpts<{
assistantStreamingEnabled: boolean;
actionTypeId: string;
isEnabledKnowledgeBase: boolean;
+ durationMs: number;
+ ['toolsInvoked.AlertCountsTool']?: number;
+ ['toolsInvoked.NaturalLanguageESQLTool']?: number;
+ ['toolsInvoked.KnowledgeBaseRetrievalTool']?: number;
+ ['toolsInvoked.KnowledgeBaseWriteTool']?: number;
+ ['toolsInvoked.OpenAndAcknowledgedAlertsTool']?: number;
+ ['toolsInvoked.SecurityLabsKnowledgeBaseTool']?: number;
+ ['toolsInvoked.CustomTool']?: number;
model?: string;
+ isOssModel?: boolean;
}> = {
eventType: 'invoke_assistant_success',
schema: {
@@ -105,6 +114,68 @@ export const INVOKE_ASSISTANT_SUCCESS_EVENT: EventTypeOpts<{
description: 'Is knowledge base enabled',
},
},
+ isOssModel: {
+ type: 'boolean',
+ _meta: {
+ description: 'Is OSS model used on the request',
+ optional: true,
+ },
+ },
+ durationMs: {
+ type: 'integer',
+ _meta: {
+ description: 'The duration of the request.',
+ },
+ },
+ 'toolsInvoked.AlertCountsTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
+ 'toolsInvoked.NaturalLanguageESQLTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
+ 'toolsInvoked.KnowledgeBaseRetrievalTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
+ 'toolsInvoked.KnowledgeBaseWriteTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
+ 'toolsInvoked.OpenAndAcknowledgedAlertsTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
+ 'toolsInvoked.SecurityLabsKnowledgeBaseTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
+ 'toolsInvoked.CustomTool': {
+ type: 'long',
+ _meta: {
+ description: 'Number of times tool was invoked.',
+ optional: true,
+ },
+ },
},
};
@@ -261,9 +332,90 @@ export const ATTACK_DISCOVERY_ERROR_EVENT: EventTypeOpts<{
},
};
+export const CREATE_KNOWLEDGE_BASE_ENTRY_SUCCESS_EVENT: EventTypeOpts<{
+ entryType: 'index' | 'document';
+ required: boolean;
+ sharing: 'private' | 'global';
+ source?: string;
+}> = {
+ eventType: 'create_knowledge_base_entry_success',
+ schema: {
+ entryType: {
+ type: 'keyword',
+ _meta: {
+ description: 'Index entry or document entry',
+ },
+ },
+ sharing: {
+ type: 'keyword',
+ _meta: {
+ description: 'Sharing setting: private or global',
+ },
+ },
+ required: {
+ type: 'boolean',
+ _meta: {
+ description: 'Whether this resource should always be included',
+ },
+ },
+ source: {
+ type: 'keyword',
+ _meta: {
+ description: 'Where the knowledge base document entry was created',
+ optional: true,
+ },
+ },
+ },
+};
+
+export const CREATE_KNOWLEDGE_BASE_ENTRY_ERROR_EVENT: EventTypeOpts<{
+ entryType: 'index' | 'document';
+ required: boolean;
+ sharing: 'private' | 'global';
+ source?: string;
+ errorMessage: string;
+}> = {
+ eventType: 'create_knowledge_base_entry_error',
+ schema: {
+ entryType: {
+ type: 'keyword',
+ _meta: {
+ description: 'Index entry or document entry',
+ },
+ },
+ sharing: {
+ type: 'keyword',
+ _meta: {
+ description: 'Sharing setting: private or global',
+ },
+ },
+ required: {
+ type: 'boolean',
+ _meta: {
+ description: 'Whether this resource should always be included',
+ },
+ },
+ source: {
+ type: 'keyword',
+ _meta: {
+ description: 'Where the knowledge base document entry was created',
+ optional: true,
+ },
+ },
+ errorMessage: {
+ type: 'keyword',
+ _meta: {
+ description: 'Error message',
+ },
+ },
+ },
+};
+
export const events: Array> = [
KNOWLEDGE_BASE_EXECUTION_SUCCESS_EVENT,
KNOWLEDGE_BASE_EXECUTION_ERROR_EVENT,
+ CREATE_KNOWLEDGE_BASE_ENTRY_SUCCESS_EVENT,
+ CREATE_KNOWLEDGE_BASE_ENTRY_ERROR_EVENT,
INVOKE_ASSISTANT_SUCCESS_EVENT,
INVOKE_ASSISTANT_ERROR_EVENT,
ATTACK_DISCOVERY_SUCCESS_EVENT,
diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts
index d5db24d44f3e4..e4f520b190b5a 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts
@@ -282,6 +282,7 @@ export const postEvaluateRoute = (
inference,
connectorId: connector.id,
size,
+ telemetry: ctx.elasticAssistant.telemetry,
};
const tools: StructuredTool[] = assistantTools.flatMap(
diff --git a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts
index d25ed5fc77f10..0c5c39f77d692 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts
@@ -30,6 +30,7 @@ import { ActionsClient } from '@kbn/actions-plugin/server';
import { AssistantFeatureKey } from '@kbn/elastic-assistant-common/impl/capabilities';
import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith';
import type { InferenceServerStart } from '@kbn/inference-plugin/server';
+import { INVOKE_ASSISTANT_SUCCESS_EVENT } from '../lib/telemetry/event_based_telemetry';
import { AIAssistantKnowledgeBaseDataClient } from '../ai_assistant_data_clients/knowledge_base';
import { FindResponse } from '../ai_assistant_data_clients/find';
import { EsPromptsSchema } from '../ai_assistant_data_clients/prompts/types';
@@ -46,7 +47,6 @@ import { executeAction, StaticResponse } from '../lib/executor';
import { getLangChainMessages } from '../lib/langchain/helpers';
import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations';
-import { INVOKE_ASSISTANT_SUCCESS_EVENT } from '../lib/telemetry/event_based_telemetry';
import { ElasticAssistantRequestHandlerContext, GetElser } from '../types';
import { callAssistantGraph } from '../lib/langchain/graphs/default_assistant_graph';
@@ -399,6 +399,7 @@ export const langChainExecute = async ({
kbDataClient,
};
+ const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient);
// Shared executor params
const executorParams: AgentExecutorParams = {
abortSignal,
@@ -422,6 +423,14 @@ export const langChainExecute = async ({
responseLanguage,
size: request.body.size,
systemPrompt,
+ telemetry,
+ telemetryParams: {
+ actionTypeId,
+ model: request.body.model,
+ assistantStreamingEnabled: isStream,
+ isEnabledKnowledgeBase: isKnowledgeBaseInstalled,
+ eventType: INVOKE_ASSISTANT_SUCCESS_EVENT.eventType,
+ },
traceOptions: {
projectName: request.body.langSmithProject,
tracers: getLangSmithTracer({
@@ -436,14 +445,6 @@ export const langChainExecute = async ({
executorParams
);
- const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient);
-
- telemetry.reportEvent(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, {
- actionTypeId,
- model: request.body.model,
- assistantStreamingEnabled: isStream,
- isEnabledKnowledgeBase: isKnowledgeBaseInstalled,
- });
return response.ok(result);
};
diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts
index fbe73525578b0..fc49068a09cc9 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts
@@ -6,7 +6,7 @@
*/
import moment from 'moment';
-import type { IKibanaResponse, KibanaResponseFactory } from '@kbn/core/server';
+import { AnalyticsServiceSetup, IKibanaResponse, KibanaResponseFactory } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import {
@@ -20,6 +20,7 @@ import {
} from '@kbn/elastic-assistant-common';
import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common';
+import { CREATE_KNOWLEDGE_BASE_ENTRY_SUCCESS_EVENT } from '../../../lib/telemetry/event_based_telemetry';
import { performChecks } from '../../helpers';
import { KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE } from '../../../../common/constants';
import {
@@ -62,7 +63,8 @@ const buildBulkResponse = (
created = [],
deleted = [],
skipped = [],
- }: KnowledgeBaseEntryBulkCrudActionResults & { errors: BulkOperationError[] }
+ }: KnowledgeBaseEntryBulkCrudActionResults & { errors: BulkOperationError[] },
+ telemetry: AnalyticsServiceSetup
): IKibanaResponse => {
const numSucceeded = updated.length + created.length + deleted.length;
const numSkipped = skipped.length;
@@ -82,6 +84,16 @@ const buildBulkResponse = (
skipped,
};
+ if (created.length) {
+ created.forEach((entry) => {
+ telemetry.reportEvent(CREATE_KNOWLEDGE_BASE_ENTRY_SUCCESS_EVENT.eventType, {
+ entryType: entry.type,
+ required: 'required' in entry ? entry.required ?? false : false,
+ sharing: entry.users.length ? 'private' : 'global',
+ ...(entry.type === 'document' ? { source: entry.source } : {}),
+ });
+ });
+ }
if (numFailed > 0) {
return response.custom({
headers: { 'content-type': 'application/json' },
@@ -289,14 +301,18 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
})
: undefined;
- return buildBulkResponse(response, {
- // @ts-ignore-next-line TS2322
- updated: transformESToKnowledgeBase(docsUpdated),
- created: created?.data ? transformESSearchToKnowledgeBaseEntry(created?.data) : [],
- deleted: docsDeleted ?? [],
- skipped: [],
- errors,
- });
+ return buildBulkResponse(
+ response,
+ {
+ // @ts-ignore-next-line TS2322
+ updated: transformESToKnowledgeBase(docsUpdated),
+ created: created?.data ? transformESSearchToKnowledgeBaseEntry(created?.data) : [],
+ deleted: docsDeleted ?? [],
+ skipped: [],
+ errors,
+ },
+ ctx.elasticAssistant.telemetry
+ );
} catch (err) {
const error = transformError(err);
return assistantResponse.error({
diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts
index 0bfe9de269f7c..d5df2d02055fd 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts
@@ -65,6 +65,7 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
const createResponse = await kbDataClient?.createKnowledgeBaseEntry({
knowledgeBaseEntry: request.body,
global: request.body.users != null && request.body.users.length === 0,
+ telemetry: ctx.elasticAssistant.telemetry,
});
if (createResponse == null) {
diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts
index e84b97ab43d7a..00fec0dcabc6d 100755
--- a/x-pack/plugins/elastic_assistant/server/types.ts
+++ b/x-pack/plugins/elastic_assistant/server/types.ts
@@ -252,4 +252,5 @@ export interface AssistantToolParams {
ExecuteConnectorRequestBody | AttackDiscoveryPostRequestBody
>;
size?: number;
+ telemetry?: AnalyticsServiceSetup;
}
diff --git a/x-pack/plugins/elastic_assistant/tsconfig.json b/x-pack/plugins/elastic_assistant/tsconfig.json
index d3436f28a1d3e..52ed30dde67f8 100644
--- a/x-pack/plugins/elastic_assistant/tsconfig.json
+++ b/x-pack/plugins/elastic_assistant/tsconfig.json
@@ -49,7 +49,8 @@
"@kbn/std",
"@kbn/zod",
"@kbn/inference-plugin",
- "@kbn/data-views-plugin"
+ "@kbn/data-views-plugin",
+ "@kbn/core-analytics-server"
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts
index 4069eeeef5b97..3ae47afbf05bf 100644
--- a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts
+++ b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts
@@ -12,10 +12,12 @@ import type { AIAssistantKnowledgeBaseDataClient } from '@kbn/elastic-assistant-
import { DocumentEntryType } from '@kbn/elastic-assistant-common';
import type { KnowledgeBaseEntryCreateProps } from '@kbn/elastic-assistant-common';
import type { LegacyKnowledgeBaseEntryCreateProps } from '@kbn/elastic-assistant-plugin/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry';
+import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
import { APP_UI_ID } from '../../../../common';
export interface KnowledgeBaseWriteToolParams extends AssistantToolParams {
kbDataClient: AIAssistantKnowledgeBaseDataClient;
+ telemetry: AnalyticsServiceSetup;
}
const toolDetails = {
@@ -34,7 +36,7 @@ export const KNOWLEDGE_BASE_WRITE_TOOL: AssistantTool = {
getTool(params: AssistantToolParams) {
if (!this.isSupported(params)) return null;
- const { kbDataClient, logger } = params as KnowledgeBaseWriteToolParams;
+ const { telemetry, kbDataClient, logger } = params as KnowledgeBaseWriteToolParams;
if (kbDataClient == null) return null;
return new DynamicStructuredTool({
@@ -77,7 +79,7 @@ export const KNOWLEDGE_BASE_WRITE_TOOL: AssistantTool = {
};
logger.debug(() => `knowledgeBaseEntry\n ${JSON.stringify(knowledgeBaseEntry, null, 2)}`);
- const resp = await kbDataClient.createKnowledgeBaseEntry({ knowledgeBaseEntry });
+ const resp = await kbDataClient.createKnowledgeBaseEntry({ knowledgeBaseEntry, telemetry });
if (resp == null) {
return "I'm sorry, but I was unable to add this entry to your knowledge base.";
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/types.ts b/x-pack/plugins/stack_connectors/common/dynamic_config/types.ts
similarity index 100%
rename from x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/types.ts
rename to x-pack/plugins/stack_connectors/common/dynamic_config/types.ts
diff --git a/x-pack/plugins/stack_connectors/common/inference/types.ts b/x-pack/plugins/stack_connectors/common/inference/types.ts
index 9dbd447cb4578..b9561efe24292 100644
--- a/x-pack/plugins/stack_connectors/common/inference/types.ts
+++ b/x-pack/plugins/stack_connectors/common/inference/types.ts
@@ -19,6 +19,7 @@ import {
TextEmbeddingParamsSchema,
TextEmbeddingResponseSchema,
} from './schema';
+import { ConfigProperties } from '../dynamic_config/types';
export type Config = TypeOf;
export type Secrets = TypeOf;
@@ -36,3 +37,17 @@ export type TextEmbeddingParams = TypeOf;
export type TextEmbeddingResponse = TypeOf;
export type StreamingResponse = TypeOf;
+
+export type FieldsConfiguration = Record;
+
+export interface InferenceTaskType {
+ task_type: string;
+ configuration: FieldsConfiguration;
+}
+
+export interface InferenceProvider {
+ provider: string;
+ task_types: InferenceTaskType[];
+ logo?: string;
+ configuration: FieldsConfiguration;
+}
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/additional_options_fields.tsx b/x-pack/plugins/stack_connectors/public/connector_types/inference/additional_options_fields.tsx
index 8973f3124bc86..7a3b1abfd800b 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/additional_options_fields.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/additional_options_fields.tsx
@@ -32,10 +32,10 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
+import { ConfigEntryView } from '../../../common/dynamic_config/types';
import { ConnectorConfigurationFormItems } from '../lib/dynamic_config/connector_configuration_form_items';
import * as i18n from './translations';
import { DEFAULT_TASK_TYPE } from './constants';
-import { ConfigEntryView } from '../lib/dynamic_config/types';
import { Config } from './types';
import { TaskTypeOption } from './helpers';
@@ -52,7 +52,7 @@ interface AdditionalOptionsConnectorFieldsProps {
isEdit: boolean;
optionalProviderFormFields: ConfigEntryView[];
onSetProviderConfigEntry: (key: string, value: unknown) => Promise;
- onTaskTypeOptionsSelect: (taskType: string, provider?: string) => Promise;
+ onTaskTypeOptionsSelect: (taskType: string, provider?: string) => void;
selectedTaskType?: string;
taskTypeFormFields: ConfigEntryView[];
taskTypeSchema: ConfigEntryView[];
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.test.tsx
index 44632e8b08331..d445504011b5f 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.test.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.test.tsx
@@ -12,13 +12,10 @@ import { ConnectorFormTestProvider } from '../lib/test_utils';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createStartServicesMock } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana/kibana_react.mock';
-import { DisplayType, FieldType } from '../lib/dynamic_config/types';
import { useProviders } from './providers/get_providers';
-import { getTaskTypes } from './get_task_types';
-import { HttpSetup } from '@kbn/core-http-browser';
+import { DisplayType, FieldType } from '../../../common/dynamic_config/types';
jest.mock('./providers/get_providers');
-jest.mock('./get_task_types');
const mockUseKibanaReturnValue = createStartServicesMock();
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana', () => ({
@@ -37,13 +34,32 @@ jest.mock('@faker-js/faker', () => ({
}));
const mockProviders = useProviders as jest.Mock;
-const mockTaskTypes = getTaskTypes as jest.Mock;
const providersSchemas = [
{
provider: 'openai',
logo: '', // should be openai logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
+ task_types: [
+ {
+ task_type: 'completion',
+ configuration: {
+ user: {
+ display: DisplayType.TEXTBOX,
+ label: 'User',
+ order: 1,
+ required: false,
+ sensitive: false,
+ tooltip: 'Specifies the user issuing the request.',
+ type: FieldType.STRING,
+ validations: [],
+ value: '',
+ ui_restrictions: [],
+ default_value: null,
+ depends_on: [],
+ },
+ },
+ },
+ ],
configuration: {
api_key: {
display: DisplayType.TEXTBOX,
@@ -106,7 +122,16 @@ const providersSchemas = [
{
provider: 'googleaistudio',
logo: '', // should be googleaistudio logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
+ task_types: [
+ {
+ task_type: 'completion',
+ configuration: {},
+ },
+ {
+ task_type: 'text_embedding',
+ configuration: {},
+ },
+ ],
configuration: {
api_key: {
display: DisplayType.TEXTBOX,
@@ -139,39 +164,6 @@ const providersSchemas = [
},
},
];
-const taskTypesSchemas: Record = {
- googleaistudio: [
- {
- task_type: 'completion',
- configuration: {},
- },
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- openai: [
- {
- task_type: 'completion',
- configuration: {
- user: {
- display: DisplayType.TEXTBOX,
- label: 'User',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the user issuing the request.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
-};
const openAiConnector = {
actionTypeId: '.inference',
@@ -222,9 +214,6 @@ describe('ConnectorFields renders', () => {
isLoading: false,
data: providersSchemas,
});
- mockTaskTypes.mockImplementation(
- (http: HttpSetup, provider: string) => taskTypesSchemas[provider]
- );
});
test('openai provider fields are rendered', async () => {
const { getAllByTestId } = render(
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.tsx
index 35314dc06167d..5f854384dbb54 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/connector.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useEffect, useCallback, useMemo } from 'react';
import {
EuiFormRow,
EuiSpacer,
@@ -31,12 +31,12 @@ import {
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
+import { ConfigEntryView } from '../../../common/dynamic_config/types';
+import { InferenceTaskType } from '../../../common/inference/types';
import { ServiceProviderKeys } from '../../../common/inference/constants';
import { ConnectorConfigurationFormItems } from '../lib/dynamic_config/connector_configuration_form_items';
-import { getTaskTypes } from './get_task_types';
import * as i18n from './translations';
import { DEFAULT_TASK_TYPE } from './constants';
-import { ConfigEntryView } from '../lib/dynamic_config/types';
import { SelectableProvider } from './providers/selectable';
import { Config, Secrets } from './types';
import { generateInferenceEndpointId, getTaskTypeOptions, TaskTypeOption } from './helpers';
@@ -116,13 +116,13 @@ const InferenceAPIConnectorFields: React.FunctionComponent {
+ (taskType: string, provider?: string) => {
// Get task type settings
- const currentTaskTypes = await getTaskTypes(http, provider ?? config?.provider);
+ const currentProvider = providers?.find((p) => p.provider === (provider ?? config?.provider));
+ const currentTaskTypes = currentProvider?.task_types;
const newTaskType = currentTaskTypes?.find((p) => p.task_type === taskType);
setSelectedTaskType(taskType);
- generateInferenceEndpointId(config, setFieldValue);
// transform the schema
const newTaskTypeSchema = Object.keys(newTaskType?.configuration ?? {}).map((k) => ({
@@ -150,19 +150,23 @@ const InferenceAPIConnectorFields: React.FunctionComponent {
+ (provider?: string) => {
const newProvider = providers?.find((p) => p.provider === provider);
// Update task types list available for the selected provider
- const providerTaskTypes = newProvider?.taskTypes ?? [];
+ const providerTaskTypes = (newProvider?.task_types ?? []).map((t) => t.task_type);
setTaskTypeOptions(getTaskTypeOptions(providerTaskTypes));
if (providerTaskTypes.length > 0) {
- await onTaskTypeOptionsSelect(providerTaskTypes[0], provider);
+ onTaskTypeOptionsSelect(providerTaskTypes[0], provider);
}
// Update connector providerSchema
@@ -203,9 +207,8 @@ const InferenceAPIConnectorFields: React.FunctionComponent {
- const getTaskTypeSchema = async () => {
- const currentTaskTypes = await getTaskTypes(http, config?.provider ?? '');
- const newTaskType = currentTaskTypes?.find((p) => p.task_type === config?.taskType);
+ const getTaskTypeSchema = (taskTypes: InferenceTaskType[]) => {
+ const newTaskType = taskTypes.find((p) => p.task_type === config?.taskType);
// transform the schema
const newTaskTypeSchema = Object.keys(newTaskType?.configuration ?? {}).map((k) => ({
@@ -228,7 +231,7 @@ const InferenceAPIConnectorFields: React.FunctionComponent
+ Object.keys(SERVICE_PROVIDERS).includes(config?.provider)
+ ? SERVICE_PROVIDERS[config?.provider as ServiceProviderKeys].icon
+ : undefined,
+ [config?.provider]
+ );
+
+ const providerName = useMemo(
+ () =>
+ Object.keys(SERVICE_PROVIDERS).includes(config?.provider)
+ ? SERVICE_PROVIDERS[config?.provider as ServiceProviderKeys].name
+ : config?.provider,
+ [config?.provider]
+ );
+
const providerSuperSelect = useCallback(
(isInvalid: boolean) => (
jest.resetAllMocks());
-
-describe.skip('getTaskTypes', () => {
- test('should call get inference task types api', async () => {
- const apiResponse = {
- amazonbedrock: [
- {
- task_type: 'completion',
- configuration: {
- max_new_tokens: {
- display: DisplayType.NUMERIC,
- label: 'Max new tokens',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Sets the maximum number for the output tokens to be generated.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- };
- http.get.mockResolvedValueOnce(apiResponse);
-
- const result = await getTaskTypes(http, 'amazonbedrock');
- expect(result).toEqual(apiResponse);
- });
-});
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/get_task_types.ts b/x-pack/plugins/stack_connectors/public/connector_types/inference/get_task_types.ts
deleted file mode 100644
index a4fbbd6a6288b..0000000000000
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/get_task_types.ts
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * 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 { HttpSetup } from '@kbn/core-http-browser';
-import { DisplayType, FieldType } from '../lib/dynamic_config/types';
-import { FieldsConfiguration } from './types';
-
-export interface InferenceTaskType {
- task_type: string;
- configuration: FieldsConfiguration;
-}
-
-// this http param is for the future migrating to real API
-export const getTaskTypes = (http: HttpSetup, provider: string): Promise => {
- const providersTaskTypes: Record = {
- openai: [
- {
- task_type: 'completion',
- configuration: {
- user: {
- display: DisplayType.TEXTBOX,
- label: 'User',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the user issuing the request.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'text_embedding',
- configuration: {
- user: {
- display: DisplayType.TEXTBOX,
- label: 'User',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the user issuing the request.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
- mistral: [
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- hugging_face: [
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- googlevertexai: [
- {
- task_type: 'text_embedding',
- configuration: {
- auto_truncate: {
- display: DisplayType.TOGGLE,
- label: 'Auto truncate',
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'Specifies if the API truncates inputs longer than the maximum token length automatically.',
- type: FieldType.BOOLEAN,
- validations: [],
- value: false,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'rerank',
- configuration: {
- top_n: {
- display: DisplayType.TOGGLE,
- label: 'Top N',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the number of the top n documents, which should be returned.',
- type: FieldType.BOOLEAN,
- validations: [],
- value: false,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
- googleaistudio: [
- {
- task_type: 'completion',
- configuration: {},
- },
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- elasticsearch: [
- {
- task_type: 'rerank',
- configuration: {
- return_documents: {
- display: DisplayType.TOGGLE,
- label: 'Return documents',
- options: [],
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Returns the document instead of only the index.',
- type: FieldType.BOOLEAN,
- validations: [],
- value: true,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'sparse_embedding',
- configuration: {},
- },
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- cohere: [
- {
- task_type: 'completion',
- configuration: {},
- },
- {
- task_type: 'text_embedding',
- configuration: {
- input_type: {
- display: DisplayType.DROPDOWN,
- label: 'Input type',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the type of input passed to the model.',
- type: FieldType.STRING,
- validations: [],
- options: [
- {
- label: 'classification',
- value: 'classification',
- },
- {
- label: 'clusterning',
- value: 'clusterning',
- },
- {
- label: 'ingest',
- value: 'ingest',
- },
- {
- label: 'search',
- value: 'search',
- },
- ],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- truncate: {
- display: DisplayType.DROPDOWN,
- options: [
- {
- label: 'NONE',
- value: 'NONE',
- },
- {
- label: 'START',
- value: 'START',
- },
- {
- label: 'END',
- value: 'END',
- },
- ],
- label: 'Truncate',
- order: 2,
- required: false,
- sensitive: false,
- tooltip: 'Specifies how the API handles inputs longer than the maximum token length.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'rerank',
- configuration: {
- return_documents: {
- display: DisplayType.TOGGLE,
- label: 'Return documents',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specify whether to return doc text within the results.',
- type: FieldType.BOOLEAN,
- validations: [],
- value: false,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- top_n: {
- display: DisplayType.NUMERIC,
- label: 'Top N',
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'The number of most relevant documents to return, defaults to the number of the documents.',
- type: FieldType.INTEGER,
- validations: [],
- value: false,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
- azureopenai: [
- {
- task_type: 'completion',
- configuration: {
- user: {
- display: DisplayType.TEXTBOX,
- label: 'User',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the user issuing the request.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'text_embedding',
- configuration: {
- user: {
- display: DisplayType.TEXTBOX,
- label: 'User',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the user issuing the request.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
- azureaistudio: [
- {
- task_type: 'completion',
- configuration: {
- user: {
- display: DisplayType.TEXTBOX,
- label: 'User',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the user issuing the request.',
- type: FieldType.STRING,
- validations: [],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'text_embedding',
- configuration: {
- do_sample: {
- display: DisplayType.NUMERIC,
- label: 'Do sample',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Instructs the inference process to perform sampling or not.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- max_new_tokens: {
- display: DisplayType.NUMERIC,
- label: 'Max new tokens',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Provides a hint for the maximum number of output tokens to be generated.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- temperature: {
- display: DisplayType.NUMERIC,
- label: 'Temperature',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'A number in the range of 0.0 to 2.0 that specifies the sampling temperature.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- top_p: {
- display: DisplayType.NUMERIC,
- label: 'Top P',
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'A number in the range of 0.0 to 2.0 that is an alternative value to temperature. Should not be used if temperature is specified.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
- amazonbedrock: [
- {
- task_type: 'completion',
- configuration: {
- max_new_tokens: {
- display: DisplayType.NUMERIC,
- label: 'Max new tokens',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Sets the maximum number for the output tokens to be generated.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- temperature: {
- display: DisplayType.NUMERIC,
- label: 'Temperature',
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'A number between 0.0 and 1.0 that controls the apparent creativity of the results.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- top_p: {
- display: DisplayType.NUMERIC,
- label: 'Top P',
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'Alternative to temperature. A number in the range of 0.0 to 1.0, to eliminate low-probability tokens.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- top_k: {
- display: DisplayType.NUMERIC,
- label: 'Top K',
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'Only available for anthropic, cohere, and mistral providers. Alternative to temperature.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- anthropic: [
- {
- task_type: 'completion',
- configuration: {
- max_tokens: {
- display: DisplayType.NUMERIC,
- label: 'Max tokens',
- order: 1,
- required: true,
- sensitive: false,
- tooltip: 'The maximum number of tokens to generate before stopping.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- temperature: {
- display: DisplayType.TEXTBOX,
- label: 'Temperature',
- order: 2,
- required: false,
- sensitive: false,
- tooltip: 'The amount of randomness injected into the response.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- top_p: {
- display: DisplayType.NUMERIC,
- label: 'Top P',
- order: 4,
- required: false,
- sensitive: false,
- tooltip: 'Specifies to use Anthropic’s nucleus sampling.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- top_k: {
- display: DisplayType.NUMERIC,
- label: 'Top K',
- order: 3,
- required: false,
- sensitive: false,
- tooltip: 'Specifies to only sample from the top K options for each subsequent token.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ],
- 'alibabacloud-ai-search': [
- {
- task_type: 'text_embedding',
- configuration: {
- input_type: {
- display: DisplayType.DROPDOWN,
- label: 'Input type',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the type of input passed to the model.',
- type: FieldType.STRING,
- validations: [],
- options: [
- {
- label: 'ingest',
- value: 'ingest',
- },
- {
- label: 'search',
- value: 'search',
- },
- ],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'sparse_embedding',
- configuration: {
- input_type: {
- display: DisplayType.DROPDOWN,
- label: 'Input type',
- order: 1,
- required: false,
- sensitive: false,
- tooltip: 'Specifies the type of input passed to the model.',
- type: FieldType.STRING,
- validations: [],
- options: [
- {
- label: 'ingest',
- value: 'ingest',
- },
- {
- label: 'search',
- value: 'search',
- },
- ],
- value: '',
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- return_token: {
- display: DisplayType.TOGGLE,
- label: 'Return token',
- options: [],
- order: 1,
- required: false,
- sensitive: false,
- tooltip:
- 'If `true`, the token name will be returned in the response. Defaults to `false` which means only the token ID will be returned in the response.',
- type: FieldType.BOOLEAN,
- validations: [],
- value: true,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- task_type: 'completion',
- configuration: {},
- },
- {
- task_type: 'rerank',
- configuration: {},
- },
- ],
- watsonxai: [
- {
- task_type: 'text_embedding',
- configuration: {},
- },
- ],
- };
- return Promise.resolve(providersTaskTypes[provider]);
-};
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/helpers.ts b/x-pack/plugins/stack_connectors/public/connector_types/inference/helpers.ts
index 0e1e4cdaa41ad..8638caa998eff 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/helpers.ts
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/helpers.ts
@@ -7,7 +7,7 @@
import { isEmpty } from 'lodash/fp';
import { ValidationFunc } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
-import { ConfigEntryView } from '../lib/dynamic_config/types';
+import { ConfigEntryView } from '../../../common/dynamic_config/types';
import { Config } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/hidden_fields.tsx b/x-pack/plugins/stack_connectors/public/connector_types/inference/hidden_fields.tsx
index 9b28d35aaaf3a..f6df891b4b9c8 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/hidden_fields.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/hidden_fields.tsx
@@ -8,8 +8,8 @@
import React from 'react';
import { HiddenField } from '@kbn/es-ui-shared-plugin/static/forms/components';
import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
+import { ConfigEntryView } from '../../../common/dynamic_config/types';
import { getNonEmptyValidator } from './helpers';
-import { ConfigEntryView } from '../lib/dynamic_config/types';
export const getProviderSecretsHiddenField = (
providerSchema: ConfigEntryView[],
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/get_providers.ts b/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/get_providers.ts
index 109266c1273fc..badc0cb61030d 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/get_providers.ts
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/get_providers.ts
@@ -9,1025 +9,11 @@ import type { HttpSetup } from '@kbn/core-http-browser';
import { i18n } from '@kbn/i18n';
import { useQuery } from '@tanstack/react-query';
import type { ToastsStart } from '@kbn/core-notifications-browser';
-import { DisplayType, FieldType } from '../../lib/dynamic_config/types';
-import { FieldsConfiguration } from '../types';
+import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../../../common';
+import { InferenceProvider } from '../../../../common/inference/types';
-export interface InferenceProvider {
- provider: string;
- taskTypes: string[];
- logo?: string;
- configuration: FieldsConfiguration;
-}
-
-export const getProviders = (http: HttpSetup): Promise => {
- const providers = [
- {
- provider: 'openai',
- logo: '', // should be openai logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 3,
- required: true,
- sensitive: true,
- tooltip: `The OpenAI API authentication key. For more details about generating OpenAI API keys, refer to the https://platform.openai.com/account/api-keys.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model_id: {
- display: DisplayType.TEXTBOX,
- label: 'Model ID',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: 'The name of the model to use for the inference task.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- organization_id: {
- display: DisplayType.TEXTBOX,
- label: 'Organization ID',
- order: 4,
- required: false,
- sensitive: false,
- tooltip: 'The unique identifier of your organization.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- url: {
- display: DisplayType.TEXTBOX,
- label: 'URL',
- order: 1,
- required: true,
- sensitive: false,
- tooltip:
- 'The OpenAI API endpoint URL. For more information on the URL, refer to the https://platform.openai.com/docs/api-reference.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: 'https://api.openai.com/v1/chat/completions',
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip:
- 'Default number of requests allowed per minute. For text_embedding is 3000. For completion is 500.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'googleaistudio',
- logo: '', // should be googleaistudio logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model_id: {
- display: DisplayType.TEXTBOX,
- label: 'Model ID',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: `ID of the LLM you're using`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'amazonbedrock',
- logo: '', // should be amazonbedrock logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
- configuration: {
- access_key: {
- display: DisplayType.TEXTBOX,
- label: 'Access Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `A valid AWS access key that has permissions to use Amazon Bedrock.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- secret_key: {
- display: DisplayType.TEXTBOX,
- label: 'Secret Key',
- order: 2,
- required: true,
- sensitive: true,
- tooltip: `A valid AWS secret key that is paired with the access_key.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- provider: {
- display: DisplayType.DROPDOWN,
- label: 'Provider',
- order: 3,
- required: true,
- options: [
- {
- label: 'amazontitan',
- value: 'amazontitan',
- },
- {
- label: 'anthropic',
- value: 'anthropic',
- },
- {
- label: 'ai21labs',
- value: 'ai21labs',
- },
- {
- label: 'cohere',
- value: 'cohere',
- },
- {
- label: 'meta',
- value: 'meta',
- },
- {
- label: 'mistral',
- value: 'mistral',
- },
- ],
- sensitive: false,
- tooltip: 'The model provider for your deployment.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model: {
- display: DisplayType.TEXTBOX,
- label: 'Model',
- order: 4,
- required: true,
- sensitive: false,
- tooltip: `The base model ID or an ARN to a custom model based on a foundational model.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- region: {
- display: DisplayType.TEXTBOX,
- label: 'Region',
- order: 5,
- required: true,
- sensitive: false,
- tooltip: `The region that your model or ARN is deployed in.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 6,
- required: false,
- sensitive: false,
- tooltip:
- 'By default, the amazonbedrock service sets the number of requests allowed per minute to 240.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'googlevertexai',
- logo: '', // should be googlevertexai logo here, the hardcoded uses assets/images
- taskTypes: ['text_embedding', 'rerank'],
- configuration: {
- service_account_json: {
- display: DisplayType.TEXTBOX,
- label: 'Credentials JSON',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model_id: {
- display: DisplayType.TEXTBOX,
- label: 'Model ID',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: `ID of the LLM you're using`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- location: {
- display: DisplayType.TEXTBOX,
- label: 'GCP Region',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: `Please provide the GCP region where the Vertex AI API(s) is enabled. For more information, refer to the {geminiVertexAIDocs}.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- project_id: {
- display: DisplayType.TEXTBOX,
- label: 'GCP Project',
- order: 3,
- required: true,
- sensitive: false,
- tooltip:
- 'The GCP Project ID which has Vertex AI API(s) enabled. For more information on the URL, refer to the {geminiVertexAIDocs}.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'mistral',
- logo: '', // should be misral logo here, the hardcoded uses assets/images
- taskTypes: ['text_embedding'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model: {
- display: DisplayType.TEXTBOX,
- label: 'Model',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: `Refer to the Mistral models documentation for the list of available text embedding models`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 4,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: 240,
- depends_on: [],
- },
- max_input_tokens: {
- display: DisplayType.NUMERIC,
- label: 'Maximum input tokens',
- order: 3,
- required: false,
- sensitive: false,
- tooltip: 'Allows you to specify the maximum number of tokens per input.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'hugging_face',
- logo: '', // should be hugging_face logo here, the hardcoded uses assets/images
- taskTypes: ['text_embedding'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 2,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- url: {
- display: DisplayType.TEXTBOX,
- label: 'URL',
- order: 1,
- required: true,
- sensitive: false,
- tooltip: 'The URL endpoint to use for the requests.',
- type: FieldType.STRING,
- validations: [],
- value: 'https://api.openai.com/v1/embeddings',
- ui_restrictions: [],
- default_value: 'https://api.openai.com/v1/embeddings',
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 3,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'elasticsearch',
- logo: '', // elasticsearch logo here
- taskTypes: ['sparse_embedding', 'text_embedding', 'rerank'],
- configuration: {
- model_id: {
- display: DisplayType.DROPDOWN,
- label: 'Model ID',
- order: 1,
- required: true,
- sensitive: false,
- tooltip: `The name of the model to use for the inference task.`,
- type: FieldType.STRING,
- validations: [],
- options: [
- {
- label: '.elser_model_1',
- value: '.elser_model_1',
- },
- {
- label: '.elser_model_2',
- value: '.elser_model_2',
- },
- {
- label: '.elser_model_2_linux-x86_64',
- value: '.elser_model_2_linux-x86_64',
- },
- {
- label: '.multilingual-e5-small',
- value: '.multilingual-e5-small',
- },
- {
- label: '.multilingual-e5-small_linux-x86_64',
- value: '.multilingual-e5-small_linux-x86_64',
- },
- ],
- value: null,
- ui_restrictions: [],
- default_value: '.multilingual-e5-small',
- depends_on: [],
- },
- num_allocations: {
- display: DisplayType.NUMERIC,
- label: 'Number allocations',
- order: 2,
- required: true,
- sensitive: false,
- tooltip:
- 'The total number of allocations this model is assigned across machine learning nodes.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: 1,
- depends_on: [],
- },
- num_threads: {
- display: DisplayType.NUMERIC,
- label: 'Number threads',
- order: 3,
- required: true,
- sensitive: false,
- tooltip: 'Sets the number of threads used by each model allocation during inference.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: 2,
- depends_on: [],
- },
- },
- },
- {
- provider: 'cohere',
- logo: '', // should be cohere logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding', 'rerank'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'azureopenai',
- logo: '', // should be azureopenai logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: false,
- sensitive: true,
- tooltip: `You must provide either an API key or an Entra ID.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- entra_id: {
- display: DisplayType.TEXTBOX,
- label: 'Entra ID',
- order: 2,
- required: false,
- sensitive: true,
- tooltip: `You must provide either an API key or an Entra ID.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- resource_name: {
- display: DisplayType.TEXTBOX,
- label: 'Resource Name',
- order: 3,
- required: true,
- sensitive: false,
- tooltip: `The name of your Azure OpenAI resource`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- api_version: {
- display: DisplayType.TEXTBOX,
- label: 'API version',
- order: 4,
- required: true,
- sensitive: false,
- tooltip: 'The Azure API version ID to use.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- deployment_id: {
- display: DisplayType.TEXTBOX,
- label: 'Deployment ID',
- order: 5,
- required: true,
- sensitive: false,
- tooltip: 'The deployment name of your deployed models.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip:
- 'The azureopenai service sets a default number of requests allowed per minute depending on the task type.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'azureaistudio',
- logo: '', // should be azureaistudio logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'text_embedding'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- target: {
- display: DisplayType.TEXTBOX,
- label: 'Target',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: `The target URL of your Azure AI Studio model deployment.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- endpoint_type: {
- display: DisplayType.DROPDOWN,
- label: 'Endpoint type',
- order: 3,
- required: true,
- sensitive: false,
- tooltip: 'Specifies the type of endpoint that is used in your model deployment.',
- type: FieldType.STRING,
- options: [
- {
- label: 'token',
- value: 'token',
- },
- {
- label: 'realtime',
- value: 'realtime',
- },
- ],
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- provider: {
- display: DisplayType.DROPDOWN,
- label: 'Provider',
- order: 3,
- required: true,
- options: [
- {
- label: 'cohere',
- value: 'cohere',
- },
- {
- label: 'meta',
- value: 'meta',
- },
- {
- label: 'microsoft_phi',
- value: 'microsoft_phi',
- },
- {
- label: 'mistral',
- value: 'mistral',
- },
- {
- label: 'openai',
- value: 'openai',
- },
- {
- label: 'databricks',
- value: 'databricks',
- },
- ],
- sensitive: false,
- tooltip: 'The model provider for your deployment.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'anthropic',
- logo: '', // should be anthropic logo here, the hardcoded uses assets/images
- taskTypes: ['completion'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: `API Key for the provider you're connecting to`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model_id: {
- display: DisplayType.TEXTBOX,
- label: 'Model ID',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: `The name of the model to use for the inference task.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 5,
- required: false,
- sensitive: false,
- tooltip:
- 'By default, the anthropic service sets the number of requests allowed per minute to 50.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'watsonxai',
- logo: '', // should be anthropic logo here, the hardcoded uses assets/images
- taskTypes: ['text_embedding'],
- configuration: {
- api_version: {
- display: DisplayType.TEXTBOX,
- label: 'API version',
- order: 1,
- required: true,
- sensitive: false,
- tooltip: 'The IBM Watsonx API version ID to use.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- project_id: {
- display: DisplayType.TEXTBOX,
- label: 'Project ID',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: '',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- model_id: {
- display: DisplayType.TEXTBOX,
- label: 'Model ID',
- order: 3,
- required: true,
- sensitive: false,
- tooltip: `The name of the model to use for the inference task.`,
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- url: {
- display: DisplayType.TEXTBOX,
- label: 'URL',
- order: 4,
- required: true,
- sensitive: false,
- tooltip: '',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- max_input_tokens: {
- display: DisplayType.NUMERIC,
- label: 'Maximum input tokens',
- order: 5,
- required: false,
- sensitive: false,
- tooltip: 'Allows you to specify the maximum number of tokens per input.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- {
- provider: 'alibabacloud-ai-search',
- logo: '', // should be anthropic logo here, the hardcoded uses assets/images
- taskTypes: ['completion', 'sparse_embedding', 'text_embedding', 'rerank'],
- configuration: {
- api_key: {
- display: DisplayType.TEXTBOX,
- label: 'API Key',
- order: 1,
- required: true,
- sensitive: true,
- tooltip: 'A valid API key for the AlibabaCloud AI Search API.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- service_id: {
- display: DisplayType.DROPDOWN,
- label: 'Project ID',
- order: 2,
- required: true,
- sensitive: false,
- tooltip: 'The name of the model service to use for the {infer} task.',
- type: FieldType.STRING,
- options: [
- {
- label: 'ops-text-embedding-001',
- value: 'ops-text-embedding-001',
- },
- {
- label: 'ops-text-embedding-zh-001',
- value: 'ops-text-embedding-zh-001',
- },
- {
- label: 'ops-text-embedding-en-001',
- value: 'ops-text-embedding-en-001',
- },
- {
- label: 'ops-text-embedding-002',
- value: 'ops-text-embedding-002',
- },
- {
- label: 'ops-text-sparse-embedding-001',
- value: 'ops-text-sparse-embedding-001',
- },
- {
- label: 'ops-bge-reranker-larger',
- value: 'ops-bge-reranker-larger',
- },
- ],
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- host: {
- display: DisplayType.TEXTBOX,
- label: 'Host',
- order: 3,
- required: true,
- sensitive: false,
- tooltip:
- 'The name of the host address used for the {infer} task. You can find the host address at https://opensearch.console.aliyun.com/cn-shanghai/rag/api-key[ the API keys section] of the documentation.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- http_schema: {
- display: DisplayType.DROPDOWN,
- label: 'HTTP Schema',
- order: 4,
- required: true,
- sensitive: false,
- tooltip: '',
- type: FieldType.STRING,
- options: [
- {
- label: 'https',
- value: 'https',
- },
- {
- label: 'http',
- value: 'http',
- },
- ],
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- workspace: {
- display: DisplayType.TEXTBOX,
- label: 'Workspace',
- order: 5,
- required: true,
- sensitive: false,
- tooltip: 'The name of the workspace used for the {infer} task.',
- type: FieldType.STRING,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- 'rate_limit.requests_per_minute': {
- display: DisplayType.NUMERIC,
- label: 'Rate limit',
- order: 6,
- required: false,
- sensitive: false,
- tooltip: 'Minimize the number of rate limit errors.',
- type: FieldType.INTEGER,
- validations: [],
- value: null,
- ui_restrictions: [],
- default_value: null,
- depends_on: [],
- },
- },
- },
- ] as InferenceProvider[];
- return Promise.resolve(
- providers.sort((a, b) => (a.provider > b.provider ? 1 : b.provider > a.provider ? -1 : 0))
- );
+export const getProviders = async (http: HttpSetup): Promise => {
+ return await http.get(`${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_inference/_services`);
};
export const useProviders = (http: HttpSetup, toasts: ToastsStart) => {
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/render_service_provider/service_provider.tsx b/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/render_service_provider/service_provider.tsx
index 5d2c99ffd92ce..5eb8518a5ea15 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/render_service_provider/service_provider.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/render_service_provider/service_provider.tsx
@@ -26,7 +26,7 @@ interface ServiceProviderProps {
searchValue?: string;
}
-type ProviderSolution = 'Observability' | 'Security' | 'Search';
+export type ProviderSolution = 'Observability' | 'Security' | 'Search';
interface ServiceProviderRecord {
icon: string;
@@ -107,9 +107,7 @@ export const ServiceProviderIcon: React.FC = ({ providerKe
return provider ? (
- ) : (
- {providerKey}
- );
+ ) : null;
};
export const ServiceProviderName: React.FC = ({
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/selectable/index.tsx b/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/selectable/index.tsx
index d4527e9c7b9a4..fc31c9dd6c4f7 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/selectable/index.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/providers/selectable/index.tsx
@@ -11,6 +11,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { ServiceProviderKeys } from '../../../../../common/inference/constants';
import {
+ ProviderSolution,
SERVICE_PROVIDERS,
ServiceProviderIcon,
ServiceProviderName,
@@ -47,7 +48,20 @@ const SelectableProviderComponent: React.FC = ({
const renderProviderOption = useCallback>(
(option, searchValue) => {
- const provider = SERVICE_PROVIDERS[option.label as ServiceProviderKeys];
+ const provider = Object.keys(SERVICE_PROVIDERS).includes(option.label)
+ ? SERVICE_PROVIDERS[option.label as ServiceProviderKeys]
+ : undefined;
+
+ const supportedBySolutions = (provider &&
+ provider.solutions.map((solution) => (
+
+ {solution}
+
+ ))) ?? (
+
+ {'Search' as ProviderSolution}
+
+ );
return (
@@ -65,12 +79,7 @@ const SelectableProviderComponent: React.FC = ({
- {provider &&
- provider.solutions.map((solution) => (
-
- {solution}
-
- ))}
+ {supportedBySolutions}
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/inference/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/inference/types.ts
index 150292894b643..1bd55793bc463 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/inference/types.ts
+++ b/x-pack/plugins/stack_connectors/public/connector_types/inference/types.ts
@@ -14,7 +14,6 @@ import {
SparseEmbeddingParams,
TextEmbeddingParams,
} from '../../../common/inference/types';
-import { ConfigProperties } from '../lib/dynamic_config/types';
export type InferenceActionParams =
| { subAction: SUB_ACTION.COMPLETION; subActionParams: ChatCompleteParams }
@@ -22,8 +21,6 @@ export type InferenceActionParams =
| { subAction: SUB_ACTION.SPARSE_EMBEDDING; subActionParams: SparseEmbeddingParams }
| { subAction: SUB_ACTION.TEXT_EMBEDDING; subActionParams: TextEmbeddingParams };
-export type FieldsConfiguration = Record;
-
export interface Config {
taskType: string;
taskTypeConfig?: Record;
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_field.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_field.tsx
index 79ae552a9528a..5560c831c4a61 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_field.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_field.tsx
@@ -25,12 +25,12 @@ import {
} from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
+import { ConfigEntryView, DisplayType } from '../../../../common/dynamic_config/types';
import {
ensureBooleanType,
ensureCorrectTyping,
ensureStringType,
} from './connector_configuration_utils';
-import { ConfigEntryView, DisplayType } from './types';
interface ConnectorConfigurationFieldProps {
configEntry: ConfigEntryView;
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_form_items.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_form_items.tsx
index 3190ac80275f1..a7063d81719a8 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_form_items.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_form_items.tsx
@@ -18,7 +18,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { ConfigEntryView, DisplayType } from './types';
+import { ConfigEntryView, DisplayType } from '../../../../common/dynamic_config/types';
import { ConnectorConfigurationField } from './connector_configuration_field';
interface ConnectorConfigurationFormItemsProps {
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_utils.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_utils.ts
index 182327a180a63..cce5bc15fa56c 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_utils.ts
+++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_utils.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConfigProperties, FieldType } from './types';
+import { ConfigProperties, FieldType } from '../../../../common/dynamic_config/types';
export type ConnectorConfigEntry = ConfigProperties & { key: string };
diff --git a/x-pack/plugins/stack_connectors/server/plugin.ts b/x-pack/plugins/stack_connectors/server/plugin.ts
index aee84d963043d..b20892938735b 100644
--- a/x-pack/plugins/stack_connectors/server/plugin.ts
+++ b/x-pack/plugins/stack_connectors/server/plugin.ts
@@ -8,7 +8,11 @@
import { PluginInitializerContext, Plugin, CoreSetup, Logger } from '@kbn/core/server';
import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server';
import { registerConnectorTypes } from './connector_types';
-import { validSlackApiChannelsRoute, getWellKnownEmailServiceRoute } from './routes';
+import {
+ validSlackApiChannelsRoute,
+ getWellKnownEmailServiceRoute,
+ getInferenceServicesRoute,
+} from './routes';
import {
ExperimentalFeatures,
parseExperimentalConfigValue,
@@ -39,6 +43,7 @@ export class StackConnectorsPlugin implements Plugin {
getWellKnownEmailServiceRoute(router);
validSlackApiChannelsRoute(router, actions.getActionsConfigurationUtilities(), this.logger);
+ getInferenceServicesRoute(router);
registerConnectorTypes({
actions,
diff --git a/x-pack/plugins/stack_connectors/server/routes/get_inference_services.test.ts b/x-pack/plugins/stack_connectors/server/routes/get_inference_services.test.ts
new file mode 100644
index 0000000000000..50596028d80a8
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/server/routes/get_inference_services.test.ts
@@ -0,0 +1,133 @@
+/*
+ * 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 { httpServiceMock, httpServerMock } from '@kbn/core/server/mocks';
+import { coreMock } from '@kbn/core/server/mocks';
+import { getInferenceServicesRoute } from './get_inference_services';
+import { DisplayType, FieldType } from '../../common/dynamic_config/types';
+
+describe('getInferenceServicesRoute', () => {
+ it('returns available service providers', async () => {
+ const router = httpServiceMock.createRouter();
+ const core = coreMock.createRequestHandlerContext();
+
+ const mockResult = [
+ {
+ provider: 'openai',
+ task_types: [
+ {
+ task_type: 'completion',
+ configuration: {
+ user: {
+ display: DisplayType.TEXTBOX,
+ label: 'User',
+ order: 1,
+ required: false,
+ sensitive: false,
+ tooltip: 'Specifies the user issuing the request.',
+ type: FieldType.STRING,
+ validations: [],
+ value: '',
+ ui_restrictions: [],
+ default_value: null,
+ depends_on: [],
+ },
+ },
+ },
+ ],
+ configuration: {
+ api_key: {
+ display: DisplayType.TEXTBOX,
+ label: 'API Key',
+ order: 3,
+ required: true,
+ sensitive: true,
+ tooltip: `The OpenAI API authentication key. For more details about generating OpenAI API keys, refer to the https://platform.openai.com/account/api-keys.`,
+ type: FieldType.STRING,
+ validations: [],
+ value: null,
+ ui_restrictions: [],
+ default_value: null,
+ depends_on: [],
+ },
+ model_id: {
+ display: DisplayType.TEXTBOX,
+ label: 'Model ID',
+ order: 2,
+ required: true,
+ sensitive: false,
+ tooltip: 'The name of the model to use for the inference task.',
+ type: FieldType.STRING,
+ validations: [],
+ value: null,
+ ui_restrictions: [],
+ default_value: null,
+ depends_on: [],
+ },
+ organization_id: {
+ display: DisplayType.TEXTBOX,
+ label: 'Organization ID',
+ order: 4,
+ required: false,
+ sensitive: false,
+ tooltip: 'The unique identifier of your organization.',
+ type: FieldType.STRING,
+ validations: [],
+ value: null,
+ ui_restrictions: [],
+ default_value: null,
+ depends_on: [],
+ },
+ url: {
+ display: DisplayType.TEXTBOX,
+ label: 'URL',
+ order: 1,
+ required: true,
+ sensitive: false,
+ tooltip:
+ 'The OpenAI API endpoint URL. For more information on the URL, refer to the https://platform.openai.com/docs/api-reference.',
+ type: FieldType.STRING,
+ validations: [],
+ value: null,
+ ui_restrictions: [],
+ default_value: 'https://api.openai.com/v1/chat/completions',
+ depends_on: [],
+ },
+ 'rate_limit.requests_per_minute': {
+ display: DisplayType.NUMERIC,
+ label: 'Rate limit',
+ order: 5,
+ required: false,
+ sensitive: false,
+ tooltip:
+ 'Default number of requests allowed per minute. For text_embedding is 3000. For completion is 500.',
+ type: FieldType.INTEGER,
+ validations: [],
+ value: null,
+ ui_restrictions: [],
+ default_value: null,
+ depends_on: [],
+ },
+ },
+ },
+ ];
+ core.elasticsearch.client.asInternalUser.transport.request.mockResolvedValue(mockResult);
+
+ getInferenceServicesRoute(router);
+
+ const [config, handler] = router.get.mock.calls[0];
+ expect(config.path).toMatchInlineSnapshot(`"/internal/stack_connectors/_inference/_services"`);
+
+ const mockResponse = httpServerMock.createResponseFactory();
+ const mockRequest = httpServerMock.createKibanaRequest();
+ await handler({ core }, mockRequest, mockResponse);
+
+ expect(mockResponse.ok).toHaveBeenCalledWith({
+ body: mockResult,
+ });
+ });
+});
diff --git a/x-pack/plugins/stack_connectors/server/routes/get_inference_services.ts b/x-pack/plugins/stack_connectors/server/routes/get_inference_services.ts
new file mode 100644
index 0000000000000..1396072834261
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/server/routes/get_inference_services.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 {
+ IRouter,
+ RequestHandlerContext,
+ KibanaRequest,
+ IKibanaResponse,
+ KibanaResponseFactory,
+} from '@kbn/core/server';
+import { InferenceProvider } from '../../common/inference/types';
+import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../common';
+
+export const getInferenceServicesRoute = (router: IRouter) => {
+ router.get(
+ {
+ path: `${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_inference/_services`,
+ options: {
+ access: 'internal',
+ },
+ validate: false,
+ },
+ handler
+ );
+
+ async function handler(
+ ctx: RequestHandlerContext,
+ req: KibanaRequest,
+ res: KibanaResponseFactory
+ ): Promise {
+ const esClient = (await ctx.core).elasticsearch.client.asInternalUser;
+
+ const response = await esClient.transport.request<{
+ endpoints: InferenceProvider[];
+ }>({
+ method: 'GET',
+ path: `/_inference/_services`,
+ });
+
+ return res.ok({
+ body: response,
+ });
+ }
+};
diff --git a/x-pack/plugins/stack_connectors/server/routes/index.ts b/x-pack/plugins/stack_connectors/server/routes/index.ts
index cd9857b2168ed..e64995e1a50ef 100644
--- a/x-pack/plugins/stack_connectors/server/routes/index.ts
+++ b/x-pack/plugins/stack_connectors/server/routes/index.ts
@@ -7,3 +7,4 @@
export { getWellKnownEmailServiceRoute } from './get_well_known_email_service';
export { validSlackApiChannelsRoute } from './valid_slack_api_channels';
+export { getInferenceServicesRoute } from './get_inference_services';
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/constants/archiver.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/constants/archiver.ts
new file mode 100644
index 0000000000000..6afc2e9eca63b
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/constants/archiver.ts
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+type ArchiveName =
+ | '8.0.0'
+ | 'apm_8.0.0'
+ | 'apm_mappings_only_8.0.0'
+ | 'infra_metrics_and_apm'
+ | 'metrics_8.0.0'
+ | 'ml_8.0.0'
+ | 'observability_overview'
+ | 'rum_8.0.0'
+ | 'rum_test_data';
+
+export const ARCHIVER_ROUTES: { [key in ArchiveName]: string } = {
+ '8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0',
+ 'apm_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0',
+ 'apm_mappings_only_8.0.0':
+ 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0',
+ infra_metrics_and_apm:
+ 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm',
+ 'metrics_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0',
+ 'ml_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/ml_8.0.0',
+ observability_overview:
+ 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/observability_overview',
+ 'rum_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_8.0.0',
+ rum_test_data: 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data',
+};
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/failed_transactions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/failed_transactions.spec.ts
new file mode 100644
index 0000000000000..549f48009197f
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/failed_transactions.spec.ts
@@ -0,0 +1,236 @@
+/*
+ * 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 { orderBy } from 'lodash';
+import expect from '@kbn/expect';
+import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
+import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
+import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
+import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
+
+// These tests go through the full sequence of queries required
+// to get the final results for a failed transactions correlation analysis.
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
+ // This matches the parameters used for the other tab's queries in `../correlations/*`.
+ const getOptions = () => ({
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ kuery: '',
+ });
+
+ describe('failed transactions', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const overallDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(overallDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${overallDistributionResponse.status}'`
+ );
+
+ const errorDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(errorDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${errorDistributionResponse.status}'`
+ );
+
+ const fieldCandidatesResponse = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
+ params: {
+ query: getOptions(),
+ },
+ });
+
+ expect(fieldCandidatesResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
+ );
+
+ const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/correlations/p_values/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
+ },
+ },
+ });
+
+ expect(failedTransactionsCorrelationsResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
+ );
+
+ const finalRawResponse: FailedTransactionsCorrelationsResponse = {
+ ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
+ percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
+ overallHistogram: overallDistributionResponse.body?.overallHistogram,
+ failedTransactionsCorrelations:
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
+ };
+
+ expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
+ 0,
+ `Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
+ );
+ });
+ });
+
+ describe('with data', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
+ it('runs queries and returns results', async () => {
+ const overallDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(overallDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${overallDistributionResponse.status}'`
+ );
+
+ const errorDistributionResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ percentileThreshold: 95,
+ termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
+ chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
+ },
+ },
+ });
+
+ expect(errorDistributionResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${errorDistributionResponse.status}'`
+ );
+
+ const fieldCandidatesResponse = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
+ params: {
+ query: getOptions(),
+ },
+ });
+
+ expect(fieldCandidatesResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
+ );
+
+ const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
+ (t) => !(t === EVENT_OUTCOME)
+ );
+
+ // Identified 80 fieldCandidates.
+ expect(fieldCandidates.length).to.eql(
+ 80,
+ `Expected field candidates length to be '80', got '${fieldCandidates.length}'`
+ );
+
+ const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
+ endpoint: 'POST /internal/apm/correlations/p_values/transactions',
+ params: {
+ body: {
+ ...getOptions(),
+ fieldCandidates,
+ },
+ },
+ });
+
+ expect(failedTransactionsCorrelationsResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
+ );
+
+ const fieldsToSample = new Set();
+ if (
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0
+ ) {
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach(
+ (d) => {
+ fieldsToSample.add(d.fieldName);
+ }
+ );
+ }
+
+ const finalRawResponse: FailedTransactionsCorrelationsResponse = {
+ ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
+ percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
+ overallHistogram: overallDistributionResponse.body?.overallHistogram,
+ errorHistogram: errorDistributionResponse.body?.overallHistogram,
+ failedTransactionsCorrelations:
+ failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
+ };
+
+ expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
+ expect(finalRawResponse?.errorHistogram?.length).to.be(101);
+ expect(finalRawResponse?.overallHistogram?.length).to.be(101);
+
+ expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
+ 29,
+ `Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
+ );
+
+ const sortedCorrelations = orderBy(
+ finalRawResponse?.failedTransactionsCorrelations,
+ ['score', 'fieldName', 'fieldValue'],
+ ['desc', 'asc', 'asc']
+ );
+ const correlation = sortedCorrelations?.[0];
+
+ expect(typeof correlation).to.be('object');
+ expect(correlation?.doc_count).to.be(31);
+ expect(correlation?.score).to.be(83.70467673605746);
+ expect(correlation?.bg_count).to.be(31);
+ expect(correlation?.fieldName).to.be('transaction.result');
+ expect(correlation?.fieldValue).to.be('HTTP 5xx');
+ expect(typeof correlation?.pValue).to.be('number');
+ expect(typeof correlation?.normalizedScore).to.be('number');
+ expect(typeof correlation?.failurePercentage).to.be('number');
+ expect(typeof correlation?.successPercentage).to.be('number');
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_candidates.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_candidates.spec.ts
new file mode 100644
index 0000000000000..8db9a7df05fd3
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_candidates.spec.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
+
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
+
+ const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';
+
+ const getOptions = () => ({
+ params: {
+ query: {
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ kuery: '',
+ },
+ },
+ });
+
+ describe('field candidates', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
+
+ expect(response.status).to.be(200);
+ // If the source indices are empty, there will be no field candidates
+ // because of the `include_empty_fields: false` option in the query.
+ expect(response.body?.fieldCandidates.length).to.be(0);
+ });
+ });
+
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
+ it('returns field candidates', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
+
+ expect(response.status).to.eql(200);
+ expect(response.body?.fieldCandidates.length).to.be(81);
+ });
+ });
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/field_value_pairs.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_value_pairs.spec.ts
similarity index 59%
rename from x-pack/test/apm_api_integration/tests/correlations/field_value_pairs.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_value_pairs.spec.ts
index 4765e83342e52..9fcd438421b6a 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/field_value_pairs.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/field_value_pairs.spec.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/field_value_pairs/transactions';
@@ -41,22 +42,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
- registry.when('field value pairs without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
+ describe('field value pairs', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
- expect(response.status).to.be(200);
- expect(response.body?.fieldValuePairs.length).to.be(0);
+ expect(response.status).to.be(200);
+ expect(response.body?.fieldValuePairs.length).to.be(0);
+ });
});
- });
- registry.when(
- 'field value pairs with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
it('returns field value pairs', async () => {
const response = await apmApiClient.readUser({
endpoint,
@@ -66,6 +72,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.fieldValuePairs.length).to.be(124);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts
new file mode 100644
index 0000000000000..660556edb8d79
--- /dev/null
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts
@@ -0,0 +1,18 @@
+/*
+ * 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 { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
+ describe('correlations', () => {
+ loadTestFile(require.resolve('./failed_transactions.spec.ts'));
+ loadTestFile(require.resolve('./field_candidates.spec.ts'));
+ loadTestFile(require.resolve('./field_value_pairs.spec.ts'));
+ loadTestFile(require.resolve('./latency.spec.ts'));
+ loadTestFile(require.resolve('./p_values.spec.ts'));
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/latency.spec.ts
similarity index 93%
rename from x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/latency.spec.ts
index 5326136976428..e0080806f6a0e 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/latency.spec.ts
@@ -14,13 +14,14 @@ import type {
LatencyCorrelationsResponse,
} from '@kbn/apm-plugin/common/correlations/latency_correlations/types';
import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
// These tests go through the full sequence of queries required
// to get the final results for a latency correlation analysis.
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
// This matches the parameters used for the other tab's queries in `../correlations/*`.
const getOptions = () => ({
@@ -30,10 +31,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
kuery: '',
});
- registry.when(
- 'correlations latency overall without data',
- { config: 'trial', archives: [] },
- () => {
+ describe('latency', () => {
+ describe('overall without data', () => {
it('handles the empty state', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
@@ -104,13 +103,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(finalRawResponse?.overallHistogram).to.be(undefined);
expect(finalRawResponse?.latencyCorrelations?.length).to.be(0);
});
- }
- );
+ });
+
+ describe('with data and opbeans-node args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
- registry.when(
- 'correlations latency with data and opbeans-node args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
// putting this into a single `it` because the responses depend on each other
it('runs queries and returns results', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
@@ -250,6 +252,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(correlation?.ksTest).to.be(1.9848961005439386e-12);
expect(correlation?.histogram?.length).to.be(101);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/p_values.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/p_values.spec.ts
similarity index 58%
rename from x-pack/test/apm_api_integration/tests/correlations/p_values.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/p_values.spec.ts
index 42a9fdadbb480..ba6e3384cec93 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/p_values.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/p_values.spec.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/p_values/transactions';
@@ -41,22 +42,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
- registry.when('p values without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
+ describe('p values', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
- expect(response.status).to.be(200);
- expect(response.body?.failedTransactionsCorrelations.length).to.be(0);
+ expect(response.status).to.be(200);
+ expect(response.body?.failedTransactionsCorrelations.length).to.be(0);
+ });
});
- });
- registry.when(
- 'p values with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
it('returns p values', async () => {
const response = await apmApiClient.readUser({
endpoint,
@@ -66,6 +72,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.failedTransactionsCorrelations.length).to.be(15);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/significant_correlations.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/significant_correlations.spec.ts
similarity index 71%
rename from x-pack/test/apm_api_integration/tests/correlations/significant_correlations.spec.ts
rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/significant_correlations.spec.ts
index d4450c192a029..e1f968d178868 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/significant_correlations.spec.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/significant_correlations.spec.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
+import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
+import { ARCHIVER_ROUTES } from '../constants/archiver';
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
+export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
+ const apmApiClient = getService('apmApi');
+ const esArchiver = getService('esArchiver');
const endpoint = 'POST /internal/apm/correlations/significant_correlations/transactions';
@@ -65,22 +66,27 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
});
- registry.when('significant correlations without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
+ describe('significant correlations', () => {
+ describe('without data', () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
- expect(response.status).to.be(200);
- expect(response.body?.latencyCorrelations.length).to.be(0);
+ expect(response.status).to.be(200);
+ expect(response.body?.latencyCorrelations.length).to.be(0);
+ });
});
- });
- registry.when(
- 'significant correlations with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
+ describe('with data and default args', () => {
+ before(async () => {
+ await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
+ });
+ after(async () => {
+ await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
+ });
+
it('returns significant correlations', async () => {
const response = await apmApiClient.readUser({
endpoint,
@@ -90,6 +96,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.eql(200);
expect(response.body?.latencyCorrelations.length).to.be(7);
});
- }
- );
+ });
+ });
}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts
index b181d73c54cff..22bf6d8aa7f52 100644
--- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts
@@ -16,6 +16,7 @@ export default function apmApiIntegrationTests({
loadTestFile(require.resolve('./custom_dashboards'));
loadTestFile(require.resolve('./dependencies'));
loadTestFile(require.resolve('./data_view'));
+ loadTestFile(require.resolve('./correlations'));
loadTestFile(require.resolve('./entities'));
loadTestFile(require.resolve('./cold_start'));
});
diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json
index a64e037343bb3..c79a4c6b52309 100644
--- a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json
+++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0/mappings.json
@@ -3786,10 +3786,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-error"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -4243,8 +4239,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -8183,10 +8178,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-metric"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -8640,8 +8631,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -12871,8 +12861,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -16653,10 +16642,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-profile"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -17110,8 +17095,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -20899,10 +20883,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-span"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -21356,8 +21336,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
@@ -25242,10 +25221,6 @@
"index": {
"auto_expand_replicas": "0-1",
"codec": "best_compression",
- "lifecycle": {
- "name": "apm-rollover-30-days",
- "rollover_alias": "apm-8.0.0-transaction"
- },
"mapping": {
"total_fields": {
"limit": "2000"
@@ -25699,8 +25674,7 @@
"transaction.message.queue.name",
"fields.*"
]
- },
- "refresh_interval": "1ms"
+ }
}
}
}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts
deleted file mode 100644
index 13754f6c7eb5a..0000000000000
--- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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 { orderBy } from 'lodash';
-import expect from '@kbn/expect';
-import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
-import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
-import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
-import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
-
-// These tests go through the full sequence of queries required
-// to get the final results for a failed transactions correlation analysis.
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
-
- // This matches the parameters used for the other tab's queries in `../correlations/*`.
- const getOptions = () => ({
- environment: 'ENVIRONMENT_ALL',
- start: '2020',
- end: '2021',
- kuery: '',
- });
-
- registry.when('failed transactions without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const overallDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(overallDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${overallDistributionResponse.status}'`
- );
-
- const errorDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(errorDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${errorDistributionResponse.status}'`
- );
-
- const fieldCandidatesResponse = await apmApiClient.readUser({
- endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
- params: {
- query: getOptions(),
- },
- });
-
- expect(fieldCandidatesResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
- );
-
- const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/correlations/p_values/transactions',
- params: {
- body: {
- ...getOptions(),
- fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
- },
- },
- });
-
- expect(failedTransactionsCorrelationsResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
- );
-
- const finalRawResponse: FailedTransactionsCorrelationsResponse = {
- ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
- percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
- overallHistogram: overallDistributionResponse.body?.overallHistogram,
- failedTransactionsCorrelations:
- failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
- };
-
- expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
- 0,
- `Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
- );
- });
- });
-
- registry.when('failed transactions with data', { config: 'trial', archives: ['8.0.0'] }, () => {
- it('runs queries and returns results', async () => {
- const overallDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(overallDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${overallDistributionResponse.status}'`
- );
-
- const errorDistributionResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
- params: {
- body: {
- ...getOptions(),
- percentileThreshold: 95,
- termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
- chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
- },
- },
- });
-
- expect(errorDistributionResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${errorDistributionResponse.status}'`
- );
-
- const fieldCandidatesResponse = await apmApiClient.readUser({
- endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
- params: {
- query: getOptions(),
- },
- });
-
- expect(fieldCandidatesResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${fieldCandidatesResponse.status}'`
- );
-
- const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
- (t) => !(t === EVENT_OUTCOME)
- );
-
- // Identified 80 fieldCandidates.
- expect(fieldCandidates.length).to.eql(
- 80,
- `Expected field candidates length to be '80', got '${fieldCandidates.length}'`
- );
-
- const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
- endpoint: 'POST /internal/apm/correlations/p_values/transactions',
- params: {
- body: {
- ...getOptions(),
- fieldCandidates,
- },
- },
- });
-
- expect(failedTransactionsCorrelationsResponse.status).to.eql(
- 200,
- `Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
- );
-
- const fieldsToSample = new Set();
- if (failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0) {
- failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach((d) => {
- fieldsToSample.add(d.fieldName);
- });
- }
-
- const finalRawResponse: FailedTransactionsCorrelationsResponse = {
- ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
- percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
- overallHistogram: overallDistributionResponse.body?.overallHistogram,
- errorHistogram: errorDistributionResponse.body?.overallHistogram,
- failedTransactionsCorrelations:
- failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
- };
-
- expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
- expect(finalRawResponse?.errorHistogram?.length).to.be(101);
- expect(finalRawResponse?.overallHistogram?.length).to.be(101);
-
- expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
- 29,
- `Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
- );
-
- const sortedCorrelations = orderBy(
- finalRawResponse?.failedTransactionsCorrelations,
- ['score', 'fieldName', 'fieldValue'],
- ['desc', 'asc', 'asc']
- );
- const correlation = sortedCorrelations?.[0];
-
- expect(typeof correlation).to.be('object');
- expect(correlation?.doc_count).to.be(31);
- expect(correlation?.score).to.be(83.70467673605746);
- expect(correlation?.bg_count).to.be(31);
- expect(correlation?.fieldName).to.be('transaction.result');
- expect(correlation?.fieldValue).to.be('HTTP 5xx');
- expect(typeof correlation?.pValue).to.be('number');
- expect(typeof correlation?.normalizedScore).to.be('number');
- expect(typeof correlation?.failurePercentage).to.be('number');
- expect(typeof correlation?.successPercentage).to.be('number');
- });
- });
-}
diff --git a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts
deleted file mode 100644
index 4a5472cf5cb23..0000000000000
--- a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../common/ftr_provider_context';
-
-export default function ApiTest({ getService }: FtrProviderContext) {
- const apmApiClient = getService('apmApiClient');
- const registry = getService('registry');
-
- const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';
-
- const getOptions = () => ({
- params: {
- query: {
- environment: 'ENVIRONMENT_ALL',
- start: '2020',
- end: '2021',
- kuery: '',
- },
- },
- });
-
- registry.when('field candidates without data', { config: 'trial', archives: [] }, () => {
- it('handles the empty state', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
-
- expect(response.status).to.be(200);
- // If the source indices are empty, there will be no field candidates
- // because of the `include_empty_fields: false` option in the query.
- expect(response.body?.fieldCandidates.length).to.be(0);
- });
- });
-
- registry.when(
- 'field candidates with data and default args',
- { config: 'trial', archives: ['8.0.0'] },
- () => {
- it('returns field candidates', async () => {
- const response = await apmApiClient.readUser({
- endpoint,
- ...getOptions(),
- });
-
- expect(response.status).to.eql(200);
- expect(response.body?.fieldCandidates.length).to.be(81);
- });
- }
- );
-}
diff --git a/yarn.lock b/yarn.lock
index b4023f405d878..e5e3c6b0020b7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1748,10 +1748,10 @@
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314"
integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ==
-"@elastic/eui@97.3.0":
- version "97.3.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-97.3.0.tgz#3961e39a6a8ac38e1af999baf0e96de8e1671943"
- integrity sha512-Ic9DXHlh9yVumYypoLSM+plM0xBjSPc8PPRT4z5bHXLXZrLuSEVoqfix3co5yl4+ibLwfxNPCZFflbFiMl2apA==
+"@elastic/eui@97.3.1":
+ version "97.3.1"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-97.3.1.tgz#b0f07c603042bd359544b41829507e65f4fa3cd2"
+ integrity sha512-zJs3aaO6qjTdxJM2mPahcqaC6FfaC34yTc3qpQq7+Cbhw2xGrwx8bAfIzhttLU87mwgr59Sqv9Ojvwk8c3js7A==
dependencies:
"@hello-pangea/dnd" "^16.6.0"
"@types/lodash" "^4.14.202"
@@ -8483,12 +8483,12 @@
require-from-string "^2.0.2"
uri-js-replace "^1.0.1"
-"@redocly/cli@^1.25.10":
- version "1.25.10"
- resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.25.10.tgz#647e33e4171d74a4f879304ba87366ac650ed83d"
- integrity sha512-zoRMvSYOLzurcb3be5HLLlc5dLGICyHY8mueCbdE2DmLbFERhJJ5iiABKvNRJSr03AR6X569f4mraBJpAsGJnQ==
+"@redocly/cli@^1.25.11":
+ version "1.25.11"
+ resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.25.11.tgz#8ec17a6535aebfd166e8cab8ffcc9d768af1b014"
+ integrity sha512-dttBsmLnnbTlJCTa+s7Sy+qtXDq692n7Ru3nUUIHp9XdCbhXIHWhpc8uAl+GmR4MGbVe8ohATl3J+zX3aFy82A==
dependencies:
- "@redocly/openapi-core" "1.25.10"
+ "@redocly/openapi-core" "1.25.11"
abort-controller "^3.0.0"
chokidar "^3.5.1"
colorette "^1.2.0"
@@ -8513,10 +8513,10 @@
resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.16.0.tgz#4b7700a5cb6e04bc6d6fdb94b871c9e260a1fba6"
integrity sha512-t9jnODbUcuANRSl/K4L9nb12V+U5acIHnVSl26NWrtSdDZVtoqUXk2yGFPZzohYf62cCfEQUT8ouJ3bhPfpnJg==
-"@redocly/openapi-core@1.25.10", "@redocly/openapi-core@^1.4.0":
- version "1.25.10"
- resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.25.10.tgz#6ca3f1ad1b826e3680f91752abf11aa40856f6b8"
- integrity sha512-wcGnSonJZvjpPaJJs+qh0ADYy0aCbaNhCXhJVES9RlknMc7V9nbqLQ67lkwaXhpp/fskm9GJWL/U9Xyiuclbqw==
+"@redocly/openapi-core@1.25.11", "@redocly/openapi-core@^1.4.0":
+ version "1.25.11"
+ resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.25.11.tgz#93f168284986da6809363b001e9aa7c2104c2fc0"
+ integrity sha512-bH+a8izQz4fnKROKoX3bEU8sQ9rjvEIZOqU6qTmxlhOJ0NsKa5e+LmU18SV0oFeg5YhWQhhEDihXkvKJ1wMMNQ==
dependencies:
"@redocly/ajv" "^8.11.2"
"@redocly/config" "^0.16.0"