feat: Refactors API on widget side to support multiple conversations (#3032)
* State structure * How to refactr action modules * feat: Add state and mutations for multiple conversation on widget * Adds actions to support multiple conversation * feat: Adds public API endpoints for widget * fixes lint errors * Refactors store * Update mutations to accommodate new changes in store * Refactors actions in messages * fixes broken tests
This commit is contained in:
parent
e4b7b9a63d
commit
211f5cdedd
15 changed files with 654 additions and 127 deletions
|
@ -3,15 +3,27 @@ import { API } from 'widget/helpers/axios';
|
||||||
const buildUrl = endPoint => `/api/v1/${endPoint}${window.location.search}`;
|
const buildUrl = endPoint => `/api/v1/${endPoint}${window.location.search}`;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get() {
|
create(inboxIdentifier, userObject) {
|
||||||
return API.get(buildUrl('widget/contact'));
|
return API.post(buildUrl(`inboxes/${inboxIdentifier}/contacts`), {
|
||||||
},
|
|
||||||
update(identifier, userObject) {
|
|
||||||
return API.patch(buildUrl('widget/contact'), {
|
|
||||||
identifier,
|
|
||||||
...userObject,
|
...userObject,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get(inboxIdentifier, contactIdentifier) {
|
||||||
|
return API.get(
|
||||||
|
buildUrl(`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}`)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(inboxIdentifier, contactIdentifier, userObject) {
|
||||||
|
return API.patch(
|
||||||
|
buildUrl(`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}`),
|
||||||
|
{
|
||||||
|
...userObject,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
setCustomAttibutes(customAttributes = {}) {
|
setCustomAttibutes(customAttributes = {}) {
|
||||||
return API.patch(buildUrl('widget/contact'), {
|
return API.patch(buildUrl('widget/contact'), {
|
||||||
custom_attributes: customAttributes,
|
custom_attributes: customAttributes,
|
||||||
|
|
34
app/javascript/widget/api/conversationPublic.js
Normal file
34
app/javascript/widget/api/conversationPublic.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { API } from 'widget/helpers/axios';
|
||||||
|
|
||||||
|
const buildUrl = endPoint => `/api/v1/${endPoint}${window.location.search}`;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Refer: https://www.chatwoot.com/developers/api#tag/Conversations-API
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
create(inboxIdentifier, contactIdentifier) {
|
||||||
|
return API.post(
|
||||||
|
buildUrl(
|
||||||
|
`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}/conversations`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(inboxIdentifier, contactIdentifier) {
|
||||||
|
return API.get(
|
||||||
|
buildUrl(
|
||||||
|
`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}/conversations`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(inboxIdentifier, contactIdentifier, userObject) {
|
||||||
|
return API.patch(
|
||||||
|
buildUrl(`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}`),
|
||||||
|
{
|
||||||
|
...userObject,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
38
app/javascript/widget/api/messagesPublic.js
Normal file
38
app/javascript/widget/api/messagesPublic.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { API } from 'widget/helpers/axios';
|
||||||
|
|
||||||
|
const buildUrl = endPoint => `/api/v1/${endPoint}${window.location.search}`;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Refer: https://www.chatwoot.com/developers/api#tag/Messages-API
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
create(inboxIdentifier, contactIdentifier, conversationId, content, echoId) {
|
||||||
|
return API.post(
|
||||||
|
buildUrl(
|
||||||
|
`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}/conversations/${conversationId}/messages`
|
||||||
|
),
|
||||||
|
{ content, echo_id: echoId }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(inboxIdentifier, contactIdentifier, conversationId) {
|
||||||
|
return API.get(
|
||||||
|
buildUrl(
|
||||||
|
`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}/conversations/${conversationId}/messages`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(inboxIdentifier, contactIdentifier, conversationId, messageObject) {
|
||||||
|
const { id: messageId } = messageObject;
|
||||||
|
return API.patch(
|
||||||
|
buildUrl(
|
||||||
|
`inboxes/${inboxIdentifier}/contacts/${contactIdentifier}/conversations/${conversationId}/messages/${messageId}`
|
||||||
|
),
|
||||||
|
{
|
||||||
|
...messageObject,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,87 @@
|
||||||
|
import conversationPublicAPI from 'widget/api/conversationPublic';
|
||||||
|
import MessagePublicAPI from 'widget/api/messagePublic';
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
fetchAllConversations: async (
|
||||||
|
{ commit },
|
||||||
|
{ inboxIdentifier, contactIdentifier }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
commit('setUIFlag', { isFetching: true });
|
||||||
|
const { data } = await conversationPublicAPI.get(
|
||||||
|
inboxIdentifier,
|
||||||
|
contactIdentifier
|
||||||
|
);
|
||||||
|
data.forEach(conversation => {
|
||||||
|
const { id: conversationId, messages } = conversation;
|
||||||
|
commit('addConversationEntry', conversation);
|
||||||
|
commit('addConversationId', conversation.id);
|
||||||
|
commit(
|
||||||
|
'messagev2/addMessagesEntry',
|
||||||
|
{ conversationId, messages },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
|
commit('addMessageIdsToConversation', {
|
||||||
|
conversationId,
|
||||||
|
messages,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setUIFlag', { isFetching: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchConversationById: async ({ commit }, params) => {
|
||||||
|
const { conversationId, inboxIdentifier, contactIdentifier } = params;
|
||||||
|
try {
|
||||||
|
commit('setConversationUIFlag', { isFetching: true });
|
||||||
|
const { data } = await MessagePublicAPI.get(
|
||||||
|
inboxIdentifier,
|
||||||
|
contactIdentifier,
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
|
||||||
|
const { messages } = data;
|
||||||
|
commit('updateConversationEntry', data);
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setConversationUIFlag', {
|
||||||
|
conversationId,
|
||||||
|
uiFlags: { isFetching: false },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createConversation: async (
|
||||||
|
{ commit },
|
||||||
|
{ inboxIdentifier, contactIdentifier }
|
||||||
|
) => {
|
||||||
|
commit('setUIFlag', { isCreating: true });
|
||||||
|
try {
|
||||||
|
const params = { inboxIdentifier, contactIdentifier };
|
||||||
|
const { data } = await conversationPublicAPI.create(params);
|
||||||
|
const { id: conversationId, messages } = data;
|
||||||
|
|
||||||
|
commit('addConversationEntry', data);
|
||||||
|
commit('addConversationId', conversationId);
|
||||||
|
commit(
|
||||||
|
'messagev2/addMessagesEntry',
|
||||||
|
{ conversationId, messages },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
|
commit('addMessageIdsToConversation', {
|
||||||
|
conversationId,
|
||||||
|
messages,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setUIFlag', { isCreating: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import groupBy from 'lodash.groupby';
|
||||||
|
import { groupConversationBySender } from './helpers';
|
||||||
|
import { formatUnixDate } from 'shared/helpers/DateHelper';
|
||||||
|
|
||||||
|
export const getters = {
|
||||||
|
getAllMessagesLoaded: _state => _state.uiFlags.allMessagesLoaded,
|
||||||
|
getIsCreating: _state => _state.uiFlags.isCreating,
|
||||||
|
getIsAgentTyping: _state => _state.uiFlags.isAgentTyping,
|
||||||
|
getConversation: _state => _state.conversations,
|
||||||
|
getConversationSize: _state => Object.keys(_state.conversations).length,
|
||||||
|
getEarliestMessage: _state => {
|
||||||
|
const conversation = Object.values(_state.conversations);
|
||||||
|
if (conversation.length) {
|
||||||
|
return conversation[0];
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
getGroupedConversation: _state => {
|
||||||
|
const conversationGroupedByDate = groupBy(
|
||||||
|
Object.values(_state.conversations),
|
||||||
|
message => formatUnixDate(message.created_at)
|
||||||
|
);
|
||||||
|
return Object.keys(conversationGroupedByDate).map(date => ({
|
||||||
|
date,
|
||||||
|
messages: groupConversationBySender(conversationGroupedByDate[date]),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
getIsFetchingList: _state => _state.uiFlags.isFetchingList,
|
||||||
|
getMessageCount: _state => {
|
||||||
|
return Object.values(_state.conversations).length;
|
||||||
|
},
|
||||||
|
getUnreadMessageCount: _state => {
|
||||||
|
const { userLastSeenAt } = _state.meta;
|
||||||
|
const count = Object.values(_state.conversations).filter(chat => {
|
||||||
|
const { created_at: createdAt, message_type: messageType } = chat;
|
||||||
|
const isOutGoing = messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
const hasNotSeen = userLastSeenAt
|
||||||
|
? createdAt * 1000 > userLastSeenAt * 1000
|
||||||
|
: true;
|
||||||
|
return hasNotSeen && isOutGoing;
|
||||||
|
}).length;
|
||||||
|
return count;
|
||||||
|
},
|
||||||
|
getUnreadTextMessages: (_state, _getters) => {
|
||||||
|
const unreadCount = _getters.getUnreadMessageCount;
|
||||||
|
const allMessages = [...Object.values(_state.conversations)];
|
||||||
|
const unreadAgentMessages = allMessages.filter(message => {
|
||||||
|
const { message_type: messageType } = message;
|
||||||
|
return messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
});
|
||||||
|
const maxUnreadCount = Math.min(unreadCount, 3);
|
||||||
|
const allUnreadMessages = unreadAgentMessages.splice(-maxUnreadCount);
|
||||||
|
return allUnreadMessages;
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||||
|
|
||||||
|
const getSenderName = message => (message.sender ? message.sender.name : '');
|
||||||
|
|
||||||
|
const shouldShowAvatar = (message, nextMessage) => {
|
||||||
|
const currentSender = getSenderName(message);
|
||||||
|
const nextSender = getSenderName(nextMessage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
currentSender !== nextSender ||
|
||||||
|
message.message_type !== nextMessage.message_type ||
|
||||||
|
isASubmittedFormMessage(nextMessage)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const groupConversationBySender = conversationsForADate =>
|
||||||
|
conversationsForADate.map((message, index) => {
|
||||||
|
let showAvatar = false;
|
||||||
|
const isLastMessage = index === conversationsForADate.length - 1;
|
||||||
|
if (isASubmittedFormMessage(message)) {
|
||||||
|
showAvatar = false;
|
||||||
|
} else if (isLastMessage) {
|
||||||
|
showAvatar = true;
|
||||||
|
} else {
|
||||||
|
const nextMessage = conversationsForADate[index + 1];
|
||||||
|
showAvatar = shouldShowAvatar(message, nextMessage);
|
||||||
|
}
|
||||||
|
return { showAvatar, ...message };
|
||||||
|
});
|
||||||
|
|
||||||
|
export const findUndeliveredMessage = (messageInbox, { content }) =>
|
||||||
|
Object.values(messageInbox).filter(
|
||||||
|
message => message.content === content && message.status === 'in_progress'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getNonDeletedMessages = ({ messages }) => {
|
||||||
|
return messages.filter(
|
||||||
|
item => !(item.content_attributes && item.content_attributes.deleted)
|
||||||
|
);
|
||||||
|
};
|
28
app/javascript/widget/store/modules/conversationv2/index.js
Executable file
28
app/javascript/widget/store/modules/conversationv2/index.js
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
import { getters } from './getters';
|
||||||
|
import { actions } from './actions';
|
||||||
|
import { mutations } from './mutations';
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
conversations: {
|
||||||
|
byId: {},
|
||||||
|
allIds: [],
|
||||||
|
uiFlags: {
|
||||||
|
byId: {
|
||||||
|
// 1: { allMessagesLoaded: false, isAgentTyping: false, isFetching: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
uiFlags: {
|
||||||
|
allConversationsLoaded: false,
|
||||||
|
isFetching: false,
|
||||||
|
isCreating: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
};
|
|
@ -0,0 +1,76 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
setUIFlag($state, uiFlags) {
|
||||||
|
$state.uiFlags = {
|
||||||
|
...$state.uiFlags,
|
||||||
|
...uiFlags,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addConversationEntry($state, conversation) {
|
||||||
|
if (!conversation.id) return;
|
||||||
|
|
||||||
|
Vue.set($state.conversations.byId, conversation.id, {
|
||||||
|
...conversation,
|
||||||
|
messages: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addConversationId($state, conversationId) {
|
||||||
|
$state.conversations.allIds.push(conversationId);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateConversationEntry($state, conversation, content_attributes = {}) {
|
||||||
|
if (!conversation.id) return;
|
||||||
|
if (!$state.conversations.allIds.includes(conversation.id)) return;
|
||||||
|
|
||||||
|
Vue.set($state.conversations.byId, conversation.id, {
|
||||||
|
...conversation,
|
||||||
|
content_attributes: {
|
||||||
|
...(conversation.content_attributes || {}),
|
||||||
|
...content_attributes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeConversationEntry($state, conversationId) {
|
||||||
|
if (!conversationId) return;
|
||||||
|
|
||||||
|
Vue.set($state.conversations.byId, conversationId, undefined);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeConversationId($state, conversationId) {
|
||||||
|
$state.conversations.allIds = $state.conversations.allIds.filter(
|
||||||
|
id => id !== conversationId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
setConversationUIFlag($state, { conversationId, uiFlags }) {
|
||||||
|
const flags = $state.conversations.uiFlags.byId[conversationId];
|
||||||
|
$state.conversations.uiFlags.byId[conversationId] = {
|
||||||
|
...flags,
|
||||||
|
...uiFlags,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addMessageIdsToConversation($state, { conversationId, messages }) {
|
||||||
|
const conversationById = $state.conversations.byId[conversationId];
|
||||||
|
if (!conversationById) return;
|
||||||
|
|
||||||
|
const messageIds = messages.map(message => message.id);
|
||||||
|
const updatedMessageIds = [...conversationById.messages, ...messageIds];
|
||||||
|
Vue.set(conversationById, 'messages', updatedMessageIds);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMessageIdFromConversation($state, { conversationId, messageId }) {
|
||||||
|
if (!messageId || !conversationId) return;
|
||||||
|
|
||||||
|
const conversationById = $state.conversations.byId[conversationId];
|
||||||
|
if (!conversationById) return;
|
||||||
|
|
||||||
|
conversationById.messages = conversationById.messages.filter(
|
||||||
|
id => id !== messageId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
108
app/javascript/widget/store/modules/messageV2/actions.js
Normal file
108
app/javascript/widget/store/modules/messageV2/actions.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import MessagePublicAPI from 'widget/api/messagesPublic';
|
||||||
|
import { refreshActionCableConnector } from 'widget/helpers/actionCable';
|
||||||
|
import {
|
||||||
|
createTemporaryMessage,
|
||||||
|
createTemporaryAttachmentMessage,
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
sendMessage: async ({ commit }, params) => {
|
||||||
|
try {
|
||||||
|
commit(
|
||||||
|
'conversationV2/setConversationUIFlag',
|
||||||
|
{ isCreating: true },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
|
const { content, conversationId } = params;
|
||||||
|
const message = createTemporaryMessage({ content });
|
||||||
|
const { id: echoId } = message;
|
||||||
|
const messages = [message];
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
await MessagePublicAPI.create(
|
||||||
|
...params,
|
||||||
|
|
||||||
|
content,
|
||||||
|
echoId
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit(
|
||||||
|
'conversationV2/setConversationUIFlag',
|
||||||
|
{ isCreating: false },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendAttachment: async ({ commit }, params) => {
|
||||||
|
try {
|
||||||
|
commit(
|
||||||
|
'conversationV2/setConversationUIFlag',
|
||||||
|
{ isCreating: true },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
attachment: { thumbUrl, fileType },
|
||||||
|
conversationId,
|
||||||
|
} = params;
|
||||||
|
const message = createTemporaryAttachmentMessage({
|
||||||
|
thumbUrl,
|
||||||
|
fileType,
|
||||||
|
});
|
||||||
|
const messages = [message];
|
||||||
|
const { id: echoId, ...rest } = message;
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
const { data } = await MessagePublicAPI.create({
|
||||||
|
echo_id: echoId,
|
||||||
|
...rest,
|
||||||
|
});
|
||||||
|
commit('updateAttachmentMessageStatus', {
|
||||||
|
message: data,
|
||||||
|
tempId: message.id,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit(
|
||||||
|
'conversationV2/setConversationUIFlag',
|
||||||
|
{ isCreating: false },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMessage: async (
|
||||||
|
{ commit, dispatch },
|
||||||
|
{ email, messageId, submittedValues }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
commit('setMessageUIFlag', {
|
||||||
|
messageId,
|
||||||
|
uiFlags: { isUpdating: true },
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: { contact: { pubsub_token: pubsubToken } = {} },
|
||||||
|
} = await MessagePublicAPI.update({
|
||||||
|
email,
|
||||||
|
messageId,
|
||||||
|
values: submittedValues,
|
||||||
|
});
|
||||||
|
commit('updateMessageEntry', {
|
||||||
|
id: messageId,
|
||||||
|
content_attributes: {
|
||||||
|
submitted_email: email,
|
||||||
|
submitted_values: email ? null : submittedValues,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch('contacts/get', {}, { root: true });
|
||||||
|
refreshActionCableConnector(pubsubToken);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setMessageUIFlag', { messageId, uiFlags: { isUpdating: false } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
3
app/javascript/widget/store/modules/messageV2/getters.js
Normal file
3
app/javascript/widget/store/modules/messageV2/getters.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const getters = {
|
||||||
|
getUIFlags: $state => $state.uiFlags,
|
||||||
|
};
|
32
app/javascript/widget/store/modules/messageV2/helpers.js
Normal file
32
app/javascript/widget/store/modules/messageV2/helpers.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import getUuid from '../../../helpers/uuid';
|
||||||
|
|
||||||
|
export const createTemporaryMessage = ({ attachments, content }) => {
|
||||||
|
const timestamp = new Date().getTime() / 1000;
|
||||||
|
return {
|
||||||
|
id: getUuid(),
|
||||||
|
content,
|
||||||
|
attachments,
|
||||||
|
status: 'in_progress',
|
||||||
|
created_at: timestamp,
|
||||||
|
message_type: MESSAGE_TYPE.INCOMING,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTemporaryAttachmentMessage = ({
|
||||||
|
thumbUrl,
|
||||||
|
fileType,
|
||||||
|
content,
|
||||||
|
}) => {
|
||||||
|
const attachment = {
|
||||||
|
thumb_url: thumbUrl,
|
||||||
|
data_url: thumbUrl,
|
||||||
|
file_type: fileType,
|
||||||
|
status: 'in_progress',
|
||||||
|
};
|
||||||
|
const message = createTemporaryMessage({
|
||||||
|
attachments: [attachment],
|
||||||
|
content,
|
||||||
|
});
|
||||||
|
return message;
|
||||||
|
};
|
23
app/javascript/widget/store/modules/messageV2/index.js
Normal file
23
app/javascript/widget/store/modules/messageV2/index.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { getters } from './getters';
|
||||||
|
import { actions } from './actions';
|
||||||
|
import { mutations } from './mutations';
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
messages: {
|
||||||
|
byId: {},
|
||||||
|
allIds: [],
|
||||||
|
uiFlags: {
|
||||||
|
byId: {
|
||||||
|
// 1: { isCreating: false, isPending: false, isDeleting: false, isUpdating: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
};
|
49
app/javascript/widget/store/modules/messageV2/mutations.js
Normal file
49
app/javascript/widget/store/modules/messageV2/mutations.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
addMessagesEntry($state, { messages = [] }) {
|
||||||
|
const allMessages = $state.messages;
|
||||||
|
const newMessages = messages.reduce(
|
||||||
|
(obj, message) => ({
|
||||||
|
...obj,
|
||||||
|
[message.id]: message,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
const updatedMessages = { ...allMessages, ...newMessages };
|
||||||
|
Vue.set($state.messages, 'byId', updatedMessages);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMessageEntry($state, message) {
|
||||||
|
const messageId = message.id;
|
||||||
|
if (!messageId) return;
|
||||||
|
|
||||||
|
const messageById = $state.messages.byId[messageId];
|
||||||
|
if (!messageById) return;
|
||||||
|
if (messageId !== message.id) return;
|
||||||
|
|
||||||
|
Vue.set($state.messages.byId, messageId, { ...message });
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMessageEntry($state, messageId) {
|
||||||
|
if (!messageId) return;
|
||||||
|
|
||||||
|
Vue.delete($state.messages.byId, messageId);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMessageId($state, messageId) {
|
||||||
|
if (!messageId) return;
|
||||||
|
|
||||||
|
$state.messages.allIds = $state.messages.allIds.filter(
|
||||||
|
id => id !== messageId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
setMessageUIFlag($state, { messageId, uiFlags }) {
|
||||||
|
const flags = $state.messages.uiFlags.byId[messageId];
|
||||||
|
$state.messages.uiFlags.byId[messageId] = {
|
||||||
|
...flags,
|
||||||
|
...uiFlags,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import { mutations } from '../../conversation_new/mutations';
|
import { mutations } from '../../conversationv2/mutations';
|
||||||
|
|
||||||
describe('#mutations', () => {
|
describe('#mutations', () => {
|
||||||
describe('#setUIFlag', () => {
|
describe('#setUIFlag', () => {
|
||||||
|
@ -82,7 +82,7 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
expect(state.conversations).toEqual({
|
expect(state.conversations).toEqual({
|
||||||
byId: {
|
byId: {
|
||||||
120: { id: 120, channel: 'facebook' },
|
120: { id: 120, channel: 'facebook', content_attributes: {} },
|
||||||
},
|
},
|
||||||
allIds: [120],
|
allIds: [120],
|
||||||
});
|
});
|
||||||
|
@ -199,7 +199,7 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#addMessageIds', () => {
|
describe('#addMessageIdsToConversation', () => {
|
||||||
it('it adds a list of message ids to existing conversation entry', () => {
|
it('it adds a list of message ids to existing conversation entry', () => {
|
||||||
const state = {
|
const state = {
|
||||||
conversations: {
|
conversations: {
|
||||||
|
@ -212,31 +212,7 @@ describe('#mutations', () => {
|
||||||
{ id: 2, content: 'hi' },
|
{ id: 2, content: 'hi' },
|
||||||
{ id: 3, content: 'hello' },
|
{ id: 3, content: 'hello' },
|
||||||
];
|
];
|
||||||
mutations.addMessageIds(state, {
|
mutations.addMessageIdsToConversation(state, {
|
||||||
conversationId: 120,
|
|
||||||
messages,
|
|
||||||
});
|
|
||||||
expect(state.conversations.byId[120].messages).toEqual([2, 3]);
|
|
||||||
});
|
|
||||||
it('it does not clear existing messages in a conversation', () => {});
|
|
||||||
it('new message id is added as last in allIds message in a conversation', () => {});
|
|
||||||
it('it does not add messages if conversation is not present in store', () => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#addMessageIds', () => {
|
|
||||||
it('it adds a list of message ids to existing conversation entry', () => {
|
|
||||||
const state = {
|
|
||||||
conversations: {
|
|
||||||
byId: { 120: { id: 120, messages: [] } },
|
|
||||||
allIds: [120],
|
|
||||||
},
|
|
||||||
messages: { byId: {}, allIds: [] },
|
|
||||||
};
|
|
||||||
const messages = [
|
|
||||||
{ id: 2, content: 'hi' },
|
|
||||||
{ id: 3, content: 'hello' },
|
|
||||||
];
|
|
||||||
mutations.addMessageIds(state, {
|
|
||||||
conversationId: 120,
|
conversationId: 120,
|
||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
@ -251,7 +227,7 @@ describe('#mutations', () => {
|
||||||
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
||||||
};
|
};
|
||||||
const messages = [{ id: 3, content: 'hello' }];
|
const messages = [{ id: 3, content: 'hello' }];
|
||||||
mutations.addMessageIds(state, {
|
mutations.addMessageIdsToConversation(state, {
|
||||||
conversationId: 120,
|
conversationId: 120,
|
||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
@ -266,7 +242,7 @@ describe('#mutations', () => {
|
||||||
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
||||||
};
|
};
|
||||||
const messages = [{ id: 3, content: 'hello' }];
|
const messages = [{ id: 3, content: 'hello' }];
|
||||||
mutations.addMessageIds(state, {
|
mutations.addMessageIdsToConversation(state, {
|
||||||
conversationId: 120,
|
conversationId: 120,
|
||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
@ -274,62 +250,6 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#updateMessageEntry', () => {
|
|
||||||
it('it updates message in conversation correctly', () => {
|
|
||||||
const state = {
|
|
||||||
conversations: {
|
|
||||||
byId: { 12: { id: 12, messages: [2] } },
|
|
||||||
allIds: [12],
|
|
||||||
},
|
|
||||||
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
|
||||||
};
|
|
||||||
const message = { id: 2, content: 'hello' };
|
|
||||||
mutations.updateMessageEntry(state, message);
|
|
||||||
expect(state.messages.byId[2].content).toEqual('hello');
|
|
||||||
});
|
|
||||||
it('it does not create message if message does not exist in conversation', () => {
|
|
||||||
const state = {
|
|
||||||
conversations: {
|
|
||||||
byId: { 12: { id: 12, messages: [2] } },
|
|
||||||
allIds: [12],
|
|
||||||
},
|
|
||||||
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
|
||||||
};
|
|
||||||
const message = { id: 23, content: 'hello' };
|
|
||||||
mutations.updateMessageEntry(state, message);
|
|
||||||
expect(state.messages.byId[23]).toEqual(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('#removeMessageEntry', () => {
|
|
||||||
it('it deletes message in conversation correctly', () => {
|
|
||||||
const state = {
|
|
||||||
conversations: {
|
|
||||||
byId: { 12: { id: 12, messages: [2] } },
|
|
||||||
allIds: [12],
|
|
||||||
},
|
|
||||||
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
|
||||||
};
|
|
||||||
const messageId = 2;
|
|
||||||
mutations.removeMessageEntry(state, messageId);
|
|
||||||
expect(state.messages.byId[2]).toEqual(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#removeMessageId', () => {
|
|
||||||
it('it deletes message id in conversation correctly', () => {
|
|
||||||
const state = {
|
|
||||||
conversations: {
|
|
||||||
byId: { 12: { id: 12, messages: [2] } },
|
|
||||||
allIds: [12],
|
|
||||||
},
|
|
||||||
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
|
||||||
};
|
|
||||||
const messageId = 2;
|
|
||||||
mutations.removeMessageId(state, messageId);
|
|
||||||
expect(state.messages.allIds).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#removeMessageIdFromConversation', () => {
|
describe('#removeMessageIdFromConversation', () => {
|
||||||
it('it deletes message id in conversation correctly', () => {
|
it('it deletes message id in conversation correctly', () => {
|
||||||
const state = {
|
const state = {
|
||||||
|
@ -348,33 +268,4 @@ describe('#mutations', () => {
|
||||||
expect(state.conversations.byId[12].messages).toEqual([]);
|
expect(state.conversations.byId[12].messages).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#setMessageUIFlag', () => {
|
|
||||||
it('it sets UI flag for conversation correctly', () => {
|
|
||||||
const state = {
|
|
||||||
messages: {
|
|
||||||
byId: {},
|
|
||||||
allIds: [],
|
|
||||||
uiFlags: {
|
|
||||||
byId: {
|
|
||||||
1: {
|
|
||||||
isCreating: false,
|
|
||||||
isPending: false,
|
|
||||||
isDeleting: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
mutations.setMessageUIFlag(state, {
|
|
||||||
messageId: 1,
|
|
||||||
uiFlags: { isCreating: true },
|
|
||||||
});
|
|
||||||
expect(state.messages.uiFlags.byId[1]).toEqual({
|
|
||||||
isCreating: true,
|
|
||||||
isPending: false,
|
|
||||||
isDeleting: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,61 @@
|
||||||
import { mutations } from '../../message';
|
import { mutations } from '../../messageV2/mutations';
|
||||||
|
|
||||||
describe('#mutations', () => {
|
describe('#mutations', () => {
|
||||||
describe('#toggleUpdateStatus', () => {
|
describe('#updateMessageEntry', () => {
|
||||||
it('set update flags', () => {
|
it('it updates message in conversation correctly', () => {
|
||||||
const state = { uiFlags: { status: '' } };
|
const state = {
|
||||||
mutations.toggleUpdateStatus(state, 'sent');
|
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
||||||
expect(state.uiFlags.isUpdating).toEqual('sent');
|
};
|
||||||
|
const message = { id: 2, content: 'hello' };
|
||||||
|
mutations.updateMessageEntry(state, message);
|
||||||
|
expect(state.messages.byId[2].content).toEqual('hello');
|
||||||
|
});
|
||||||
|
it('it does not create message if message does not exist in conversation', () => {
|
||||||
|
const state = {
|
||||||
|
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
||||||
|
};
|
||||||
|
const message = { id: 23, content: 'hello' };
|
||||||
|
mutations.updateMessageEntry(state, message);
|
||||||
|
expect(state.messages.byId[23]).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#removeMessageEntry', () => {
|
||||||
|
it('it deletes message in conversation correctly', () => {
|
||||||
|
const state = {
|
||||||
|
messages: { byId: { 2: { id: 2, content: 'hi' } }, allIds: [2] },
|
||||||
|
};
|
||||||
|
const messageId = 2;
|
||||||
|
mutations.removeMessageEntry(state, messageId);
|
||||||
|
expect(state.messages.byId[2]).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setMessageUIFlag', () => {
|
||||||
|
it('it sets UI flag for conversation correctly', () => {
|
||||||
|
const state = {
|
||||||
|
messages: {
|
||||||
|
byId: {},
|
||||||
|
allIds: [],
|
||||||
|
uiFlags: {
|
||||||
|
byId: {
|
||||||
|
1: {
|
||||||
|
isCreating: false,
|
||||||
|
isPending: false,
|
||||||
|
isDeleting: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mutations.setMessageUIFlag(state, {
|
||||||
|
messageId: 1,
|
||||||
|
uiFlags: { isCreating: true },
|
||||||
|
});
|
||||||
|
expect(state.messages.uiFlags.byId[1]).toEqual({
|
||||||
|
isCreating: true,
|
||||||
|
isPending: false,
|
||||||
|
isDeleting: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue