[8.12] Adds EBT event for User Prompts, refactor existing EBT event tracking to be typesafe (#173175) (#173450)

# Backport

This will backport the following commits from `main` to `8.12`:
- [Adds EBT event for User Prompts, refactor existing EBT event tracking
to be typesafe (#173175)](https://github.com/elastic/kibana/pull/173175)

<!--- Backport version: 8.9.7 -->

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

<!--BACKPORT [{"author":{"name":"Coen
Warmer","email":"coen.warmer@gmail.com"},"sourceCommit":{"committedDate":"2023-12-15T09:21:20Z","message":"Adds
EBT event for User Prompts, refactor existing EBT event tracking to be
typesafe (#173175)\n\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"13ad0ef510ec23a380335935b4557efc01f01f2d","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","v8.12.0","v8.13.0"],"number":173175,"url":"https://github.com/elastic/kibana/pull/173175","mergeCommit":{"message":"Adds
EBT event for User Prompts, refactor existing EBT event tracking to be
typesafe (#173175)\n\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"13ad0ef510ec23a380335935b4557efc01f01f2d"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/173175","number":173175,"mergeCommit":{"message":"Adds
EBT event for User Prompts, refactor existing EBT event tracking to be
typesafe (#173175)\n\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"13ad0ef510ec23a380335935b4557efc01f01f2d"}}]}]
BACKPORT-->

Co-authored-by: Coen Warmer <coen.warmer@gmail.com>
This commit is contained in:
Kibana Machine 2023-12-15 05:34:28 -05:00 committed by GitHub
parent 1b761f16b0
commit ba5f326a3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 416 additions and 147 deletions

View file

@ -0,0 +1,49 @@
/*
* 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 { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { Message } from '../../common';
import {
eventType as chatFeedbackEventType,
chatFeedbackEventSchema,
ChatFeedback,
} from './schemas/chat_feedback';
import {
eventType as insightFeedbackEventType,
insightFeedbackEventSchema,
InsightFeedback,
} from './schemas/insight_feedback';
import {
eventType as userSentPromptEventType,
userSentPromptEventSchema,
} from './schemas/user_sent_prompt';
const schemas = [chatFeedbackEventSchema, insightFeedbackEventSchema, userSentPromptEventSchema];
export const TELEMETRY = {
[chatFeedbackEventType]: chatFeedbackEventType,
[insightFeedbackEventType]: insightFeedbackEventType,
[userSentPromptEventType]: userSentPromptEventType,
} as const;
export type TelemetryEventTypeWithPayload =
| { type: typeof chatFeedbackEventType; payload: ChatFeedback }
| { type: typeof insightFeedbackEventType; payload: InsightFeedback }
| { type: typeof userSentPromptEventType; payload: Message };
export const registerTelemetryEventTypes = (analytics: AnalyticsServiceSetup) => {
schemas.forEach((schema) => {
analytics.registerEventType<{}>(schema);
});
};
export function sendEvent(
analytics: AnalyticsServiceStart,
eventType: TelemetryEventTypeWithPayload
): void {
analytics.reportEvent(eventType.type, eventType.payload);
}

View file

@ -1,95 +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 { RootSchema } from '@kbn/analytics-client';
import { Message } from '../../common';
import type { Feedback } from '../components/feedback_buttons';
export const MESSAGE_FEEDBACK = 'observability_ai_assistant_chat_message_feedback' as const;
export const INSIGHT_FEEDBACK = 'observability_ai_assistant_chat_insight_feedback' as const;
export interface MessageFeedback extends Message {
feedback: Feedback;
}
export interface TelemetryEvent {
eventType: typeof MESSAGE_FEEDBACK | typeof INSIGHT_FEEDBACK;
schema: RootSchema<MessageFeedback>;
}
export const MESSAGE_FEEDBACK_SCHEMA: TelemetryEvent = {
eventType: MESSAGE_FEEDBACK,
schema: {
'@timestamp': {
type: 'text',
_meta: {
description: 'The timestamp of the message.',
},
},
feedback: {
type: 'text',
_meta: {
description: 'Whether the user has deemed this response useful or not',
},
},
message: {
properties: {
content: {
type: 'text',
_meta: {
description: 'The response generated by the LLM.',
optional: true,
},
},
name: {
type: 'text',
_meta: {
description: 'The name of the function that was executed.',
optional: true,
},
},
role: {
type: 'text',
_meta: {
description: 'The actor that generated the response.',
},
},
data: {
type: 'text',
_meta: {
description: '',
optional: true,
},
},
function_call: {
properties: {
name: {
type: 'text',
_meta: {
description: 'The name of the function that was executed.',
optional: false,
},
},
arguments: {
type: 'text',
_meta: {
description: 'The arguments that were used when executing the function.',
optional: true,
},
},
trigger: {
type: 'text',
_meta: {
description: 'The actor which triggered the execution of this function.',
},
},
},
},
},
},
},
};

View file

@ -0,0 +1,122 @@
/*
* 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 { EventTypeOpts } from '@kbn/analytics-client';
import type { Message, Conversation } from '../../../common';
import type { Feedback } from '../../components/feedback_buttons';
import { messageSchema } from './common';
export interface ChatFeedback {
messageWithFeedback: {
message: Message;
feedback: Feedback;
};
conversation: Conversation;
}
export const eventType = 'observability_ai_assistant_chat_feedback';
export const chatFeedbackEventSchema: EventTypeOpts<ChatFeedback> = {
eventType,
schema: {
messageWithFeedback: {
properties: {
message: {
properties: messageSchema,
},
feedback: {
type: 'text',
_meta: {
description: 'Whether the user has deemed this response useful or not',
},
},
},
},
conversation: {
properties: {
'@timestamp': {
type: 'text',
_meta: {
description: 'The timestamp of the conversation.',
},
},
user: {
properties: {
id: {
type: 'text',
_meta: {
description: 'The id of the user.',
optional: true,
},
},
name: {
type: 'text',
_meta: {
description: 'The name of the user.',
},
},
},
},
conversation: {
properties: {
id: {
type: 'text',
_meta: {
description: 'The id of the conversation.',
},
},
title: {
type: 'text',
_meta: {
description: 'The title of the conversation.',
},
},
last_updated: {
type: 'text',
_meta: {
description: 'The timestamp of the last message in the conversation.',
},
},
},
},
messages: {
type: 'array',
items: {
properties: messageSchema,
},
_meta: {
description: 'The messages in the conversation.',
},
},
labels: {
type: 'pass_through',
_meta: {
description: 'The labels of the conversation.',
},
},
numeric_labels: {
type: 'pass_through',
_meta: {
description: 'The numeric labels of the conversation.',
},
},
namespace: {
type: 'text',
_meta: {
description: 'The namespace of the conversation.',
},
},
public: {
type: 'boolean',
_meta: {
description: 'Whether the conversation is public or not.',
},
},
},
},
},
};

View file

@ -0,0 +1,77 @@
/*
* 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 { RootSchema } from '@kbn/analytics-client';
import type { Message } from '../../../common';
export const messageSchema: RootSchema<Message> = {
'@timestamp': {
type: 'text',
_meta: {
description: 'The timestamp of the message.',
},
},
message: {
properties: {
content: {
type: 'text',
_meta: {
description: 'The response generated by the LLM.',
optional: true,
},
},
name: {
type: 'text',
_meta: {
description: 'The name of the function that was executed.',
optional: true,
},
},
role: {
type: 'text',
_meta: {
description: 'The actor that generated the response.',
},
},
data: {
type: 'text',
_meta: {
description: '',
optional: true,
},
},
function_call: {
_meta: {
description: 'The function call that was executed.',
optional: true,
},
properties: {
name: {
type: 'text',
_meta: {
description: 'The name of the function that was executed.',
optional: false,
},
},
arguments: {
type: 'text',
_meta: {
description: 'The arguments that were used when executing the function.',
optional: true,
},
},
trigger: {
type: 'text',
_meta: {
description: 'The actor which triggered the execution of this function.',
},
},
},
},
},
},
};

View file

@ -0,0 +1,33 @@
/*
* 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 { EventTypeOpts } from '@kbn/analytics-client';
import type { Message } from '../../../common';
import type { Feedback } from '../../components/feedback_buttons';
import { messageSchema } from './common';
export interface InsightFeedback {
feedback: Feedback;
message: Message;
}
export const eventType = 'observability_ai_assistant_insight_feedback';
export const insightFeedbackEventSchema: EventTypeOpts<InsightFeedback> = {
eventType,
schema: {
feedback: {
type: 'text',
_meta: {
description: 'Whether the user has deemed this response useful or not',
},
},
message: {
properties: messageSchema,
},
},
};

View file

@ -0,0 +1,16 @@
/*
* 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 { EventTypeOpts } from '@kbn/analytics-client';
import type { Message } from '../../../common';
import { messageSchema } from './common';
export const eventType = 'observability_ai_assistant_user_sent_prompt_in_chat';
export const userSentPromptEventSchema: EventTypeOpts<Message> = {
eventType,
schema: messageSchema,
};

View file

@ -32,9 +32,9 @@ import { Feedback } from '../feedback_buttons';
import { IncorrectLicensePanel } from './incorrect_license_panel';
import { WelcomeMessage } from './welcome_message';
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
import { MESSAGE_FEEDBACK } from '../../analytics/schema';
import { ChatActionClickType } from './types';
import type { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
import { TELEMETRY, sendEvent } from '../../analytics';
const fullHeightClassName = css`
height: 100%;
@ -140,8 +140,15 @@ export function ChatBody({
parent.scrollTop + parent.clientHeight >= parent.scrollHeight;
const handleFeedback = (message: Message, feedback: Feedback) => {
const feedbackEvent = { ...message, feedback };
chatService.analytics.reportEvent(MESSAGE_FEEDBACK, feedbackEvent);
if (conversation.value?.conversation && 'user' in conversation.value.conversation) {
sendEvent(chatService.analytics, {
type: TELEMETRY.observability_ai_assistant_chat_feedback,
payload: {
messageWithFeedback: { message, feedback },
conversation: conversation.value.conversation,
},
});
}
};
useEffect(() => {
@ -196,6 +203,9 @@ export function ChatBody({
onSubmit={(message) => {
next(messages.concat(message));
}}
onSendTelemetry={(eventWithPayload) =>
sendEvent(chatService.analytics, eventWithPayload)
}
/>
<EuiSpacer size="s" />
</EuiPanel>
@ -236,6 +246,9 @@ export function ChatBody({
const indexOf = messages.indexOf(message);
next(messages.slice(0, indexOf));
}}
onSendTelemetry={(eventWithPayload) =>
sendEvent(chatService.analytics, eventWithPayload)
}
onStopGenerating={() => {
stop();
}}
@ -283,8 +296,11 @@ export function ChatBody({
className={promptEditorContainerClassName}
>
<ChatPromptEditor
loading={isLoading}
disabled={!connectors.selectedConnector || !hasCorrectLicense}
loading={isLoading}
onSendTelemetry={(eventWithPayload) =>
sendEvent(chatService.analytics, eventWithPayload)
}
onSubmit={(message) => {
setStickToBottom(true);
return next(messages.concat(message));

View file

@ -49,18 +49,20 @@ const noPanelStyle = css`
export function ChatConsolidatedItems({
consolidatedItem,
onActionClick,
onEditSubmit,
onFeedback,
onRegenerate,
onEditSubmit,
onSendTelemetry,
onStopGenerating,
onActionClick,
}: {
consolidatedItem: ChatTimelineItem[];
onActionClick: ChatTimelineProps['onActionClick'];
onEditSubmit: ChatTimelineProps['onEdit'];
onFeedback: ChatTimelineProps['onFeedback'];
onRegenerate: ChatTimelineProps['onRegenerate'];
onEditSubmit: ChatTimelineProps['onEdit'];
onSendTelemetry: ChatTimelineProps['onSendTelemetry'];
onStopGenerating: ChatTimelineProps['onStopGenerating'];
onActionClick: ChatTimelineProps['onActionClick'];
}) {
const [expanded, setExpanded] = useState(false);
@ -122,15 +124,16 @@ export function ChatConsolidatedItems({
// use index, not id to prevent unmounting of component when message is persisted
key={index}
{...item}
onActionClick={onActionClick}
onEditSubmit={(message) => onEditSubmit(item.message, message)}
onFeedbackClick={(feedback) => {
onFeedback(item.message, feedback);
}}
onRegenerateClick={() => {
onRegenerate(item.message);
}}
onEditSubmit={(message) => onEditSubmit(item.message, message)}
onSendTelemetry={onSendTelemetry}
onStopGeneratingClick={onStopGenerating}
onActionClick={onActionClick}
/>
))
: null}

View file

@ -21,17 +21,19 @@ import { ChatItemContentInlinePromptEditor } from './chat_item_content_inline_pr
import { ChatItemControls } from './chat_item_controls';
import { ChatTimelineItem } from './chat_timeline';
import { getRoleTranslation } from '../../utils/get_role_translation';
import type { Feedback } from '../feedback_buttons';
import { Message } from '../../../common';
import { FailedToLoadResponse } from '../message_panel/failed_to_load_response';
import { ChatActionClickHandler } from './types';
import type { Message } from '../../../common';
import type { Feedback } from '../feedback_buttons';
import type { ChatActionClickHandler } from './types';
import type { TelemetryEventTypeWithPayload } from '../../analytics';
export interface ChatItemProps extends ChatTimelineItem {
onActionClick: ChatActionClickHandler;
onEditSubmit: (message: Message) => void;
onFeedbackClick: (feedback: Feedback) => void;
onRegenerateClick: () => void;
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
onStopGeneratingClick: () => void;
onActionClick: ChatActionClickHandler;
}
const normalMessageClassName = css`
@ -66,21 +68,22 @@ const noPanelMessageClassName = css`
export function ChatItem({
actions: { canCopy, canEdit, canGiveFeedback, canRegenerate },
display: { collapsed },
message: {
message: { function_call: functionCall, role },
},
content,
currentUser,
display: { collapsed },
element,
error,
loading,
message: {
message: { function_call: functionCall, role },
},
title,
onActionClick,
onEditSubmit,
onFeedbackClick,
onRegenerateClick,
onSendTelemetry,
onStopGeneratingClick,
onActionClick,
}: ChatItemProps) {
const accordionId = useGeneratedHtmlId({ prefix: 'chat' });
@ -130,6 +133,7 @@ export function ChatItem({
loading={loading}
onSubmit={handleInlineEditSubmit}
onActionClick={onActionClick}
onSendTelemetry={onSendTelemetry}
/>
) : null;

View file

@ -8,8 +8,9 @@
import React from 'react';
import { MessageText } from '../message_panel/message_text';
import { ChatPromptEditor } from './chat_prompt_editor';
import { MessageRole, type Message } from '../../../common';
import { ChatActionClickHandler } from './types';
import { type Message, MessageRole } from '../../../common';
import type { ChatActionClickHandler } from './types';
import type { TelemetryEventTypeWithPayload } from '../../analytics';
interface Props {
content: string | undefined;
@ -22,16 +23,18 @@ interface Props {
| undefined;
loading: boolean;
editing: boolean;
onSubmit: (message: Message) => void;
onActionClick: ChatActionClickHandler;
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
onSubmit: (message: Message) => void;
}
export function ChatItemContentInlinePromptEditor({
content,
functionCall,
editing,
loading,
onSubmit,
onActionClick,
onSendTelemetry,
onSubmit,
}: Props) {
return !editing ? (
<MessageText content={content || ''} loading={loading} onActionClick={onActionClick} />
@ -44,6 +47,7 @@ export function ChatItemContentInlinePromptEditor({
initialSelectedFunctionName={functionCall?.name}
trigger={functionCall?.trigger}
onSubmit={onSubmit}
onSendTelemetry={onSendTelemetry}
/>
);
}

View file

@ -46,14 +46,14 @@ export function ChatItemControls({
controls = <StopGeneratingButton onClick={onStopGeneratingClick} />;
} else if (displayFeedback || displayRegenerate) {
controls = (
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
{displayFeedback ? (
<EuiFlexItem grow={true}>
<FeedbackButtons onClickFeedback={onFeedbackClick} />
</EuiFlexItem>
) : null}
{displayRegenerate ? (
<EuiFlexItem grow={false} style={{ alignSelf: 'flex-end' }}>
<EuiFlexItem grow={false}>
<RegenerateResponseButton onClick={onRegenerateClick} />
</EuiFlexItem>
) : null}

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiButtonEmpty,
EuiButtonIcon,
@ -16,12 +18,11 @@ import {
EuiTextArea,
keys,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CodeEditor } from '@kbn/kibana-react-plugin/public';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { MessageRole, type Message } from '../../../common';
import { useJsonEditorModel } from '../../hooks/use_json_editor_model';
import { FunctionListPopover } from './function_list_popover';
import { useJsonEditorModel } from '../../hooks/use_json_editor_model';
import { TelemetryEventTypeWithPayload, TELEMETRY } from '../../analytics';
export interface ChatPromptEditorProps {
disabled: boolean;
@ -31,6 +32,7 @@ export interface ChatPromptEditorProps {
initialFunctionPayload?: string;
trigger?: MessageRole;
onSubmit: (message: Message) => void;
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
}
export function ChatPromptEditor({
@ -40,6 +42,7 @@ export function ChatPromptEditor({
initialSelectedFunctionName,
initialFunctionPayload,
onSubmit,
onSendTelemetry,
}: ChatPromptEditorProps) {
const isFocusTrapEnabled = Boolean(initialPrompt);
@ -112,9 +115,11 @@ export function ChatPromptEditor({
setFunctionPayload(undefined);
handleResetTextArea();
let message: Message;
try {
if (selectedFunctionName) {
await onSubmit({
message = {
'@timestamp': new Date().toISOString(),
message: {
role: MessageRole.Assistant,
@ -125,20 +130,27 @@ export function ChatPromptEditor({
arguments: currentPayload,
},
},
});
};
onSubmit(message);
setFunctionPayload(undefined);
setSelectedFunctionName(undefined);
} else {
await onSubmit({
message = {
'@timestamp': new Date().toISOString(),
message: { role: MessageRole.User, content: currentPrompt },
});
};
onSubmit(message);
}
onSendTelemetry({
type: TELEMETRY.observability_ai_assistant_user_sent_prompt_in_chat,
payload: message,
});
} catch (_) {
setPrompt(currentPrompt);
}
}, [functionPayload, loading, onSubmit, prompt, selectedFunctionName]);
}, [functionPayload, loading, onSendTelemetry, onSubmit, prompt, selectedFunctionName]);
useEffect(() => {
setFunctionPayload(initialJsonString);

View file

@ -119,11 +119,12 @@ const defaultProps: ComponentProps<typeof Component> = {
},
}),
],
onActionClick: async () => {},
onEdit: async () => {},
onFeedback: () => {},
onRegenerate: () => {},
onSendTelemetry: () => {},
onStopGenerating: () => {},
onActionClick: async () => {},
};
export const ChatTimeline = Template.bind({});

View file

@ -14,6 +14,7 @@ import type { Message } from '../../../common';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import type { ChatActionClickHandler } from './types';
import type { ObservabilityAIAssistantChatService } from '../../types';
import type { TelemetryEventTypeWithPayload } from '../../analytics';
import { ChatItem } from './chat_item';
import { ChatConsolidatedItems } from './chat_consolidated_items';
import { ChatState } from '../../hooks/use_chat';
@ -54,13 +55,13 @@ export interface ChatTimelineProps {
onEdit: (message: Message, messageAfterEdit: Message) => void;
onFeedback: (message: Message, feedback: Feedback) => void;
onRegenerate: (message: Message) => void;
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
onStopGenerating: () => void;
onActionClick: ChatActionClickHandler;
}
export function ChatTimeline({
messages,
knowledgeBase,
chatService,
hasConnector,
currentUser,
@ -68,6 +69,7 @@ export function ChatTimeline({
onEdit,
onFeedback,
onRegenerate,
onSendTelemetry,
onStopGenerating,
onActionClick,
chatState,
@ -115,17 +117,19 @@ export function ChatTimeline({
<ChatConsolidatedItems
key={index}
consolidatedItem={item}
onActionClick={onActionClick}
onFeedback={onFeedback}
onRegenerate={onRegenerate}
onEditSubmit={onEdit}
onSendTelemetry={onSendTelemetry}
onStopGenerating={onStopGenerating}
onActionClick={onActionClick}
/>
) : (
<ChatItem
// use index, not id to prevent unmounting of component when message is persisted
key={index}
{...item}
onActionClick={onActionClick}
onFeedbackClick={(feedback) => {
onFeedback(item.message, feedback);
}}
@ -135,8 +139,8 @@ export function ChatTimeline({
onEditSubmit={(message) => {
onEdit(item.message, message);
}}
onSendTelemetry={onSendTelemetry}
onStopGeneratingClick={onStopGenerating}
onActionClick={onActionClick}
/>
);
})}

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import React from 'react';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { useKibana } from '../hooks/use_kibana';
export type Feedback = 'positive' | 'negative';
@ -16,6 +17,22 @@ interface FeedbackButtonsProps {
}
export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) {
const { notifications } = useKibana().services;
const [hasBeenClicked, setHasBeenClicked] = useState(false);
const handleClickPositive = () => {
onClickFeedback('positive');
setHasBeenClicked(true);
notifications.toasts.addSuccess('Thanks for your feedback!');
};
const handleClickNegative = () => {
onClickFeedback('negative');
setHasBeenClicked(true);
notifications.toasts.addSuccess('Thanks for your feedback!');
};
return (
<EuiFlexGroup responsive={false} direction="row" alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
@ -34,9 +51,10 @@ export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) {
<EuiButtonEmpty
data-test-subj="observabilityAiAssistantFeedbackButtonsPositiveButton"
color="success"
disabled={hasBeenClicked}
iconType="faceHappy"
size="s"
onClick={() => onClickFeedback('positive')}
onClick={handleClickPositive}
>
{i18n.translate('xpack.observabilityAiAssistant.insight.feedbackButtons.positive', {
defaultMessage: 'Yes',
@ -48,9 +66,10 @@ export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) {
<EuiButtonEmpty
data-test-subj="observabilityAiAssistantFeedbackButtonsNegativeButton"
color="danger"
disabled={hasBeenClicked}
iconType="faceSad"
size="s"
onClick={() => onClickFeedback('negative')}
onClick={handleClickNegative}
>
{i18n.translate('xpack.observabilityAiAssistant.insight.feedbackButtons.negative', {
defaultMessage: 'No',

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { last, noop } from 'lodash';
import { last } from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
import { MessageRole, type Message } from '../../../common/types';
import { INSIGHT_FEEDBACK } from '../../analytics/schema';
import { sendEvent, TELEMETRY } from '../../analytics';
import { ObservabilityAIAssistantChatServiceProvider } from '../../context/observability_ai_assistant_chat_service_provider';
import { useAbortableAsync } from '../../hooks/use_abortable_async';
import { ChatState, useChat } from '../../hooks/use_chat';
@ -79,14 +79,17 @@ function ChatContent({
) : (
<EuiFlexGroup direction="row">
<FeedbackButtons
onClickFeedback={(feedback) =>
lastAssistantResponse
? chatService.analytics.reportEvent(INSIGHT_FEEDBACK, {
onClickFeedback={(feedback) => {
if (lastAssistantResponse) {
sendEvent(chatService.analytics, {
type: TELEMETRY.observability_ai_assistant_insight_feedback,
payload: {
feedback,
...lastAssistantResponse,
})
: noop
}
message: lastAssistantResponse,
},
});
}
}}
/>
<EuiFlexItem grow={false}>
<RegenerateResponseButton

View file

@ -27,7 +27,7 @@ import type {
ObservabilityAIAssistantPluginStartDependencies,
ObservabilityAIAssistantService,
} from './types';
import { MessageFeedback, MESSAGE_FEEDBACK_SCHEMA } from './analytics/schema';
import { registerTelemetryEventTypes } from './analytics';
export class ObservabilityAIAssistantPlugin
implements
@ -89,7 +89,7 @@ export class ObservabilityAIAssistantPlugin
},
});
coreSetup.analytics.registerEventType<MessageFeedback>(MESSAGE_FEEDBACK_SCHEMA);
registerTelemetryEventTypes(coreSetup.analytics);
return {};
}

View file

@ -52,7 +52,8 @@
"@kbn/analytics-client",
"@kbn/tooling-log",
"@kbn/babel-register",
"@kbn/dev-cli-runner"
"@kbn/dev-cli-runner",
"@kbn/core-analytics-browser"
],
"exclude": ["target/**/*"]
}