Skip to content

Commit b3cca2b

Browse files
fix(lightspeed): reset localstorage when the conversations are empty (#892)
* reset localstorage when the conversations are empty * add changeset
1 parent 6ff5f7e commit b3cca2b

3 files changed

Lines changed: 189 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed': patch
3+
---
4+
5+
Reset localstorage if the conversations are empty

workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ export const LightspeedChat = ({
141141

142142
const queryClient = useQueryClient();
143143

144-
const { data: conversations = [] } = useConversations();
144+
const {
145+
data: conversations = [],
146+
isLoading,
147+
isRefetching,
148+
} = useConversations();
145149
const { mutateAsync: deleteConversation } = useDeleteConversation();
146150
const { allowed: hasDeleteAccess } = useLightspeedDeletePermission();
147151
const samplePrompts = useWelcomePrompts();
@@ -152,6 +156,18 @@ export const LightspeedChat = ({
152156
}
153157
}, [user, isReady, lastOpenedId, setConversationId]);
154158

159+
React.useEffect(() => {
160+
// Clear last opened conversationId when there are no conversations.
161+
if (
162+
!isLoading &&
163+
!isRefetching &&
164+
conversations.length === 0 &&
165+
lastOpenedId
166+
) {
167+
clearLastOpenedId();
168+
}
169+
}, [isLoading, isRefetching, conversations, lastOpenedId, clearLastOpenedId]);
170+
155171
React.useEffect(() => {
156172
// Update last opened conversation whenever `conversationId` changes
157173
if (conversationId) {
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import React from 'react';
17+
18+
import {
19+
configApiRef,
20+
IdentityApi,
21+
identityApiRef,
22+
} from '@backstage/core-plugin-api';
23+
import { usePermission } from '@backstage/plugin-permission-react';
24+
import { mockApis, TestApiProvider } from '@backstage/test-utils';
25+
26+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
27+
import { render, screen, waitFor } from '@testing-library/react';
28+
29+
import FileAttachmentContextProvider from '../AttachmentContext';
30+
import { LightspeedChat } from '../LightSpeedChat';
31+
32+
const identityApi = {
33+
async getCredentials() {
34+
return { token: 'test-token' };
35+
},
36+
getBackstageIdentity: jest
37+
.fn()
38+
.mockReturnValue({ userEntityRef: 'user:test' }),
39+
} as unknown as IdentityApi;
40+
41+
// Create a query client with no retries for test purposes
42+
const queryClient = new QueryClient({
43+
defaultOptions: {
44+
queries: {
45+
retry: false,
46+
experimental_prefetchInRender: true,
47+
},
48+
},
49+
});
50+
51+
jest.mock('@backstage/plugin-permission-react', () => ({
52+
usePermission: jest.fn(),
53+
RequirePermission: jest.fn(),
54+
}));
55+
56+
jest.mock('../../hooks/useConversations', () => ({
57+
useConversations: jest.fn().mockReturnValue({
58+
data: [],
59+
isRefetching: false,
60+
isLoading: false,
61+
}),
62+
}));
63+
jest.mock('../../hooks/useDeleteConversation', () => ({
64+
useDeleteConversation: jest.fn().mockResolvedValue({
65+
data: [],
66+
}),
67+
}));
68+
69+
jest.mock('../../hooks/useConversationMessages', () => ({
70+
useConversationMessages: jest.fn().mockReturnValue({
71+
conversationMessages: [],
72+
}),
73+
}));
74+
75+
jest.mock('@patternfly/chatbot', () => {
76+
const actual = jest.requireActual('@patternfly/chatbot');
77+
return {
78+
...actual,
79+
MessageBox: () => <>MessageBox</>,
80+
};
81+
});
82+
83+
const mockUsePermission = usePermission as jest.MockedFunction<
84+
typeof usePermission
85+
>;
86+
87+
const configAPi = mockApis.config({});
88+
89+
const setupLightspeedChat = () => (
90+
<TestApiProvider
91+
apis={[
92+
[identityApiRef, identityApi],
93+
[configApiRef, configAPi],
94+
]}
95+
>
96+
<FileAttachmentContextProvider>
97+
<QueryClientProvider client={queryClient}>
98+
<LightspeedChat
99+
selectedModel="granite"
100+
profileLoading={false}
101+
handleSelectedModel={() => {}}
102+
models={[]}
103+
avatar="test"
104+
userName="user:test"
105+
/>
106+
</QueryClientProvider>
107+
</FileAttachmentContextProvider>
108+
</TestApiProvider>
109+
);
110+
111+
describe('LightspeedChat', () => {
112+
beforeEach(() => {
113+
mockUsePermission.mockReturnValue({ loading: true, allowed: true });
114+
});
115+
const localStorageKey = 'lastOpenedConversation';
116+
const mockUser = 'user:test';
117+
118+
it('should render lightspeed chat', async () => {
119+
render(setupLightspeedChat());
120+
121+
await waitFor(() => {
122+
expect(screen.getByText('Developer Hub Lightspeed')).toBeInTheDocument();
123+
});
124+
});
125+
126+
it('should not reset localstorage if the conversations are available', async () => {
127+
jest.mock('../../hooks/useConversations', () => ({
128+
useConversations: jest.fn().mockReturnValue({
129+
data: [
130+
{
131+
conversation_id: 'test-conversation-id',
132+
topic_summary: 'Greetings',
133+
last_message_timestamp: 1749023603.806369,
134+
},
135+
],
136+
isRefetching: false,
137+
isLoading: false,
138+
}),
139+
}));
140+
141+
const storedData = JSON.stringify({ [mockUser]: 'test-conversation-id' });
142+
localStorage.setItem(localStorageKey, storedData);
143+
144+
render(setupLightspeedChat());
145+
146+
await waitFor(() => {
147+
expect(screen.getByText('Developer Hub Lightspeed')).toBeInTheDocument();
148+
149+
expect(screen.queryByText('New chat')).not.toBeInTheDocument();
150+
expect(JSON.parse(localStorage.getItem(localStorageKey)!)).toEqual({});
151+
});
152+
});
153+
154+
it('should reset localstorage if the conversations are empty', async () => {
155+
const storedData = JSON.stringify({ [mockUser]: 'test-conversation-id' });
156+
localStorage.setItem(localStorageKey, storedData);
157+
158+
render(setupLightspeedChat());
159+
160+
await waitFor(() => {
161+
expect(screen.getByText('Developer Hub Lightspeed')).toBeInTheDocument();
162+
163+
expect(screen.queryByText('New chat')).not.toBeInTheDocument();
164+
expect(JSON.parse(localStorage.getItem(localStorageKey)!)).toEqual({});
165+
});
166+
});
167+
});

0 commit comments

Comments
 (0)