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

@@ -22,7 +22,7 @@ class MessageApi extends ApiClient {
sendAttachment([conversationId, { file }]) {
const formData = new FormData();
formData.append('attachment[file]', file);
formData.append('attachments[]', file, file.name);
return axios({
method: 'post',
url: `${this.url}/${conversationId}/messages`,

View File

@@ -105,15 +105,17 @@ export default {
router.push({ path: frontendURL(path) });
},
extractMessageText(chatItem) {
if (chatItem.content) {
return chatItem.content;
const { content, attachments } = chatItem;
if (content) {
return content;
}
let fileType = '';
if (chatItem.attachment) {
fileType = chatItem.attachment.file_type;
} else {
if (!attachments) {
return ' ';
}
const [attachment] = attachments;
const { file_type: fileType } = attachment;
const key = `CHAT_LIST.ATTACHMENTS.${fileType}`;
return `
<i class="small-icon ${this.$t(`${key}.ICON`)}"></i>

View File

@@ -1,22 +1,26 @@
<template>
<li v-if="data.attachment || data.content" :class="alignBubble">
<li v-if="hasAttachments || data.content" :class="alignBubble">
<div :class="wrapClass">
<p v-tooltip.top-start="sentByMessage" :class="bubbleClass">
<bubble-image
v-if="data.attachment && data.attachment.file_type === 'image'"
:url="data.attachment.data_url"
:readable-time="readableTime"
/>
<bubble-file
v-if="data.attachment && data.attachment.file_type !== 'image'"
:url="data.attachment.data_url"
:readable-time="readableTime"
/>
<bubble-text
v-if="data.content"
:message="message"
:readable-time="readableTime"
/>
<span v-if="hasAttachments">
<span v-for="attachment in data.attachments" :key="attachment.id">
<bubble-image
v-if="attachment.file_type === 'image'"
:url="attachment.data_url"
:readable-time="readableTime"
/>
<bubble-file
v-if="attachment.file_type !== 'image'"
:url="attachment.data_url"
:readable-time="readableTime"
/>
</span>
</span>
<i
v-if="isPrivate"
v-tooltip.top-start="toolTipMessage"
@@ -71,10 +75,16 @@ export default {
isBubble() {
return [0, 1, 3].includes(this.data.message_type);
},
hasAttachments() {
return !!(this.data.attachments && this.data.attachments.length > 0);
},
hasImageAttachment() {
const { attachment = {} } = this.data;
const { file_type: fileType } = attachment;
return fileType === 'image';
if (this.hasAttachments && this.data.attachments.length > 0) {
const { attachments = [{}] } = this.data;
const { file_type: fileType } = attachments[0];
return fileType === 'image';
}
return false;
},
isPrivate() {
return this.data.private;

View File

@@ -6,6 +6,7 @@ class ActionCableConnector extends BaseActionCableConnector {
super(app, pubsubToken);
this.events = {
'message.created': this.onMessageCreated,
'message.updated': this.onMessageUpdated,
'conversation.created': this.onConversationCreated,
'status_change:conversation': this.onStatusChange,
'user:logout': this.onLogout,
@@ -14,6 +15,10 @@ class ActionCableConnector extends BaseActionCableConnector {
};
}
onMessageUpdated = data => {
this.app.$store.dispatch('updateMessage', data);
};
onAssigneeChanged = payload => {
const { meta = {}, id } = payload;
const { assignee } = meta || {};

View File

@@ -145,6 +145,10 @@ const actions = {
commit(types.default.ADD_MESSAGE, message);
},
updateMessage({ commit }, message) {
commit(types.default.ADD_MESSAGE, message);
},
addConversation({ commit }, conversation) {
commit(types.default.ADD_CONVERSATION, conversation);
},
@@ -192,7 +196,7 @@ const actions = {
sendAttachment: async ({ commit }, data) => {
try {
const response = MessageApi.sendAttachment(data);
const response = await MessageApi.sendAttachment(data);
commit(types.default.SEND_MESSAGE, response.data);
} catch (error) {
// Handle error

View File

@@ -122,12 +122,13 @@ const mutations = {
_state.selectedChat.status = status;
},
[types.default.SEND_MESSAGE](_state, data) {
[types.default.SEND_MESSAGE](_state, currentMessage) {
const [chat] = getSelectedChatConversation(_state);
const previousMessageIds = chat.messages.map(m => m.id);
if (!previousMessageIds.includes(data.id)) {
chat.messages.push(data);
}
const allMessagesExceptCurrent = (chat.messages || []).filter(
message => message.id !== currentMessage.id
);
allMessagesExceptCurrent.push(currentMessage);
chat.messages = allMessagesExceptCurrent;
},
[types.default.ADD_MESSAGE](_state, message) {
@@ -135,12 +136,16 @@ const mutations = {
c => c.id === message.conversation_id
);
if (!chat) return;
const previousMessageIds = chat.messages.map(m => m.id);
if (!previousMessageIds.includes(message.id)) {
const previousMessageIndex = chat.messages.findIndex(
m => m.id === message.id
);
if (previousMessageIndex === -1) {
chat.messages.push(message);
if (_state.selectedChat.id === message.conversation_id) {
window.bus.$emit('scrollToMessage');
}
} else {
chat.messages[previousMessageIndex] = message;
}
},