mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	Add an intermediate pending state for widget messages (#323)
* Add an intermediate pending state for widget messages * Remove unnecessary setTimeout * Rename method
This commit is contained in:
		| @@ -1,5 +1,9 @@ | ||||
| <template> | ||||
|   <UserMessage v-if="isUserMessage" :message="message.content" /> | ||||
|   <UserMessage | ||||
|     v-if="isUserMessage" | ||||
|     :message="message.content" | ||||
|     :status="message.status" | ||||
|   /> | ||||
|   <AgentMessage v-else :agent-name="agentName" :message="message.content" /> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
|   <div class="user-message"> | ||||
|     <div class="message-wrap"> | ||||
|       <UserMessageBubble :message="message" /> | ||||
|       <UserMessageBubble :message="message" :status="status" /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| @@ -15,8 +15,12 @@ export default { | ||||
|     UserMessageBubble, | ||||
|   }, | ||||
|   props: { | ||||
|     message: String, | ||||
|     avatarUrl: String, | ||||
|     message: String, | ||||
|     status: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
|   <div | ||||
|     class="chat-bubble user" | ||||
|     :style="{ background: widgetColor }" | ||||
|     :style="{ background: backgroundColor }" | ||||
|     v-html="formatMessage(message)" | ||||
|   ></div> | ||||
| </template> | ||||
| @@ -16,10 +16,17 @@ export default { | ||||
|     ...mapGetters({ | ||||
|       widgetColor: 'appConfig/getWidgetColor', | ||||
|     }), | ||||
|     backgroundColor() { | ||||
|       return this.status !== 'in_progress' ? this.widgetColor : '#c0ccda'; | ||||
|     }, | ||||
|   }, | ||||
|   mixins: [messageFormatterMixin], | ||||
|   props: { | ||||
|     message: String, | ||||
|     status: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										10
									
								
								app/javascript/widget/helpers/uuid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/javascript/widget/helpers/uuid.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| const getUuid = () => | ||||
|   'xxxxxxxx4xxx'.replace(/[xy]/g, c => { | ||||
|     // eslint-disable-next-line | ||||
|     const r = (Math.random() * 16) | 0; | ||||
|     // eslint-disable-next-line | ||||
|     const v = c === 'x' ? r : (r & 0x3) | 0x8; | ||||
|     return v.toString(16); | ||||
|   }); | ||||
|  | ||||
| export default getUuid; | ||||
| @@ -1,6 +1,24 @@ | ||||
| /* eslint-disable no-param-reassign */ | ||||
| import Vue from 'vue'; | ||||
| import { sendMessageAPI, getConversationAPI } from 'widget/api/conversation'; | ||||
| import { MESSAGE_TYPE } from 'widget/helpers/constants'; | ||||
| import getUuid from '../../helpers/uuid'; | ||||
|  | ||||
| export const createTemporaryMessage = content => { | ||||
|   const timestamp = new Date().getTime(); | ||||
|   return { | ||||
|     id: getUuid(), | ||||
|     content, | ||||
|     status: 'in_progress', | ||||
|     created_at: timestamp, | ||||
|     message_type: MESSAGE_TYPE.INCOMING, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const findUndeliveredMessage = (messageInbox, { content }) => | ||||
|   Object.values(messageInbox).filter( | ||||
|     message => message.content === content && message.status === 'in_progress' | ||||
|   ); | ||||
|  | ||||
| export const DEFAULT_CONVERSATION = 'default'; | ||||
| const state = { | ||||
| @@ -13,8 +31,9 @@ const getters = { | ||||
| }; | ||||
|  | ||||
| const actions = { | ||||
|   sendMessage: async (_, params) => { | ||||
|   sendMessage: async ({ commit }, params) => { | ||||
|     const { content } = params; | ||||
|     commit('pushMessageToConversations', createTemporaryMessage(content)); | ||||
|     await sendMessageAPI(content); | ||||
|   }, | ||||
|  | ||||
| @@ -38,9 +57,27 @@ const mutations = { | ||||
|   }, | ||||
|  | ||||
|   pushMessageToConversations($state, message) { | ||||
|     const { id } = message; | ||||
|     const { id, status, message_type: type } = message; | ||||
|     const messagesInbox = $state.conversations; | ||||
|     Vue.set(messagesInbox, id, message); | ||||
|     const isMessageIncoming = type === MESSAGE_TYPE.INCOMING; | ||||
|     const isTemporaryMessage = status === 'in_progress'; | ||||
|  | ||||
|     if (!isMessageIncoming || isTemporaryMessage) { | ||||
|       Vue.set(messagesInbox, id, message); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const [messageInConversation] = findUndeliveredMessage( | ||||
|       messagesInbox, | ||||
|       message | ||||
|     ); | ||||
|  | ||||
|     if (!messageInConversation) { | ||||
|       Vue.set(messagesInbox, id, message); | ||||
|     } else { | ||||
|       Vue.delete(messagesInbox, messageInConversation.id); | ||||
|       Vue.set(messagesInbox, id, message); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   initMessagesInConversation(_state, payload) { | ||||
|   | ||||
| @@ -0,0 +1,37 @@ | ||||
| import { | ||||
|   findUndeliveredMessage, | ||||
|   createTemporaryMessage, | ||||
| } from '../conversation'; | ||||
|  | ||||
| describe('#findUndeliveredMessage', () => { | ||||
|   it('returns message objects if exist', () => { | ||||
|     const conversation = { | ||||
|       1: { | ||||
|         id: 1, | ||||
|         content: 'Hello', | ||||
|         status: 'in_progress', | ||||
|       }, | ||||
|       2: { | ||||
|         id: 2, | ||||
|         content: 'Hello', | ||||
|         status: 'sent', | ||||
|       }, | ||||
|       3: { | ||||
|         id: 3, | ||||
|         content: 'How may I help you', | ||||
|         status: 'sent', | ||||
|       }, | ||||
|     }; | ||||
|     expect( | ||||
|       findUndeliveredMessage(conversation, { content: 'Hello' }) | ||||
|     ).toStrictEqual([{ id: 1, content: 'Hello', status: 'in_progress' }]); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| describe('#createTemporaryMessage', () => { | ||||
|   it('returns message object', () => { | ||||
|     const message = createTemporaryMessage('hello'); | ||||
|     expect(message.content).toBe('hello'); | ||||
|     expect(message.status).toBe('in_progress'); | ||||
|   }); | ||||
| }); | ||||
| @@ -2,15 +2,15 @@ process.env.VUE_CLI_BABEL_TARGET_NODE = true; | ||||
| process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true; | ||||
|  | ||||
| module.exports = { | ||||
|   moduleDirectories: ['node_modules', 'app/javascript/app'], | ||||
|   moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx', 'vue'], | ||||
|   moduleDirectories: ['node_modules', 'app/javascript'], | ||||
|   moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx'], | ||||
|   automock: false, | ||||
|   resetMocks: true, | ||||
|   transform: { | ||||
|     '^.+\\.vue$': 'vue-jest', | ||||
|     '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2|svg)$': | ||||
|     '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': | ||||
|       'jest-transform-stub', | ||||
|     '^.+\\.jsx?$': 'babel-jest', | ||||
|     '^.+\\.(js|jsx)?$': 'babel-jest', | ||||
|   }, | ||||
|   cacheDirectory: '<rootDir>/.jest-cache', | ||||
|   collectCoverage: false, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S