diff --git a/packages/botonic-plugin-flow-builder/src/action/knowledge-bases.ts b/packages/botonic-plugin-flow-builder/src/action/knowledge-bases.ts index 73395c6922..ab84519bfe 100644 --- a/packages/botonic-plugin-flow-builder/src/action/knowledge-bases.ts +++ b/packages/botonic-plugin-flow-builder/src/action/knowledge-bases.ts @@ -71,16 +71,17 @@ async function getContentsWithKnowledgeResponse( return undefined } - return updateContentsWithAnswer(contents, knowledgeBaseResponse.answer) + return updateContentsWithResponse(contents, knowledgeBaseResponse) } -function updateContentsWithAnswer( +function updateContentsWithResponse( contents: FlowContent[], - answer: string + response: KnowledgeBaseResponse ): FlowContent[] { return contents.map(content => { if (content instanceof FlowKnowledgeBase) { - content.text = answer + content.text = response.answer + content.inferenceId = response.inferenceId } return content diff --git a/packages/botonic-plugin-flow-builder/src/content-fields/flow-knowledge-base.tsx b/packages/botonic-plugin-flow-builder/src/content-fields/flow-knowledge-base.tsx index 4ab2ff475e..fc76bfb139 100644 --- a/packages/botonic-plugin-flow-builder/src/content-fields/flow-knowledge-base.tsx +++ b/packages/botonic-plugin-flow-builder/src/content-fields/flow-knowledge-base.tsx @@ -5,19 +5,30 @@ import { ContentFieldsBase } from './content-fields-base' import { HtKnowledgeBaseNode } from './hubtype-fields' export class FlowKnowledgeBase extends ContentFieldsBase { - public code = '' + public code: string = '' + public feedbackEnabled: boolean = false public sources: string[] = [] - public text = '' + public text: string = '' + public inferenceId?: string static fromHubtypeCMS(component: HtKnowledgeBaseNode): FlowKnowledgeBase { const newKnowledgeBase = new FlowKnowledgeBase(component.id) newKnowledgeBase.code = component.code + newKnowledgeBase.feedbackEnabled = component.content.feedback_enabled newKnowledgeBase.sources = component.content.sources return newKnowledgeBase } toBotonic(id: string): JSX.Element { - return {this.text} + return ( + + {this.text} + + ) } } diff --git a/packages/botonic-plugin-flow-builder/src/content-fields/hubtype-fields/knowledge-base.ts b/packages/botonic-plugin-flow-builder/src/content-fields/hubtype-fields/knowledge-base.ts index 9e7e7cbc6c..190f8ad4c1 100644 --- a/packages/botonic-plugin-flow-builder/src/content-fields/hubtype-fields/knowledge-base.ts +++ b/packages/botonic-plugin-flow-builder/src/content-fields/hubtype-fields/knowledge-base.ts @@ -4,6 +4,7 @@ import { HtNodeWithContentType } from './node-types' export interface HtKnowledgeBaseNode extends HtBaseNode { type: HtNodeWithContentType.KNOWLEDGE_BASE content: { + feedback_enabled: boolean sources: string[] } } diff --git a/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback-knowledgebase.ts b/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback-knowledgebase.ts new file mode 100644 index 0000000000..776cde2fea --- /dev/null +++ b/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback-knowledgebase.ts @@ -0,0 +1,33 @@ +import { + EventAction, + EventFeedbackKnowledgebase, + EventType, + RequestData, +} from '../types' +import { HtEvent } from './ht-event' + +export class HtEventFeedbackKnowledgebase extends HtEvent { + action: EventAction.FeedbackKnowledgebase + knowledgebase_inference_id: string + feedback_target_id: string + feedback_group_id: string + possible_options: string[] + possible_values?: number[] + option: string + value?: number + comment?: string + + constructor(event: EventFeedbackKnowledgebase, requestData: RequestData) { + super(event, requestData) + this.type = EventType.WebEvent + this.action = event.action + this.knowledgebase_inference_id = event.knowledgebaseInferenceId + this.feedback_target_id = event.feedbackTargetId + this.feedback_group_id = event.feedbackGroupId + this.possible_options = event.possibleOptions + this.possible_values = event.possibleValues + this.option = event.option + this.value = event.value + this.comment = event.comment + } +} diff --git a/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback.ts b/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback.ts index c05c373d04..b11ec83686 100644 --- a/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback.ts +++ b/packages/botonic-plugin-hubtype-analytics/src/event-models/ht-event-feedback.ts @@ -7,20 +7,20 @@ export class HtEventFeedback extends HtEvent { | EventAction.FeedbackConversation | EventAction.FeedbackMessage | EventAction.FeedbackWebview - feedback_target_id?: string - feedback_group_id?: string + feedback_target_id: string + feedback_group_id: string possible_options: string[] - possible_values: number[] + possible_values?: number[] option: string - value: number + value?: number comment?: string constructor(event: EventFeedback, requestData: RequestData) { super(event, requestData) this.type = EventType.WebEvent this.action = event.action - this.feedback_target_id = event.feedbackTargetId // ?? case_id, message_id, conversation_id ???, webview_name - this.feedback_group_id = event.feedbackGroupId // ?? + this.feedback_target_id = event.feedbackTargetId + this.feedback_group_id = event.feedbackGroupId this.possible_options = event.possibleOptions this.possible_values = event.possibleValues this.option = event.option diff --git a/packages/botonic-plugin-hubtype-analytics/src/event-models/index.ts b/packages/botonic-plugin-hubtype-analytics/src/event-models/index.ts index 9fac8e84d2..db6546c7b2 100644 --- a/packages/botonic-plugin-hubtype-analytics/src/event-models/index.ts +++ b/packages/botonic-plugin-hubtype-analytics/src/event-models/index.ts @@ -2,6 +2,7 @@ export { HtEvent } from './ht-event' export { HtEventCustom } from './ht-event-custom' export { HtEventFallback } from './ht-event-fallback' export { HtEventFeedback } from './ht-event-feedback' +export { HtEventFeedbackKnowledgebase } from './ht-event-feedback-knowledgebase' export { HtEventFlow } from './ht-event-flow' export { HtEventHandoff } from './ht-event-handoff' export { HtEventHandoffOption } from './ht-event-handoff-option' diff --git a/packages/botonic-plugin-hubtype-analytics/src/types.ts b/packages/botonic-plugin-hubtype-analytics/src/types.ts index b8457360d4..507015c7f3 100644 --- a/packages/botonic-plugin-hubtype-analytics/src/types.ts +++ b/packages/botonic-plugin-hubtype-analytics/src/types.ts @@ -7,6 +7,7 @@ export enum EventAction { FeedbackCase = 'feedback_case', FeedbackMessage = 'feedback_message', FeedbackConversation = 'feedback_conversation', + FeedbackKnowledgebase = 'feedback_knowledgebase', FeedbackWebview = 'feedback_webview', FlowNode = 'flow_node', HandoffOption = 'handoff_option', @@ -32,13 +33,24 @@ export interface EventFeedback extends HtBaseEventProps { | EventAction.FeedbackConversation | EventAction.FeedbackMessage | EventAction.FeedbackWebview - messageGeneratedBy?: string - feedbackTargetId?: string - feedbackGroupId?: string + feedbackTargetId: string + feedbackGroupId: string possibleOptions: string[] - possibleValues: number[] + possibleValues?: number[] option: string - value: number + value?: number + comment?: string +} + +export interface EventFeedbackKnowledgebase extends HtBaseEventProps { + action: EventAction.FeedbackKnowledgebase + knowledgebaseInferenceId: string + feedbackTargetId: string + feedbackGroupId: string + possibleOptions: string[] + possibleValues?: number[] + option: string + value?: number comment?: string } @@ -135,6 +147,7 @@ export interface EventCustom extends HtBaseEventProps { export type HtEventProps = | EventFeedback + | EventFeedbackKnowledgebase | EventFlow | EventHandoff | EventHandoffOption diff --git a/packages/botonic-plugin-hubtype-analytics/src/utils.ts b/packages/botonic-plugin-hubtype-analytics/src/utils.ts index 74c45185a3..74be62543c 100644 --- a/packages/botonic-plugin-hubtype-analytics/src/utils.ts +++ b/packages/botonic-plugin-hubtype-analytics/src/utils.ts @@ -3,6 +3,7 @@ import { HtEventCustom, HtEventFallback, HtEventFeedback, + HtEventFeedbackKnowledgebase, HtEventFlow, HtEventHandoff, HtEventHandoffOption, @@ -25,6 +26,9 @@ export function createHtEvent( case EventAction.FeedbackWebview: return new HtEventFeedback(htEventProps, requestData) + case EventAction.FeedbackKnowledgebase: + return new HtEventFeedbackKnowledgebase(htEventProps, requestData) + case EventAction.FlowNode: return new HtEventFlow(htEventProps, requestData) diff --git a/packages/botonic-plugin-hubtype-analytics/tests/event-feedback-knowledgebase.test.ts b/packages/botonic-plugin-hubtype-analytics/tests/event-feedback-knowledgebase.test.ts new file mode 100644 index 0000000000..d9bcdc1314 --- /dev/null +++ b/packages/botonic-plugin-hubtype-analytics/tests/event-feedback-knowledgebase.test.ts @@ -0,0 +1,34 @@ +import { createHtEvent, EventAction, EventType } from '../src' +import { getRequestData } from './helpers' + +describe('Create feedback knowledgebase event', () => { + test('A message generated for a knowledge base recive feedback', () => { + const requestData = getRequestData() + const htEvent = createHtEvent(requestData, { + action: EventAction.FeedbackKnowledgebase, + feedbackTargetId: 'messageIdTest', + feedbackGroupId: 'groupIdTest', + knowledgebaseInferenceId: 'knowledgebaseInferenceIdTest', + possibleOptions: ['thumbs_down', 'thumbs_up'], + possibleValues: [0, 1], + option: 'thumbs_down', + value: 0, + }) + + expect(htEvent).toEqual({ + chat_id: 'chatIdTest', + chat_language: 'es', + chat_country: 'ES', + format_version: 2, + action: EventAction.FeedbackKnowledgebase, + feedback_target_id: 'messageIdTest', + feedback_group_id: 'groupIdTest', + knowledgebase_inference_id: 'knowledgebaseInferenceIdTest', + possible_options: ['thumbs_down', 'thumbs_up'], + possible_values: [0, 1], + option: 'thumbs_down', + value: 0, + type: EventType.WebEvent, + }) + }) +}) diff --git a/packages/botonic-plugin-hubtype-analytics/tests/event-feedback.test.ts b/packages/botonic-plugin-hubtype-analytics/tests/event-feedback.test.ts index 2d0526e381..a71f55ee7b 100644 --- a/packages/botonic-plugin-hubtype-analytics/tests/event-feedback.test.ts +++ b/packages/botonic-plugin-hubtype-analytics/tests/event-feedback.test.ts @@ -5,7 +5,9 @@ describe('Create feedback event', () => { test('A conversation feedback event is created', () => { const requestData = getRequestData() const htEvent = createHtEvent(requestData, { - action: EventAction.FeedbackMessage, + action: EventAction.FeedbackCase, + feedbackTargetId: 'caseId', + feedbackGroupId: 'groupIdTest', possibleOptions: ['*', '**', '***', '****', '*****'], possibleValues: [1, 2, 3, 4, 5], option: '**', @@ -17,7 +19,9 @@ describe('Create feedback event', () => { chat_language: 'es', chat_country: 'ES', format_version: 2, - action: EventAction.FeedbackMessage, + action: EventAction.FeedbackCase, + feedback_target_id: 'caseId', + feedback_group_id: 'groupIdTest', possible_options: ['*', '**', '***', '****', '*****'], possible_values: [1, 2, 3, 4, 5], option: '**', @@ -30,6 +34,8 @@ describe('Create feedback event', () => { const requestData = getRequestData() const htEvent = createHtEvent(requestData, { action: EventAction.FeedbackConversation, + feedbackTargetId: 'chatIdTest', + feedbackGroupId: 'groupIdTest', possibleOptions: ['*', '**', '***', '****', '*****'], possibleValues: [1, 2, 3, 4, 5], option: '**', @@ -43,6 +49,8 @@ describe('Create feedback event', () => { chat_country: 'ES', format_version: 2, action: EventAction.FeedbackConversation, + feedback_target_id: 'chatIdTest', + feedback_group_id: 'groupIdTest', possible_options: ['*', '**', '***', '****', '*****'], possible_values: [1, 2, 3, 4, 5], option: '**', diff --git a/packages/botonic-react/src/assets/thumbs-down.svg b/packages/botonic-react/src/assets/thumbs-down.svg new file mode 100644 index 0000000000..2d967cb3b8 --- /dev/null +++ b/packages/botonic-react/src/assets/thumbs-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/botonic-react/src/assets/thumbs-up.svg b/packages/botonic-react/src/assets/thumbs-up.svg new file mode 100644 index 0000000000..4885323b86 --- /dev/null +++ b/packages/botonic-react/src/assets/thumbs-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/botonic-react/src/components/index-types.ts b/packages/botonic-react/src/components/index-types.ts index e70fc4c50c..44ddd963b9 100644 --- a/packages/botonic-react/src/components/index-types.ts +++ b/packages/botonic-react/src/components/index-types.ts @@ -1,4 +1,4 @@ -import React, { ErrorInfo } from 'react' +import React from 'react' import { SENDERS } from '../index-types' import { CoverComponentProps } from '../webchat/index-types' @@ -29,6 +29,8 @@ export interface MessageProps { export interface TextProps extends MessageProps { // converts markdown syntax to HTML markdown?: boolean + feedbackEnabled?: boolean + inferenceId?: string } export interface Webview { @@ -204,21 +206,3 @@ export interface WebchatSettingsProps { export type WrappedComponent = React.FunctionComponent & { customTypeName: string } - -// TODO: Reuse types to be typed in respective functions -// export class ErrorBoundary extends React.Component { -// componentDidCatch(error: Error, errorInfo: ErrorInfo): void -// } - -// export function createErrorBoundary(_?: { -// errorComponent: React.ComponentType -// }): ErrorBoundary - -// export function customMessage(_: { -// name: string -// component: React.ComponentType -// defaultProps?: Record -// errorBoundary?: ErrorBoundary -// }): WrappedComponent - -// export function getDisplayName(component: React.ComponentType): string diff --git a/packages/botonic-react/src/components/message/index.jsx b/packages/botonic-react/src/components/message/index.jsx index 9017c740ae..7f662e7f0c 100644 --- a/packages/botonic-react/src/components/message/index.jsx +++ b/packages/botonic-react/src/components/message/index.jsx @@ -12,6 +12,7 @@ import { Button } from '../button' import { ButtonsDisabler } from '../buttons-disabler' import { getMarkdownStyle, renderLinks, renderMarkdown } from '../markdown' import { Reply } from '../reply' +import { MessageFooter } from './message-footer' import { MessageImage } from './message-image' import { BlobContainer, @@ -20,7 +21,7 @@ import { BlobTickContainer, MessageContainer, } from './styles' -import { MessageTimestamp, resolveMessageTimestamps } from './timestamps' +import { resolveMessageTimestamps } from './timestamps' export const Message = props => { const { defaultTyping, defaultDelay } = useContext(RequestContext) @@ -36,6 +37,8 @@ export const Message = props => { style, imagestyle = props.imagestyle || props.imageStyle, isUnread = true, + feedbackEnabled, + inferenceId, ...otherProps } = props @@ -67,8 +70,10 @@ export const Message = props => { typeof e === 'string' ? renderLinks(e) : e ) - const { timestampsEnabled, getFormattedTimestamp, timestampStyle } = - resolveMessageTimestamps(getThemeProperty, enabletimestamps) + const { timestampsEnabled, getFormattedTimestamp } = resolveMessageTimestamps( + getThemeProperty, + enabletimestamps + ) const getEnvAck = () => { if (isDev) return 1 @@ -111,6 +116,8 @@ export const Message = props => { customTypeName: decomposedChildren.customTypeName, ack: ack, isUnread: isUnread === 1 || isUnread === true, + feedbackEnabled, + inferenceId, } addMessage(message) } @@ -255,13 +262,15 @@ export const Message = props => { {Boolean(blob) && hasBlobTick() && getBlobTick(5)} - {timestampsEnabled && ( - - )} + ) : null} ) diff --git a/packages/botonic-react/src/components/message/message-feedback.tsx b/packages/botonic-react/src/components/message/message-feedback.tsx new file mode 100644 index 0000000000..0c4713fb7d --- /dev/null +++ b/packages/botonic-react/src/components/message/message-feedback.tsx @@ -0,0 +1,99 @@ +import React, { useContext, useEffect, useState } from 'react' +import { v4 as uuid } from 'uuid' + +import ThumbsDown from '../../assets/thumbs-down.svg' +import ThumbsUp from '../../assets/thumbs-up.svg' +import { RequestContext, WebchatContext } from '../../contexts' +import { ActionRequest } from '../../index-types' +import { resolveImage } from '../../util' +import { EventAction, FeedbackOption } from '../../webchat/tracking' +import { FeedbackButton, FeedbackMessageContainer } from './styles' + +interface ButtonsState { + positive: boolean + negative: boolean +} + +interface RatingProps { + inferenceId?: string + messageId: string +} + +export const MessageFeedback = ({ inferenceId, messageId }: RatingProps) => { + const { webchatState, updateMessage, trackEvent } = useContext(WebchatContext) + const request = useContext(RequestContext) + + const [className, setClassName] = useState('') + const [disabled, setDisabled] = useState({ + positive: false, + negative: false, + }) + + const updateMsgWithFeedback = (feedbackEnabled: boolean) => { + const message = webchatState.messagesJSON.find( + message => message.id === messageId + ) + const updatedMsg = { + ...message, + feedbackEnabled, + } + updateMessage(updatedMsg) + } + + useEffect(() => { + updateMsgWithFeedback(true) + }, []) + + useEffect(() => { + if (disabled.positive || disabled.negative) { + setClassName('clicked') + updateMsgWithFeedback(false) + } + }, [disabled]) + + const handleClick = async (isUseful: boolean) => { + if (!trackEvent) { + return + } + + if (isUseful) { + setDisabled({ positive: false, negative: true }) + } else { + setDisabled({ positive: true, negative: false }) + } + + const args = { + knowledgebaseInferenceId: inferenceId, + feedbackTargetId: messageId, + feedbackGroupId: uuid(), + possibleOptions: [FeedbackOption.ThumbsUp, FeedbackOption.ThumbsDown], + possibleValues: [0, 1], + option: isUseful ? FeedbackOption.ThumbsUp : FeedbackOption.ThumbsDown, + value: isUseful ? 1 : 0, + } + await trackEvent( + request as ActionRequest, + EventAction.FeedbackKnowledgebase, + args + ) + } + + return ( + + handleClick(true)} + > + + + handleClick(false)} + > + + + + ) +} diff --git a/packages/botonic-react/src/components/message/message-footer.tsx b/packages/botonic-react/src/components/message/message-footer.tsx new file mode 100644 index 0000000000..3ec50f170c --- /dev/null +++ b/packages/botonic-react/src/components/message/message-footer.tsx @@ -0,0 +1,52 @@ +import React, { useContext } from 'react' + +import { WebchatContext } from '../../contexts' +import { SENDERS } from '../../index-types' +import { MessageFeedback } from './message-feedback' +import { MessageFooterContainer } from './styles' +import { MessageTimestamp, resolveMessageTimestamps } from './timestamps' + +interface MessageFooterProps { + enabletimestamps: boolean + messageJSON: any + sentBy: SENDERS + feedbackEnabled: boolean + inferenceId?: string +} + +export const MessageFooter = ({ + enabletimestamps, + messageJSON, + sentBy, + feedbackEnabled, + inferenceId, +}: MessageFooterProps) => { + const { getThemeProperty } = useContext(WebchatContext) + + const { timestampsEnabled, timestampStyle } = resolveMessageTimestamps( + getThemeProperty, + enabletimestamps + ) + const isSentByUser = sentBy === SENDERS.user + const messageFotterClass = isSentByUser + ? 'message-footer-user' + : 'message-footer-bot' + + return ( + + {timestampsEnabled ? ( + + ) : null} + {feedbackEnabled ? ( + + ) : null} + + ) +} diff --git a/packages/botonic-react/src/components/message/styles.ts b/packages/botonic-react/src/components/message/styles.ts index 5b8d26597a..71bf0d88e1 100644 --- a/packages/botonic-react/src/components/message/styles.ts +++ b/packages/botonic-react/src/components/message/styles.ts @@ -85,8 +85,6 @@ export const TimestampContainer = styled.div` box-sizing: border-box; width: 100%; - padding: 0px 15px 4px 15px; - padding-top: ${props => (props.isSentByUser ? '0px' : '4px')}; img { max-width: 20px; @@ -103,3 +101,53 @@ export const TimestampText = styled.div` color: ${COLORS.SOLID_BLACK}; text-align: ${props => (props.isSentByUser ? 'right' : 'left')}; ` + +export const MessageFooterContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + box-sizing: border-box; + padding: 0px 15px 4px 15px; + padding-top: ${props => (props.isSentByUser ? '0px' : '4px')}; + width: 100%; +` + +export const FeedbackMessageContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + gap: 4px; + + box-sizing: border-box; +` + +export const FeedbackButton = styled.button` + display: flex; + justify-content: center; + align-items: center; + + background: none; + box-sizing: border-box; + border: none; + border-radius: 4px; + padding: 8px 8px; + height: 24px; + width: 24px; + + &:hover { + cursor: pointer; + background-color: #f4f3f4; + } + + &:disabled { + cursor: default; + background: none; + opacity: 0.3; + } + + &.clicked { + opacity: 0; + transition: 1s 1s; + } +` diff --git a/packages/botonic-react/src/components/text.jsx b/packages/botonic-react/src/components/text.tsx similarity index 85% rename from packages/botonic-react/src/components/text.jsx rename to packages/botonic-react/src/components/text.tsx index 58b0d9d1dc..8cb5947fd8 100644 --- a/packages/botonic-react/src/components/text.jsx +++ b/packages/botonic-react/src/components/text.tsx @@ -2,6 +2,7 @@ import { INPUT } from '@botonic/core' import React, { Children } from 'react' import { mapObjectNonBooleanValues } from '../util/react' +import { TextProps } from './index-types' import { serializeMarkdown, toMarkdownChildren } from './markdown' import { Message } from './message' @@ -17,7 +18,7 @@ const serializeText = children => { return text } -const serialize = textProps => { +const serialize = (textProps: TextProps) => { if (!textProps.markdown) return { text: serializeText(textProps.children), @@ -25,20 +26,19 @@ const serialize = textProps => { return { text: serializeMarkdown(textProps.children) } } -/** - * - * @param {TextProps} props - * @returns {JSX.Element} - */ -export const Text = props => { +export const Text = (props: TextProps) => { const defaultTextProps = { markdown: props.markdown === undefined ? true : props.markdown, + feedbackEnabled: props.feedbackEnabled, + inferenceId: props.inferenceId, } + const textProps = mapObjectNonBooleanValues({ ...props, ...defaultTextProps, ...{ children: Children.toArray(props.children) }, }) + if (!textProps.markdown) return ( diff --git a/packages/botonic-react/src/contexts.tsx b/packages/botonic-react/src/contexts.tsx index 04568567bd..0c78d72b40 100644 --- a/packages/botonic-react/src/contexts.tsx +++ b/packages/botonic-react/src/contexts.tsx @@ -91,4 +91,7 @@ export const WebchatContext = createContext({ return }, webchatState: webchatInitialState, + trackEvent: async () => { + return + }, }) diff --git a/packages/botonic-react/src/dev-app.jsx b/packages/botonic-react/src/dev-app.jsx index 0bcd7f87fb..77980f510a 100644 --- a/packages/botonic-react/src/dev-app.jsx +++ b/packages/botonic-react/src/dev-app.jsx @@ -26,6 +26,7 @@ export class DevApp extends WebchatApp { onOpen, onClose, onMessage, + onTrackEvent, ...botOptions }) { super({ @@ -45,6 +46,7 @@ export class DevApp extends WebchatApp { onOpen, onClose, onMessage, + onTrackEvent, }) this.bot = new ReactBot({ ...botOptions, @@ -67,6 +69,7 @@ export class DevApp extends WebchatApp { onOpen, onClose, onMessage, + onTrackEvent, hostId, ...webchatOptions } = optionsAtRuntime @@ -84,6 +87,7 @@ export class DevApp extends WebchatApp { this.onOpen = onOpen || this.onOpen this.onClose = onClose || this.onClose this.onMessage = onMessage || this.onMessage + this.onTrackEvent = onTrackEvent || this.onTrackEvent this.hostId = hostId || this.hostId this.createRootElement(host) return ( @@ -108,6 +112,7 @@ export class DevApp extends WebchatApp { onOpen={(...args) => this.onOpenWebchat(...args)} onClose={(...args) => this.onCloseWebchat(...args)} onUserInput={(...args) => this.onUserInput(...args)} + onTrackEvent={(...args) => this.onTrackEvent(...args)} /> ) } diff --git a/packages/botonic-react/src/index-types.ts b/packages/botonic-react/src/index-types.ts index 4ddee29ba9..139ffa3a30 100644 --- a/packages/botonic-react/src/index-types.ts +++ b/packages/botonic-react/src/index-types.ts @@ -117,13 +117,20 @@ export interface WebchatArgs { onMessage?: (app: WebchatApp, message: WebchatMessage) => void onOpen?: (app: WebchatApp, args: any) => void onConnectionChange?: (app: WebchatApp, isOnline: boolean) => void + onTrackEvent?: TrackEventFunction persistentMenu?: PersistentMenuTheme storage?: Storage | null storageKey?: any theme?: ThemeProps } -// export interface WebchatAppArgs extends WebchatArgs { +type EventArgs = { [key: string]: any } +type TrackEventFunction = ( + request: ActionRequest, + eventName: string, + args?: EventArgs +) => Promise + export interface WebchatAppArgs { appId?: string visibility?: () => boolean @@ -201,6 +208,7 @@ export interface WebchatContextProps { updateUser: (user: Partial) => void updateWebchatDevSettings: (settings: WebchatSettingsProps) => void webchatState: WebchatState + trackEvent: TrackEventFunction } // export class DevApp extends WebchatApp { diff --git a/packages/botonic-react/src/webchat-app.jsx b/packages/botonic-react/src/webchat-app.jsx index b524501a98..5afe510c49 100644 --- a/packages/botonic-react/src/webchat-app.jsx +++ b/packages/botonic-react/src/webchat-app.jsx @@ -29,6 +29,7 @@ export class WebchatApp { onOpen, onClose, onMessage, + onTrackEvent, onConnectionChange, appId, visibility, @@ -58,6 +59,7 @@ export class WebchatApp { this.onOpen = onOpen this.onClose = onClose this.onMessage = onMessage + this.onTrackEvent = onTrackEvent this.onConnectionChange = onConnectionChange this.visibility = visibility this.server = server @@ -284,6 +286,7 @@ export class WebchatApp { onClose, onMessage, onConnectionChange, + onTrackEvent, appId, visibility, server, @@ -307,6 +310,7 @@ export class WebchatApp { this.onOpen = onOpen || this.onOpen this.onClose = onClose || this.onClose this.onMessage = onMessage || this.onMessage + this.onTrackEvent = onTrackEvent || this.onTrackEvent this.onConnectionChange = onConnectionChange || this.onConnectionChange this.visibility = visibility || this.visibility this.appId = appId || this.appId @@ -335,6 +339,7 @@ export class WebchatApp { onClose={(...args) => this.onCloseWebchat(...args)} onUserInput={(...args) => this.onUserInput(...args)} onStateChange={webchatState => this.onStateChange(webchatState)} + onTrackEvent={(...args) => this.onTrackEvent(...args)} server={server} /> ) diff --git a/packages/botonic-react/src/webchat/tracking.ts b/packages/botonic-react/src/webchat/tracking.ts new file mode 100644 index 0000000000..c30603d403 --- /dev/null +++ b/packages/botonic-react/src/webchat/tracking.ts @@ -0,0 +1,8 @@ +export enum EventAction { + FeedbackKnowledgebase = 'feedback_knowledgebase', +} + +export enum FeedbackOption { + ThumbsUp = 'thumbsUp', + ThumbsDown = 'thumbsDown', +} diff --git a/packages/botonic-react/src/webchat/webchat.jsx b/packages/botonic-react/src/webchat/webchat.jsx index 8a414de81b..12de1c8f05 100644 --- a/packages/botonic-react/src/webchat/webchat.jsx +++ b/packages/botonic-react/src/webchat/webchat.jsx @@ -880,6 +880,7 @@ export const Webchat = forwardRef((props, ref) => { updateUser: updateSessionWithUser, updateWebchatDevSettings: updateWebchatDevSettings, webchatState, + trackEvent: props.onTrackEvent, }} > {!webchatState.isWebchatOpen && }