Skip to content

Commit 3628c3d

Browse files
authored
Merge pull request #3355 from RedisInsight/fe/feature/RI-5647-suggest-import-data
#RI-5647 - suggest to import data when there are no indexes
2 parents a989d62 + 2e8c52f commit 3628c3d

File tree

14 files changed

+204
-55
lines changed

14 files changed

+204
-55
lines changed

redisinsight/ui/src/components/side-panels/panels/ai-assistant/AiAssistant.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
1-
import React from 'react'
2-
import { useSelector } from 'react-redux'
1+
import React, { useEffect, useRef } from 'react'
2+
import { useDispatch, useSelector } from 'react-redux'
33

44
import { oauthCloudUserSelector } from 'uiSrc/slices/oauth/cloud'
55
import { FeatureFlags } from 'uiSrc/constants'
66
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
7+
import { clearExpertChatHistory } from 'uiSrc/slices/panels/aiAssistant'
78
import { WelcomeAiAssistant, ChatsWrapper } from './components'
89
import styles from './styles.module.scss'
910

1011
const AiAssistant = () => {
11-
const { data } = useSelector(oauthCloudUserSelector)
12+
const { data: userOAuthProfile } = useSelector(oauthCloudUserSelector)
1213
const { [FeatureFlags.cloudSso]: cloudSsoFeature } = useSelector(appFeatureFlagsFeaturesSelector)
1314

14-
const isShowAuth = cloudSsoFeature?.flag && !data
15+
const currentAccountIdRef = useRef(userOAuthProfile?.id)
16+
const isShowAuth = cloudSsoFeature?.flag && !userOAuthProfile
17+
18+
const dispatch = useDispatch()
19+
20+
useEffect(() => {
21+
// user logout
22+
if (currentAccountIdRef.current && !userOAuthProfile?.id) {
23+
dispatch(clearExpertChatHistory())
24+
}
25+
26+
currentAccountIdRef.current = userOAuthProfile?.id
27+
}, [userOAuthProfile])
1528

1629
return (
1730
<div className={styles.wrapper} data-testid="redis-copilot">

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/assistance-chat/AssistanceChat.spec.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import React from 'react'
22
import { cloneDeep } from 'lodash'
3-
import { act, cleanup, fireEvent, mockedStore, render, screen, waitForEuiPopoverVisible } from 'uiSrc/utils/test-utils'
3+
import {
4+
act,
5+
cleanup,
6+
fireEvent,
7+
mockedStore,
8+
mockedStoreFn,
9+
render,
10+
screen,
11+
waitForEuiPopoverVisible
12+
} from 'uiSrc/utils/test-utils'
413

514
import {
615
aiAssistantChatSelector,
@@ -28,25 +37,25 @@ jest.mock('uiSrc/slices/panels/aiAssistant', () => ({
2837
let store: typeof mockedStore
2938
beforeEach(() => {
3039
cleanup()
31-
store = cloneDeep(mockedStore)
40+
store = cloneDeep(mockedStoreFn())
3241
store.clearActions()
3342
})
3443

3544
describe('AssistanceChat', () => {
3645
it('should render', () => {
37-
expect(render(<AssistanceChat />)).toBeTruthy()
46+
expect(render(<AssistanceChat />, { store })).toBeTruthy()
3847
})
3948

4049
it('should proper components render by default', () => {
41-
render(<AssistanceChat />)
50+
render(<AssistanceChat />, { store })
4251

4352
expect(screen.getByTestId('ai-general-restart-session-btn')).toBeInTheDocument()
4453
expect(screen.getByTestId('ai-chat-empty-history')).toBeInTheDocument()
4554
expect(screen.getByTestId('ai-submit-message-btn')).toBeInTheDocument()
4655
})
4756

4857
it('should call proper actions by default', () => {
49-
render(<AssistanceChat />)
58+
render(<AssistanceChat />, { store })
5059

5160
expect(store.getActions()).toEqual([])
5261
})
@@ -56,7 +65,7 @@ describe('AssistanceChat', () => {
5665
id: '1',
5766
messages: []
5867
})
59-
render(<AssistanceChat />)
68+
render(<AssistanceChat />, { store })
6069

6170
expect(store.getActions()).toEqual([getAssistantChatHistory()])
6271
})
@@ -66,7 +75,7 @@ describe('AssistanceChat', () => {
6675
id: '',
6776
messages: []
6877
})
69-
render(<AssistanceChat />)
78+
render(<AssistanceChat />, { store })
7079

7180
act(() => {
7281
fireEvent.change(
@@ -88,7 +97,7 @@ describe('AssistanceChat', () => {
8897
id: '1',
8998
messages: []
9099
})
91-
render(<AssistanceChat />)
100+
render(<AssistanceChat />, { store })
92101

93102
const afterRenderActions = [...store.getActions()]
94103

@@ -124,15 +133,16 @@ describe('AssistanceChat', () => {
124133
messages: [{}]
125134
})
126135

127-
render(<AssistanceChat />)
136+
render(<AssistanceChat />, { store })
128137

129138
const afterRenderActions = [...store.getActions()]
130139

131140
fireEvent.click(screen.getByTestId('ai-general-restart-session-btn'))
132141

133142
await waitForEuiPopoverVisible()
134-
135-
fireEvent.click(screen.getByTestId('ai-chat-restart-confirm'))
143+
await act(async () => {
144+
fireEvent.click(screen.getByTestId('ai-chat-restart-confirm'))
145+
})
136146

137147
expect(store.getActions()).toEqual([
138148
...afterRenderActions,

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/assistance-chat/AssistanceChat.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { AiChatMessage, AiChatType } from 'uiSrc/slices/interfaces/aiAssistant'
1616
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
1717
import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands'
1818

19-
import { AssistanceChatInitialMessage, ChatHistory, ChatForm } from '../shared'
19+
import { AssistanceChatInitialMessage, ChatHistory, ChatForm, RestartChat } from '../shared'
2020

2121
import styles from './styles.module.scss'
2222

@@ -121,13 +121,17 @@ const AssistanceChat = () => {
121121
<div className={styles.wrapper} data-testid="ai-general-chat">
122122
<div className={styles.header}>
123123
<span />
124-
<EuiButtonEmpty
125-
disabled={!!inProgressMessage || !messages?.length}
126-
iconType="eraser"
127-
size="xs"
128-
onClick={onClearSession}
129-
className={styles.headerBtn}
130-
data-testid="ai-general-restart-session-btn"
124+
<RestartChat
125+
button={(
126+
<EuiButtonEmpty
127+
disabled={!!inProgressMessage || !messages?.length}
128+
iconType="eraser"
129+
size="xs"
130+
className={styles.headerBtn}
131+
data-testid="ai-general-restart-session-btn"
132+
/>
133+
)}
134+
onConfirm={onClearSession}
131135
/>
132136
</div>
133137
<div className={styles.chatHistory}>

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/expert-chat/ExpertChat.tsx

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
1414
import { AiChatMessage, AiChatType } from 'uiSrc/slices/interfaces/aiAssistant'
1515
import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands'
1616
import { oauthCloudUserSelector } from 'uiSrc/slices/oauth/cloud'
17+
import { fetchRedisearchListAction } from 'uiSrc/slices/browser/redisearch'
18+
import NoIndexesInitialMessage from './components/no-indexes-initial-message'
1719
import ExpertChatHeader from './components/expert-chat-header'
1820
import { ChatForm, ChatHistory, ExpertChatInitialMessage } from '../shared'
1921

@@ -26,6 +28,8 @@ const ExpertChat = () => {
2628
const { data: userOAuthProfile } = useSelector(oauthCloudUserSelector)
2729
const freeInstances = useSelector(freeInstancesSelector) || []
2830

31+
const [isNoIndexes, setIsNoIndexes] = useState(false)
32+
const [isLoading, setIsLoading] = useState(false)
2933
const [inProgressMessage, setinProgressMessage] = useState<Nullable<AiChatMessage>>(null)
3034

3135
const currentAccountIdRef = useRef(userOAuthProfile?.id)
@@ -35,8 +39,12 @@ const ExpertChat = () => {
3539
const dispatch = useDispatch()
3640

3741
useEffect(() => {
42+
if (!instanceId) {
43+
return
44+
}
45+
3846
// changed account
39-
if (instanceId && currentAccountIdRef.current !== userOAuthProfile?.id) {
47+
if (currentAccountIdRef.current !== userOAuthProfile?.id) {
4048
currentAccountIdRef.current = userOAuthProfile?.id
4149
dispatch(getExpertChatHistoryAction(instanceId, () => scrollToBottom('auto')))
4250
return
@@ -47,11 +55,31 @@ const ExpertChat = () => {
4755
return
4856
}
4957

50-
if (instanceId) {
51-
dispatch(getExpertChatHistoryAction(instanceId, () => scrollToBottom('auto')))
52-
}
58+
dispatch(getExpertChatHistoryAction(instanceId, () => scrollToBottom('auto')))
5359
}, [instanceId, userOAuthProfile])
5460

61+
useEffect(() => {
62+
if (!instanceId) return
63+
if (!isRedisearchAvailable(modules)) return
64+
if (messages.length) return
65+
66+
getIndexes()
67+
}, [instanceId, modules])
68+
69+
const getIndexes = () => {
70+
setIsLoading(true)
71+
dispatch(
72+
fetchRedisearchListAction(
73+
(indexes) => {
74+
setIsLoading(false)
75+
setIsNoIndexes(!indexes.length)
76+
},
77+
() => setIsLoading(false),
78+
false
79+
)
80+
)
81+
}
82+
5583
const handleSubmit = useCallback((message: string) => {
5684
scrollToBottom()
5785

@@ -148,9 +176,11 @@ const ExpertChat = () => {
148176
/>
149177
<div className={styles.chatHistory}>
150178
<ChatHistory
151-
isLoading={loading}
179+
isLoading={loading || isLoading}
152180
modules={modules}
153-
initialMessage={ExpertChatInitialMessage}
181+
initialMessage={isNoIndexes
182+
? <NoIndexesInitialMessage onSuccess={getIndexes} />
183+
: ExpertChatInitialMessage}
154184
inProgressMessage={inProgressMessage}
155185
history={messages}
156186
scrollDivRef={scrollDivRef}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react'
2+
import { render, screen } from 'uiSrc/utils/test-utils'
3+
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
4+
5+
import NoIndexesInitialMessage from './NoIndexesInitialMessage'
6+
7+
jest.mock('uiSrc/telemetry', () => ({
8+
...jest.requireActual('uiSrc/telemetry'),
9+
sendEventTelemetry: jest.fn(),
10+
}))
11+
12+
describe('NoIndexesInitialMessage', () => {
13+
it('should render', () => {
14+
expect(render(<NoIndexesInitialMessage />)).toBeTruthy()
15+
})
16+
17+
it('should render load sample data button', () => {
18+
render(<NoIndexesInitialMessage />)
19+
20+
expect(screen.getByTestId('load-sample-data-btn')).toBeInTheDocument()
21+
})
22+
23+
it('should call telemetry on init', () => {
24+
const sendEventTelemetryMock = jest.fn();
25+
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)
26+
27+
render(<NoIndexesInitialMessage />)
28+
29+
expect(sendEventTelemetry).toBeCalledWith({
30+
event: TelemetryEvent.AI_CHAT_BOT_NO_INDEXES_MESSAGE_DISPLAYED
31+
});
32+
(sendEventTelemetry as jest.Mock).mockRestore()
33+
})
34+
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { useEffect } from 'react'
2+
import { EuiSpacer, EuiText } from '@elastic/eui'
3+
import LoadSampleData from 'uiSrc/pages/browser/components/load-sample-data'
4+
5+
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
6+
import styles from './styles.module.scss'
7+
8+
export interface Props {
9+
onSuccess?: () => void
10+
}
11+
12+
const NoIndexesInitialMessage = (props: Props) => {
13+
const { onSuccess } = props
14+
15+
useEffect(() => {
16+
sendEventTelemetry({
17+
event: TelemetryEvent.AI_CHAT_BOT_NO_INDEXES_MESSAGE_DISPLAYED
18+
})
19+
}, [])
20+
21+
return (
22+
<div data-testid="no-indexes-chat-message">
23+
<EuiText size="xs">Hi!</EuiText>
24+
<EuiText size="xs">I am here to help you get started with data querying.</EuiText>
25+
<EuiText size="xs">I noticed that you have no indexes created.</EuiText>
26+
<EuiSpacer />
27+
<EuiText size="xs">Would you like to load the sample data to see what Redis Copilot can help you do?</EuiText>
28+
<EuiSpacer />
29+
<LoadSampleData anchorClassName={styles.anchorClassName} onSuccess={onSuccess} />
30+
<EuiSpacer size="xs" />
31+
</div>
32+
)
33+
}
34+
35+
export default NoIndexesInitialMessage
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import NoIndexesInitialMessage from './NoIndexesInitialMessage'
2+
3+
export default NoIndexesInitialMessage
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.anchorClassName {
2+
:global {
3+
.euiButton {
4+
height: 28px !important;
5+
line-height: 28px !important;
6+
7+
.euiButton__text {
8+
font-size: 13px !important;
9+
}
10+
}
11+
}
12+
}

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/shared/error-message/ErrorMessage.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('ErrorMessage', () => {
1616
expect(screen.getByTestId('ai-chat-error-message')).toHaveTextContent(AiChatErrors.Default)
1717
expect(screen.getByTestId('ai-chat-error-report-link')).toBeInTheDocument()
1818

19-
expect(screen.getByTestId('ai-general-restart-session-btn')).toBeInTheDocument()
19+
expect(screen.getByTestId('ai-chat-error-restart-session-btn')).toBeInTheDocument()
2020
})
2121

2222
it('should not render restart button with timeout error', () => {
@@ -26,6 +26,6 @@ describe('ErrorMessage', () => {
2626
expect(screen.getByTestId('ai-chat-error-message')).toHaveTextContent(AiChatErrors.Timeout)
2727
expect(screen.getByTestId('ai-chat-error-report-link')).toBeInTheDocument()
2828

29-
expect(screen.queryByTestId('ai-general-restart-session-btn')).not.toBeInTheDocument()
29+
expect(screen.queryByTestId('ai-chat-error-restart-session-btn')).not.toBeInTheDocument()
3030
})
3131
})

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/shared/error-message/ErrorMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const ErrorMessage = (props: Props) => {
5454
color="secondary"
5555
iconType="eraser"
5656
className={styles.restartSessionBtn}
57-
data-testid="ai-general-restart-session-btn"
57+
data-testid="ai-chat-error-restart-session-btn"
5858
>
5959
Restart session
6060
</EuiButton>

0 commit comments

Comments
 (0)