Skip to content

Commit ada197a

Browse files
authored
SearchOrAskAi Error Handling Improvements (#2166)
* Add error handling utilities * Improve error display on AskAi, and interrupt streams when rate limits are reached.
1 parent 97220f2 commit ada197a

29 files changed

+1983
-259
lines changed

src/Elastic.Documentation.Site/Assets/eui-icons-cache.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
22
// @ts-nocheck: EUI icons do not have types
33
import { icon as EuiIconVisualizeApp } from '@elastic/eui/es/components/icon/assets/app_visualize'
4+
import { icon as EuiIconArrowEnd } from '@elastic/eui/es/components/icon/assets/arrowEnd'
5+
import { icon as EuiIconArrowStart } from '@elastic/eui/es/components/icon/assets/arrowStart'
46
import { icon as EuiIconArrowDown } from '@elastic/eui/es/components/icon/assets/arrow_down'
57
import { icon as EuiIconArrowLeft } from '@elastic/eui/es/components/icon/assets/arrow_left'
68
import { icon as EuiIconArrowRight } from '@elastic/eui/es/components/icon/assets/arrow_right'
79
import { icon as EuiIconCheck } from '@elastic/eui/es/components/icon/assets/check'
10+
import { icon as EuiIconComment } from '@elastic/eui/es/components/icon/assets/comment'
811
import { icon as EuiIconCopy } from '@elastic/eui/es/components/icon/assets/copy'
912
import { icon as EuiIconCopyClipboard } from '@elastic/eui/es/components/icon/assets/copy_clipboard'
1013
import { icon as EuiIconCross } from '@elastic/eui/es/components/icon/assets/cross'
@@ -59,4 +62,7 @@ appendIconComponentCache({
5962
copy: EuiIconCopy,
6063
play: EuiIconPlay,
6164
sortUp: EuiIconSortUp,
65+
arrowStart: EuiIconArrowStart,
66+
arrowEnd: EuiIconArrowEnd,
67+
comment: EuiIconComment,
6268
})

src/Elastic.Documentation.Site/Assets/modal.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,35 @@
2929
@apply text-ink-dark hover:text-ink text-2xl font-bold no-underline;
3030
}
3131
}
32+
33+
/* Search or Ask AI animation for secondary buttons */
34+
35+
@keyframes slideInFromRight {
36+
from {
37+
right: 8px;
38+
opacity: 0;
39+
}
40+
to {
41+
right: 40px;
42+
opacity: 1;
43+
}
44+
}
45+
46+
@keyframes slideOutToRight {
47+
from {
48+
right: 40px;
49+
opacity: 1;
50+
}
51+
to {
52+
right: 8px;
53+
opacity: 0;
54+
}
55+
}
56+
57+
.slideInSearchOrAskAiInputAnimation {
58+
animation: slideInFromRight 0.2s ease-out forwards;
59+
}
60+
61+
.slideOutSearchOrAskAiInputAnimation {
62+
animation: slideOutToRight 0.2s ease-out forwards;
63+
}

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/AskAiSuggestions.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const ALL_SUGGESTIONS: AskAiSuggestion[] = [
3737
},
3838
]
3939

40-
export const AskAiSuggestions = () => {
40+
export const AskAiSuggestions = ({ disabled }: { disabled?: boolean }) => {
4141
const { submitQuestion } = useChatActions()
4242
const { setModalMode } = useModalActions()
4343
const { euiTheme } = useEuiTheme()
@@ -64,9 +64,12 @@ export const AskAiSuggestions = () => {
6464
fullWidth
6565
size="s"
6666
onClick={() => {
67-
submitQuestion(suggestion.question)
68-
setModalMode('askAi')
67+
if (!disabled) {
68+
submitQuestion(suggestion.question)
69+
setModalMode('askAi')
70+
}
6971
}}
72+
disabled={disabled}
7073
>
7174
{suggestion.question}
7275
</EuiButton>

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/Chat.test.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jest.mock('./chat.store', () => ({
1313
useChatActions: jest.fn(() => ({
1414
submitQuestion: jest.fn(),
1515
clearChat: jest.fn(),
16+
clearNon429Errors: jest.fn(),
1617
setAiProvider: jest.fn(),
1718
})),
1819
}))
@@ -29,6 +30,44 @@ jest.mock('./AskAiSuggestions', () => ({
2930
),
3031
}))
3132

33+
// Mock AiProviderSelector
34+
jest.mock('./AiProviderSelector', () => ({
35+
AiProviderSelector: () => (
36+
<div data-testid="ai-provider-selector">Provider Selector</div>
37+
),
38+
}))
39+
40+
// Mock modal.store
41+
jest.mock('../modal.store', () => ({
42+
useModalActions: jest.fn(() => ({
43+
setModalMode: jest.fn(),
44+
openModal: jest.fn(),
45+
closeModal: jest.fn(),
46+
toggleModal: jest.fn(),
47+
})),
48+
}))
49+
50+
// Mock cooldown hooks
51+
jest.mock('./useAskAiCooldown', () => ({
52+
useIsAskAiCooldownActive: jest.fn(() => false),
53+
useAskAiCooldown: jest.fn(() => null),
54+
useAskAiCooldownActions: jest.fn(() => ({
55+
setCooldown: jest.fn(),
56+
updateCooldown: jest.fn(),
57+
notifyCooldownFinished: jest.fn(),
58+
acknowledgeCooldownFinished: jest.fn(),
59+
})),
60+
}))
61+
62+
jest.mock('../useCooldown', () => ({
63+
useCooldown: jest.fn(),
64+
}))
65+
66+
// Mock SearchOrAskAiErrorCallout
67+
jest.mock('../SearchOrAskAiErrorCallout', () => ({
68+
SearchOrAskAiErrorCallout: () => null,
69+
}))
70+
3271
const mockUseChatMessages = jest.mocked(
3372
jest.requireMock('./chat.store').useChatMessages
3473
)
@@ -39,12 +78,14 @@ const mockUseChatActions = jest.mocked(
3978
describe('Chat Component', () => {
4079
const mockSubmitQuestion = jest.fn()
4180
const mockClearChat = jest.fn()
81+
const mockClearNon429Errors = jest.fn()
4282

4383
beforeEach(() => {
4484
jest.clearAllMocks()
4585
mockUseChatActions.mockReturnValue({
4686
submitQuestion: mockSubmitQuestion,
4787
clearChat: mockClearChat,
88+
clearNon429Errors: mockClearNon429Errors,
4889
})
4990
})
5091

@@ -196,7 +237,9 @@ describe('Chat Component', () => {
196237
/Ask Elastic Docs AI Assistant/i
197238
)
198239
await user.type(input, question)
199-
await user.click(screen.getByRole('button', { name: /send/i }))
240+
await user.click(
241+
screen.getByRole('button', { name: /send message/i })
242+
)
200243

201244
// Assert
202245
expect(mockSubmitQuestion).toHaveBeenCalledWith(question)

0 commit comments

Comments
 (0)