mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	Feature: Customise widget for bot conversations (#834)
* Feature: Customise widget for bot conversations
This commit is contained in:
		| @@ -106,3 +106,6 @@ CHARGEBEE_WEBHOOK_PASSWORD= | |||||||
| ## generate a new key value here : https://d3v.one/vapid-key-generator/ | ## generate a new key value here : https://d3v.one/vapid-key-generator/ | ||||||
| # VAPID_PUBLIC_KEY= | # VAPID_PUBLIC_KEY= | ||||||
| # VAPID_PRIVATE_KEY= | # VAPID_PRIVATE_KEY= | ||||||
|  |  | ||||||
|  | ## Bot Customizations | ||||||
|  | USE_INBOX_AVATAR_FOR_BOT=true | ||||||
|   | |||||||
| @@ -3,6 +3,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController | |||||||
|   before_action :set_web_widget |   before_action :set_web_widget | ||||||
|   before_action :set_contact |   before_action :set_contact | ||||||
|  |  | ||||||
|  |   def index | ||||||
|  |     @conversation = conversation | ||||||
|  |   end | ||||||
|  |  | ||||||
|   def toggle_typing |   def toggle_typing | ||||||
|     head :ok && return if conversation.nil? |     head :ok && return if conversation.nil? | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ | |||||||
|         class="button block" |         class="button block" | ||||||
|         type="submit" |         type="submit" | ||||||
|         :disabled="!isFormValid" |         :disabled="!isFormValid" | ||||||
|  |         :style="{ background: widgetColor, borderColor: widgetColor }" | ||||||
|       > |       > | ||||||
|         {{ $t('COMPONENTS.FORM_BUBBLE.SUBMIT') }} |         {{ $t('COMPONENTS.FORM_BUBBLE.SUBMIT') }} | ||||||
|       </button> |       </button> | ||||||
| @@ -32,6 +33,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|  | import { mapGetters } from 'vuex'; | ||||||
| export default { | export default { | ||||||
|   props: { |   props: { | ||||||
|     items: { |     items: { | ||||||
| @@ -49,6 +51,9 @@ export default { | |||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  |     ...mapGetters({ | ||||||
|  |       widgetColor: 'appConfig/getWidgetColor', | ||||||
|  |     }), | ||||||
|     isFormValid() { |     isFormValid() { | ||||||
|       return this.items.reduce((acc, { name }) => { |       return this.items.reduce((acc, { name }) => { | ||||||
|         return !!this.formValues[name] && acc; |         return !!this.formValues[name] && acc; | ||||||
|   | |||||||
| @@ -60,6 +60,8 @@ export default { | |||||||
|         this.$store.dispatch('contacts/update', message); |         this.$store.dispatch('contacts/update', message); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     this.$store.dispatch('conversationAttributes/get'); | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     ...mapActions('appConfig', ['setWidgetColor']), |     ...mapActions('appConfig', ['setWidgetColor']), | ||||||
|   | |||||||
| @@ -13,12 +13,16 @@ const sendAttachmentAPI = async attachment => { | |||||||
|   return result; |   return result; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const getConversationAPI = async ({ before }) => { | const getMessagesAPI = async ({ before }) => { | ||||||
|   const urlData = endPoints.getConversation({ before }); |   const urlData = endPoints.getConversation({ before }); | ||||||
|   const result = await API.get(urlData.url, { params: urlData.params }); |   const result = await API.get(urlData.url, { params: urlData.params }); | ||||||
|   return result; |   return result; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const getConversationAPI = async () => { | ||||||
|  |   return API.get(`/api/v1/widget/conversations${window.location.search}`); | ||||||
|  | }; | ||||||
|  |  | ||||||
| const toggleTyping = async ({ typingStatus }) => { | const toggleTyping = async ({ typingStatus }) => { | ||||||
|   return API.post( |   return API.post( | ||||||
|     `/api/v1/widget/conversations/toggle_typing${window.location.search}`, |     `/api/v1/widget/conversations/toggle_typing${window.location.search}`, | ||||||
| @@ -26,4 +30,10 @@ const toggleTyping = async ({ typingStatus }) => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export { sendMessageAPI, getConversationAPI, sendAttachmentAPI, toggleTyping }; | export { | ||||||
|  |   sendMessageAPI, | ||||||
|  |   getConversationAPI, | ||||||
|  |   getMessagesAPI, | ||||||
|  |   sendAttachmentAPI, | ||||||
|  |   toggleTyping, | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ import ImageBubble from 'widget/components/ImageBubble'; | |||||||
| import FileBubble from 'widget/components/FileBubble'; | import FileBubble from 'widget/components/FileBubble'; | ||||||
| import Thumbnail from 'dashboard/components/widgets/Thumbnail'; | import Thumbnail from 'dashboard/components/widgets/Thumbnail'; | ||||||
| import { MESSAGE_TYPE } from 'widget/helpers/constants'; | import { MESSAGE_TYPE } from 'widget/helpers/constants'; | ||||||
|  | import configMixin from '../mixins/configMixin'; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: 'AgentMessage', |   name: 'AgentMessage', | ||||||
| @@ -63,7 +64,7 @@ export default { | |||||||
|     UserMessage, |     UserMessage, | ||||||
|     FileBubble, |     FileBubble, | ||||||
|   }, |   }, | ||||||
|   mixins: [timeMixin], |   mixins: [timeMixin, configMixin], | ||||||
|   props: { |   props: { | ||||||
|     message: { |     message: { | ||||||
|       type: Object, |       type: Object, | ||||||
| @@ -112,11 +113,17 @@ export default { | |||||||
|     avatarUrl() { |     avatarUrl() { | ||||||
|       // eslint-disable-next-line |       // eslint-disable-next-line | ||||||
|       const BotImage = require('dashboard/assets/images/chatwoot_bot.png'); |       const BotImage = require('dashboard/assets/images/chatwoot_bot.png'); | ||||||
|  |       const displayImage = this.useInboxAvatarForBot | ||||||
|  |         ? this.inboxAvatarUrl | ||||||
|  |         : BotImage; | ||||||
|  |  | ||||||
|       if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) { |       if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) { | ||||||
|         return BotImage; |         return displayImage; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return this.message.sender ? this.message.sender.avatar_url : BotImage; |       return this.message.sender | ||||||
|  |         ? this.message.sender.avatar_url | ||||||
|  |         : displayImage; | ||||||
|     }, |     }, | ||||||
|     hasRecordedResponse() { |     hasRecordedResponse() { | ||||||
|       return ( |       return ( | ||||||
|   | |||||||
| @@ -8,9 +8,15 @@ class ActionCableConnector extends BaseActionCableConnector { | |||||||
|       'message.updated': this.onMessageUpdated, |       'message.updated': this.onMessageUpdated, | ||||||
|       'conversation.typing_on': this.onTypingOn, |       'conversation.typing_on': this.onTypingOn, | ||||||
|       'conversation.typing_off': this.onTypingOff, |       'conversation.typing_off': this.onTypingOff, | ||||||
|  |       'conversation.resolved': this.onStatusChange, | ||||||
|  |       'conversation.opened': this.onStatusChange, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   onStatusChange = data => { | ||||||
|  |     this.app.$store.dispatch('conversationAttributes/update', data); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   onMessageCreated = data => { |   onMessageCreated = data => { | ||||||
|     this.app.$store.dispatch('conversation/addMessage', data); |     this.app.$store.dispatch('conversation/addMessage', data); | ||||||
|   }; |   }; | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								app/javascript/widget/mixins/configMixin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/javascript/widget/mixins/configMixin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | export default { | ||||||
|  |   computed: { | ||||||
|  |     hideInputForBotConversations() { | ||||||
|  |       return window.chatwootWebChannel.hideInputForBotConversations; | ||||||
|  |     }, | ||||||
|  |     useInboxAvatarForBot() { | ||||||
|  |       return window.chatwootWidgetDefaults.useInboxAvatarForBot; | ||||||
|  |     }, | ||||||
|  |     hasAConnectedAgentBot() { | ||||||
|  |       return !!window.chatwootWebChannel.hasAConnectedAgentBot; | ||||||
|  |     }, | ||||||
|  |     inboxAvatarUrl() { | ||||||
|  |       return window.chatwootWebChannel.avatarUrl; | ||||||
|  |     }, | ||||||
|  |     channelConfig() { | ||||||
|  |       return window.chatwootWebChannel; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										35
									
								
								app/javascript/widget/mixins/specs/configMixin.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/javascript/widget/mixins/specs/configMixin.spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | import { createWrapper } from '@vue/test-utils'; | ||||||
|  | import configMixin from '../configMixin'; | ||||||
|  | import Vue from 'vue'; | ||||||
|  |  | ||||||
|  | global.chatwootWebChannel = { | ||||||
|  |   hideInputForBotConversations: true, | ||||||
|  |   avatarUrl: 'https://test.url', | ||||||
|  |   hasAConnectedAgentBot: 'AgentBot', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | global.chatwootWidgetDefaults = { | ||||||
|  |   useInboxAvatarForBot: true, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | describe('configMixin', () => { | ||||||
|  |   test('returns config', () => { | ||||||
|  |     const Component = { | ||||||
|  |       render() {}, | ||||||
|  |       title: 'TestComponent', | ||||||
|  |       mixins: [configMixin], | ||||||
|  |     }; | ||||||
|  |     const Constructor = Vue.extend(Component); | ||||||
|  |     const vm = new Constructor().$mount(); | ||||||
|  |     const wrapper = createWrapper(vm); | ||||||
|  |     expect(wrapper.vm.hideInputForBotConversations).toBe(true); | ||||||
|  |     expect(wrapper.vm.hasAConnectedAgentBot).toBe(true); | ||||||
|  |     expect(wrapper.vm.useInboxAvatarForBot).toBe(true); | ||||||
|  |     expect(wrapper.vm.inboxAvatarUrl).toBe('https://test.url'); | ||||||
|  |     expect(wrapper.vm.channelConfig).toEqual({ | ||||||
|  |       hideInputForBotConversations: true, | ||||||
|  |       avatarUrl: 'https://test.url', | ||||||
|  |       hasAConnectedAgentBot: 'AgentBot', | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -4,6 +4,7 @@ import agent from 'widget/store/modules/agent'; | |||||||
| import appConfig from 'widget/store/modules/appConfig'; | import appConfig from 'widget/store/modules/appConfig'; | ||||||
| import contacts from 'widget/store/modules/contacts'; | import contacts from 'widget/store/modules/contacts'; | ||||||
| import conversation from 'widget/store/modules/conversation'; | import conversation from 'widget/store/modules/conversation'; | ||||||
|  | import conversationAttributes from 'widget/store/modules/conversationAttributes'; | ||||||
| import conversationLabels from 'widget/store/modules/conversationLabels'; | import conversationLabels from 'widget/store/modules/conversationLabels'; | ||||||
| import events from 'widget/store/modules/events'; | import events from 'widget/store/modules/events'; | ||||||
| import message from 'widget/store/modules/message'; | import message from 'widget/store/modules/message'; | ||||||
| @@ -16,6 +17,7 @@ export default new Vuex.Store({ | |||||||
|     appConfig, |     appConfig, | ||||||
|     contacts, |     contacts, | ||||||
|     conversation, |     conversation, | ||||||
|  |     conversationAttributes, | ||||||
|     conversationLabels, |     conversationLabels, | ||||||
|     events, |     events, | ||||||
|     message, |     message, | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { | import { | ||||||
|   sendMessageAPI, |   sendMessageAPI, | ||||||
|   getConversationAPI, |   getMessagesAPI, | ||||||
|   sendAttachmentAPI, |   sendAttachmentAPI, | ||||||
|   toggleTyping, |   toggleTyping, | ||||||
| } from 'widget/api/conversation'; | } from 'widget/api/conversation'; | ||||||
| @@ -116,7 +116,7 @@ export const actions = { | |||||||
|   fetchOldConversations: async ({ commit }, { before } = {}) => { |   fetchOldConversations: async ({ commit }, { before } = {}) => { | ||||||
|     try { |     try { | ||||||
|       commit('setConversationListLoading', true); |       commit('setConversationListLoading', true); | ||||||
|       const { data } = await getConversationAPI({ before }); |       const { data } = await getMessagesAPI({ before }); | ||||||
|       commit('setMessagesInConversation', data); |       commit('setMessagesInConversation', data); | ||||||
|       commit('setConversationListLoading', false); |       commit('setConversationListLoading', false); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|   | |||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | import { | ||||||
|  |   SET_CONVERSATION_ATTRIBUTES, | ||||||
|  |   UPDATE_CONVERSATION_ATTRIBUTES, | ||||||
|  | } from '../types'; | ||||||
|  | import { getConversationAPI } from '../../api/conversation'; | ||||||
|  |  | ||||||
|  | const state = { | ||||||
|  |   id: '', | ||||||
|  |   status: '', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const getters = { | ||||||
|  |   getConversationParams: $state => $state, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const actions = { | ||||||
|  |   get: async ({ commit }) => { | ||||||
|  |     try { | ||||||
|  |       const { data } = await getConversationAPI(); | ||||||
|  |       commit(SET_CONVERSATION_ATTRIBUTES, data); | ||||||
|  |     } catch (error) { | ||||||
|  |       // Ignore error | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   update({ commit }, data) { | ||||||
|  |     commit(UPDATE_CONVERSATION_ATTRIBUTES, data); | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const mutations = { | ||||||
|  |   [SET_CONVERSATION_ATTRIBUTES]($state, data) { | ||||||
|  |     $state.id = data.id; | ||||||
|  |     $state.status = data.status; | ||||||
|  |   }, | ||||||
|  |   [UPDATE_CONVERSATION_ATTRIBUTES]($state, data) { | ||||||
|  |     if (data.id === $state.id) { | ||||||
|  |       $state.id = data.id; | ||||||
|  |       $state.status = data.status; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   namespaced: true, | ||||||
|  |   state, | ||||||
|  |   getters, | ||||||
|  |   actions, | ||||||
|  |   mutations, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | import { actions } from '../../conversationAttributes'; | ||||||
|  | import { API } from 'widget/helpers/axios'; | ||||||
|  |  | ||||||
|  | const commit = jest.fn(); | ||||||
|  | jest.mock('widget/helpers/axios'); | ||||||
|  |  | ||||||
|  | describe('#actions', () => { | ||||||
|  |   describe('#update', () => { | ||||||
|  |     it('sends mutation if api is success', async () => { | ||||||
|  |       API.get.mockResolvedValue({ data: { id: 1, status: 'bot' } }); | ||||||
|  |       await actions.get({ commit }); | ||||||
|  |       expect(commit.mock.calls).toEqual([ | ||||||
|  |         ['SET_CONVERSATION_ATTRIBUTES', { id: 1, status: 'bot' }], | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |     it('doesnot send mutation if api is error', async () => { | ||||||
|  |       API.get.mockRejectedValue({ message: 'Invalid Headers' }); | ||||||
|  |       await actions.get({ commit }); | ||||||
|  |       expect(commit.mock.calls).toEqual([]); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   describe('#update', () => { | ||||||
|  |     it('sends correct mutations', () => { | ||||||
|  |       actions.update({ commit }, { id: 1, status: 'bot' }); | ||||||
|  |       expect(commit).toBeCalledWith('UPDATE_CONVERSATION_ATTRIBUTES', { | ||||||
|  |         id: 1, | ||||||
|  |         status: 'bot', | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | import { getters } from '../../conversationAttributes'; | ||||||
|  |  | ||||||
|  | describe('#getters', () => { | ||||||
|  |   it('getConversationParams', () => { | ||||||
|  |     const state = { | ||||||
|  |       id: 1, | ||||||
|  |       status: 'bot', | ||||||
|  |     }; | ||||||
|  |     expect(getters.getConversationParams(state)).toEqual({ | ||||||
|  |       id: 1, | ||||||
|  |       status: 'bot', | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | import { mutations } from '../../conversationAttributes'; | ||||||
|  |  | ||||||
|  | describe('#mutations', () => { | ||||||
|  |   describe('#SET_CONVERSATION_ATTRIBUTES', () => { | ||||||
|  |     it('set status of the conversation', () => { | ||||||
|  |       const state = { id: '', status: '' }; | ||||||
|  |       mutations.SET_CONVERSATION_ATTRIBUTES(state, { | ||||||
|  |         id: 1, | ||||||
|  |         status: 'open', | ||||||
|  |       }); | ||||||
|  |       expect(state).toEqual({ id: 1, status: 'open' }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   describe('#UPDATE_CONVERSATION_ATTRIBUTES', () => { | ||||||
|  |     it('update status if it is same conversation', () => { | ||||||
|  |       const state = { id: 1, status: 'bot' }; | ||||||
|  |       mutations.UPDATE_CONVERSATION_ATTRIBUTES(state, { | ||||||
|  |         id: 1, | ||||||
|  |         status: 'open', | ||||||
|  |       }); | ||||||
|  |       expect(state).toEqual({ id: 1, status: 'open' }); | ||||||
|  |     }); | ||||||
|  |     it('doesnot update status if it is not the same conversation', () => { | ||||||
|  |       const state = { id: 1, status: 'bot' }; | ||||||
|  |       mutations.UPDATE_CONVERSATION_ATTRIBUTES(state, { | ||||||
|  |         id: 2, | ||||||
|  |         status: 'open', | ||||||
|  |       }); | ||||||
|  |       expect(state).toEqual({ id: 1, status: 'bot' }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1 +1,3 @@ | |||||||
| export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR'; | export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR'; | ||||||
|  | export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES'; | ||||||
|  | export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES'; | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   <div class="home"> |   <div class="home"> | ||||||
|     <div class="header-wrap"> |     <div class="header-wrap"> | ||||||
|       <ChatHeaderExpanded |       <ChatHeaderExpanded | ||||||
|         v-if="isHeaderExpanded" |         v-if="isHeaderExpanded && !hideWelcomeHeader" | ||||||
|         :intro-heading="introHeading" |         :intro-heading="introHeading" | ||||||
|         :intro-body="introBody" |         :intro-body="introBody" | ||||||
|         :avatar-url="channelConfig.avatarUrl" |         :avatar-url="channelConfig.avatarUrl" | ||||||
| @@ -16,7 +16,7 @@ | |||||||
|     <AvailableAgents v-if="showAvailableAgents" :agents="availableAgents" /> |     <AvailableAgents v-if="showAvailableAgents" :agents="availableAgents" /> | ||||||
|     <ConversationWrap :grouped-messages="groupedMessages" /> |     <ConversationWrap :grouped-messages="groupedMessages" /> | ||||||
|     <div class="footer-wrap"> |     <div class="footer-wrap"> | ||||||
|       <div class="input-wrap"> |       <div v-if="showInputTextArea" class="input-wrap"> | ||||||
|         <ChatFooter /> |         <ChatFooter /> | ||||||
|       </div> |       </div> | ||||||
|       <branding></branding> |       <branding></branding> | ||||||
| @@ -33,6 +33,7 @@ import ChatHeaderExpanded from 'widget/components/ChatHeaderExpanded.vue'; | |||||||
| import ChatHeader from 'widget/components/ChatHeader.vue'; | import ChatHeader from 'widget/components/ChatHeader.vue'; | ||||||
| import ConversationWrap from 'widget/components/ConversationWrap.vue'; | import ConversationWrap from 'widget/components/ConversationWrap.vue'; | ||||||
| import AvailableAgents from 'widget/components/AvailableAgents.vue'; | import AvailableAgents from 'widget/components/AvailableAgents.vue'; | ||||||
|  | import configMixin from '../mixins/configMixin'; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: 'Home', |   name: 'Home', | ||||||
| @@ -44,30 +45,41 @@ export default { | |||||||
|     Branding, |     Branding, | ||||||
|     AvailableAgents, |     AvailableAgents, | ||||||
|   }, |   }, | ||||||
|  |   mixins: [configMixin], | ||||||
|   computed: { |   computed: { | ||||||
|     ...mapGetters({ |     ...mapGetters({ | ||||||
|       groupedMessages: 'conversation/getGroupedConversation', |       groupedMessages: 'conversation/getGroupedConversation', | ||||||
|       conversationSize: 'conversation/getConversationSize', |       conversationSize: 'conversation/getConversationSize', | ||||||
|       availableAgents: 'agent/availableAgents', |       availableAgents: 'agent/availableAgents', | ||||||
|       hasFetched: 'agent/uiFlags/hasFetched', |       hasFetched: 'agent/uiFlags/hasFetched', | ||||||
|  |       conversationAttributes: 'conversationAttributes/getConversationParams', | ||||||
|     }), |     }), | ||||||
|  |     isOpen() { | ||||||
|  |       return this.conversationAttributes.status === 'open'; | ||||||
|  |     }, | ||||||
|  |     showInputTextArea() { | ||||||
|  |       if (this.hideInputForBotConversations) { | ||||||
|  |         if (this.isOpen) { | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       return true; | ||||||
|  |     }, | ||||||
|     isHeaderExpanded() { |     isHeaderExpanded() { | ||||||
|       return this.conversationSize === 0; |       return this.conversationSize === 0; | ||||||
|     }, |     }, | ||||||
|     channelConfig() { |  | ||||||
|       return window.chatwootWebChannel; |  | ||||||
|     }, |  | ||||||
|     showAvailableAgents() { |     showAvailableAgents() { | ||||||
|       return this.availableAgents.length > 0 && this.conversationSize < 1; |       return this.availableAgents.length > 0 && this.conversationSize < 1; | ||||||
|     }, |     }, | ||||||
|     introHeading() { |     introHeading() { | ||||||
|       return this.channelConfig.welcomeTitle || 'Hi there ! 🙌🏼'; |       return this.channelConfig.welcomeTitle; | ||||||
|     }, |     }, | ||||||
|     introBody() { |     introBody() { | ||||||
|       return ( |       return this.channelConfig.welcomeTagline; | ||||||
|         this.channelConfig.welcomeTagline || |     }, | ||||||
|         'We make it simple to connect with us. Ask us anything, or share your feedback.' |     hideWelcomeHeader() { | ||||||
|       ); |       return !(this.introHeading || this.introBody); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -32,14 +32,16 @@ class ActionCableListener < BaseListener | |||||||
|  |  | ||||||
|   def conversation_resolved(event) |   def conversation_resolved(event) | ||||||
|     conversation, account, timestamp = extract_conversation_and_account(event) |     conversation, account, timestamp = extract_conversation_and_account(event) | ||||||
|  |     tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token] | ||||||
|  |  | ||||||
|     broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_RESOLVED, conversation.push_event_data) |     broadcast(tokens, CONVERSATION_RESOLVED, conversation.push_event_data) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def conversation_opened(event) |   def conversation_opened(event) | ||||||
|     conversation, account, timestamp = extract_conversation_and_account(event) |     conversation, account, timestamp = extract_conversation_and_account(event) | ||||||
|  |     tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token] | ||||||
|  |  | ||||||
|     broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_OPENED, conversation.push_event_data) |     broadcast(tokens, CONVERSATION_OPENED, conversation.push_event_data) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def conversation_lock_toggle(event) |   def conversation_lock_toggle(event) | ||||||
|   | |||||||
| @@ -2,12 +2,13 @@ | |||||||
| # | # | ||||||
| # Table name: agent_bots | # Table name: agent_bots | ||||||
| # | # | ||||||
| #  id           :bigint           not null, primary key | #  id                               :bigint           not null, primary key | ||||||
| #  description  :string | #  description                      :string | ||||||
| #  name         :string | #  hide_input_for_bot_conversations :boolean          default(FALSE) | ||||||
| #  outgoing_url :string | #  name                             :string | ||||||
| #  created_at   :datetime         not null | #  outgoing_url                     :string | ||||||
| #  updated_at   :datetime         not null | #  created_at                       :datetime         not null | ||||||
|  | #  updated_at                       :datetime         not null | ||||||
| # | # | ||||||
|  |  | ||||||
| class AgentBot < ApplicationRecord | class AgentBot < ApplicationRecord | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | if @conversation | ||||||
|  |   json.id @conversation.display_id | ||||||
|  |   json.inbox_id @conversation.inbox_id | ||||||
|  |   json.status @conversation.status | ||||||
|  | end | ||||||
| @@ -7,6 +7,8 @@ | |||||||
|     <script> |     <script> | ||||||
|       window.chatwootWebChannel = { |       window.chatwootWebChannel = { | ||||||
|         avatarUrl: '<%= @web_widget.inbox.avatar_url %>', |         avatarUrl: '<%= @web_widget.inbox.avatar_url %>', | ||||||
|  |         hasAConnectedAgentBot: '<%= @web_widget.inbox.agent_bot&.name %>', | ||||||
|  |         hideInputForBotConversations: <%= ActiveModel::Type::Boolean.new.cast(@web_widget.inbox.agent_bot&.hide_input_for_bot_conversations) %>, | ||||||
|         locale: '<%= @web_widget.account.locale %>', |         locale: '<%= @web_widget.account.locale %>', | ||||||
|         websiteName: '<%= @web_widget.inbox.name %>', |         websiteName: '<%= @web_widget.inbox.name %>', | ||||||
|         websiteToken: '<%= @web_widget.website_token %>', |         websiteToken: '<%= @web_widget.website_token %>', | ||||||
| @@ -14,6 +16,9 @@ | |||||||
|         welcomeTitle: '<%= @web_widget.welcome_title %>', |         welcomeTitle: '<%= @web_widget.welcome_title %>', | ||||||
|         widgetColor: '<%= @web_widget.widget_color %>', |         widgetColor: '<%= @web_widget.widget_color %>', | ||||||
|       } |       } | ||||||
|  |       window.chatwootWidgetDefaults = { | ||||||
|  |         useInboxAvatarForBot: <%= ActiveModel::Type::Boolean.new.cast(ENV.fetch('USE_INBOX_AVATAR_FOR_BOT', false)) %>, | ||||||
|  |       } | ||||||
|       window.chatwootPubsubToken = '<%= @contact.pubsub_token %>' |       window.chatwootPubsubToken = '<%= @contact.pubsub_token %>' | ||||||
|       window.authToken = '<%= @token %>' |       window.authToken = '<%= @token %>' | ||||||
|     </script> |     </script> | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | class AddHideInputFlagToBotConfig < ActiveRecord::Migration[6.0] | ||||||
|  |   def change | ||||||
|  |     add_column :agent_bots, :hide_input_for_bot_conversations, :boolean, default: false | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -10,7 +10,7 @@ | |||||||
| # | # | ||||||
| # It's strongly recommended that you check this file into your version control system. | # It's strongly recommended that you check this file into your version control system. | ||||||
|  |  | ||||||
| ActiveRecord::Schema.define(version: 2020_05_04_144712) do | ActiveRecord::Schema.define(version: 2020_05_09_044639) do | ||||||
|  |  | ||||||
|   # These are extensions that must be enabled in order to support this database |   # These are extensions that must be enabled in order to support this database | ||||||
|   enable_extension "pgcrypto" |   enable_extension "pgcrypto" | ||||||
| @@ -94,6 +94,7 @@ ActiveRecord::Schema.define(version: 2020_05_04_144712) do | |||||||
|     t.string "outgoing_url" |     t.string "outgoing_url" | ||||||
|     t.datetime "created_at", precision: 6, null: false |     t.datetime "created_at", precision: 6, null: false | ||||||
|     t.datetime "updated_at", precision: 6, null: false |     t.datetime "updated_at", precision: 6, null: false | ||||||
|  |     t.boolean "hide_input_for_bot_conversations", default: false | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   create_table "attachments", id: :serial, force: :cascade do |t| |   create_table "attachments", id: :serial, force: :cascade do |t| | ||||||
|   | |||||||
| @@ -24,4 +24,22 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do | |||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   describe 'POST /api/v1/widget/conversations' do | ||||||
|  |     context 'with a conversation' do | ||||||
|  |       it 'returns the correct conversation params' do | ||||||
|  |         allow(Rails.configuration.dispatcher).to receive(:dispatch) | ||||||
|  |         get '/api/v1/widget/conversations', | ||||||
|  |             headers: { 'X-Auth-Token' => token }, | ||||||
|  |             params: { website_token: web_widget.website_token }, | ||||||
|  |             as: :json | ||||||
|  |  | ||||||
|  |         expect(response).to have_http_status(:success) | ||||||
|  |         json_response = JSON.parse(response.body) | ||||||
|  |  | ||||||
|  |         expect(json_response['id']).to eq(conversation.display_id) | ||||||
|  |         expect(json_response['status']).to eq(conversation.status) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S