From 3c7025b05cc0ac13e2d63e719f9781d2eeaf2e48 Mon Sep 17 00:00:00 2001 From: Arturo Liduena Date: Wed, 11 Dec 2024 10:16:14 +0100 Subject: [PATCH 01/16] Add alert status management to AI Assistant connector --- .../common/constants.ts | 47 +++++++++++++++++ .../common/types.ts | 25 +++++++++ .../public/rule_connector/ai_assistant.tsx | 11 +++- .../rule_connector/ai_assistant_params.tsx | 51 ++++++++++++++++++- .../public/rule_connector/translations.ts | 7 +++ .../public/rule_connector/types.ts | 1 + .../server/rule_connector/index.ts | 27 ++++++++-- 7 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts create mode 100644 x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts new file mode 100644 index 0000000000000..65069f773c64b --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts @@ -0,0 +1,47 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, +} from '@kbn/rule-data-utils'; + +import { AlertStatus } from './types'; + +export const ALERT_STATUS_ALL = 'all'; + +export const ALL_ALERTS: AlertStatus = { + id: ALERT_STATUS_ALL, + label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.showAll', { + defaultMessage: 'All', + }), +}; + +export const ACTIVE_ALERTS: AlertStatus = { + id: ALERT_STATUS_ACTIVE, + label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.active', { + defaultMessage: 'Active', + }), +}; + +export const RECOVERED_ALERTS: AlertStatus = { + id: ALERT_STATUS_RECOVERED, + label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.recovered', { + defaultMessage: 'Recovered', + }), +}; + +export const UNTRACKED_ALERTS: AlertStatus = { + id: ALERT_STATUS_UNTRACKED, + label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.untracked', { + defaultMessage: 'Untracked', + }), +}; + +export const ALERT_STATUSES = [ALL_ALERTS, ACTIVE_ALERTS, RECOVERED_ALERTS, UNTRACKED_ALERTS]; diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts new file mode 100644 index 0000000000000..9948863e7718e --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts @@ -0,0 +1,25 @@ +/* + * 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 { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, +} from '@kbn/rule-data-utils'; + +import { ALERT_STATUS_ALL } from './constants'; + +type Status = + | typeof ALERT_STATUS_ACTIVE + | typeof ALERT_STATUS_RECOVERED + | typeof ALERT_STATUS_UNTRACKED + | typeof ALERT_STATUS_ALL; + +export interface AlertStatus { + id: Status; + label: string; +} diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx index 79d9678733941..7a04e0ace2b3e 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx @@ -19,6 +19,7 @@ import { CONNECTOR_REQUIRED, CONNECTOR_TITLE, MESSAGE_REQUIRED, + STATUS_REQUIRED, } from './translations'; export function getConnectorType( @@ -36,7 +37,11 @@ export function getConnectorType( actionParams: ObsAIAssistantActionParams ): Promise> => { const validationResult = { - errors: { connector: new Array(), message: new Array() }, + errors: { + connector: new Array(), + message: new Array(), + status: new Array(), + }, }; if (!actionParams.connector) { @@ -47,6 +52,10 @@ export function getConnectorType( validationResult.errors.message.push(MESSAGE_REQUIRED); } + if (!actionParams.status) { + validationResult.errors.status.push(STATUS_REQUIRED); + } + return validationResult; }, actionParamsFields: lazy(() => diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx index 08faf2166a6e5..d77e486ffda1b 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx @@ -8,12 +8,22 @@ import React, { useEffect } from 'react'; import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiFlexItem, EuiSelect, EuiSpacer, EuiTextArea } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFormRow, + EuiFlexItem, + EuiSelect, + EuiSpacer, + EuiTextArea, + EuiSuperSelect, + EuiFormLabel, +} from '@elastic/eui'; import { ObservabilityAIAssistantService, useGenAIConnectorsWithoutContext, } from '@kbn/observability-ai-assistant-plugin/public'; import { ObsAIAssistantActionParams } from './types'; +import { ALERT_STATUSES, ALL_ALERTS } from '../../common/constants'; const ObsAIAssistantParamsFields: React.FunctionComponent< ActionParamsProps & { service: ObservabilityAIAssistantService } @@ -26,6 +36,12 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedConnector, index]); + useEffect(() => { + if (!actionParams.status) { + editAction('status', ALL_ALERTS.id, index); + } + }, [actionParams.status, editAction, index]); + return ( <> - + + + + + } + fullWidth + id={`addNewActionConnectorActionGroup-${index}`} + data-test-subj={`addNewActionConnectorActionGroup-${index}`} + options={ALERT_STATUSES.map(({ id: value, label }) => ({ + value, + inputDisplay: label, + }))} + valueOfSelected={actionParams.status} + onChange={(status) => { + editAction('status', status, index); + }} + /> + + = { const ParamsSchema = schema.object({ connector: schema.string(), message: schema.string({ minLength: 1 }), + status: schema.string(), }); const RuleSchema = schema.object({ @@ -83,6 +85,7 @@ const AlertSummarySchema = schema.object({ const ConnectorParamsSchema = schema.object({ connector: schema.string(), + status: schema.string(), message: schema.string({ minLength: 1 }), rule: RuleSchema, alerts: AlertSummarySchema, @@ -141,6 +144,7 @@ function renderParameterTemplates( return { connector: params.connector, message: params.message, + status: params.status, rule: params.rule, alerts: params.alerts, }; @@ -151,13 +155,29 @@ async function executor( initResources: (request: KibanaRequest) => Promise, alertDetailsContextService: AlertDetailsContextualInsightsService ): Promise> { - const request = execOptions.request; - const alerts = execOptions.params.alerts; + const { request, params } = execOptions; + const alerts = { + new: [...(params.alerts?.new || [])], + recovered: [...(params.alerts?.recovered || [])], + }; if (!request) { throw new Error('AI Assistant connector requires a kibana request'); } + if ( + execOptions.params.status === ACTIVE_ALERTS.id || + execOptions.params.status === RECOVERED_ALERTS.id || + execOptions.params.status === UNTRACKED_ALERTS.id + ) { + alerts.new = alerts.new.filter( + (alert) => get(alert, 'kibana.alert.status') === execOptions.params.status + ); + alerts.recovered = alerts.recovered.filter( + (alert) => get(alert, 'kibana.alert.status') === execOptions.params.status + ); + } + if (alerts.new.length === 0 && alerts.recovered.length === 0) { // connector could be executed with only ongoing actions. we use this path as // dedup mechanism to prevent triggering the same worfklow for an ongoing alert @@ -220,7 +240,7 @@ If available, include the link of the conversation at the end of your answer.` const alertsContext = await getAlertsContext( execOptions.params.rule, - execOptions.params.alerts, + alerts, async (alert: Record) => { const prompt = await alertDetailsContextService.getAlertDetailsContext( { @@ -342,6 +362,7 @@ export const getObsAIAssistantConnectorAdapter = (): ConnectorAdapter< return { connector: params.connector, message: params.message, + status: params.status, rule: { id: rule.id, name: rule.name, tags: rule.tags, ruleUrl: ruleUrl ?? null }, alerts: { new: alerts.new.data, From 7b4ecc2c5d8188e83c94dd4e6f6cd225f9da77b5 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:36:20 +0000 Subject: [PATCH 02/16] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../plugins/observability_ai_assistant_app/tsconfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json index 763bf6659170e..48fd4332704b5 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json @@ -83,8 +83,8 @@ "@kbn/charts-theme", "@kbn/ai-assistant-icon", "@kbn/product-doc-base-plugin", + "@kbn/rule-data-utils", + "@kbn/i18n-react" ], - "exclude": [ - "target/**/*" - ] + "exclude": ["target/**/*"] } From 2f615f6bbac895407811cc21468a897fc25695de Mon Sep 17 00:00:00 2001 From: Arturo Liduena Date: Wed, 11 Dec 2024 11:44:17 +0100 Subject: [PATCH 03/16] fix types error --- .../server/rule_connector/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts index 04fd10c3e506f..d8587d69bc04c 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -28,7 +28,7 @@ describe('observabilityAIAssistant rule_connector', () => { it('builds action params', () => { const adapter = getObsAIAssistantConnectorAdapter(); const params = adapter.buildActionParams({ - params: { connector: '.azure', message: 'hello' }, + params: { connector: '.azure', message: 'hello', status: 'all' }, rule: { id: 'foo', name: 'bar', tags: [], consumer: '', producer: '' }, ruleUrl: 'http://myrule.com', spaceId: 'default', From 0bae13a03054dad7811d1a204f8b97b2772a80a1 Mon Sep 17 00:00:00 2001 From: Arturo Liduena Date: Thu, 12 Dec 2024 11:57:24 +0100 Subject: [PATCH 04/16] test fix: Add status field to connector types and update tests --- .../__snapshots__/connector_types.test.ts.snap | 14 ++++++++++++++ .../server/rule_connector/index.test.ts | 1 + 2 files changed, 15 insertions(+) diff --git a/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index 9367ecb221b57..30188b4fba5f3 100644 --- a/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -6885,6 +6885,20 @@ Object { }, "type": "object", }, + "status": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, }, "type": "object", } diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts index d8587d69bc04c..6d9fd95c50559 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -43,6 +43,7 @@ describe('observabilityAIAssistant rule_connector', () => { expect(params).toEqual({ connector: '.azure', message: 'hello', + status: 'all', rule: { id: 'foo', name: 'bar', tags: [], ruleUrl: 'http://myrule.com' }, alerts: { new: [{ _id: 'new_alert' }], From c584c0c70c731be0e623706946fabb1b248ca7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 21 Jan 2025 12:04:57 +0100 Subject: [PATCH 05/16] add multiple prompts to ai-assistant action --- .../common/constants.ts | 9 +- .../rule_connector/ai_assistant_params.tsx | 188 ++++++++++++------ .../public/rule_connector/types.ts | 7 +- .../server/rule_connector/index.ts | 129 ++++++++---- 4 files changed, 224 insertions(+), 109 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts index 65069f773c64b..99e0bcee4c29e 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts @@ -16,13 +16,6 @@ import { AlertStatus } from './types'; export const ALERT_STATUS_ALL = 'all'; -export const ALL_ALERTS: AlertStatus = { - id: ALERT_STATUS_ALL, - label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.showAll', { - defaultMessage: 'All', - }), -}; - export const ACTIVE_ALERTS: AlertStatus = { id: ALERT_STATUS_ACTIVE, label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.active', { @@ -44,4 +37,4 @@ export const UNTRACKED_ALERTS: AlertStatus = { }), }; -export const ALERT_STATUSES = [ALL_ALERTS, ACTIVE_ALERTS, RECOVERED_ALERTS, UNTRACKED_ALERTS]; +export const ALERT_STATUSES = [ACTIVE_ALERTS, RECOVERED_ALERTS, UNTRACKED_ALERTS]; diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx index d77e486ffda1b..d25b43974e282 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx @@ -15,15 +15,16 @@ import { EuiSelect, EuiSpacer, EuiTextArea, - EuiSuperSelect, - EuiFormLabel, + EuiComboBox, + EuiButton, + EuiFlexGroup, } from '@elastic/eui'; import { ObservabilityAIAssistantService, useGenAIConnectorsWithoutContext, } from '@kbn/observability-ai-assistant-plugin/public'; import { ObsAIAssistantActionParams } from './types'; -import { ALERT_STATUSES, ALL_ALERTS } from '../../common/constants'; +import { ALERT_STATUSES } from '../../common/constants'; const ObsAIAssistantParamsFields: React.FunctionComponent< ActionParamsProps & { service: ObservabilityAIAssistantService } @@ -37,10 +38,49 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< }, [selectedConnector, index]); useEffect(() => { - if (!actionParams.status) { - editAction('status', ALL_ALERTS.id, index); + // Ensure backwards compatibility by using the message field as a prompt if prompts are missing + if (!actionParams.prompts) { + editAction( + 'prompts', + [ + { + statuses: ALERT_STATUSES.map(({ id }) => id), + message: actionParams.message || '', + }, + ], + index + ); + } + }, [actionParams, editAction, index]); + + const handleOnChange = ( + key: 'statuses' | 'message', + value: string | string[], + promptIndex: number + ) => { + const prompts = actionParams.prompts ? [...actionParams.prompts] : []; + prompts[promptIndex] = { ...prompts[promptIndex], [key]: value }; + editAction('prompts', prompts, index); + }; + + const handleAddPrompt = () => { + if (actionParams.prompts) { + const prompts = [ + ...actionParams.prompts, + { + statuses: ALERT_STATUSES.map(({ id }) => id), + message: '', + }, + ]; + editAction('prompts', prompts, index); + } + }; + const handleRemovePrompt = () => { + if (actionParams.prompts) { + const prompts = actionParams.prompts.slice(0, -1); + editAction('prompts', prompts, index); } - }, [actionParams.status, editAction, index]); + }; return ( <> @@ -67,61 +107,95 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< /> - - - - ( +
+ + + ({ + label: id, + }))} + selectedOptions={prompt.statuses.map((id) => ({ label: id }))} + onChange={(statuses) => { + handleOnChange( + 'statuses', + statuses.map((status) => status.label), + promptIndex + ); + }} + isClearable={true} + /> + + + + + { + handleOnChange('message', event.target.value, promptIndex); + }} + // @ts-expect-error upgrade typescript v5.1.6 + isInvalid={errors.message?.length > 0} /> - - } - fullWidth - id={`addNewActionConnectorActionGroup-${index}`} - data-test-subj={`addNewActionConnectorActionGroup-${index}`} - options={ALERT_STATUSES.map(({ id: value, label }) => ({ - value, - inputDisplay: label, - }))} - valueOfSelected={actionParams.status} - onChange={(status) => { - editAction('status', status, index); - }} - /> - + + +
+ ))} - - - + + { - editAction('message', event.target.value, index); - }} - // @ts-expect-error upgrade typescript v5.1.6 - isInvalid={errors.message?.length > 0} - /> + color="danger" + iconType="minusInCircle" + data-test-subj="removePropmptButton" + onClick={handleRemovePrompt} + > + + - + + + + + + ); }; diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/types.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/types.ts index 1561f6e32828e..80c64d2430a00 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/types.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/types.ts @@ -7,6 +7,9 @@ export interface ObsAIAssistantActionParams { connector: string; - message: string; - status: string; + prompts: Array<{ + message: string; + statuses: string[]; + }>; + message?: string; // this is a legacy field } diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts index 008321007b844..b8d7acb9cd204 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts @@ -37,9 +37,13 @@ import { AlertDetailsContextualInsightsService } from '@kbn/observability-plugin import { getSystemMessageFromInstructions } from '@kbn/observability-ai-assistant-plugin/server/service/util/get_system_message_from_instructions'; import { AdHocInstruction } from '@kbn/observability-ai-assistant-plugin/common/types'; import { EXECUTE_CONNECTOR_FUNCTION_NAME } from '@kbn/observability-ai-assistant-plugin/server/functions/execute_connector'; +import { ObservabilityAIAssistantClient } from '@kbn/observability-ai-assistant-plugin/server'; +import { ChatFunctionClient } from '@kbn/observability-ai-assistant-plugin/server/service/chat_function_client'; +import { ActionsClient } from '@kbn/actions-plugin/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { convertSchemaToOpenApi } from './convert_schema_to_open_api'; import { OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID } from '../../common/rule_connector'; -import { ACTIVE_ALERTS, RECOVERED_ALERTS, UNTRACKED_ALERTS } from '../../common/constants'; +import { ALERT_STATUSES } from '../../common/constants'; const CONNECTOR_PRIVILEGES = ['api:observabilityAIAssistant', 'app:observabilityAIAssistant']; @@ -65,8 +69,16 @@ const connectorParamsSchemas: Record = { const ParamsSchema = schema.object({ connector: schema.string(), - message: schema.string({ minLength: 1 }), - status: schema.string(), + prompts: schema.maybe( + schema.arrayOf( + schema.object({ + statuses: schema.arrayOf(schema.string()), + message: schema.string({ minLength: 1 }), + }) + ) + ), + status: schema.maybe(schema.string()), + message: schema.maybe(schema.string({ minLength: 1 })), // this is a legacy field }); const RuleSchema = schema.object({ @@ -85,8 +97,12 @@ const AlertSummarySchema = schema.object({ const ConnectorParamsSchema = schema.object({ connector: schema.string(), - status: schema.string(), - message: schema.string({ minLength: 1 }), + prompts: schema.arrayOf( + schema.object({ + statuses: schema.arrayOf(schema.string()), + message: schema.string({ minLength: 1 }), + }) + ), rule: RuleSchema, alerts: AlertSummarySchema, }); @@ -143,8 +159,7 @@ function renderParameterTemplates( ): ConnectorParamsType { return { connector: params.connector, - message: params.message, - status: params.status, + prompts: params.prompts, rule: params.rule, alerts: params.alerts, }; @@ -156,34 +171,11 @@ async function executor( alertDetailsContextService: AlertDetailsContextualInsightsService ): Promise> { const { request, params } = execOptions; - const alerts = { - new: [...(params.alerts?.new || [])], - recovered: [...(params.alerts?.recovered || [])], - }; if (!request) { throw new Error('AI Assistant connector requires a kibana request'); } - if ( - execOptions.params.status === ACTIVE_ALERTS.id || - execOptions.params.status === RECOVERED_ALERTS.id || - execOptions.params.status === UNTRACKED_ALERTS.id - ) { - alerts.new = alerts.new.filter( - (alert) => get(alert, 'kibana.alert.status') === execOptions.params.status - ); - alerts.recovered = alerts.recovered.filter( - (alert) => get(alert, 'kibana.alert.status') === execOptions.params.status - ); - } - - if (alerts.new.length === 0 && alerts.recovered.length === 0) { - // connector could be executed with only ongoing actions. we use this path as - // dedup mechanism to prevent triggering the same worfklow for an ongoing alert - return { actionId: execOptions.actionId, status: 'ok' }; - } - const resources = await initResources(request); const client = await resources.service.getClient({ request, scopes: ['observability'] }); const functionClient = await resources.service.getFunctionClient({ @@ -197,6 +189,54 @@ async function executor( await resources.plugins.actions.start() ).getActionsClientWithRequest(request); + for (const prompt of params.prompts) { + executeAlertsChatCompletion( + resources, + prompt, + params, + execOptions.actionId, + alertDetailsContextService, + client, + functionClient, + actionsClient, + execOptions.logger + ); + } + + return { actionId: execOptions.actionId, status: 'ok' }; +} + +async function executeAlertsChatCompletion( + resources: ObservabilityAIAssistantRouteHandlerResources, + prompt: { statuses: string[]; message: string }, + params: ConnectorParamsType, + actionId: string, + alertDetailsContextService: AlertDetailsContextualInsightsService, + client: ObservabilityAIAssistantClient, + functionClient: ChatFunctionClient, + actionsClient: PublicMethodsOf, + logger: Logger +) { + const alerts = { + new: [...(params.alerts?.new || [])], + recovered: [...(params.alerts?.recovered || [])], + }; + + if (ALERT_STATUSES.some((status) => prompt.statuses.includes(status.id))) { + alerts.new = alerts.new.filter((alert) => + prompt.statuses.includes(get(alert, 'kibana.alert.status')) + ); + alerts.recovered = alerts.recovered.filter((alert) => + prompt.statuses.includes(get(alert, 'kibana.alert.status')) + ); + } + + if (alerts.new.length === 0 && alerts.recovered.length === 0) { + // connector could be executed with only ongoing actions. we use this path as + // dedup mechanism to prevent triggering the same worfklow for an ongoing alert + return { actionId, status: 'ok' }; + } + const connectorsList = await actionsClient.getAll().then((connectors) => { return connectors.map((connector) => { if (connector.actionTypeId in connectorParamsSchemas) { @@ -230,8 +270,8 @@ If available, include the link of the conversation at the end of your answer.` text: dedent( `The execute_connector function can be used to invoke Kibana connectors. To send to the Slack connector, you need the following arguments: - - the "id" of the connector - - the "params" parameter that you will fill with the message + - the "id" of the connector + - the "params" parameter that you will fill with the message Please include both "id" and "params.message" in the function arguments when executing the Slack connector..` ), }; @@ -239,10 +279,10 @@ If available, include the link of the conversation at the end of your answer.` } const alertsContext = await getAlertsContext( - execOptions.params.rule, + params.rule, alerts, async (alert: Record) => { - const prompt = await alertDetailsContextService.getAlertDetailsContext( + const alertDetailsContext = await alertDetailsContextService.getAlertDetailsContext( { core: resources.context.core, licensing: resources.context.licensing, @@ -255,7 +295,7 @@ If available, include the link of the conversation at the end of your answer.` 'host.name': get(alert, 'host.name'), } ); - return prompt + return alertDetailsContext .map(({ description, data }) => `${description}:\n${JSON.stringify(data, null, 2)}`) .join('\n\n'); } @@ -266,7 +306,7 @@ If available, include the link of the conversation at the end of your answer.` functionClient, persist: true, isPublic: true, - connectorId: execOptions.params.connector, + connectorId: params.connector, signal: new AbortController().signal, kibanaPublicUrl: (await resources.plugins.core.start()).http.basePath.publicBaseUrl, instructions: [backgroundInstruction], @@ -287,7 +327,7 @@ If available, include the link of the conversation at the end of your answer.` '@timestamp': new Date().toISOString(), message: { role: MessageRole.User, - content: execOptions.params.message, + content: prompt.message, }, }, { @@ -343,11 +383,9 @@ If available, include the link of the conversation at the end of your answer.` .pipe(concatenateChatCompletionChunks()) .subscribe({ error: (err) => { - execOptions.logger.error(err); + logger.error(err); }, }); - - return { actionId: execOptions.actionId, status: 'ok' }; } export const getObsAIAssistantConnectorAdapter = (): ConnectorAdapter< @@ -361,8 +399,15 @@ export const getObsAIAssistantConnectorAdapter = (): ConnectorAdapter< buildActionParams: ({ params, rule, ruleUrl, alerts }) => { return { connector: params.connector, - message: params.message, - status: params.status, + // Ensure backwards compatibility by using the message field as a prompt if prompts are missing + prompts: params.prompts + ? params.prompts + : [ + { + statuses: ALERT_STATUSES.map((status) => status.id), + message: params.message || '', + }, + ], rule: { id: rule.id, name: rule.name, tags: rule.tags, ruleUrl: ruleUrl ?? null }, alerts: { new: alerts.new.data, From 50bd854626775585f25162ec1dd4b8aac151aada Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:14:57 +0000 Subject: [PATCH 06/16] [CI] Auto-commit changed files from 'node scripts/styled_components_mapping' --- .../plugins/observability_ai_assistant_app/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json index 48fd4332704b5..100f2c41cedb5 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json @@ -84,7 +84,8 @@ "@kbn/ai-assistant-icon", "@kbn/product-doc-base-plugin", "@kbn/rule-data-utils", - "@kbn/i18n-react" + "@kbn/i18n-react", + "@kbn/utility-types" ], "exclude": ["target/**/*"] } From fc546a8151226c6d057cea3b75c934184d584146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 21 Jan 2025 13:21:08 +0100 Subject: [PATCH 07/16] update prompt validation --- .../public/rule_connector/ai_assistant.tsx | 13 ++++++-- .../server/rule_connector/index.ts | 32 +++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx index 7a04e0ace2b3e..b27b7958efd30 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx @@ -40,7 +40,7 @@ export function getConnectorType( errors: { connector: new Array(), message: new Array(), - status: new Array(), + prompts: new Array(), }, }; @@ -52,8 +52,15 @@ export function getConnectorType( validationResult.errors.message.push(MESSAGE_REQUIRED); } - if (!actionParams.status) { - validationResult.errors.status.push(STATUS_REQUIRED); + if (actionParams.prompts) { + actionParams.prompts.forEach((prompt) => { + if (!prompt.message) { + validationResult.errors.prompts.push(MESSAGE_REQUIRED); + } + if (!prompt.statuses) { + validationResult.errors.prompts.push(STATUS_REQUIRED); + } + }); } return validationResult; diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts index b8d7acb9cd204..fc770e5dae802 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts @@ -189,19 +189,20 @@ async function executor( await resources.plugins.actions.start() ).getActionsClientWithRequest(request); - for (const prompt of params.prompts) { - executeAlertsChatCompletion( - resources, - prompt, - params, - execOptions.actionId, - alertDetailsContextService, - client, - functionClient, - actionsClient, - execOptions.logger - ); - } + await Promise.all( + params.prompts.map((prompt) => + executeAlertsChatCompletion( + resources, + prompt, + params, + alertDetailsContextService, + client, + functionClient, + actionsClient, + execOptions.logger + ) + ) + ); return { actionId: execOptions.actionId, status: 'ok' }; } @@ -210,13 +211,12 @@ async function executeAlertsChatCompletion( resources: ObservabilityAIAssistantRouteHandlerResources, prompt: { statuses: string[]; message: string }, params: ConnectorParamsType, - actionId: string, alertDetailsContextService: AlertDetailsContextualInsightsService, client: ObservabilityAIAssistantClient, functionClient: ChatFunctionClient, actionsClient: PublicMethodsOf, logger: Logger -) { +): Promise { const alerts = { new: [...(params.alerts?.new || [])], recovered: [...(params.alerts?.recovered || [])], @@ -234,7 +234,7 @@ async function executeAlertsChatCompletion( if (alerts.new.length === 0 && alerts.recovered.length === 0) { // connector could be executed with only ongoing actions. we use this path as // dedup mechanism to prevent triggering the same worfklow for an ongoing alert - return { actionId, status: 'ok' }; + return; } const connectorsList = await actionsClient.getAll().then((connectors) => { From 2fb4fc30f52c9080b55c7a252560d56499bdaef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 21 Jan 2025 15:48:19 +0100 Subject: [PATCH 08/16] update tests --- .../connector_types.test.ts.snap | 89 ++++++++++++------- .../common/constants.ts | 2 - .../server/rule_connector/index.test.ts | 13 +-- .../server/rule_connector/index.ts | 8 +- 4 files changed, 73 insertions(+), 39 deletions(-) diff --git a/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index 30188b4fba5f3..409ae8b417ef3 100644 --- a/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/platform/plugins/shared/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -6757,30 +6757,73 @@ Object { ], "type": "string", }, - "message": Object { + "prompts": Object { "flags": Object { "error": [Function], }, - "metas": Array [ - Object { - "x-oas-min-length": 1, - }, - ], - "rules": Array [ + "items": Array [ Object { - "args": Object { - "method": [Function], + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", }, - "name": "custom", - }, - Object { - "args": Object { - "method": [Function], + "keys": Object { + "message": Object { + "flags": Object { + "error": [Function], + }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "statuses": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, }, - "name": "custom", + "type": "object", }, ], - "type": "string", + "type": "array", }, "rule": Object { "flags": Object { @@ -6885,20 +6928,6 @@ Object { }, "type": "object", }, - "status": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, }, "type": "object", } diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts index 99e0bcee4c29e..7a4885fff8191 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts @@ -14,8 +14,6 @@ import { import { AlertStatus } from './types'; -export const ALERT_STATUS_ALL = 'all'; - export const ACTIVE_ALERTS: AlertStatus = { id: ALERT_STATUS_ACTIVE, label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.active', { diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts index 6d9fd95c50559..3b16e592f11cf 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -9,6 +9,7 @@ import { AlertHit } from '@kbn/alerting-plugin/server/types'; import { ObservabilityAIAssistantRouteHandlerResources } from '@kbn/observability-ai-assistant-plugin/server/routes/types'; import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request'; import { OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID } from '../../common/rule_connector'; +import { ACTIVE_ALERTS, ALERT_STATUSES } from '../../common/constants'; import { getObsAIAssistantConnectorAdapter, getObsAIAssistantConnectorType, @@ -28,7 +29,7 @@ describe('observabilityAIAssistant rule_connector', () => { it('builds action params', () => { const adapter = getObsAIAssistantConnectorAdapter(); const params = adapter.buildActionParams({ - params: { connector: '.azure', message: 'hello', status: 'all' }, + params: { connector: '.azure', message: 'hello' }, rule: { id: 'foo', name: 'bar', tags: [], consumer: '', producer: '' }, ruleUrl: 'http://myrule.com', spaceId: 'default', @@ -42,8 +43,7 @@ describe('observabilityAIAssistant rule_connector', () => { expect(params).toEqual({ connector: '.azure', - message: 'hello', - status: 'all', + prompts: [{ message: 'hello', statuses: ALERT_STATUSES.map(({ id }) => id) }], rule: { id: 'foo', name: 'bar', tags: [], ruleUrl: 'http://myrule.com' }, alerts: { new: [{ _id: 'new_alert' }], @@ -125,9 +125,12 @@ describe('observabilityAIAssistant rule_connector', () => { actionId: 'observability-ai-assistant', request: getFakeKibanaRequest({ id: 'foo', api_key: 'bar' }), params: { - message: 'hello', + prompts: [{ message: 'hello', statuses: ALERT_STATUSES.map(({ id }) => id) }], connector: 'azure-open-ai', - alerts: { new: [{ _id: 'new_alert' }], recovered: [] }, + alerts: { + new: [{ kibana: { alert: { status: ACTIVE_ALERTS.id, _id: 'new_alert' } } }], + recovered: [], + }, }, } as unknown as ObsAIAssistantConnectorTypeExecutorOptions); diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts index fc770e5dae802..d4be7def84a8a 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts @@ -172,6 +172,12 @@ async function executor( ): Promise> { const { request, params } = execOptions; + if ((params.alerts?.new || []).length === 0 && (params.alerts?.recovered || []).length === 0) { + // connector could be executed with only ongoing actions. we use this path as + // dedup mechanism to prevent triggering the same worfklow for an ongoing alert + return { actionId: execOptions.actionId, status: 'ok' }; + } + if (!request) { throw new Error('AI Assistant connector requires a kibana request'); } @@ -232,8 +238,6 @@ async function executeAlertsChatCompletion( } if (alerts.new.length === 0 && alerts.recovered.length === 0) { - // connector could be executed with only ongoing actions. we use this path as - // dedup mechanism to prevent triggering the same worfklow for an ongoing alert return; } From 04618aa0e0ebbe1d1041fc0f571c7dd812057f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 21 Jan 2025 16:35:12 +0100 Subject: [PATCH 09/16] remove ALERT_STATUS_ALL from Status type definition --- .../plugins/observability_ai_assistant_app/common/types.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts index 9948863e7718e..ef95465e891a1 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts @@ -11,13 +11,10 @@ import { ALERT_STATUS_UNTRACKED, } from '@kbn/rule-data-utils'; -import { ALERT_STATUS_ALL } from './constants'; - type Status = | typeof ALERT_STATUS_ACTIVE | typeof ALERT_STATUS_RECOVERED - | typeof ALERT_STATUS_UNTRACKED - | typeof ALERT_STATUS_ALL; + | typeof ALERT_STATUS_UNTRACKED; export interface AlertStatus { id: Status; From 7472824c4a52ec3b85cfca5659a82373d02bfb84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Mon, 27 Jan 2025 10:38:01 +0100 Subject: [PATCH 10/16] enhance prompt validation to support multiple prompts and improve error handling --- .../public/rule_connector/ai_assistant.tsx | 13 +++++++------ .../public/rule_connector/ai_assistant_params.tsx | 11 +++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx index b27b7958efd30..4d665d588fea6 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx @@ -40,7 +40,7 @@ export function getConnectorType( errors: { connector: new Array(), message: new Array(), - prompts: new Array(), + prompts: new Array(), }, }; @@ -48,17 +48,18 @@ export function getConnectorType( validationResult.errors.connector.push(CONNECTOR_REQUIRED); } - if (!actionParams.message) { + if (!actionParams.message && !actionParams.prompts) { validationResult.errors.message.push(MESSAGE_REQUIRED); } if (actionParams.prompts) { - actionParams.prompts.forEach((prompt) => { + actionParams.prompts.forEach((prompt, index) => { + validationResult.errors.prompts[index] = []; if (!prompt.message) { - validationResult.errors.prompts.push(MESSAGE_REQUIRED); + validationResult.errors.prompts[index].push(MESSAGE_REQUIRED); } - if (!prompt.statuses) { - validationResult.errors.prompts.push(STATUS_REQUIRED); + if (!prompt.statuses.length) { + validationResult.errors.prompts[index].push(STATUS_REQUIRED); } }); } diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx index d25b43974e282..8da829bd68c95 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx @@ -23,8 +23,10 @@ import { ObservabilityAIAssistantService, useGenAIConnectorsWithoutContext, } from '@kbn/observability-ai-assistant-plugin/public'; +import { RuleFormParamsErrors } from '@kbn/alerts-ui-shared'; import { ObsAIAssistantActionParams } from './types'; import { ALERT_STATUSES } from '../../common/constants'; +import { MESSAGE_REQUIRED, STATUS_REQUIRED } from './translations'; const ObsAIAssistantParamsFields: React.FunctionComponent< ActionParamsProps & { service: ObservabilityAIAssistantService } @@ -82,6 +84,11 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< } }; + const isValidField = (statusError: string, promptIndex: number) => { + const errorsList = ((errors.prompts as RuleFormParamsErrors)?.[promptIndex] as string[]) || []; + return errorsList.includes(statusError); + }; + return ( <> @@ -155,8 +163,7 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< onChange={(event) => { handleOnChange('message', event.target.value, promptIndex); }} - // @ts-expect-error upgrade typescript v5.1.6 - isInvalid={errors.message?.length > 0} + isInvalid={isValidField(MESSAGE_REQUIRED, promptIndex)} />
From e1428a62b419500df08b7f2e10009975f928a0e7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:49:09 +0000 Subject: [PATCH 11/16] [CI] Auto-commit changed files from 'node scripts/styled_components_mapping' --- .../plugins/observability_ai_assistant_app/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json index 100f2c41cedb5..145005f900fa1 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/tsconfig.json @@ -85,7 +85,8 @@ "@kbn/product-doc-base-plugin", "@kbn/rule-data-utils", "@kbn/i18n-react", - "@kbn/utility-types" + "@kbn/utility-types", + "@kbn/alerts-ui-shared" ], "exclude": ["target/**/*"] } From 2fca1a87e0f56400641891b9fabed29e1606003c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 28 Jan 2025 11:49:48 +0100 Subject: [PATCH 12/16] refactor validation logic in AI assistan connector --- .../public/rule_connector/ai_assistant.tsx | 43 ++++++++----------- .../rule_connector/ai_assistant_params.tsx | 8 ++-- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx index 4d665d588fea6..f52219ee34aea 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx @@ -36,35 +36,26 @@ export function getConnectorType( validateParams: async ( actionParams: ObsAIAssistantActionParams ): Promise> => { - const validationResult = { - errors: { - connector: new Array(), - message: new Array(), - prompts: new Array(), - }, - }; - - if (!actionParams.connector) { - validationResult.errors.connector.push(CONNECTOR_REQUIRED); - } + const validatePrompt = (prompt: { message: string; statuses: string[] }): string[] => { + const errors: string[] = []; - if (!actionParams.message && !actionParams.prompts) { - validationResult.errors.message.push(MESSAGE_REQUIRED); - } + if (!prompt.message) { + errors.push(MESSAGE_REQUIRED); + } + if (!prompt.statuses || prompt.statuses.length === 0) { + errors.push(STATUS_REQUIRED); + } - if (actionParams.prompts) { - actionParams.prompts.forEach((prompt, index) => { - validationResult.errors.prompts[index] = []; - if (!prompt.message) { - validationResult.errors.prompts[index].push(MESSAGE_REQUIRED); - } - if (!prompt.statuses.length) { - validationResult.errors.prompts[index].push(STATUS_REQUIRED); - } - }); - } + return errors; + }; - return validationResult; + return { + errors: { + connector: actionParams.connector ? [] : [CONNECTOR_REQUIRED], + message: actionParams.message && !actionParams.prompts ? [MESSAGE_REQUIRED] : [], + prompts: actionParams.prompts?.map(validatePrompt) || [], + }, + }; }, actionParamsFields: lazy(() => import('./ai_assistant_params').then(({ default: ActionParamsFields }) => ({ diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx index 8da829bd68c95..3e9ae33742f99 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx @@ -35,9 +35,11 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< useGenAIConnectorsWithoutContext(service); useEffect(() => { - editAction('connector', selectedConnector, index); + if (selectedConnector !== actionParams.connector) { + editAction('connector', selectedConnector, index); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedConnector, index]); + }, [actionParams, selectedConnector, index]); useEffect(() => { // Ensure backwards compatibility by using the message field as a prompt if prompts are missing @@ -110,7 +112,7 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< selectConnector(event.target.value); editAction('connector', event.target.value, index); }} - value={selectedConnector} + value={actionParams.connector} />
From 7ef17846f55ca65235ba5e05cd740d99918bf198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 28 Jan 2025 21:29:08 +0100 Subject: [PATCH 13/16] add tests --- .../server/rule_connector/index.test.ts | 323 ++++++++++++------ 1 file changed, 218 insertions(+), 105 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts index 3b16e592f11cf..d3b4219d7e2ff 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -19,6 +19,63 @@ import { Observable } from 'rxjs'; import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; import { AlertDetailsContextualInsightsService } from '@kbn/observability-plugin/server/services'; +const buildConversation = (contentMessage: string) => [ + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.System, + content: '', + }, + }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.User, + content: contentMessage, + }, + }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.Assistant, + content: '', + function_call: { + name: 'get_alerts_context', + arguments: JSON.stringify({}), + trigger: MessageRole.Assistant as const, + }, + }, + }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.User, + name: 'get_alerts_context', + content: expect.any(String), + }, + }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.Assistant, + content: '', + function_call: { + name: 'get_connectors', + arguments: JSON.stringify({}), + trigger: MessageRole.Assistant as const, + }, + }, + }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.User, + name: 'get_connectors', + content: JSON.stringify({ connectors: [{ id: 'connector_1' }] }), + }, + }, +]; + describe('observabilityAIAssistant rule_connector', () => { describe('getObsAIAssistantConnectorAdapter', () => { it('uses correct connector_id', () => { @@ -53,11 +110,85 @@ describe('observabilityAIAssistant rule_connector', () => { }); }); - describe('getObsAIAssistantConnectorType', () => { - it('is correctly configured', () => { - const initResources = jest - .fn() - .mockResolvedValue({} as ObservabilityAIAssistantRouteHandlerResources); + describe('Connector Type - getObsAIAssistantConnectorType', () => { + const completeMock = jest.fn().mockReturnValue(new Observable()); + + const initResources = jest.fn().mockResolvedValue({ + service: { + getClient: async () => ({ complete: completeMock }), + getFunctionClient: async () => ({ + getFunctions: () => [], + getInstructions: () => [], + getAdhocInstructions: () => [], + }), + }, + context: {}, + plugins: { + core: { + start: () => + Promise.resolve({ + http: { basePath: { publicBaseUrl: 'http://kibana.com' } }, + }), + }, + actions: { + start: async () => { + return { + getActionsClientWithRequest: jest.fn().mockResolvedValue({ + async getAll() { + return [{ id: 'connector_1' }]; + }, + }), + }; + }, + }, + }, + } as unknown as ObservabilityAIAssistantRouteHandlerResources); + + const adapter = getObsAIAssistantConnectorAdapter(); + const buildActionParams = (params: { + connector: string; + message?: string; + prompts?: Array<{ message: string; statuses: string[] }>; + }) => { + return adapter.buildActionParams({ + params, + rule: { id: 'foo', name: 'bar', tags: [], consumer: '', producer: '' }, + spaceId: 'default', + alerts: { + all: { count: 1, data: [] }, + new: { + count: 1, + data: [ + { + '@timestamp': new Date().toISOString(), + _id: 'new_alert', + _index: 'alert_index', + 'kibana.alert.instance.id': 'instance_id', + 'kibana.alert.rule.category': 'rule_category', + 'kibana.alert.rule.consumer': 'rule_consumer', + 'kibana.alert.rule.name': 'rule_name', + 'kibana.alert.rule.producer': 'rule_producer', + 'kibana.alert.rule.revision': 1, + 'kibana.alert.rule.tags': [], + 'kibana.alert.rule.rule_type_id': 'rule_type_id', + 'kibana.alert.uuid': 'alert_uuid', + 'kibana.alert.rule.uuid': 'rule_uuid', + 'kibana.alert.start': new Date().toISOString(), + 'kibana.alert.status': ACTIVE_ALERTS.id, + 'kibana.space_ids': ['default'], + }, + ], + }, + ongoing: { count: 1, data: [] }, + recovered: { count: 0, data: [] }, + }, + }); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should have the correct configuration', () => { const connectorType = getObsAIAssistantConnectorType( initResources, new AlertDetailsContextualInsightsService() @@ -67,10 +198,7 @@ describe('observabilityAIAssistant rule_connector', () => { expect(connectorType.minimumLicenseRequired).toEqual('enterprise'); }); - it('does not execute when no new or recovered alerts', async () => { - const initResources = jest - .fn() - .mockResolvedValue({} as ObservabilityAIAssistantRouteHandlerResources); + it('should not execute when there are no new or recovered alerts', async () => { const connectorType = getObsAIAssistantConnectorType( initResources, new AlertDetailsContextualInsightsService() @@ -84,38 +212,40 @@ describe('observabilityAIAssistant rule_connector', () => { expect(initResources).not.toHaveBeenCalled(); }); - it('calls complete api', async () => { - const completeMock = jest.fn().mockReturnValue(new Observable()); - const initResources = jest.fn().mockResolvedValue({ - service: { - getClient: async () => ({ complete: completeMock }), - getFunctionClient: async () => ({ - getFunctions: () => [], - getInstructions: () => [], - getAdhocInstructions: () => [], - }), - }, - context: {}, - plugins: { - core: { - start: () => - Promise.resolve({ - http: { basePath: { publicBaseUrl: 'http://kibana.com' } }, - }), - }, - actions: { - start: async () => { - return { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - async getAll() { - return [{ id: 'connector_1' }]; - }, - }), - }; - }, - }, - }, - } as unknown as ObservabilityAIAssistantRouteHandlerResources); + it('should call the complete API with a single message', async () => { + const message = 'hello'; + const params = buildActionParams({ connector: 'azure-open-ai', message }); + const connectorType = getObsAIAssistantConnectorType( + initResources, + new AlertDetailsContextualInsightsService() + ); + const result = await connectorType.executor({ + actionId: 'observability-ai-assistant', + request: getFakeKibanaRequest({ id: 'foo', api_key: 'bar' }), + params, + } as unknown as ObsAIAssistantConnectorTypeExecutorOptions); + + expect(result).toEqual({ actionId: 'observability-ai-assistant', status: 'ok' }); + expect(initResources).toHaveBeenCalledTimes(1); + expect(completeMock).toHaveBeenCalledTimes(1); + + expect(completeMock).toHaveBeenCalledWith( + expect.objectContaining({ + persist: true, + isPublic: true, + connectorId: 'azure-open-ai', + kibanaPublicUrl: 'http://kibana.com', + messages: buildConversation(message), + }) + ); + }); + + it('executes the complete API with a single prompt', async () => { + const message = 'hello'; + const params = buildActionParams({ + connector: 'azure-open-ai', + prompts: [{ message, statuses: ALERT_STATUSES.map(({ id }) => id) }], + }); const connectorType = getObsAIAssistantConnectorType( initResources, @@ -124,14 +254,7 @@ describe('observabilityAIAssistant rule_connector', () => { const result = await connectorType.executor({ actionId: 'observability-ai-assistant', request: getFakeKibanaRequest({ id: 'foo', api_key: 'bar' }), - params: { - prompts: [{ message: 'hello', statuses: ALERT_STATUSES.map(({ id }) => id) }], - connector: 'azure-open-ai', - alerts: { - new: [{ kibana: { alert: { status: ACTIVE_ALERTS.id, _id: 'new_alert' } } }], - recovered: [], - }, - }, + params, } as unknown as ObsAIAssistantConnectorTypeExecutorOptions); expect(result).toEqual({ actionId: 'observability-ai-assistant', status: 'ok' }); @@ -144,62 +267,52 @@ describe('observabilityAIAssistant rule_connector', () => { isPublic: true, connectorId: 'azure-open-ai', kibanaPublicUrl: 'http://kibana.com', - messages: [ - { - '@timestamp': expect.any(String), - message: { - role: MessageRole.System, - content: '', - }, - }, - { - '@timestamp': expect.any(String), - message: { - role: MessageRole.User, - content: 'hello', - }, - }, - { - '@timestamp': expect.any(String), - message: { - role: MessageRole.Assistant, - content: '', - function_call: { - name: 'get_alerts_context', - arguments: JSON.stringify({}), - trigger: MessageRole.Assistant as const, - }, - }, - }, - { - '@timestamp': expect.any(String), - message: { - role: MessageRole.User, - name: 'get_alerts_context', - content: expect.any(String), - }, - }, - { - '@timestamp': expect.any(String), - message: { - role: MessageRole.Assistant, - content: '', - function_call: { - name: 'get_connectors', - arguments: JSON.stringify({}), - trigger: MessageRole.Assistant as const, - }, - }, - }, - { - '@timestamp': expect.any(String), - message: { - role: MessageRole.User, - name: 'get_connectors', - content: JSON.stringify({ connectors: [{ id: 'connector_1' }] }), - }, - }, - ], + messages: buildConversation(message), + }) + ); + }); + + it('should call the complete API with multiple prompts', async () => { + const message = 'hello'; + const message2 = 'bye'; + const params = buildActionParams({ + connector: 'azure-open-ai', + prompts: [ + { message, statuses: ALERT_STATUSES.map(({ id }) => id) }, + { message: message2, statuses: ALERT_STATUSES.map(({ id }) => id) }, + ], + }); + + const connectorType = getObsAIAssistantConnectorType( + initResources, + new AlertDetailsContextualInsightsService() + ); + const result = await connectorType.executor({ + actionId: 'observability-ai-assistant', + request: getFakeKibanaRequest({ id: 'foo', api_key: 'bar' }), + params, + } as unknown as ObsAIAssistantConnectorTypeExecutorOptions); + + expect(result).toEqual({ actionId: 'observability-ai-assistant', status: 'ok' }); + expect(initResources).toHaveBeenCalledTimes(1); + expect(completeMock).toHaveBeenCalledTimes(2); + + expect(completeMock).toHaveBeenCalledWith( + expect.objectContaining({ + persist: true, + isPublic: true, + connectorId: 'azure-open-ai', + kibanaPublicUrl: 'http://kibana.com', + messages: buildConversation(message), + }) + ); + expect(completeMock).toHaveBeenCalledWith( + expect.objectContaining({ + persist: true, + isPublic: true, + connectorId: 'azure-open-ai', + kibanaPublicUrl: 'http://kibana.com', + messages: buildConversation(message2), }) ); }); From 447c8b0c1fca2a74041db153c60fb361953facf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Wed, 29 Jan 2025 09:57:40 +0100 Subject: [PATCH 14/16] forward-compatible fallback. --- .../public/rule_connector/ai_assistant_params.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx index 3e9ae33742f99..417782d399421 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx @@ -55,6 +55,10 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< index ); } + // forward-compatible fallback. + if (actionParams.prompts && actionParams.prompts[0].message !== actionParams.message) { + editAction('message', actionParams.prompts[0].message, index); + } }, [actionParams, editAction, index]); const handleOnChange = ( From a4bfe6d09fb32b4174c2d442238222800cdbca5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Fri, 31 Jan 2025 10:52:57 +0100 Subject: [PATCH 15/16] removing redundant constants --- .../common/constants.ts | 26 +------------------ .../common/types.ts | 22 ---------------- .../rule_connector/ai_assistant_params.tsx | 6 ++--- .../server/rule_connector/index.test.ts | 13 +++++----- .../server/rule_connector/index.ts | 4 +-- 5 files changed, 13 insertions(+), 58 deletions(-) delete mode 100644 x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts index 7a4885fff8191..f5977cd72ad7b 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/constants.ts @@ -5,34 +5,10 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_STATUS_UNTRACKED, } from '@kbn/rule-data-utils'; -import { AlertStatus } from './types'; - -export const ACTIVE_ALERTS: AlertStatus = { - id: ALERT_STATUS_ACTIVE, - label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.active', { - defaultMessage: 'Active', - }), -}; - -export const RECOVERED_ALERTS: AlertStatus = { - id: ALERT_STATUS_RECOVERED, - label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.recovered', { - defaultMessage: 'Recovered', - }), -}; - -export const UNTRACKED_ALERTS: AlertStatus = { - id: ALERT_STATUS_UNTRACKED, - label: i18n.translate('xpack.observabilityAiAssistant.alertConnector.alertStatus.untracked', { - defaultMessage: 'Untracked', - }), -}; - -export const ALERT_STATUSES = [ACTIVE_ALERTS, RECOVERED_ALERTS, UNTRACKED_ALERTS]; +export const ALERT_STATUSES = [ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_STATUS_UNTRACKED]; diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts deleted file mode 100644 index ef95465e891a1..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/common/types.ts +++ /dev/null @@ -1,22 +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 { - ALERT_STATUS_ACTIVE, - ALERT_STATUS_RECOVERED, - ALERT_STATUS_UNTRACKED, -} from '@kbn/rule-data-utils'; - -type Status = - | typeof ALERT_STATUS_ACTIVE - | typeof ALERT_STATUS_RECOVERED - | typeof ALERT_STATUS_UNTRACKED; - -export interface AlertStatus { - id: Status; - label: string; -} diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx index 417782d399421..a16baeadc5666 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant_params.tsx @@ -48,7 +48,7 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< 'prompts', [ { - statuses: ALERT_STATUSES.map(({ id }) => id), + statuses: ALERT_STATUSES, message: actionParams.message || '', }, ], @@ -76,7 +76,7 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< const prompts = [ ...actionParams.prompts, { - statuses: ALERT_STATUSES.map(({ id }) => id), + statuses: ALERT_STATUSES, message: '', }, ]; @@ -136,7 +136,7 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< fullWidth id={`addNewActionConnectorActionGroup-${index}`} data-test-subj={`addNewActionConnectorActionGroup-${index}`} - options={ALERT_STATUSES.map(({ id }) => ({ + options={ALERT_STATUSES.map((id) => ({ label: id, }))} selectedOptions={prompt.statuses.map((id) => ({ label: id }))} diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts index d3b4219d7e2ff..e2c0f97d14a0d 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -8,8 +8,9 @@ import { AlertHit } from '@kbn/alerting-plugin/server/types'; import { ObservabilityAIAssistantRouteHandlerResources } from '@kbn/observability-ai-assistant-plugin/server/routes/types'; import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request'; +import { ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; import { OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID } from '../../common/rule_connector'; -import { ACTIVE_ALERTS, ALERT_STATUSES } from '../../common/constants'; +import { ALERT_STATUSES } from '../../common/constants'; import { getObsAIAssistantConnectorAdapter, getObsAIAssistantConnectorType, @@ -100,7 +101,7 @@ describe('observabilityAIAssistant rule_connector', () => { expect(params).toEqual({ connector: '.azure', - prompts: [{ message: 'hello', statuses: ALERT_STATUSES.map(({ id }) => id) }], + prompts: [{ message: 'hello', statuses: ALERT_STATUSES }], rule: { id: 'foo', name: 'bar', tags: [], ruleUrl: 'http://myrule.com' }, alerts: { new: [{ _id: 'new_alert' }], @@ -174,7 +175,7 @@ describe('observabilityAIAssistant rule_connector', () => { 'kibana.alert.uuid': 'alert_uuid', 'kibana.alert.rule.uuid': 'rule_uuid', 'kibana.alert.start': new Date().toISOString(), - 'kibana.alert.status': ACTIVE_ALERTS.id, + 'kibana.alert.status': ALERT_STATUS_ACTIVE, 'kibana.space_ids': ['default'], }, ], @@ -244,7 +245,7 @@ describe('observabilityAIAssistant rule_connector', () => { const message = 'hello'; const params = buildActionParams({ connector: 'azure-open-ai', - prompts: [{ message, statuses: ALERT_STATUSES.map(({ id }) => id) }], + prompts: [{ message, statuses: ALERT_STATUSES }], }); const connectorType = getObsAIAssistantConnectorType( @@ -278,8 +279,8 @@ describe('observabilityAIAssistant rule_connector', () => { const params = buildActionParams({ connector: 'azure-open-ai', prompts: [ - { message, statuses: ALERT_STATUSES.map(({ id }) => id) }, - { message: message2, statuses: ALERT_STATUSES.map(({ id }) => id) }, + { message, statuses: ALERT_STATUSES }, + { message: message2, statuses: ALERT_STATUSES }, ], }); diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts index d4be7def84a8a..0de8e5e8edcaa 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/server/rule_connector/index.ts @@ -228,7 +228,7 @@ async function executeAlertsChatCompletion( recovered: [...(params.alerts?.recovered || [])], }; - if (ALERT_STATUSES.some((status) => prompt.statuses.includes(status.id))) { + if (ALERT_STATUSES.some((status) => prompt.statuses.includes(status))) { alerts.new = alerts.new.filter((alert) => prompt.statuses.includes(get(alert, 'kibana.alert.status')) ); @@ -408,7 +408,7 @@ export const getObsAIAssistantConnectorAdapter = (): ConnectorAdapter< ? params.prompts : [ { - statuses: ALERT_STATUSES.map((status) => status.id), + statuses: ALERT_STATUSES, message: params.message || '', }, ], From c06d00a0ec46a175751058c4af95c381888f3346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Fri, 31 Jan 2025 10:59:42 +0100 Subject: [PATCH 16/16] use lodash's isEmpty for status validation check --- .../public/rule_connector/ai_assistant.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx index f52219ee34aea..830380c9b949e 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/ai_assistant.tsx @@ -6,6 +6,7 @@ */ import React, { lazy } from 'react'; +import { isEmpty } from 'lodash'; import type { ActionTypeModel as ConnectorTypeModel, GenericValidationResult, @@ -42,7 +43,7 @@ export function getConnectorType( if (!prompt.message) { errors.push(MESSAGE_REQUIRED); } - if (!prompt.statuses || prompt.statuses.length === 0) { + if (isEmpty(prompt.statuses)) { errors.push(STATUS_REQUIRED); }