diff --git a/frontend/app/javascript/dashboard/components/ChatList.vue b/frontend/app/javascript/dashboard/components/ChatList.vue index e4fd99d2..26f2bfde 100644 --- a/frontend/app/javascript/dashboard/components/ChatList.vue +++ b/frontend/app/javascript/dashboard/components/ChatList.vue @@ -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, () => { />

+ {{ $t('CHAT_LIST.LIST.ERROR') }} +

+

{{ $t('CHAT_LIST.LIST.404') }} @@ -933,6 +940,7 @@ watch(chatLists, () => { [] }, 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') }}

diff --git a/frontend/app/javascript/dashboard/i18n/locale/en/chatlist.json b/frontend/app/javascript/dashboard/i18n/locale/en/chatlist.json index 1384dae2..1ad72e57 100644 --- a/frontend/app/javascript/dashboard/i18n/locale/en/chatlist.json +++ b/frontend/app/javascript/dashboard/i18n/locale/en/chatlist.json @@ -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", diff --git a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/chatlist.json b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/chatlist.json index 9d170012..90a3e11e 100644 --- a/frontend/app/javascript/dashboard/i18n/locale/zh_CN/chatlist.json +++ b/frontend/app/javascript/dashboard/i18n/locale/zh_CN/chatlist.json @@ -4,7 +4,8 @@ "LOAD_MORE_CONVERSATIONS": "加载更多对话", "EOF": "所有对话已加载 🎉", "LIST": { - "404": "没有有效的对话在这个群组里面" + "404": "没有有效的对话在这个群组里面", + "ERROR": "无法加载会话,正在等待服务器重新连接。" }, "FAILED_TO_SEND": "发送失败", "TAB_HEADING": "会话", diff --git a/frontend/app/javascript/dashboard/store/modules/conversations/actions.js b/frontend/app/javascript/dashboard/store/modules/conversations/actions.js index bbc35b5f..d9da8f1d 100644 --- a/frontend/app/javascript/dashboard/store/modules/conversations/actions.js +++ b/frontend/app/javascript/dashboard/store/modules/conversations/actions.js @@ -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); } }, diff --git a/frontend/app/javascript/dashboard/store/modules/conversations/getters.js b/frontend/app/javascript/dashboard/store/modules/conversations/getters.js index 5e85c423..60aa6e75 100644 --- a/frontend/app/javascript/dashboard/store/modules/conversations/getters.js +++ b/frontend/app/javascript/dashboard/store/modules/conversations/getters.js @@ -138,6 +138,7 @@ const getters = { }); }, getChatListLoadingStatus: ({ listLoadingStatus }) => listLoadingStatus, + getChatListErrorStatus: ({ listErrorStatus }) => listErrorStatus, getAllMessagesLoaded(_state) { const [chat] = getSelectedChatConversation(_state); return !chat || chat.allMessagesLoaded === undefined diff --git a/frontend/app/javascript/dashboard/store/modules/conversations/index.js b/frontend/app/javascript/dashboard/store/modules/conversations/index.js index 8a13940c..2dfe0f13 100644 --- a/frontend/app/javascript/dashboard/store/modules/conversations/index.js +++ b/frontend/app/javascript/dashboard/store/modules/conversations/index.js @@ -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 } diff --git a/frontend/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/frontend/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js index fa052ec1..89bffb9e 100644 --- a/frontend/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js +++ b/frontend/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js @@ -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], ]); }); }); diff --git a/frontend/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js b/frontend/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js index fc1c61b3..77867bb0 100644 --- a/frontend/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js +++ b/frontend/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js @@ -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 = { diff --git a/frontend/app/javascript/dashboard/store/mutation-types.js b/frontend/app/javascript/dashboard/store/mutation-types.js index 1597b7ea..88b928ea 100644 --- a/frontend/app/javascript/dashboard/store/mutation-types.js +++ b/frontend/app/javascript/dashboard/store/mutation-types.js @@ -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',