diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index 9f6b8baf1..22548499d 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -105,6 +105,16 @@ class ConversationApi extends ApiClient { custom_attributes: customAttributes, }); } + + fetchParticipants(conversationId) { + return axios.get(`${this.url}/${conversationId}/participants`); + } + + updateParticipants({ conversationId, userIds }) { + return axios.patch(`${this.url}/${conversationId}/participants`, { + user_ids: userIds, + }); + } } export default new ConversationApi(); diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index 481c6f107..632c14d2c 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -23,6 +23,7 @@ import conversations from './modules/conversations'; import conversationSearch from './modules/conversationSearch'; import conversationStats from './modules/conversationStats'; import conversationTypingStatus from './modules/conversationTypingStatus'; +import conversationWatchers from './modules/conversationWatchers'; import csat from './modules/csat'; import customViews from './modules/customViews'; import dashboardApps from './modules/dashboardApps'; @@ -66,6 +67,7 @@ export default new Vuex.Store({ conversationSearch, conversationStats, conversationTypingStatus, + conversationWatchers, csat, customViews, dashboardApps, diff --git a/app/javascript/dashboard/store/modules/conversationWatchers.js b/app/javascript/dashboard/store/modules/conversationWatchers.js new file mode 100644 index 000000000..d8da9cdb8 --- /dev/null +++ b/app/javascript/dashboard/store/modules/conversationWatchers.js @@ -0,0 +1,90 @@ +import Vue from 'vue'; +import types from '../mutation-types'; +import { throwErrorMessage } from 'dashboard/store/utils/api'; + +import ConversationInboxApi from '../../api/inbox/conversation'; + +const state = { + records: {}, + uiFlags: { + isFetching: false, + isUpdating: false, + }, +}; + +export const getters = { + getUIFlags($state) { + return $state.uiFlags; + }, + getByConversationId: _state => conversationId => { + return _state.records[conversationId]; + }, +}; + +export const actions = { + show: async ({ commit }, { conversationId }) => { + commit(types.SET_CONVERSATION_WATCHERS_UI_FLAG, { + isFetching: true, + }); + + try { + const response = await ConversationInboxApi.fetchParticipants( + conversationId + ); + commit(types.SET_CONVERSATION_WATCHERS, { + conversationId, + data: response.data, + }); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_CONVERSATION_WATCHERS_UI_FLAG, { + isFetching: false, + }); + } + }, + + update: async ({ commit }, { conversationId, userIds }) => { + commit(types.SET_CONVERSATION_WATCHERS_UI_FLAG, { + isUpdating: true, + }); + + try { + const response = await ConversationInboxApi.updateParticipants({ + conversationId, + userIds, + }); + commit(types.SET_CONVERSATION_WATCHERS, { + conversationId, + data: response.data, + }); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_CONVERSATION_WATCHERS_UI_FLAG, { + isUpdating: false, + }); + } + }, +}; + +export const mutations = { + [types.SET_CONVERSATION_WATCHERS_UI_FLAG]($state, data) { + $state.uiFlags = { + ...$state.uiFlags, + ...data, + }; + }, + + [types.SET_CONVERSATION_WATCHERS]($state, { data, conversationId }) { + Vue.set($state.records, conversationId, data); + }, +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations, +}; diff --git a/app/javascript/dashboard/store/modules/specs/conversationWatchers/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversationWatchers/actions.spec.js new file mode 100644 index 000000000..115c505ce --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/conversationWatchers/actions.spec.js @@ -0,0 +1,62 @@ +import axios from 'axios'; +import { actions } from '../../conversationWatchers'; +import types from '../../../mutation-types'; + +const commit = jest.fn(); +global.axios = axios; +jest.mock('axios'); + +describe('#actions', () => { + describe('#get', () => { + it('sends correct actions if API is success', async () => { + axios.get.mockResolvedValue({ data: { id: 1 } }); + await actions.show({ commit }, { conversationId: 1 }); + expect(commit.mock.calls).toEqual([ + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isFetching: true }], + [ + types.SET_CONVERSATION_WATCHERS, + { conversationId: 1, data: { id: 1 } }, + ], + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isFetching: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.get.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.show({ commit }, { conversationId: 1 }) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isFetching: true }], + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isFetching: false }], + ]); + }); + }); + + describe('#update', () => { + it('sends correct actions if API is success', async () => { + axios.patch.mockResolvedValue({ data: [{ id: 2 }] }); + await actions.update( + { commit }, + { conversationId: 2, userIds: [{ id: 2 }] } + ); + expect(commit.mock.calls).toEqual([ + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isUpdating: true }], + [ + types.SET_CONVERSATION_WATCHERS, + { conversationId: 2, data: [{ id: 2 }] }, + ], + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isUpdating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.patch.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.update({ commit }, { conversationId: 1, content: 'hi' }) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isUpdating: true }], + [types.SET_CONVERSATION_WATCHERS_UI_FLAG, { isUpdating: false }], + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/conversationWatchers/fixtures.js b/app/javascript/dashboard/store/modules/specs/conversationWatchers/fixtures.js new file mode 100644 index 000000000..6b1ed4def --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/conversationWatchers/fixtures.js @@ -0,0 +1,14 @@ +export const data = [ + { + id: 1, + name: 'Uno', + }, + { + id: 2, + name: 'Dos', + }, + { + id: 3, + name: 'Tres', + }, +]; diff --git a/app/javascript/dashboard/store/modules/specs/conversationWatchers/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversationWatchers/getters.spec.js new file mode 100644 index 000000000..4bdcb3950 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/conversationWatchers/getters.spec.js @@ -0,0 +1,22 @@ +import { getters } from '../../conversationWatchers'; +import watchersData from './fixtures'; + +describe('#getters', () => { + it('getByConversationId', () => { + const state = { records: { 1: watchersData } }; + expect(getters.getByConversationId(state)(1)).toEqual(watchersData); + }); + + it('getUIFlags', () => { + const state = { + uiFlags: { + isFetching: false, + isUpdating: false, + }, + }; + expect(getters.getUIFlags(state)).toEqual({ + isFetching: false, + isUpdating: false, + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/conversationWatchers/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversationWatchers/mutations.spec.js new file mode 100644 index 000000000..781e8eaa8 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/conversationWatchers/mutations.spec.js @@ -0,0 +1,37 @@ +import types from '../../../mutation-types'; +import { mutations } from '../../conversationWatchers'; + +describe('#mutations', () => { + describe('#SET_CONVERSATION_WATCHERS', () => { + it('sets an individual record', () => { + let state = { + records: {}, + }; + + mutations[types.SET_CONVERSATION_WATCHERS](state, { + data: [], + conversationId: 1, + }); + expect(state.records).toEqual({ 1: [] }); + }); + }); + + describe('#SET_CONVERSATION_WATCHERS_UI_FLAG', () => { + it('set ui flags', () => { + let state = { + uiFlags: { + isFetching: true, + isUpdating: false, + }, + }; + + mutations[types.SET_CONVERSATION_WATCHERS_UI_FLAG](state, { + isFetching: false, + }); + expect(state.uiFlags).toEqual({ + isFetching: false, + isUpdating: false, + }); + }); + }); +}); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 0f801eac9..97801bb33 100755 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -258,4 +258,7 @@ export default { ADD_MACRO: 'ADD_MACRO', EDIT_MACRO: 'EDIT_MACRO', DELETE_MACRO: 'DELETE_MACRO', + + SET_CONVERSATION_WATCHERS_UI_FLAG: 'SET_CONVERSATION_WATCHERS_UI_FLAG', + SET_CONVERSATION_WATCHERS: 'SET_CONVERSATION_WATCHERS', };