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> | <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" /> |   <AgentMessage v-else :agent-name="agentName" :message="message.content" /> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="user-message"> |   <div class="user-message"> | ||||||
|     <div class="message-wrap"> |     <div class="message-wrap"> | ||||||
|       <UserMessageBubble :message="message" /> |       <UserMessageBubble :message="message" :status="status" /> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| @@ -15,8 +15,12 @@ export default { | |||||||
|     UserMessageBubble, |     UserMessageBubble, | ||||||
|   }, |   }, | ||||||
|   props: { |   props: { | ||||||
|     message: String, |  | ||||||
|     avatarUrl: String, |     avatarUrl: String, | ||||||
|  |     message: String, | ||||||
|  |     status: { | ||||||
|  |       type: String, | ||||||
|  |       default: '', | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
|   <div |   <div | ||||||
|     class="chat-bubble user" |     class="chat-bubble user" | ||||||
|     :style="{ background: widgetColor }" |     :style="{ background: backgroundColor }" | ||||||
|     v-html="formatMessage(message)" |     v-html="formatMessage(message)" | ||||||
|   ></div> |   ></div> | ||||||
| </template> | </template> | ||||||
| @@ -16,10 +16,17 @@ export default { | |||||||
|     ...mapGetters({ |     ...mapGetters({ | ||||||
|       widgetColor: 'appConfig/getWidgetColor', |       widgetColor: 'appConfig/getWidgetColor', | ||||||
|     }), |     }), | ||||||
|  |     backgroundColor() { | ||||||
|  |       return this.status !== 'in_progress' ? this.widgetColor : '#c0ccda'; | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   mixins: [messageFormatterMixin], |   mixins: [messageFormatterMixin], | ||||||
|   props: { |   props: { | ||||||
|     message: String, |     message: String, | ||||||
|  |     status: { | ||||||
|  |       type: String, | ||||||
|  |       default: '', | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </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 */ | /* eslint-disable no-param-reassign */ | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { sendMessageAPI, getConversationAPI } from 'widget/api/conversation'; | 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'; | export const DEFAULT_CONVERSATION = 'default'; | ||||||
| const state = { | const state = { | ||||||
| @@ -13,8 +31,9 @@ const getters = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const actions = { | const actions = { | ||||||
|   sendMessage: async (_, params) => { |   sendMessage: async ({ commit }, params) => { | ||||||
|     const { content } = params; |     const { content } = params; | ||||||
|  |     commit('pushMessageToConversations', createTemporaryMessage(content)); | ||||||
|     await sendMessageAPI(content); |     await sendMessageAPI(content); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
| @@ -38,9 +57,27 @@ const mutations = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   pushMessageToConversations($state, message) { |   pushMessageToConversations($state, message) { | ||||||
|     const { id } = message; |     const { id, status, message_type: type } = message; | ||||||
|     const messagesInbox = $state.conversations; |     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) { |   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; | process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   moduleDirectories: ['node_modules', 'app/javascript/app'], |   moduleDirectories: ['node_modules', 'app/javascript'], | ||||||
|   moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx', 'vue'], |   moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx'], | ||||||
|   automock: false, |   automock: false, | ||||||
|   resetMocks: true, |   resetMocks: true, | ||||||
|   transform: { |   transform: { | ||||||
|     '^.+\\.vue$': 'vue-jest', |     '^.+\\.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', |       'jest-transform-stub', | ||||||
|     '^.+\\.jsx?$': 'babel-jest', |     '^.+\\.(js|jsx)?$': 'babel-jest', | ||||||
|   }, |   }, | ||||||
|   cacheDirectory: '<rootDir>/.jest-cache', |   cacheDirectory: '<rootDir>/.jest-cache', | ||||||
|   collectCoverage: false, |   collectCoverage: false, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S