Skip to content

Commit 7efc32c

Browse files
reset chat or reload history after data source change (#194) (#204)
* reset chat or reload history when data source change Signed-off-by: Lin Wang <[email protected]> * Add change log Signed-off-by: Lin Wang <[email protected]> * Address PR comments Signed-off-by: Lin Wang <[email protected]> * Set search and page after data source change Signed-off-by: Lin Wang <[email protected]> * Remove skip first value when subscribe dataSourceId$ Signed-off-by: Lin Wang <[email protected]> * Add dataSourceIdUdpates$ and finalDataSourceId Signed-off-by: Lin Wang <[email protected]> * Remove data source service mock in chat header button Signed-off-by: Lin Wang <[email protected]> * Remove no need useRef Signed-off-by: Lin Wang <[email protected]> * Refactor load history after data source change Signed-off-by: Lin Wang <[email protected]> --------- Signed-off-by: Lin Wang <[email protected]> (cherry picked from commit a2a98f6) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> # Conflicts: # CHANGELOG.md Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent bd1a617 commit 7efc32c

18 files changed

+375
-93
lines changed

public/chat_header_button.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { EuiBadge, EuiFieldText, EuiIcon } from '@elastic/eui';
77
import classNames from 'classnames';
88
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
99
import { useEffectOnce } from 'react-use';
10+
1011
import { ApplicationStart, SIDECAR_DOCKED_MODE } from '../../../src/core/public';
1112
// TODO: Replace with getChrome().logos.Chat.url
1213
import chatIcon from './assets/chat.svg';

public/components/agent_framework_traces_flyout_body.test.tsx

+32-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
import React from 'react';
77
import '@testing-library/jest-dom/extend-expect';
8-
import { act, waitFor, render, screen, fireEvent } from '@testing-library/react';
8+
import { waitFor, render, screen, fireEvent } from '@testing-library/react';
99
import * as chatContextExports from '../contexts/chat_context';
10+
import * as coreContextExports from '../contexts/core_context';
1011
import { AgentFrameworkTracesFlyoutBody } from './agent_framework_traces_flyout_body';
1112
import { TAB_ID } from '../utils/constants';
13+
import { BehaviorSubject, Subject } from 'rxjs';
1214

1315
jest.mock('./agent_framework_traces', () => {
1416
return {
@@ -17,6 +19,20 @@ jest.mock('./agent_framework_traces', () => {
1719
});
1820

1921
describe('<AgentFrameworkTracesFlyout/> spec', () => {
22+
let dataSourceIdUpdates$: Subject<string | null>;
23+
beforeEach(() => {
24+
dataSourceIdUpdates$ = new Subject<string | null>();
25+
jest.spyOn(coreContextExports, 'useCore').mockImplementation(() => {
26+
return {
27+
services: {
28+
dataSource: {
29+
dataSourceIdUpdates$,
30+
},
31+
},
32+
};
33+
});
34+
});
35+
2036
it('show back button if interactionId exists', async () => {
2137
const onCloseMock = jest.fn();
2238
jest.spyOn(chatContextExports, 'useChatContext').mockReturnValue({
@@ -70,4 +86,19 @@ describe('<AgentFrameworkTracesFlyout/> spec', () => {
7086
expect(onCloseMock).toHaveBeenCalledWith(TAB_ID.HISTORY);
7187
});
7288
});
89+
90+
it('should set tab to chat after data source changed', () => {
91+
const setSelectedTabIdMock = jest.fn();
92+
jest.spyOn(chatContextExports, 'useChatContext').mockReturnValue({
93+
interactionId: 'test-interaction-id',
94+
flyoutFullScreen: true,
95+
setSelectedTabId: setSelectedTabIdMock,
96+
preSelectedTabId: TAB_ID.HISTORY,
97+
});
98+
render(<AgentFrameworkTracesFlyoutBody />);
99+
100+
expect(setSelectedTabIdMock).not.toHaveBeenCalled();
101+
dataSourceIdUpdates$.next('foo');
102+
expect(setSelectedTabIdMock).toHaveBeenCalled();
103+
});
73104
});

public/components/agent_framework_traces_flyout_body.tsx

+13-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,26 @@ import {
1313
EuiButtonIcon,
1414
EuiPageHeaderSection,
1515
} from '@elastic/eui';
16-
import React from 'react';
16+
import React, { useEffect } from 'react';
1717
import { useChatContext } from '../contexts/chat_context';
18+
import { useCore } from '../../public/contexts';
1819
import { AgentFrameworkTraces } from './agent_framework_traces';
1920
import { TAB_ID } from '../utils/constants';
2021

2122
export const AgentFrameworkTracesFlyoutBody: React.FC = () => {
23+
const core = useCore();
2224
const chatContext = useChatContext();
2325
const interactionId = chatContext.interactionId;
26+
27+
useEffect(() => {
28+
const subscription = core.services.dataSource.dataSourceIdUpdates$.subscribe(() => {
29+
chatContext.setSelectedTabId(TAB_ID.CHAT);
30+
});
31+
return () => {
32+
subscription.unsubscribe();
33+
};
34+
}, [core.services.dataSource, chatContext.setSelectedTabId]);
35+
2436
if (!interactionId) {
2537
return null;
2638
}

public/hooks/use_chat_actions.test.tsx

+54-6
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ jest.mock('../services/conversations_service', () => {
2626
jest.mock('../services/conversation_load_service', () => {
2727
return {
2828
ConversationLoadService: jest.fn().mockImplementation(() => {
29-
return { load: jest.fn().mockReturnValue({ messages: [], interactions: [] }) };
29+
const conversationLoadMock = {
30+
abortController: new AbortController(),
31+
load: jest.fn().mockImplementation(async () => {
32+
conversationLoadMock.abortController = new AbortController();
33+
return { messages: [], interactions: [] };
34+
}),
35+
};
36+
return conversationLoadMock;
3037
}),
3138
};
3239
});
@@ -126,7 +133,7 @@ describe('useChatActions hook', () => {
126133
messages: [SEND_MESSAGE_RESPONSE.messages[0]],
127134
input: INPUT_MESSAGE,
128135
}),
129-
query: await dataSourceServiceMock.getDataSourceQuery(),
136+
query: dataSourceServiceMock.getDataSourceQuery(),
130137
});
131138

132139
// it should send dispatch `receive` action to remove the message without messageId
@@ -201,7 +208,7 @@ describe('useChatActions hook', () => {
201208
messages: [],
202209
input: { type: 'input', content: 'message that send as input', contentType: 'text' },
203210
}),
204-
query: await dataSourceServiceMock.getDataSourceQuery(),
211+
query: dataSourceServiceMock.getDataSourceQuery(),
205212
});
206213
});
207214

@@ -264,7 +271,7 @@ describe('useChatActions hook', () => {
264271
expect(chatStateDispatchMock).toHaveBeenCalledWith({ type: 'abort' });
265272
expect(httpMock.post).toHaveBeenCalledWith(ASSISTANT_API.ABORT_AGENT_EXECUTION, {
266273
body: JSON.stringify({ conversationId: 'conversation_id_to_abort' }),
267-
query: await dataSourceServiceMock.getDataSourceQuery(),
274+
query: dataSourceServiceMock.getDataSourceQuery(),
268275
});
269276
});
270277

@@ -292,7 +299,7 @@ describe('useChatActions hook', () => {
292299
conversationId: 'conversation_id_mock',
293300
interactionId: 'interaction_id_mock',
294301
}),
295-
query: await dataSourceServiceMock.getDataSourceQuery(),
302+
query: dataSourceServiceMock.getDataSourceQuery(),
296303
});
297304
expect(chatStateDispatchMock).toHaveBeenCalledWith(
298305
expect.objectContaining({ type: 'receive', payload: { messages: [], interactions: [] } })
@@ -312,6 +319,7 @@ describe('useChatActions hook', () => {
312319
it('should not handle regenerate response if the regenerate operation has already aborted', async () => {
313320
const AbortControllerMock = jest.spyOn(window, 'AbortController').mockImplementation(() => ({
314321
signal: { aborted: true },
322+
abort: jest.fn(),
315323
}));
316324

317325
httpMock.put.mockResolvedValue(SEND_MESSAGE_RESPONSE);
@@ -328,7 +336,7 @@ describe('useChatActions hook', () => {
328336
conversationId: 'conversation_id_mock',
329337
interactionId: 'interaction_id_mock',
330338
}),
331-
query: await dataSourceServiceMock.getDataSourceQuery(),
339+
query: dataSourceServiceMock.getDataSourceQuery(),
332340
});
333341
expect(chatStateDispatchMock).not.toHaveBeenCalledWith(
334342
expect.objectContaining({ type: 'receive' })
@@ -353,6 +361,7 @@ describe('useChatActions hook', () => {
353361
it('should not handle regenerate error if the regenerate operation has already aborted', async () => {
354362
const AbortControllerMock = jest.spyOn(window, 'AbortController').mockImplementation(() => ({
355363
signal: { aborted: true },
364+
abort: jest.fn(),
356365
}));
357366
httpMock.put.mockImplementationOnce(() => {
358367
throw new Error();
@@ -369,4 +378,43 @@ describe('useChatActions hook', () => {
369378
);
370379
AbortControllerMock.mockRestore();
371380
});
381+
382+
it('should clear chat title, conversation id, flyoutComponent and call reset action', async () => {
383+
const { result } = renderHook(() => useChatActions());
384+
result.current.resetChat();
385+
386+
expect(chatContextMock.setConversationId).toHaveBeenLastCalledWith(undefined);
387+
expect(chatContextMock.setTitle).toHaveBeenLastCalledWith(undefined);
388+
expect(chatContextMock.setFlyoutComponent).toHaveBeenLastCalledWith(null);
389+
390+
expect(chatStateDispatchMock).toHaveBeenLastCalledWith({ type: 'reset' });
391+
});
392+
393+
it('should abort send action after reset chat', async () => {
394+
const abortFn = jest.fn();
395+
const AbortControllerMock = jest.spyOn(window, 'AbortController').mockImplementation(() => ({
396+
signal: { aborted: true },
397+
abort: abortFn,
398+
}));
399+
const { result } = renderHook(() => useChatActions());
400+
await result.current.send(INPUT_MESSAGE);
401+
result.current.resetChat();
402+
403+
expect(abortFn).toHaveBeenCalled();
404+
AbortControllerMock.mockRestore();
405+
});
406+
407+
it('should abort load action after reset chat', async () => {
408+
const abortFn = jest.fn();
409+
const AbortControllerMock = jest.spyOn(window, 'AbortController').mockImplementation(() => ({
410+
signal: { aborted: true },
411+
abort: abortFn,
412+
}));
413+
const { result } = renderHook(() => useChatActions());
414+
await result.current.loadChat('conversation_id_mock');
415+
result.current.resetChat();
416+
417+
expect(abortFn).toHaveBeenCalled();
418+
AbortControllerMock.mockRestore();
419+
});
372420
});

public/hooks/use_chat_actions.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const useChatActions = (): AssistantActions => {
3535
...(!chatContext.conversationId && { messages: chatState.messages }), // include all previous messages for new chats
3636
input,
3737
}),
38-
query: await core.services.dataSource.getDataSourceQuery(),
38+
query: core.services.dataSource.getDataSourceQuery(),
3939
});
4040
if (abortController.signal.aborted) return;
4141
// Refresh history list after new conversation created if new conversation saved and history list page visible
@@ -106,6 +106,15 @@ export const useChatActions = (): AssistantActions => {
106106
}
107107
};
108108

109+
const resetChat = () => {
110+
abortControllerRef?.abort();
111+
core.services.conversationLoad.abortController?.abort();
112+
chatContext.setConversationId(undefined);
113+
chatContext.setTitle(undefined);
114+
chatContext.setFlyoutComponent(null);
115+
chatStateDispatch({ type: 'reset' });
116+
};
117+
109118
const openChatUI = () => {
110119
chatContext.setFlyoutVisible(true);
111120
chatContext.setSelectedTabId(TAB_ID.CHAT);
@@ -163,7 +172,7 @@ export const useChatActions = (): AssistantActions => {
163172
// abort agent execution
164173
await core.services.http.post(`${ASSISTANT_API.ABORT_AGENT_EXECUTION}`, {
165174
body: JSON.stringify({ conversationId }),
166-
query: await core.services.dataSource.getDataSourceQuery(),
175+
query: core.services.dataSource.getDataSourceQuery(),
167176
});
168177
}
169178
};
@@ -180,7 +189,7 @@ export const useChatActions = (): AssistantActions => {
180189
conversationId: chatContext.conversationId,
181190
interactionId,
182191
}),
183-
query: await core.services.dataSource.getDataSourceQuery(),
192+
query: core.services.dataSource.getDataSourceQuery(),
184193
});
185194

186195
if (abortController.signal.aborted) {
@@ -225,5 +234,5 @@ export const useChatActions = (): AssistantActions => {
225234
}
226235
};
227236

228-
return { send, loadChat, executeAction, openChatUI, abortAction, regenerate };
237+
return { send, loadChat, resetChat, executeAction, openChatUI, abortAction, regenerate };
229238
};

public/hooks/use_conversations.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ export const useDeleteConversation = () => {
1414
const abortControllerRef = useRef<AbortController>();
1515

1616
const deleteConversation = useCallback(
17-
async (conversationId: string) => {
17+
(conversationId: string) => {
1818
abortControllerRef.current = new AbortController();
1919
dispatch({ type: 'request' });
2020
return core.services.http
2121
.delete(`${ASSISTANT_API.CONVERSATION}/${conversationId}`, {
2222
signal: abortControllerRef.current.signal,
23-
query: await core.services.dataSource.getDataSourceQuery(),
23+
query: core.services.dataSource.getDataSourceQuery(),
2424
})
2525
.then((payload) => {
2626
dispatch({ type: 'success', payload });
@@ -53,15 +53,15 @@ export const usePatchConversation = () => {
5353
const abortControllerRef = useRef<AbortController>();
5454

5555
const patchConversation = useCallback(
56-
async (conversationId: string, title: string) => {
56+
(conversationId: string, title: string) => {
5757
abortControllerRef.current = new AbortController();
5858
dispatch({ type: 'request' });
5959
return core.services.http
6060
.put(`${ASSISTANT_API.CONVERSATION}/${conversationId}`, {
6161
body: JSON.stringify({
6262
title,
6363
}),
64-
query: await core.services.dataSource.getDataSourceQuery(),
64+
query: core.services.dataSource.getDataSourceQuery(),
6565
signal: abortControllerRef.current.signal,
6666
})
6767
.then((payload) => dispatch({ type: 'success', payload }))

public/hooks/use_feed_back.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('useFeedback hook', () => {
8484
body: JSON.stringify({
8585
satisfaction: true,
8686
}),
87-
query: await dataSourceMock.getDataSourceQuery(),
87+
query: dataSourceMock.getDataSourceQuery(),
8888
}
8989
);
9090
expect(result.current.feedbackResult).toBe(true);
@@ -119,7 +119,7 @@ describe('useFeedback hook', () => {
119119
body: JSON.stringify({
120120
satisfaction: true,
121121
}),
122-
query: await dataSourceMock.getDataSourceQuery(),
122+
query: dataSourceMock.getDataSourceQuery(),
123123
}
124124
);
125125
expect(result.current.feedbackResult).toBe(undefined);

public/hooks/use_feed_back.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const useFeedback = (interaction?: Interaction | null) => {
3838
try {
3939
await core.services.http.put(`${ASSISTANT_API.FEEDBACK}/${message.interactionId}`, {
4040
body: JSON.stringify(body),
41-
query: await core.services.dataSource.getDataSourceQuery(),
41+
query: core.services.dataSource.getDataSourceQuery(),
4242
});
4343
setFeedbackResult(correct);
4444
} catch (error) {

public/hooks/use_fetch_agentframework_traces.ts

+15-17
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,24 @@ export const useFetchAgentFrameworkTraces = (interactionId: string) => {
2222
return;
2323
}
2424

25-
core.services.dataSource.getDataSourceQuery().then((query) => {
26-
core.services.http
27-
.get<AgentFrameworkTrace[]>(`${ASSISTANT_API.TRACE}/${interactionId}`, {
28-
signal: abortController.signal,
29-
query,
25+
core.services.http
26+
.get<AgentFrameworkTrace[]>(`${ASSISTANT_API.TRACE}/${interactionId}`, {
27+
signal: abortController.signal,
28+
query: core.services.dataSource.getDataSourceQuery(),
29+
})
30+
.then((payload) =>
31+
dispatch({
32+
type: 'success',
33+
payload,
3034
})
31-
.then((payload) =>
32-
dispatch({
33-
type: 'success',
34-
payload,
35-
})
36-
)
37-
.catch((error) => {
38-
if (error.name === 'AbortError') return;
39-
dispatch({ type: 'failure', error });
40-
});
41-
});
35+
)
36+
.catch((error) => {
37+
if (error.name === 'AbortError') return;
38+
dispatch({ type: 'failure', error });
39+
});
4240

4341
return () => abortController.abort();
44-
}, [core.services.http, interactionId]);
42+
}, [core.services.http, interactionId, core.services.dataSource]);
4543

4644
return { ...state };
4745
};

0 commit comments

Comments
 (0)