Skip to content

Commit

Permalink
feedback knoweldege base with thumbsUp/thumbsDown (#2859)
Browse files Browse the repository at this point in the history
## Description

Add feedback_knowledgebase event
Be able to add ThumbsUp and ThumbsDown buttons to give feedback on
webchat messages.
When the flow builder creates a message using the knowledge base Uses
the Text component with new withfeedback and inferenceid attributes

The withfeedback and inferenceid attributes cannot be declared using
camelCase because when they pass through the backen they come back with
everything in lowercase, it doesn't work with snake_case either. I'm not
sure but I think it's because of the html parser, the <message>
attributes have to be single words or use kebab-case.

## Context

Review this PR commit by commit:
[plugin-hubtype-analytics](dc8f373):
add feedback_knowledgebase event

[botonic-react](3fa97b0): Refactor the
FooterMessage and add the FeedbackMessage which contains the ThumbsUp
ThumbsDown buttons and uses the trackEvent function when clicked.

[botonic-react](bb98f33): Add the
possibility to inject the onTrackEvent function from the bot to the
webchat to create events from the frontend. Just like we already have
the onInit, onOpen, onClose, onMessage functions.

[plugin-flow-builder](8d90eb7): When
the flow builder plugin uses the knowledge base to generate a message it
creates a @botonic/react Text with the new withfeedback and inferenceid
attributes needed for tracking.

The other commits are a refactor that renumbers the with_feedback
attrbute to feebackEnabled using camelCase

## Approach taken / Explain the design

Design
![Captura de pantalla 2024-06-21 a las 14 02
23](https://github.com/hubtype/botonic/assets/36898236/992cac28-fcdc-4109-bd9e-fc27d67e7204)

## To document / Usage example

How to pass the tracking function from bot to webchat
/webchat/index.ts
```typescript
export const webchat: WebchatArgs = {
  onInit: app => {
    window.botonicOnInit(app)
  },
  ...
  onTrackEvent: async (request, eventName, args) => {
    const pluginHubtypeAnalytics = new BotonicPluginHubtypeAnalytics()
    const htEventProps = {
      action: eventName as EventAction,
      ...args,
    } as HtEventProps
    await pluginHubtypeAnalytics.trackEvent(request, htEventProps)
  },
  theme: {
  ...
```

## Testing

Adds a test for the new feedback_knowledgebase event
  • Loading branch information
Iru89 committed Jul 9, 2024
1 parent 74a05fb commit e97f213
Show file tree
Hide file tree
Showing 24 changed files with 391 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Text key={id}>{this.text}</Text>
return (
<Text
key={id}
feedbackEnabled={this.feedbackEnabled}
inferenceId={this.inferenceId}
>
{this.text}
</Text>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HtNodeWithContentType } from './node-types'
export interface HtKnowledgeBaseNode extends HtBaseNode {
type: HtNodeWithContentType.KNOWLEDGE_BASE
content: {
feedback_enabled: boolean
sources: string[]
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
23 changes: 18 additions & 5 deletions packages/botonic-plugin-hubtype-analytics/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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
}

Expand Down Expand Up @@ -135,6 +147,7 @@ export interface EventCustom extends HtBaseEventProps {

export type HtEventProps =
| EventFeedback
| EventFeedbackKnowledgebase
| EventFlow
| EventHandoff
| EventHandoffOption
Expand Down
4 changes: 4 additions & 0 deletions packages/botonic-plugin-hubtype-analytics/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
HtEventCustom,
HtEventFallback,
HtEventFeedback,
HtEventFeedbackKnowledgebase,
HtEventFlow,
HtEventHandoff,
HtEventHandoffOption,
Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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: '**',
Expand All @@ -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: '**',
Expand All @@ -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: '**',
Expand All @@ -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: '**',
Expand Down
3 changes: 3 additions & 0 deletions packages/botonic-react/src/assets/thumbs-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/botonic-react/src/assets/thumbs-up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 3 additions & 19 deletions packages/botonic-react/src/components/index-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ErrorInfo } from 'react'
import React from 'react'

import { SENDERS } from '../index-types'
import { CoverComponentProps } from '../webchat/index-types'
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -204,21 +206,3 @@ export interface WebchatSettingsProps {
export type WrappedComponent<Props> = React.FunctionComponent<Props> & {
customTypeName: string
}

// TODO: Reuse types to be typed in respective functions
// export class ErrorBoundary<Props> extends React.Component<Props> {
// componentDidCatch(error: Error, errorInfo: ErrorInfo): void
// }

// export function createErrorBoundary<Props>(_?: {
// errorComponent: React.ComponentType
// }): ErrorBoundary<Props>

// export function customMessage<Props>(_: {
// name: string
// component: React.ComponentType<Props>
// defaultProps?: Record<string, unknown>
// errorBoundary?: ErrorBoundary<Props>
// }): WrappedComponent<Props>

// export function getDisplayName(component: React.ComponentType): string
25 changes: 17 additions & 8 deletions packages/botonic-react/src/components/message/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -36,6 +37,8 @@ export const Message = props => {
style,
imagestyle = props.imagestyle || props.imageStyle,
isUnread = true,
feedbackEnabled,
inferenceId,
...otherProps
} = props

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -111,6 +116,8 @@ export const Message = props => {
customTypeName: decomposedChildren.customTypeName,
ack: ack,
isUnread: isUnread === 1 || isUnread === true,
feedbackEnabled,
inferenceId,
}
addMessage(message)
}
Expand Down Expand Up @@ -255,13 +262,15 @@ export const Message = props => {
{Boolean(blob) && hasBlobTick() && getBlobTick(5)}
</BlobContainer>
</MessageContainer>
{timestampsEnabled && (
<MessageTimestamp
{timestampsEnabled || feedbackEnabled ? (
<MessageFooter
enabletimestamps={timestampsEnabled}
messageJSON={messageJSON}
sentBy={sentBy}
style={timestampStyle}
timestamp={messageJSON.timestamp}
feedbackEnabled={feedbackEnabled}
inferenceId={inferenceId}
/>
)}
) : null}
</>
</ConditionalWrapper>
)
Expand Down
Loading

0 comments on commit e97f213

Please sign in to comment.