mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	chore: Allow admins to choose the agent bot from the UI (#5895)
This commit is contained in:
		| @@ -13,6 +13,16 @@ class Inboxes extends ApiClient { | ||||
|   deleteInboxAvatar(inboxId) { | ||||
|     return axios.delete(`${this.url}/${inboxId}/avatar`); | ||||
|   } | ||||
|  | ||||
|   getAgentBot(inboxId) { | ||||
|     return axios.get(`${this.url}/${inboxId}/agent_bot`); | ||||
|   } | ||||
|  | ||||
|   setAgentBot(inboxId, botId) { | ||||
|     return axios.post(`${this.url}/${inboxId}/set_agent_bot`, { | ||||
|       agent_bot: botId, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default new Inboxes(); | ||||
|   | ||||
| @@ -11,6 +11,8 @@ describe('#InboxesAPI', () => { | ||||
|     expect(inboxesAPI).toHaveProperty('update'); | ||||
|     expect(inboxesAPI).toHaveProperty('delete'); | ||||
|     expect(inboxesAPI).toHaveProperty('getCampaigns'); | ||||
|     expect(inboxesAPI).toHaveProperty('getAgentBot'); | ||||
|     expect(inboxesAPI).toHaveProperty('setAgentBot'); | ||||
|   }); | ||||
|   describeWithAPIMock('API calls', context => { | ||||
|     it('#getCampaigns', () => { | ||||
|   | ||||
| @@ -20,6 +20,14 @@ | ||||
|       }, | ||||
|       "SUBMIT": "Validate and save" | ||||
|     }, | ||||
|     "BOT_CONFIGURATION": { | ||||
|       "TITLE": "Select an agent bot", | ||||
|       "DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.", | ||||
|       "SUBMIT": "Update", | ||||
|       "SUCCESS_MESSAGE": "Successfully updated the agent bot", | ||||
|       "ERROR_MESSAGE": "Could not update the agent bot, please try again later", | ||||
|       "SELECT_PLACEHOLDER": "Select Bot" | ||||
|     }, | ||||
|     "ADD": { | ||||
|       "TITLE": "Configure new bot", | ||||
|       "CANCEL_BUTTON_TEXT": "Cancel", | ||||
|   | ||||
| @@ -416,7 +416,8 @@ | ||||
|       "CAMPAIGN": "Campaigns", | ||||
|       "PRE_CHAT_FORM": "Pre Chat Form", | ||||
|       "BUSINESS_HOURS": "Business Hours", | ||||
|       "WIDGET_BUILDER": "Widget Builder" | ||||
|       "WIDGET_BUILDER": "Widget Builder", | ||||
|       "BOT_CONFIGURATION": "Bot Configuration" | ||||
|     }, | ||||
|     "SETTINGS": "Settings", | ||||
|     "FEATURES": { | ||||
|   | ||||
| @@ -333,6 +333,9 @@ | ||||
|     <div v-if="selectedTabKey === 'widgetBuilder'"> | ||||
|       <widget-builder :inbox="inbox" /> | ||||
|     </div> | ||||
|     <div v-if="selectedTabKey === 'botConfiguration'"> | ||||
|       <bot-configuration :inbox="inbox" /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @@ -351,17 +354,20 @@ import GreetingsEditor from 'shared/components/GreetingsEditor'; | ||||
| import ConfigurationPage from './settingsPage/ConfigurationPage'; | ||||
| import CollaboratorsPage from './settingsPage/CollaboratorsPage'; | ||||
| import WidgetBuilder from './WidgetBuilder'; | ||||
| import BotConfiguration from './components/BotConfiguration'; | ||||
| import { FEATURE_FLAGS } from '../../../../featureFlags'; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     BotConfiguration, | ||||
|     CollaboratorsPage, | ||||
|     ConfigurationPage, | ||||
|     FacebookReauthorize, | ||||
|     GreetingsEditor, | ||||
|     PreChatFormSettings, | ||||
|     SettingIntroBanner, | ||||
|     SettingsSection, | ||||
|     FacebookReauthorize, | ||||
|     PreChatFormSettings, | ||||
|     WeeklyAvailability, | ||||
|     GreetingsEditor, | ||||
|     ConfigurationPage, | ||||
|     CollaboratorsPage, | ||||
|     WidgetBuilder, | ||||
|   }, | ||||
|   mixins: [alertMixin, configMixin, inboxMixin], | ||||
| @@ -388,6 +394,8 @@ export default { | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters({ | ||||
|       accountId: 'getCurrentAccountId', | ||||
|       isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount', | ||||
|       uiFlags: 'inboxes/getUIFlags', | ||||
|     }), | ||||
|     selectedTabKey() { | ||||
| @@ -406,7 +414,7 @@ export default { | ||||
|       return ''; | ||||
|     }, | ||||
|     tabs() { | ||||
|       const visibleToAllChannelTabs = [ | ||||
|       let visibleToAllChannelTabs = [ | ||||
|         { | ||||
|           key: 'inbox_settings', | ||||
|           name: this.$t('INBOX_MGMT.TABS.SETTINGS'), | ||||
| @@ -422,16 +430,12 @@ export default { | ||||
|       ]; | ||||
|  | ||||
|       if (this.isAWebWidgetInbox) { | ||||
|         return [ | ||||
|         visibleToAllChannelTabs = [ | ||||
|           ...visibleToAllChannelTabs, | ||||
|           { | ||||
|             key: 'preChatForm', | ||||
|             name: this.$t('INBOX_MGMT.TABS.PRE_CHAT_FORM'), | ||||
|           }, | ||||
|           { | ||||
|             key: 'configuration', | ||||
|             name: this.$t('INBOX_MGMT.TABS.CONFIGURATION'), | ||||
|           }, | ||||
|           { | ||||
|             key: 'widgetBuilder', | ||||
|             name: this.$t('INBOX_MGMT.TABS.WIDGET_BUILDER'), | ||||
| @@ -444,9 +448,10 @@ export default { | ||||
|         this.isALineChannel || | ||||
|         this.isAPIInbox || | ||||
|         this.isAnEmailChannel || | ||||
|         this.isAWhatsAppChannel | ||||
|         this.isAWhatsAppChannel || | ||||
|         this.isAWebWidgetInbox | ||||
|       ) { | ||||
|         return [ | ||||
|         visibleToAllChannelTabs = [ | ||||
|           ...visibleToAllChannelTabs, | ||||
|           { | ||||
|             key: 'configuration', | ||||
| @@ -455,6 +460,21 @@ export default { | ||||
|         ]; | ||||
|       } | ||||
|  | ||||
|       if ( | ||||
|         this.isFeatureEnabledonAccount( | ||||
|           this.accountId, | ||||
|           FEATURE_FLAGS.AGENT_BOTS | ||||
|         ) && | ||||
|         !(this.isAnEmailChannel || this.isATwitterInbox) | ||||
|       ) { | ||||
|         visibleToAllChannelTabs = [ | ||||
|           ...visibleToAllChannelTabs, | ||||
|           { | ||||
|             key: 'botConfiguration', | ||||
|             name: this.$t('INBOX_MGMT.TABS.BOT_CONFIGURATION'), | ||||
|           }, | ||||
|         ]; | ||||
|       } | ||||
|       return visibleToAllChannelTabs; | ||||
|     }, | ||||
|     currentInboxId() { | ||||
|   | ||||
| @@ -0,0 +1,91 @@ | ||||
| <template> | ||||
|   <div class="settings--content"> | ||||
|     <loading-state v-if="uiFlags.isFetching || uiFlags.isFetchingAgentBot" /> | ||||
|     <form v-else class="row" @submit.prevent="updateActiveAgentBot"> | ||||
|       <settings-section | ||||
|         :title="$t('AGENT_BOTS.BOT_CONFIGURATION.TITLE')" | ||||
|         :sub-title="$t('AGENT_BOTS.BOT_CONFIGURATION.DESC')" | ||||
|       > | ||||
|         <div class="medium-7 columns"> | ||||
|           <label> | ||||
|             <select v-model="selectedAgentBotId"> | ||||
|               <option value="" disabled selected>{{ | ||||
|                 $t('AGENT_BOTS.BOT_CONFIGURATION.SELECT_PLACEHOLDER') | ||||
|               }}</option> | ||||
|               <option | ||||
|                 v-for="agentBot in agentBots" | ||||
|                 :key="agentBot.id" | ||||
|                 :value="agentBot.id" | ||||
|               > | ||||
|                 {{ agentBot.name }} | ||||
|               </option> | ||||
|             </select> | ||||
|           </label> | ||||
|           <woot-submit-button | ||||
|             :button-text="$t('AGENT_BOTS.BOT_CONFIGURATION.SUBMIT')" | ||||
|             :loading="uiFlags.isSettingAgentBot" | ||||
|           /> | ||||
|         </div> | ||||
|       </settings-section> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from 'vuex'; | ||||
| import SettingsSection from 'dashboard/components/SettingsSection'; | ||||
| import LoadingState from 'dashboard/components/widgets/LoadingState'; | ||||
| import alertMixin from 'shared/mixins/alertMixin'; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     LoadingState, | ||||
|     SettingsSection, | ||||
|   }, | ||||
|   mixins: [alertMixin], | ||||
|   props: { | ||||
|     inbox: { | ||||
|       type: Object, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       selectedAgentBotId: null, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters({ | ||||
|       agentBots: 'agentBots/getBots', | ||||
|       uiFlags: 'agentBots/getUIFlags', | ||||
|     }), | ||||
|     activeAgentBot() { | ||||
|       return this.$store.getters['agentBots/getActiveAgentBot'](this.inbox.id); | ||||
|     }, | ||||
|   }, | ||||
|   watch: { | ||||
|     activeAgentBot() { | ||||
|       this.selectedAgentBotId = this.activeAgentBot.id; | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.$store.dispatch('agentBots/get'); | ||||
|     this.$store.dispatch('agentBots/fetchAgentBotInbox', this.inbox.id); | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     async updateActiveAgentBot() { | ||||
|       try { | ||||
|         await this.$store.dispatch('agentBots/setAgentBotInbox', { | ||||
|           inboxId: this.inbox.id, | ||||
|           // Added this to make sure that empty values are not sent to the API | ||||
|           botId: this.selectedAgentBotId ? this.selectedAgentBotId : undefined, | ||||
|         }); | ||||
|         this.showAlert(this.$t('AGENT_BOTS.BOT_CONFIGURATION.SUCCESS_MESSAGE')); | ||||
|       } catch (error) { | ||||
|         this.showAlert(this.$t('AGENT_BOTS.BOT_CONFIGURATION.ERROR_MESSAGE')); | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @@ -1,6 +1,8 @@ | ||||
| import Vue from 'vue'; | ||||
| import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; | ||||
| import types from '../mutation-types'; | ||||
| import AgentBotsAPI from '../../api/agentBots'; | ||||
| import InboxesAPI from '../../api/inboxes'; | ||||
| import { throwErrorMessage } from '../utils/api'; | ||||
|  | ||||
| export const state = { | ||||
| @@ -11,7 +13,10 @@ export const state = { | ||||
|     isCreating: false, | ||||
|     isDeleting: false, | ||||
|     isUpdating: false, | ||||
|     isFetchingAgentBot: false, | ||||
|     isSettingAgentBot: false, | ||||
|   }, | ||||
|   agentBotInbox: {}, | ||||
| }; | ||||
|  | ||||
| export const getters = { | ||||
| @@ -25,6 +30,10 @@ export const getters = { | ||||
|     const [bot] = $state.records.filter(record => record.id === Number(botId)); | ||||
|     return bot || {}; | ||||
|   }, | ||||
|   getActiveAgentBot: $state => inboxId => { | ||||
|     const associatedAgentBotId = $state.agentBotInbox[Number(inboxId)]; | ||||
|     return getters.getBot($state)(associatedAgentBotId); | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export const actions = { | ||||
| @@ -85,6 +94,31 @@ export const actions = { | ||||
|       commit(types.SET_AGENT_BOT_UI_FLAG, { isFetchingItem: false }); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   fetchAgentBotInbox: async ({ commit }, inboxId) => { | ||||
|     commit(types.SET_AGENT_BOT_UI_FLAG, { isFetchingAgentBot: true }); | ||||
|     try { | ||||
|       const { data } = await InboxesAPI.getAgentBot(inboxId); | ||||
|       const { agent_bot: agentBot = {} } = data || {}; | ||||
|       commit(types.SET_AGENT_BOT_INBOX, { agentBotId: agentBot.id, inboxId }); | ||||
|     } catch (error) { | ||||
|       throwErrorMessage(error); | ||||
|     } finally { | ||||
|       commit(types.SET_AGENT_BOT_UI_FLAG, { isFetchingAgentBot: false }); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   setAgentBotInbox: async ({ commit }, { inboxId, botId }) => { | ||||
|     commit(types.SET_AGENT_BOT_UI_FLAG, { isSettingAgentBot: true }); | ||||
|     try { | ||||
|       await InboxesAPI.setAgentBot(inboxId, botId); | ||||
|       commit(types.SET_AGENT_BOT_INBOX, { agentBotId: botId, inboxId }); | ||||
|     } catch (error) { | ||||
|       throwErrorMessage(error); | ||||
|     } finally { | ||||
|       commit(types.SET_AGENT_BOT_UI_FLAG, { isSettingAgentBot: false }); | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export const mutations = { | ||||
| @@ -98,6 +132,9 @@ export const mutations = { | ||||
|   [types.SET_AGENT_BOTS]: MutationHelpers.set, | ||||
|   [types.EDIT_AGENT_BOT]: MutationHelpers.update, | ||||
|   [types.DELETE_AGENT_BOT]: MutationHelpers.destroy, | ||||
|   [types.SET_AGENT_BOT_INBOX]($state, { agentBotId, inboxId }) { | ||||
|     Vue.set($state.agentBotInbox, inboxId, agentBotId); | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default { | ||||
|   | ||||
| @@ -90,4 +90,46 @@ describe('#actions', () => { | ||||
|       ]); | ||||
|     }); | ||||
|   }); | ||||
|   describe('#setAgentBotInbox', () => { | ||||
|     it('sends correct actions if API is success', async () => { | ||||
|       axios.post.mockResolvedValue({ data: {} }); | ||||
|       await actions.setAgentBotInbox({ commit }, { inboxId: 2, botId: 3 }); | ||||
|       expect(commit.mock.calls).toEqual([ | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isSettingAgentBot: true }], | ||||
|         [types.SET_AGENT_BOT_INBOX, { inboxId: 2, agentBotId: 3 }], | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isSettingAgentBot: false }], | ||||
|       ]); | ||||
|     }); | ||||
|     it('sends correct actions if API is error', async () => { | ||||
|       axios.post.mockRejectedValue({ message: 'Incorrect header' }); | ||||
|       await expect( | ||||
|         actions.setAgentBotInbox({ commit }, { inboxId: 2, botId: 3 }) | ||||
|       ).rejects.toThrow(Error); | ||||
|       expect(commit.mock.calls).toEqual([ | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isSettingAgentBot: true }], | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isSettingAgentBot: false }], | ||||
|       ]); | ||||
|     }); | ||||
|   }); | ||||
|   describe('#fetchAgentBotInbox', () => { | ||||
|     it('sends correct actions if API is success', async () => { | ||||
|       axios.get.mockResolvedValue({ data: { agent_bot: { id: 3 } } }); | ||||
|       await actions.fetchAgentBotInbox({ commit }, 2); | ||||
|       expect(commit.mock.calls).toEqual([ | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isFetchingAgentBot: true }], | ||||
|         [types.SET_AGENT_BOT_INBOX, { inboxId: 2, agentBotId: 3 }], | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isFetchingAgentBot: false }], | ||||
|       ]); | ||||
|     }); | ||||
|     it('sends correct actions if API is error', async () => { | ||||
|       axios.get.mockRejectedValue({ message: 'Incorrect header' }); | ||||
|       await expect( | ||||
|         actions.fetchAgentBotInbox({ commit }, { inboxId: 2, agentBotId: 3 }) | ||||
|       ).rejects.toThrow(Error); | ||||
|       expect(commit.mock.calls).toEqual([ | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isFetchingAgentBot: true }], | ||||
|         [types.SET_AGENT_BOT_UI_FLAG, { isFetchingAgentBot: false }], | ||||
|       ]); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -41,4 +41,14 @@ describe('#mutations', () => { | ||||
|       expect(state.records).toEqual([agentBotRecords[0]]); | ||||
|     }); | ||||
|   }); | ||||
|   describe('#SET_AGENT_BOT_INBOX', () => { | ||||
|     it('set agent bot in the object', () => { | ||||
|       const state = { agentBotInbox: {} }; | ||||
|       mutations[types.SET_AGENT_BOT_INBOX](state, { | ||||
|         agentBotId: 2, | ||||
|         inboxId: 3, | ||||
|       }); | ||||
|       expect(state.agentBotInbox).toEqual({ 3: 2 }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -251,6 +251,7 @@ export default { | ||||
|   ADD_AGENT_BOT: 'ADD_AGENT_BOT', | ||||
|   EDIT_AGENT_BOT: 'EDIT_AGENT_BOT', | ||||
|   DELETE_AGENT_BOT: 'DELETE_AGENT_BOT', | ||||
|   SET_AGENT_BOT_INBOX: 'SET_AGENT_BOT_INBOX', | ||||
|  | ||||
|   // MACROS | ||||
|   SET_MACROS_UI_FLAG: 'SET_MACROS_UI_FLAG', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S