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 ea3590740ab57..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, @@ -19,6 +20,7 @@ import { CONNECTOR_REQUIRED, CONNECTOR_TITLE, MESSAGE_REQUIRED, + STATUS_REQUIRED, } from './translations'; export function getConnectorType( @@ -35,23 +37,26 @@ export function getConnectorType( validateParams: async ( actionParams: ObsAIAssistantActionParams ): Promise> => { - const validationResult = { - errors: { - connector: [] as string[], - message: [] as string[], - prompts: [] as string[], - }, - }; + const validatePrompt = (prompt: { message: string; statuses: string[] }): string[] => { + const errors: string[] = []; - if (!actionParams.connector) { - validationResult.errors.connector.push(CONNECTOR_REQUIRED); - } + if (!prompt.message) { + errors.push(MESSAGE_REQUIRED); + } + if (isEmpty(prompt.statuses)) { + errors.push(STATUS_REQUIRED); + } - if (!actionParams.message) { - validationResult.errors.message.push(MESSAGE_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 08faf2166a6e5..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 @@ -8,12 +8,25 @@ 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, + EuiComboBox, + EuiButton, + EuiFlexGroup, +} from '@elastic/eui'; 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 } @@ -22,9 +35,65 @@ 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 + if (!actionParams.prompts) { + editAction( + 'prompts', + [ + { + statuses: ALERT_STATUSES, + message: actionParams.message || '', + }, + ], + 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 = ( + 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, + message: '', + }, + ]; + editAction('prompts', prompts, index); + } + }; + const handleRemovePrompt = () => { + if (actionParams.prompts) { + const prompts = actionParams.prompts.slice(0, -1); + editAction('prompts', prompts, index); + } + }; + + const isValidField = (statusError: string, promptIndex: number) => { + const errorsList = ((errors.prompts as RuleFormParamsErrors)?.[promptIndex] as string[]) || []; + return errorsList.includes(statusError); + }; return ( <> @@ -47,34 +116,99 @@ const ObsAIAssistantParamsFields: React.FunctionComponent< selectConnector(event.target.value); editAction('connector', event.target.value, index); }} - value={selectedConnector} + value={actionParams.connector} /> + {actionParams?.prompts?.map((prompt, promptIndex) => ( +
+ + + ({ + label: id, + }))} + selectedOptions={prompt.statuses.map((id) => ({ label: id }))} + onChange={(statuses) => { + handleOnChange( + 'statuses', + statuses.map((status) => status.label), + promptIndex + ); + }} + isClearable={true} + isInvalid={isValidField(STATUS_REQUIRED, promptIndex)} + /> + + + + + { + handleOnChange('message', event.target.value, promptIndex); + }} + isInvalid={isValidField(MESSAGE_REQUIRED, promptIndex)} + /> + + +
+ ))} - - - - + + { - 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/translations.ts b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/translations.ts index 2dde4cd5e92db..5b2a8aa732130 100644 --- a/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/translations.ts +++ b/x-pack/solutions/observability/plugins/observability_ai_assistant_app/public/rule_connector/translations.ts @@ -34,3 +34,10 @@ export const MESSAGE_REQUIRED = i18n.translate( defaultMessage: 'Message is required.', } ); + +export const STATUS_REQUIRED = i18n.translate( + 'xpack.observabilityAiAssistant.requiredStatusField', + { + defaultMessage: 'Status is required.', + } +); 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 fabeb14f8512b..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 @@ -84,7 +84,9 @@ "@kbn/ai-assistant-icon", "@kbn/product-doc-base-plugin", "@kbn/rule-data-utils", + "@kbn/i18n-react", "@kbn/utility-types", + "@kbn/alerts-ui-shared" ], "exclude": ["target/**/*"] }