Bugfix: Update conversation counters in realtime (#944)

* Bug: Update conversation counters in realtime
This commit is contained in:
Pranav Raj S 2020-06-09 16:26:33 +05:30 committed by GitHub
parent 602132119b
commit 40481f6462
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 99 additions and 14 deletions

View file

@ -43,6 +43,16 @@ class ConversationApi extends ApiClient {
mute(conversationId) { mute(conversationId) {
return axios.post(`${this.url}/${conversationId}/mute`); return axios.post(`${this.url}/${conversationId}/mute`);
} }
meta({ inboxId, status, assigneeType }) {
return axios.get(`${this.url}/meta`, {
params: {
inbox_id: inboxId,
status,
assignee_type: assigneeType,
},
});
}
} }
export default new ConversationApi(); export default new ConversationApi();

View file

@ -0,0 +1,19 @@
import conversationAPI from '../../inbox/conversation';
import ApiClient from '../../ApiClient';
describe('#ConversationAPI', () => {
it('creates correct instance', () => {
expect(conversationAPI).toBeInstanceOf(ApiClient);
expect(conversationAPI).toHaveProperty('get');
expect(conversationAPI).toHaveProperty('show');
expect(conversationAPI).toHaveProperty('create');
expect(conversationAPI).toHaveProperty('update');
expect(conversationAPI).toHaveProperty('delete');
expect(conversationAPI).toHaveProperty('toggleStatus');
expect(conversationAPI).toHaveProperty('assignAgent');
expect(conversationAPI).toHaveProperty('markMessageRead');
expect(conversationAPI).toHaveProperty('toggleTyping');
expect(conversationAPI).toHaveProperty('mute');
expect(conversationAPI).toHaveProperty('meta');
});
});

View file

@ -1,7 +1,7 @@
import inboxes from '../inboxes'; import inboxes from '../inboxes';
import ApiClient from '../ApiClient'; import ApiClient from '../ApiClient';
describe('#AgentAPI', () => { describe('#InboxesAPI', () => {
it('creates correct instance', () => { it('creates correct instance', () => {
expect(inboxes).toBeInstanceOf(ApiClient); expect(inboxes).toBeInstanceOf(ApiClient);
expect(inboxes).toHaveProperty('get'); expect(inboxes).toHaveProperty('get');

View file

@ -66,6 +66,8 @@
} }
.button.resolve--button { .button.resolve--button {
@include flex-align($x: center, $y: middle);
width: 13.2rem; width: 13.2rem;
>.icon { >.icon {

View file

@ -26,6 +26,7 @@
.link { .link {
color: $color-white; color: $color-white;
text-decoration: underline;
} }
} }

View file

@ -55,6 +55,7 @@
<script> <script>
/* eslint-env browser */ /* eslint-env browser */
/* eslint no-console: 0 */ /* eslint no-console: 0 */
/* global bus */
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import ChatFilter from './widgets/conversation/ChatFilter'; import ChatFilter from './widgets/conversation/ChatFilter';
@ -109,6 +110,14 @@ export default {
this.activeAssigneeTab this.activeAssigneeTab
); );
}, },
conversationFilters() {
return {
inboxId: this.conversationInbox ? this.conversationInbox : undefined,
assigneeType: this.activeAssigneeTab,
status: this.activeStatus,
page: this.currentPage + 1,
};
},
}, },
watch: { watch: {
conversationInbox() { conversationInbox() {
@ -119,6 +128,10 @@ export default {
this.$store.dispatch('setChatFilter', this.activeStatus); this.$store.dispatch('setChatFilter', this.activeStatus);
this.resetAndFetchData(); this.resetAndFetchData();
this.$store.dispatch('agents/get'); this.$store.dispatch('agents/get');
bus.$on('fetch_conversation_stats', () => {
this.$store.dispatch('getConversationStats', this.conversationFilters);
});
}, },
methods: { methods: {
resetAndFetchData() { resetAndFetchData() {
@ -127,12 +140,7 @@ export default {
this.fetchConversations(); this.fetchConversations();
}, },
fetchConversations() { fetchConversations() {
this.$store.dispatch('fetchAllConversations', { this.$store.dispatch('fetchAllConversations', this.conversationFilters);
inboxId: this.conversationInbox ? this.conversationInbox : undefined,
assigneeType: this.activeAssigneeTab,
status: this.activeStatus,
page: this.currentPage + 1,
});
}, },
updateAssigneeTab(selectedTab) { updateAssigneeTab(selectedTab) {
if (this.activeAssigneeTab !== selectedTab) { if (this.activeAssigneeTab !== selectedTab) {

View file

@ -1,5 +1,6 @@
import AuthAPI from '../api/auth'; import AuthAPI from '../api/auth';
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector'; import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
/* global bus */
class ActionCableConnector extends BaseActionCableConnector { class ActionCableConnector extends BaseActionCableConnector {
constructor(app, pubsubToken) { constructor(app, pubsubToken) {
@ -45,10 +46,12 @@ class ActionCableConnector extends BaseActionCableConnector {
if (id) { if (id) {
this.app.$store.dispatch('updateAssignee', { id, assignee }); this.app.$store.dispatch('updateAssignee', { id, assignee });
} }
this.fetchConversationStats();
}; };
onConversationCreated = data => { onConversationCreated = data => {
this.app.$store.dispatch('addConversation', data); this.app.$store.dispatch('addConversation', data);
this.fetchConversationStats();
}; };
onLogout = () => AuthAPI.logout(); onLogout = () => AuthAPI.logout();
@ -61,6 +64,7 @@ class ActionCableConnector extends BaseActionCableConnector {
onStatusChange = data => { onStatusChange = data => {
this.app.$store.dispatch('updateConversation', data); this.app.$store.dispatch('updateConversation', data);
this.fetchConversationStats();
}; };
onTypingOn = ({ conversation, user }) => { onTypingOn = ({ conversation, user }) => {
@ -100,6 +104,10 @@ class ActionCableConnector extends BaseActionCableConnector {
this.onTypingOff({ conversation, user }); this.onTypingOff({ conversation, user });
}, 30000); }, 30000);
}; };
fetchConversationStats = () => {
bus.$emit('fetch_conversation_stats');
};
} }
export default { export default {

View file

@ -236,6 +236,15 @@ const actions = {
// //
} }
}, },
getConversationStats: async ({ commit }, params) => {
try {
const response = await ConversationApi.meta(params);
commit(types.default.SET_CONV_TAB_META, response.data.meta);
} catch (error) {
// Ignore error
}
},
}; };
export default actions; export default actions;

View file

@ -0,0 +1,30 @@
import axios from 'axios';
import actions from '../actions';
import * as types from '../../../mutation-types';
const commit = jest.fn();
global.axios = axios;
jest.mock('axios');
describe('#actions', () => {
describe('#getConversationStats', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({ data: { meta: { mine_count: 1 } } });
await actions.getConversationStats(
{ commit },
{ inboxId: 1, assigneeTpe: 'me', status: 'open' }
);
expect(commit.mock.calls).toEqual([
[types.default.SET_CONV_TAB_META, { mine_count: 1 }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
await actions.getConversationStats(
{ commit },
{ inboxId: 1, assigneeTpe: 'me', status: 'open' }
);
expect(commit.mock.calls).toEqual([]);
});
});
});

View file

@ -1,7 +1,5 @@
json.data do json.meta do
json.meta do json.mine_count @conversations_count[:mine_count]
json.mine_count @conversations_count[:mine_count] json.unassigned_count @conversations_count[:unassigned_count]
json.unassigned_count @conversations_count[:unassigned_count] json.all_count @conversations_count[:all_count]
json.all_count @conversations_count[:all_count]
end
end end

View file

@ -54,7 +54,7 @@ RSpec.describe 'Conversations API', type: :request do
as: :json as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(JSON.parse(response.body, symbolize_names: true)[:data][:meta][:all_count]).to eq(1) expect(JSON.parse(response.body, symbolize_names: true)[:meta][:all_count]).to eq(1)
end end
end end
end end