Chore: Message to support multiple attachments (#730)

* Changes for the message to have multiple attachments
* changed the message association to attachments from has_one to has_many
* changed all the references of this association in building and fetching to reflect this change

* Added number of attachments validation to the message model

* Modified the backend responses and endpoints to reflect multiple attachment support (#737)

* Changing the frontend components for multiple attachments
* changed the request structure to reflect the multiple attachment structures
* changed the message bubbles to support multiple attachments
* bugfix: agent side attachment was not showing because of a missing await
* broken message was shown because of the store filtering
* Added documentation for ImageMagick

* spec fixes

* refactored code to reflect more apt namings

* Added updated message listener for the dashboard (#727)
* Added the publishing for message updated event
* Implemented the listener for dashboard

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
Sony Mathew
2020-04-17 21:15:20 +05:30
committed by GitHub
parent 0817414957
commit 818c769bb7
31 changed files with 212 additions and 137 deletions

View File

@@ -8,7 +8,7 @@ const sendMessageAPI = async content => {
};
const sendAttachmentAPI = async attachment => {
const urlData = endPoints.sendAttachmnet(attachment);
const urlData = endPoints.sendAttachment(attachment);
const result = await API.post(urlData.url, urlData.params);
return result;
};

View File

@@ -9,14 +9,13 @@ const sendMessage = content => ({
},
});
const sendAttachmnet = ({ attachment }) => {
const sendAttachment = ({ attachment }) => {
const { refererURL = '' } = window;
const timestamp = new Date().toString();
const { file, file_type: fileType } = attachment;
const { file } = attachment;
const formData = new FormData();
formData.append('message[attachment][file]', file);
formData.append('message[attachment][file_type]', fileType);
formData.append('message[attachments][]', file, file.name);
formData.append('message[referer_url]', refererURL);
formData.append('message[timestamp]', timestamp);
return {
@@ -43,7 +42,7 @@ const getAvailableAgents = token => ({
export default {
sendMessage,
sendAttachmnet,
sendAttachment,
getConversation,
updateMessage,
getAvailableAgents,

View File

@@ -11,26 +11,26 @@
</div>
<div class="message-wrap">
<AgentMessageBubble
v-if="showTextBubble && shouldDisplayAgentMessage"
v-if="!hasAttachments && shouldDisplayAgentMessage"
:content-type="contentType"
:message-content-attributes="messageContentAttributes"
:message-id="message.id"
:message-type="messageType"
:message="message.content"
/>
<div v-if="hasAttachment" class="chat-bubble has-attachment agent">
<file-bubble
v-if="
message.attachment && message.attachment.file_type !== 'image'
"
:url="message.attachment.data_url"
/>
<image-bubble
v-else
:url="message.attachment.data_url"
:thumb="message.attachment.thumb_url"
:readable-time="readableTime"
/>
<div v-if="hasAttachments" class="chat-bubble has-attachment agent">
<div v-for="attachment in message.attachments" :key="attachment.id">
<file-bubble
v-if="attachment.file_type !== 'image'"
:url="attachment.data_url"
/>
<image-bubble
v-else
:url="attachment.data_url"
:thumb="attachment.thumb_url"
:readable-time="readableTime"
/>
</div>
</div>
<p v-if="message.showAvatar || hasRecordedResponse" class="agent-name">
{{ agentName }}
@@ -78,12 +78,10 @@ export default {
}
return true;
},
hasAttachment() {
return !!this.message.attachment;
},
showTextBubble() {
const { message } = this;
return !message.attachment;
hasAttachments() {
return !!(
this.message.attachments && this.message.attachments.length > 0
);
},
readableTime() {
const { created_at: createdAt = '' } = this.message;

View File

@@ -47,6 +47,7 @@ export default {
img {
width: 100%;
max-width: 250px;
}
.time {

View File

@@ -6,18 +6,20 @@
:message="message.content"
:status="message.status"
/>
<div v-if="hasAttachment" class="chat-bubble has-attachment user">
<file-bubble
v-if="message.attachment && message.attachment.file_type !== 'image'"
:url="message.attachment.data_url"
:is-in-progress="isInProgress"
/>
<image-bubble
v-else
:url="message.attachment.data_url"
:thumb="message.attachment.thumb_url"
:readable-time="readableTime"
/>
<div v-if="hasAttachments" class="chat-bubble has-attachment user">
<div v-for="attachment in message.attachments" :key="attachment.id">
<file-bubble
v-if="attachment.file_type !== 'image'"
:url="attachment.data_url"
:is-in-progress="isInProgress"
/>
<image-bubble
v-else
:url="attachment.data_url"
:thumb="attachment.thumb_url"
:readable-time="readableTime"
/>
</div>
</div>
</div>
</div>
@@ -48,8 +50,10 @@ export default {
const { status = '' } = this.message;
return status === 'in_progress';
},
hasAttachment() {
return !!this.message.attachment;
hasAttachments() {
return !!(
this.message.attachments && this.message.attachments.length > 0
);
},
showTextBubble() {
const { message } = this;

View File

@@ -5,12 +5,17 @@ class ActionCableConnector extends BaseActionCableConnector {
super(app, pubsubToken);
this.events = {
'message.created': this.onMessageCreated,
'message.updated': this.onMessageUpdated,
};
}
onMessageCreated = data => {
this.app.$store.dispatch('conversation/addMessage', data);
};
onMessageUpdated = data => {
this.app.$store.dispatch('conversation/updateMessage', data);
};
}
export const refreshActionCableConnector = pubsubToken => {

View File

@@ -12,12 +12,12 @@ import DateHelper from '../../../shared/helpers/DateHelper';
const groupBy = require('lodash.groupby');
export const createTemporaryMessage = ({ attachment, content }) => {
export const createTemporaryMessage = ({ attachments, content }) => {
const timestamp = new Date().getTime() / 1000;
return {
id: getUuid(),
content,
attachment,
attachments,
status: 'in_progress',
created_at: timestamp,
message_type: MESSAGE_TYPE.INCOMING,
@@ -97,11 +97,14 @@ export const actions = {
file_type: fileType,
status: 'in_progress',
};
const tempMessage = createTemporaryMessage({ attachment });
const tempMessage = createTemporaryMessage({ attachments: [attachment] });
commit('pushMessageToConversation', tempMessage);
try {
const { data } = await sendAttachmentAPI(params);
commit('setMessageStatus', { message: data, tempId: tempMessage.id });
commit('updateAttachmentMessageStatus', {
message: data,
tempId: tempMessage.id,
});
} catch (error) {
// Show error
}
@@ -125,6 +128,10 @@ export const actions = {
commit('pushMessageToConversation', data);
},
updateMessage({ commit }, data) {
commit('pushMessageToConversation', data);
},
};
export const mutations = {
@@ -151,24 +158,15 @@ export const mutations = {
}
},
setMessageStatus($state, { message, tempId }) {
const { status, id } = message;
updateAttachmentMessageStatus($state, { message, tempId }) {
const { id } = message;
const messagesInbox = $state.conversations;
const messageInConversation = messagesInbox[tempId];
if (messageInConversation) {
Vue.delete(messagesInbox, tempId);
const { attachment } = messageInConversation;
if (attachment.file_type === 'file') {
attachment.data_url = message.attachment.data_url;
}
Vue.set(messagesInbox, id, {
...messageInConversation,
attachment,
id,
status,
});
Vue.set(messagesInbox, id, { ...message });
}
},

View File

@@ -26,6 +26,13 @@ describe('#actions', () => {
});
});
describe('#updateMessage', () => {
it('sends correct mutations', () => {
actions.updateMessage({ commit }, { id: 1 });
expect(commit).toBeCalledWith('pushMessageToConversation', { id: 1 });
});
});
describe('#sendMessage', () => {
it('sends correct mutations', () => {
const mockDate = new Date(1466424490000);
@@ -59,12 +66,14 @@ describe('#actions', () => {
status: 'in_progress',
created_at: 1466424490,
message_type: 0,
attachment: {
thumb_url: '',
data_url: '',
file_type: 'file',
status: 'in_progress',
},
attachments: [
{
thumb_url: '',
data_url: '',
file_type: 'file',
status: 'in_progress',
},
],
});
});
});

View File

@@ -93,7 +93,7 @@ describe('#mutations', () => {
});
});
describe('#setMessageStatus', () => {
describe('#updateAttachmentMessageStatus', () => {
it('Updates status of loading messages if payload is not empty', () => {
const state = {
conversations: {
@@ -113,12 +113,18 @@ describe('#mutations', () => {
id: '1',
content: '',
status: 'sent',
attachment: {
file: '',
file_type: 'image',
},
message_type: 0,
attachments: [
{
file: '',
file_type: 'image',
},
],
};
mutations.setMessageStatus(state, { message, tempId: 'rand_id_123' });
mutations.updateAttachmentMessageStatus(state, {
message,
tempId: 'rand_id_123',
});
expect(state.conversations).toEqual({
1: {
@@ -126,10 +132,12 @@ describe('#mutations', () => {
content: '',
message_type: 0,
status: 'sent',
attachment: {
file: '',
file_type: 'image',
},
attachments: [
{
file: '',
file_type: 'image',
},
],
},
});
});