Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3028: Show user icon after robot message in Chat #3078

Merged
4 changes: 4 additions & 0 deletions translations/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,12 +268,14 @@
},
"chat": {
"de": {
"bot": "Chatbot",
"button": "Chat (beta)",
"conversationTitle": "Bitte gib deine Frage in das Textfeld ein.",
"conversationText": "Du kannst alles fragen, von lokalen Informationen bis hin zu spezifischen Anfragen bezüglich deiner Situation.",
"dataSecurity": "Datenschutz und Sicherheit: Deine Privatsphäre ist uns sehr wichtig. Alle Gespräche werden vertraulich behandelt und deine Daten werden sicher verarbeitet. Mehr Informationen zum Datenschutz findest du in unseren Datenschutzrichtlinien.",
"errorMessage": "Es ist ein Fehler bei der Kommunikation aufgetreten.",
"header": "{{appName}} Chat Support",
"human": "Berater*in",
"initialMessage": "Deine Chatanfrage wurde gesendet. Die Berater*in beantwortet deine Nachricht so schnell wie möglich.",
"inputLabel": "Meine Frage:",
"inputPlaceholder": "Ich möchte wissen...",
Expand Down Expand Up @@ -380,12 +382,14 @@
"user": "Εσείς"
},
"en": {
"bot": "Chatbot",
"button": "Chat (beta)",
"conversationTitle": "Please enter your question in the text field.",
"conversationText": "You can ask anything from local information to specific requests regarding your situation.",
"dataSecurity": "Data protection and security: Your privacy is very important to us. All conversations are treated confidentially and your data is processed securely. You can find more information on data protection in our privacy policy.",
"errorMessage": "An error has occurred during communication.",
"header": "{{appName}} Chat Support",
"human": "Advisor",
"initialMessage": "Your chat request has been sent. The advisor will answer your message as soon as possible.",
"inputLabel": "My question:",
"inputPlaceholder": "I would like to know...",
Expand Down
7 changes: 7 additions & 0 deletions web/src/__mocks__/react-inlinesvg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { ReactElement } from 'react'

export default ({ src, title, ...props }: { src: string; title: string }): ReactElement => (
<svg id={src} role='img' {...props}>
<title>{title}</title>
</svg>
)
14 changes: 9 additions & 5 deletions web/src/components/ChatConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
isVisible: boolean
}

const shouldShowIcon = (index: number, message: ChatMessageModel, messages: ChatMessageModel[]): boolean => {
const previousMessage = messages[index - 1]
return (
previousMessage?.userIsAuthor !== message.userIsAuthor ||
previousMessage.isAutomaticAnswer !== message.isAutomaticAnswer
)
}

const TypingIndicator = ({ isVisible }: TypingIndicatorProps): ReactElement | null =>
isVisible ? (
<TypingIndicatorWrapper>
Expand Down Expand Up @@ -81,11 +89,7 @@
<>
{!hasError && <InitialMessage>{t('initialMessage')}</InitialMessage>}
{messages.map((message, index) => (
<ChatMessage
message={message}
key={message.id}
showIcon={messages[index - 1]?.userIsAuthor !== message.userIsAuthor}
/>
<ChatMessage message={message} key={message.id} showIcon={shouldShowIcon(index, message, messages)} />

Check notice on line 92 in web/src/components/ChatConversation.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

✅ No longer an issue: Complex Method

ChatConversation is no longer above the threshold for cyclomatic complexity
))}
<TypingIndicator isVisible={typingIndicatorVisible} />
<div ref={messagesEndRef} />
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const getIcon = (userIsAuthor: boolean, isAutomaticAnswer: boolean, t: TFunction
return <Circle>{t('user')}</Circle>
}
const icon = isAutomaticAnswer ? ChatBot : ChatPerson
return <Icon src={icon} />
return <Icon src={icon} title={isAutomaticAnswer ? t('bot') : t('human')} />
}

const ChatMessage = ({ message, showIcon }: ChatMessageProps): ReactElement => {
Expand Down
73 changes: 73 additions & 0 deletions web/src/components/__tests__/ChatConversation.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { renderWithRouterAndTheme } from '../../testing/render'
import ChatConversation from '../ChatConversation'

jest.mock('react-i18next')
jest.mock('react-inlinesvg')

window.HTMLElement.prototype.scrollIntoView = jest.fn()
jest.useFakeTimers()

Expand Down Expand Up @@ -40,6 +42,50 @@ describe('ChatConversation', () => {
automaticAnswer: false,
}),
]
const testMessages2: ChatMessageModel[] = [
new ChatMessageModel({
id: 1,
body: 'Human Message 1',
userIsAuthor: false,
automaticAnswer: false,
}),
new ChatMessageModel({
id: 2,
body: 'Bot Message 1',
userIsAuthor: false,
automaticAnswer: true,
}),
new ChatMessageModel({
id: 3,
body: 'User Message 1',
userIsAuthor: true,
automaticAnswer: false,
}),
new ChatMessageModel({
id: 4,
body: 'Human Message 2',
userIsAuthor: false,
automaticAnswer: false,
}),
new ChatMessageModel({
id: 5,
body: 'Human Message 3',
userIsAuthor: false,
automaticAnswer: false,
}),
new ChatMessageModel({
id: 6,
body: 'Bot Message 2',
userIsAuthor: false,
automaticAnswer: true,
}),
new ChatMessageModel({
id: 7,
body: 'Bot Message 3',
userIsAuthor: false,
automaticAnswer: true,
}),
]

it('should display welcome text if conversation has not started', () => {
const { getByText } = render([], false)
Expand All @@ -59,6 +105,7 @@ describe('ChatConversation', () => {
expect(getByTestId(testMessages[0]!.id)).toBeTruthy()
expect(getByText('...')).toBeTruthy()
expect(getByTestId(testMessages[1]!.id)).toBeTruthy()
expect(getByText('chat:humanIcon')).toBeTruthy()
expect(getByText('...')).toBeTruthy()

act(() => jest.runAllTimers())
Expand All @@ -76,4 +123,30 @@ describe('ChatConversation', () => {
const { getByText } = render([], true)
expect(getByText('chat:errorMessage')).toBeTruthy()
})

it('should display icon after automaticAnswer or author changes', () => {
const expectedResults = [
{ icon: 'human', text: 'Human Message 1', opacity: '1' },
{ icon: 'bot', text: 'Bot Message 1', opacity: '1' },
{ icon: 'human', text: 'Human Message 2', opacity: '1' },
{ icon: 'human', text: 'Human Message 3', opacity: '0' },
{ icon: 'bot', text: 'Bot Message 2', opacity: '1' },
{ icon: 'bot', text: 'Bot Message 3', opacity: '0' },
]

const { getAllByRole } = render(testMessages2, false)
const icons = getAllByRole('img')

expect(icons).toHaveLength(6)

icons.forEach((icon, index) => {
const expected = expectedResults[index]!
const parent = icon.parentElement
const grandparent = parent?.parentElement

expect(icon.textContent).toMatch(expected.icon)
expect(grandparent?.textContent).toMatch(expected.text)
expect(parent).toHaveStyle(`opacity: ${expected.opacity}`)
})
})
})
Loading