feat: Attachments view (#7156)

* feat: Attachments view with key shortcuts and dynamically updates when user delete or sent new attachments

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sivin Varghese
2023-06-05 19:21:47 +05:30
committed by GitHub
parent 9f3d155822
commit b333d0c986
15 changed files with 607 additions and 32 deletions

View File

@@ -86,6 +86,18 @@ const actions = {
}
},
fetchAllAttachments: async ({ commit }, conversationId) => {
try {
const { data } = await ConversationApi.getAllAttachments(conversationId);
commit(types.SET_ALL_ATTACHMENTS, {
id: conversationId,
data: data.payload,
});
} catch (error) {
// Handle error
}
},
syncActiveConversationMessages: async (
{ commit, state, dispatch },
{ conversationId }
@@ -247,6 +259,10 @@ const actions = {
...response.data,
status: MESSAGE_STATUS.SENT,
});
commit(types.ADD_CONVERSATION_ATTACHMENTS, {
...response.data,
status: MESSAGE_STATUS.SENT,
});
} catch (error) {
const errorMessage = error.response
? error.response.data.error
@@ -269,6 +285,7 @@ const actions = {
conversationId: message.conversation_id,
canReply: true,
});
commit(types.ADD_CONVERSATION_ATTACHMENTS, message);
}
},
@@ -283,6 +300,7 @@ const actions = {
try {
const { data } = await MessageApi.delete(conversationId, messageId);
commit(types.ADD_MESSAGE, data);
commit(types.DELETE_CONVERSATION_ATTACHMENTS, data);
} catch (error) {
throw new Error(error);
}

View File

@@ -32,6 +32,11 @@ const getters = {
);
return selectedChat || {};
},
getSelectedChatAttachments: (_state, _getters) => {
const selectedChat = _getters.getSelectedChat;
const { attachments } = selectedChat;
return attachments;
},
getLastEmailInSelectedChat: (stage, _getters) => {
const selectedChat = _getters.getSelectedChat;
const { messages = [] } = selectedChat;

View File

@@ -3,6 +3,7 @@ import types from '../../mutation-types';
import getters, { getSelectedChatConversation } from './getters';
import actions from './actions';
import { findPendingMessageIndex } from './helpers';
import { MESSAGE_STATUS } from 'shared/constants/messages';
import wootConstants from 'dashboard/constants/globals';
import { BUS_EVENTS } from '../../../../shared/constants/busEvents';
@@ -56,6 +57,13 @@ export const mutations = {
chat.messages.unshift(...data);
}
},
[types.SET_ALL_ATTACHMENTS](_state, { id, data }) {
if (data.length) {
const [chat] = _state.allConversations.filter(c => c.id === id);
Vue.set(chat, 'attachments', []);
chat.attachments.push(...data);
}
},
[types.SET_MISSING_MESSAGES](_state, { id, data }) {
const [chat] = _state.allConversations.filter(c => c.id === id);
if (!chat) return;
@@ -115,6 +123,44 @@ export const mutations = {
Vue.set(chat, 'muted', false);
},
[types.ADD_CONVERSATION_ATTACHMENTS]({ allConversations }, message) {
const { conversation_id: conversationId } = message;
const [chat] = getSelectedChatConversation({
allConversations,
selectedChatId: conversationId,
});
if (!chat) return;
const isMessageSent =
message.status === MESSAGE_STATUS.SENT && message.attachments;
if (isMessageSent) {
message.attachments.forEach(attachment => {
if (!chat.attachments.some(a => a.id === attachment.id)) {
chat.attachments.push(attachment);
}
});
}
},
[types.DELETE_CONVERSATION_ATTACHMENTS]({ allConversations }, message) {
const { conversation_id: conversationId } = message;
const [chat] = getSelectedChatConversation({
allConversations,
selectedChatId: conversationId,
});
if (!chat) return;
const isMessageSent = message.status === MESSAGE_STATUS.SENT;
if (isMessageSent) {
const attachmentIndex = chat.attachments.findIndex(
a => a.message_id === message.id
);
if (attachmentIndex !== -1) chat.attachments.splice(attachmentIndex, 1);
}
},
[types.ADD_MESSAGE]({ allConversations, selectedChatId }, message) {
const { conversation_id: conversationId } = message;
const [chat] = getSelectedChatConversation({

View File

@@ -204,6 +204,7 @@ describe('#actions', () => {
]);
});
});
describe('#addMessage', () => {
it('sends correct mutations if message is incoming', () => {
const message = {
@@ -218,6 +219,7 @@ describe('#actions', () => {
types.SET_CONVERSATION_CAN_REPLY,
{ conversationId: 1, canReply: true },
],
[types.ADD_CONVERSATION_ATTACHMENTS, message],
]);
});
it('sends correct mutations if message is not an incoming message', () => {
@@ -436,10 +438,13 @@ describe('#actions', () => {
describe('#deleteMessage', () => {
it('sends correct actions if API is success', async () => {
const [conversationId, messageId] = [1, 1];
axios.delete.mockResolvedValue({ data: { id: 1, content: 'deleted' } });
axios.delete.mockResolvedValue({
data: { id: 1, content: 'deleted' },
});
await actions.deleteMessage({ commit }, { conversationId, messageId });
expect(commit.mock.calls).toEqual([
[types.ADD_MESSAGE, { id: 1, content: 'deleted' }],
[types.DELETE_CONVERSATION_ATTACHMENTS, { id: 1, content: 'deleted' }],
]);
});
it('sends no actions if API is error', async () => {
@@ -554,4 +559,40 @@ describe('#addMentions', () => {
],
]);
});
describe('#fetchAllAttachments', () => {
it('fetches all attachments', async () => {
axios.get.mockResolvedValue({
data: {
payload: [
{
id: 1,
message_id: 1,
file_type: 'image',
data_url: '',
thumb_url: '',
},
],
},
});
await actions.fetchAllAttachments({ commit }, 1);
expect(commit.mock.calls).toEqual([
[
types.SET_ALL_ATTACHMENTS,
{
id: 1,
data: [
{
id: 1,
message_id: 1,
file_type: 'image',
data_url: '',
thumb_url: '',
},
],
},
],
]);
});
});
});

View File

@@ -305,4 +305,34 @@ describe('#getters', () => {
});
});
});
describe('#getSelectedChatAttachments', () => {
it('Returns attachments in selected chat', () => {
const state = {};
const getSelectedChat = {
attachments: [
{
id: 1,
file_name: 'test1',
},
{
id: 2,
file_name: 'test2',
},
],
};
expect(
getters.getSelectedChatAttachments(state, { getSelectedChat })
).toEqual([
{
id: 1,
file_name: 'test1',
},
{
id: 2,
file_name: 'test2',
},
]);
});
});
});

View File

@@ -278,4 +278,121 @@ describe('#mutations', () => {
expect(state.appliedFilters).toEqual([]);
});
});
describe('#SET_ALL_ATTACHMENTS', () => {
it('set all attachments', () => {
const state = {
allConversations: [{ id: 1 }],
};
const data = [{ id: 1, name: 'test' }];
mutations[types.SET_ALL_ATTACHMENTS](state, { id: 1, data });
expect(state.allConversations[0].attachments).toEqual(data);
});
});
describe('#ADD_CONVERSATION_ATTACHMENTS', () => {
it('add conversation attachments', () => {
const state = {
allConversations: [{ id: 1, attachments: [] }],
};
const message = {
conversation_id: 1,
status: 'sent',
attachments: [{ id: 1, name: 'test' }],
};
mutations[types.ADD_CONVERSATION_ATTACHMENTS](state, message);
expect(state.allConversations[0].attachments).toEqual(
message.attachments
);
});
it('should not add duplicate attachments', () => {
const state = {
allConversations: [
{
id: 1,
attachments: [{ id: 1, name: 'existing' }],
},
],
};
const message = {
conversation_id: 1,
status: 'sent',
attachments: [
{ id: 1, name: 'existing' },
{ id: 2, name: 'new' },
],
};
mutations[types.ADD_CONVERSATION_ATTACHMENTS](state, message);
expect(state.allConversations[0].attachments).toHaveLength(2);
expect(state.allConversations[0].attachments).toContainEqual({
id: 1,
name: 'existing',
});
expect(state.allConversations[0].attachments).toContainEqual({
id: 2,
name: 'new',
});
});
it('should not add attachments if chat not found', () => {
const state = {
allConversations: [{ id: 1, attachments: [] }],
};
const message = {
conversation_id: 2,
status: 'sent',
attachments: [{ id: 1, name: 'test' }],
};
mutations[types.ADD_CONVERSATION_ATTACHMENTS](state, message);
expect(state.allConversations[0].attachments).toHaveLength(0);
});
});
describe('#DELETE_CONVERSATION_ATTACHMENTS', () => {
it('delete conversation attachments', () => {
const state = {
allConversations: [{ id: 1, attachments: [{ id: 1, message_id: 1 }] }],
};
const message = {
conversation_id: 1,
status: 'sent',
id: 1,
};
mutations[types.DELETE_CONVERSATION_ATTACHMENTS](state, message);
expect(state.allConversations[0].attachments).toHaveLength(0);
});
it('should not delete attachments for non-matching message id', () => {
const state = {
allConversations: [{ id: 1, attachments: [{ id: 1, message_id: 1 }] }],
};
const message = {
conversation_id: 1,
status: 'sent',
id: 2,
};
mutations[types.DELETE_CONVERSATION_ATTACHMENTS](state, message);
expect(state.allConversations[0].attachments).toHaveLength(1);
});
it('should not delete attachments if chat not found', () => {
const state = {
allConversations: [{ id: 1, attachments: [{ id: 1, message_id: 1 }] }],
};
const message = {
conversation_id: 2,
status: 'sent',
id: 1,
};
mutations[types.DELETE_CONVERSATION_ATTACHMENTS](state, message);
expect(state.allConversations[0].attachments).toHaveLength(1);
});
});
});

View File

@@ -49,6 +49,10 @@ export default {
UPDATE_CONVERSATION_LAST_ACTIVITY: 'UPDATE_CONVERSATION_LAST_ACTIVITY',
SET_MISSING_MESSAGES: 'SET_MISSING_MESSAGES',
SET_ALL_ATTACHMENTS: 'SET_ALL_ATTACHMENTS',
ADD_CONVERSATION_ATTACHMENTS: 'ADD_CONVERSATION_ATTACHMENTS',
DELETE_CONVERSATION_ATTACHMENTS: 'DELETE_CONVERSATION_ATTACHMENTS',
SET_CONVERSATION_CAN_REPLY: 'SET_CONVERSATION_CAN_REPLY',
// Inboxes