fix(frontend): stop offline conversation request loop

This commit is contained in:
2026-07-12 22:16:16 +08:00
parent f923791d39
commit b0bb7f0702
10 changed files with 152 additions and 12 deletions
@@ -97,6 +97,7 @@ const allChatList = useMapGetter('getAllStatusChats');
const unAssignedChatsList = useMapGetter('getUnAssignedChats');
const participatingChatsList = useMapGetter('getParticipatingChats');
const chatListLoading = useMapGetter('getChatListLoadingStatus');
const chatListError = useMapGetter('getChatListErrorStatus');
const activeInbox = useMapGetter('getSelectedInbox');
const conversationStats = useMapGetter('conversationStats/getStats');
const appliedFilters = useMapGetter('getAppliedConversationFiltersV2');
@@ -915,7 +916,13 @@ watch(chatLists, () => {
/>
<p
v-if="!chatListLoading && !conversationList.length"
v-if="chatListError"
class="flex overflow-auto justify-center items-center p-4 text-n-slate-11"
>
{{ $t('CHAT_LIST.LIST.ERROR') }}
</p>
<p
v-else-if="!chatListLoading && !conversationList.length"
class="flex overflow-auto justify-center items-center p-4"
>
{{ $t('CHAT_LIST.LIST.404') }}
@@ -933,6 +940,7 @@ watch(chatLists, () => {
<ConversationList
:conversation-list="conversationList"
:is-loading="chatListLoading"
:has-error="chatListError"
:show-end-of-list-message="showEndOfListMessage"
:label="label"
:team-id="teamId"
@@ -12,6 +12,7 @@ import wootConstants from 'dashboard/constants/globals';
const props = defineProps({
conversationList: { type: Array, default: () => [] },
isLoading: { type: Boolean, default: false },
hasError: { type: Boolean, default: false },
showEndOfListMessage: { type: Boolean, default: false },
label: { type: String, default: '' },
teamId: { type: [String, Number], default: 0 },
@@ -86,7 +87,7 @@ defineExpose({ conversationListRef });
{{ $t('CHAT_LIST.EOF') }}
</p>
<IntersectionObserver
v-else
v-else-if="!hasError"
:options="intersectionObserverOptions"
@observed="loadMoreConversations"
/>
@@ -4,7 +4,8 @@
"LOAD_MORE_CONVERSATIONS": "Load more conversations",
"EOF": "All conversations loaded 🎉",
"LIST": {
"404": "There are no active conversations in this group."
"404": "There are no active conversations in this group.",
"ERROR": "Unable to load conversations. Waiting for the server to reconnect."
},
"FAILED_TO_SEND": "Failed to send",
"TAB_HEADING": "Conversations",
@@ -4,7 +4,8 @@
"LOAD_MORE_CONVERSATIONS": "加载更多对话",
"EOF": "所有对话已加载 🎉",
"LIST": {
"404": "没有有效的对话在这个群组里面"
"404": "没有有效的对话在这个群组里面",
"ERROR": "无法加载会话,正在等待服务器重新连接。"
},
"FAILED_TO_SEND": "发送失败",
"TAB_HEADING": "会话",
@@ -44,6 +44,7 @@ const actions = {
},
fetchAllConversations: async ({ commit, state, dispatch }, explicitFilters) => {
commit(types.CLEAR_LIST_ERROR_STATUS);
commit(types.SET_LIST_LOADING_STATUS);
try {
const params = explicitFilters || state.conversationFilters;
@@ -56,12 +57,19 @@ const actions = {
data,
params.assigneeType
);
commit(types.CLEAR_LIST_ERROR_STATUS);
} catch (error) {
// Handle error
// Stop the infinite-scroll observer from immediately retrying a failed
// page while the backend is unavailable. Reconnect/manual fetches clear
// this flag before trying again.
commit(types.SET_LIST_ERROR_STATUS);
} finally {
commit(types.CLEAR_LIST_LOADING_STATUS);
}
},
fetchFilteredConversations: async ({ commit, dispatch }, params) => {
commit(types.CLEAR_LIST_ERROR_STATUS);
commit(types.SET_LIST_LOADING_STATUS);
try {
const { data } = await ConversationApi.filter(params);
@@ -71,8 +79,11 @@ const actions = {
data,
'appliedFilters'
);
commit(types.CLEAR_LIST_ERROR_STATUS);
} catch (error) {
// Handle error
commit(types.SET_LIST_ERROR_STATUS);
} finally {
commit(types.CLEAR_LIST_LOADING_STATUS);
}
},
@@ -138,6 +138,7 @@ const getters = {
});
},
getChatListLoadingStatus: ({ listLoadingStatus }) => listLoadingStatus,
getChatListErrorStatus: ({ listErrorStatus }) => listErrorStatus,
getAllMessagesLoaded(_state) {
const [chat] = getSelectedChatConversation(_state);
return !chat || chat.allMessagesLoaded === undefined
@@ -12,6 +12,7 @@ const state = {
allConversations: [],
attachments: {},
listLoadingStatus: true,
listErrorStatus: false,
chatStatusFilter: wootConstants.STATUS_TYPE.OPEN,
chatSortFilter: wootConstants.SORT_BY_TYPE.LATEST,
currentInbox: null,
@@ -274,6 +275,14 @@ export const mutations = {
_state.listLoadingStatus = false;
},
[types.SET_LIST_ERROR_STATUS](_state) {
_state.listErrorStatus = true;
},
[types.CLEAR_LIST_ERROR_STATUS](_state) {
_state.listErrorStatus = false;
},
[types.UPDATE_MESSAGE_UNREAD_COUNT](
_state,
{ id, lastSeen, unreadCount = 0 }
@@ -431,15 +431,101 @@ describe('#actions', () => {
});
describe('#fetchFilteredConversations', () => {
it('fetches filtered conversations with a mock commit', async () => {
it('fetches filtered conversations and clears the loading state', async () => {
const localCommit = vi.fn();
const localDispatch = vi.fn();
axios.post.mockResolvedValue({
data: dataReceived,
});
await actions.fetchFilteredConversations({ commit }, dataToSend);
expect(commit).toHaveBeenCalledTimes(2);
expect(commit.mock.calls).toEqual([
['SET_LIST_LOADING_STATUS'],
['SET_ALL_CONVERSATION', dataReceived.payload],
await actions.fetchFilteredConversations(
{ commit: localCommit, dispatch: localDispatch },
dataToSend
);
expect(localCommit.mock.calls).toEqual([
[types.CLEAR_LIST_ERROR_STATUS],
[types.SET_LIST_LOADING_STATUS],
[types.SET_ALL_CONVERSATION, dataReceived.payload],
[
`contacts/${types.SET_CONTACTS}`,
dataReceived.payload.map(conversation => conversation.meta.sender),
],
[types.CLEAR_LIST_ERROR_STATUS],
[types.CLEAR_LIST_LOADING_STATUS],
]);
expect(localDispatch).toHaveBeenCalledWith(
'conversationStats/set',
dataReceived.meta
);
});
it('clears the loading state when the API request fails', async () => {
const localCommit = vi.fn();
axios.post.mockRejectedValue(new Error('Request failed'));
await actions.fetchFilteredConversations(
{ commit: localCommit, dispatch: vi.fn() },
dataToSend
);
expect(localCommit.mock.calls).toEqual([
[types.CLEAR_LIST_ERROR_STATUS],
[types.SET_LIST_LOADING_STATUS],
[types.SET_LIST_ERROR_STATUS],
[types.CLEAR_LIST_LOADING_STATUS],
]);
});
});
describe('#fetchAllConversations', () => {
it('fetches conversations and clears the loading state', async () => {
const localCommit = vi.fn();
const localDispatch = vi.fn();
const filters = {
inboxId: 4,
status: 'open',
assigneeType: 'all',
page: 1,
};
axios.get.mockResolvedValue({ data: { data: dataReceived } });
await actions.fetchAllConversations({
commit: localCommit,
dispatch: localDispatch,
state: { conversationFilters: filters },
});
expect(localCommit.mock.calls).toEqual([
[types.CLEAR_LIST_ERROR_STATUS],
[types.SET_LIST_LOADING_STATUS],
[types.SET_ALL_CONVERSATION, dataReceived.payload],
[
`contacts/${types.SET_CONTACTS}`,
dataReceived.payload.map(conversation => conversation.meta.sender),
],
[types.CLEAR_LIST_ERROR_STATUS],
[types.CLEAR_LIST_LOADING_STATUS],
]);
expect(localDispatch).toHaveBeenCalledWith(
'conversationStats/set',
dataReceived.meta
);
});
it('clears the loading state when the API request fails', async () => {
const localCommit = vi.fn();
axios.get.mockRejectedValue(new Error('Request failed'));
await actions.fetchAllConversations({
commit: localCommit,
dispatch: vi.fn(),
state: { conversationFilters: {} },
});
expect(localCommit.mock.calls).toEqual([
[types.CLEAR_LIST_ERROR_STATUS],
[types.SET_LIST_LOADING_STATUS],
[types.SET_LIST_ERROR_STATUS],
[types.CLEAR_LIST_LOADING_STATUS],
]);
});
});
@@ -1066,6 +1066,26 @@ describe('#mutations', () => {
});
});
describe('#SET_LIST_ERROR_STATUS', () => {
it('marks the conversation list request as failed', () => {
const state = { listErrorStatus: false };
mutations[types.SET_LIST_ERROR_STATUS](state);
expect(state.listErrorStatus).toBe(true);
});
});
describe('#CLEAR_LIST_ERROR_STATUS', () => {
it('clears the conversation list request error', () => {
const state = { listErrorStatus: true };
mutations[types.CLEAR_LIST_ERROR_STATUS](state);
expect(state.listErrorStatus).toBe(false);
});
});
describe('#CHANGE_CHAT_STATUS_FILTER', () => {
it('should update chat status filter', () => {
const state = {
@@ -15,6 +15,8 @@ export default {
SET_CONV_TAB_META: 'SET_CONV_TAB_META',
CLEAR_LIST_LOADING_STATUS: 'CLEAR_LIST_LOADING_STATUS',
SET_LIST_LOADING_STATUS: 'SET_LIST_LOADING_STATUS',
CLEAR_LIST_ERROR_STATUS: 'CLEAR_LIST_ERROR_STATUS',
SET_LIST_ERROR_STATUS: 'SET_LIST_ERROR_STATUS',
SET_ALL_MESSAGES_LOADED: 'SET_ALL_MESSAGES_LOADED',
CLEAR_ALL_MESSAGES_LOADED: 'CLEAR_ALL_MESSAGES_LOADED',
CHANGE_CHAT_STATUS_FILTER: 'CHANGE_CHAT_STATUS_FILTER',