feat: Add ability to create a new conversation if the previous conversation is resolved (#2512)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Muhsin Keloth 2021-06-30 21:09:44 +05:30 committed by GitHub
parent e6e9916fdb
commit f0f66c7da6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 119 additions and 16 deletions

View file

@ -2,4 +2,5 @@ export const BUS_EVENTS = {
SET_REFERRER_HOST: 'SET_REFERRER_HOST', SET_REFERRER_HOST: 'SET_REFERRER_HOST',
SET_TWEET_REPLY: 'SET_TWEET_REPLY', SET_TWEET_REPLY: 'SET_TWEET_REPLY',
ATTACHMENT_SIZE_CHECK_ERROR: 'ATTACHMENT_SIZE_CHECK_ERROR', ATTACHMENT_SIZE_CHECK_ERROR: 'ATTACHMENT_SIZE_CHECK_ERROR',
START_NEW_CONVERSATION: 'START_NEW_CONVERSATION',
}; };

View file

@ -77,7 +77,7 @@ export default {
this.registerListeners(); this.registerListeners();
this.sendRNWebViewLoadedEvent(); this.sendRNWebViewLoadedEvent();
} }
this.$store.dispatch('conversationAttributes/get'); this.$store.dispatch('conversationAttributes/getAttributes');
this.setWidgetColor(window.chatwootWebChannel); this.setWidgetColor(window.chatwootWebChannel);
this.registerUnreadEvents(); this.registerUnreadEvents();
this.registerCampaignEvents(); this.registerCampaignEvents();

View file

@ -1,19 +1,33 @@
<template> <template>
<footer class="footer"> <footer v-if="!hideReplyBox" class="footer">
<ChatInputWrap <ChatInputWrap
:on-send-message="handleSendMessage" :on-send-message="handleSendMessage"
:on-send-attachment="handleSendAttachment" :on-send-attachment="handleSendAttachment"
/> />
</footer> </footer>
<custom-button
v-else
class="font-medium"
block
:bg-color="widgetColor"
:text-color="textColor"
@click="startNewConversation"
>
{{ $t('START_NEW_CONVERSATION') }}
</custom-button>
</template> </template>
<script> <script>
import { mapActions } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { getContrastingTextColor } from '@chatwoot/utils';
import CustomButton from 'shared/components/Button';
import ChatInputWrap from 'widget/components/ChatInputWrap.vue'; import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents';
export default { export default {
components: { components: {
ChatInputWrap, ChatInputWrap,
CustomButton,
}, },
props: { props: {
msg: { msg: {
@ -21,16 +35,49 @@ export default {
default: '', default: '',
}, },
}, },
computed: {
...mapGetters({
conversationAttributes: 'conversationAttributes/getConversationParams',
widgetColor: 'appConfig/getWidgetColor',
getConversationSize: 'conversation/getConversationSize',
}),
textColor() {
return getContrastingTextColor(this.widgetColor);
},
hideReplyBox() {
const { csatSurveyEnabled } = window.chatwootWebChannel;
const { status } = this.conversationAttributes;
return csatSurveyEnabled && status === 'resolved';
},
},
methods: { methods: {
...mapActions('conversation', ['sendMessage', 'sendAttachment']), ...mapActions('conversation', [
handleSendMessage(content) { 'sendMessage',
this.sendMessage({ 'sendAttachment',
'clearConversations',
]),
...mapActions('conversationAttributes', [
'getAttributes',
'clearConversationAttributes',
]),
async handleSendMessage(content) {
const conversationSize = this.getConversationSize;
await this.sendMessage({
content, content,
}); });
// Update conversation attributes on new conversation
if (conversationSize === 0) {
this.getAttributes();
}
}, },
handleSendAttachment(attachment) { handleSendAttachment(attachment) {
this.sendAttachment({ attachment }); this.sendAttachment({ attachment });
}, },
startNewConversation() {
this.clearConversations();
this.clearConversationAttributes();
window.bus.$emit(BUS_EVENTS.START_NEW_CONVERSATION);
},
}, },
}; };
</script> </script>

View file

@ -18,6 +18,7 @@
"IN_A_DAY": "Typically replies in a day" "IN_A_DAY": "Typically replies in a day"
}, },
"START_CONVERSATION": "Start Conversation", "START_CONVERSATION": "Start Conversation",
"START_NEW_CONVERSATION": "Start a new conversation",
"UNREAD_VIEW": { "UNREAD_VIEW": {
"VIEW_MESSAGES_BUTTON": "See new messages", "VIEW_MESSAGES_BUTTON": "See new messages",
"CLOSE_MESSAGES_BUTTON": "Close", "CLOSE_MESSAGES_BUTTON": "Close",

View file

@ -22,10 +22,16 @@ export default {
return window.chatwootWebChannel.preChatFormEnabled; return window.chatwootWebChannel.preChatFormEnabled;
}, },
preChatFormOptions() { preChatFormOptions() {
let requireEmail = false;
let preChatMessage = '';
const options = window.chatwootWebChannel.preChatFormOptions || {}; const options = window.chatwootWebChannel.preChatFormOptions || {};
if (!this.isOnNewConversation) {
requireEmail = options.require_email;
preChatMessage = options.pre_chat_message;
}
return { return {
requireEmail: options.require_email, requireEmail,
preChatMessage: options.pre_chat_message, preChatMessage,
}; };
}, },
}, },

View file

@ -6,6 +6,7 @@ global.chatwootWebChannel = {
avatarUrl: 'https://test.url', avatarUrl: 'https://test.url',
hasAConnectedAgentBot: 'AgentBot', hasAConnectedAgentBot: 'AgentBot',
enabledFeatures: ['emoji_picker', 'attachments'], enabledFeatures: ['emoji_picker', 'attachments'],
preChatFormOptions: { require_email: false, pre_chat_message: '' },
}; };
global.chatwootWidgetDefaults = { global.chatwootWidgetDefaults = {
@ -31,6 +32,14 @@ describe('configMixin', () => {
avatarUrl: 'https://test.url', avatarUrl: 'https://test.url',
hasAConnectedAgentBot: 'AgentBot', hasAConnectedAgentBot: 'AgentBot',
enabledFeatures: ['emoji_picker', 'attachments'], enabledFeatures: ['emoji_picker', 'attachments'],
preChatFormOptions: {
pre_chat_message: '',
require_email: false,
},
});
expect(wrapper.vm.preChatFormOptions).toEqual({
requireEmail: false,
preChatMessage: '',
}); });
}); });
}); });

View file

@ -11,7 +11,7 @@ import { refreshActionCableConnector } from '../../../helpers/actionCable';
import { createTemporaryMessage, onNewMessageCreated } from './helpers'; import { createTemporaryMessage, onNewMessageCreated } from './helpers';
export const actions = { export const actions = {
createConversation: async ({ commit }, params) => { createConversation: async ({ commit, dispatch }, params) => {
commit('setConversationUIFlag', { isCreating: true }); commit('setConversationUIFlag', { isCreating: true });
try { try {
const { data } = await createConversationAPI(params); const { data } = await createConversationAPI(params);
@ -22,6 +22,7 @@ export const actions = {
const [message = {}] = messages; const [message = {}] = messages;
commit('pushMessageToConversation', message); commit('pushMessageToConversation', message);
refreshActionCableConnector(pubsubToken); refreshActionCableConnector(pubsubToken);
dispatch('conversationAttributes/getAttributes', {}, { root: true });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
// Ignore error // Ignore error

View file

@ -1,6 +1,7 @@
import { import {
SET_CONVERSATION_ATTRIBUTES, SET_CONVERSATION_ATTRIBUTES,
UPDATE_CONVERSATION_ATTRIBUTES, UPDATE_CONVERSATION_ATTRIBUTES,
CLEAR_CONVERSATION_ATTRIBUTES,
} from '../types'; } from '../types';
import { getConversationAPI } from '../../api/conversation'; import { getConversationAPI } from '../../api/conversation';
@ -14,7 +15,7 @@ export const getters = {
}; };
export const actions = { export const actions = {
get: async ({ commit }) => { getAttributes: async ({ commit }) => {
try { try {
const { data } = await getConversationAPI(); const { data } = await getConversationAPI();
const { contact_last_seen_at: lastSeen } = data; const { contact_last_seen_at: lastSeen } = data;
@ -27,6 +28,9 @@ export const actions = {
update({ commit }, data) { update({ commit }, data) {
commit(UPDATE_CONVERSATION_ATTRIBUTES, data); commit(UPDATE_CONVERSATION_ATTRIBUTES, data);
}, },
clearConversationAttributes: ({ commit }) => {
commit('CLEAR_CONVERSATION_ATTRIBUTES');
},
}; };
export const mutations = { export const mutations = {
@ -40,6 +44,10 @@ export const mutations = {
$state.status = data.status; $state.status = data.status;
} }
}, },
[CLEAR_CONVERSATION_ATTRIBUTES]($state) {
$state.id = '';
$state.status = '';
},
}; };
export default { export default {

View file

@ -5,10 +5,10 @@ const commit = jest.fn();
jest.mock('widget/helpers/axios'); jest.mock('widget/helpers/axios');
describe('#actions', () => { describe('#actions', () => {
describe('#update', () => { describe('#get attributes', () => {
it('sends mutation if api is success', async () => { it('sends mutation if api is success', async () => {
API.get.mockResolvedValue({ data: { id: 1, status: 'bot' } }); API.get.mockResolvedValue({ data: { id: 1, status: 'bot' } });
await actions.get({ commit }); await actions.getAttributes({ commit });
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
['SET_CONVERSATION_ATTRIBUTES', { id: 1, status: 'bot' }], ['SET_CONVERSATION_ATTRIBUTES', { id: 1, status: 'bot' }],
['conversation/setMetaUserLastSeenAt', undefined, { root: true }], ['conversation/setMetaUserLastSeenAt', undefined, { root: true }],
@ -16,12 +16,12 @@ describe('#actions', () => {
}); });
it('doesnot send mutation if api is error', async () => { it('doesnot send mutation if api is error', async () => {
API.get.mockRejectedValue({ message: 'Invalid Headers' }); API.get.mockRejectedValue({ message: 'Invalid Headers' });
await actions.get({ commit }); await actions.getAttributes({ commit });
expect(commit.mock.calls).toEqual([]); expect(commit.mock.calls).toEqual([]);
}); });
}); });
describe('#update', () => { describe('#update attributes', () => {
it('sends correct mutations', () => { it('sends correct mutations', () => {
actions.update({ commit }, { id: 1, status: 'bot' }); actions.update({ commit }, { id: 1, status: 'bot' });
expect(commit).toBeCalledWith('UPDATE_CONVERSATION_ATTRIBUTES', { expect(commit).toBeCalledWith('UPDATE_CONVERSATION_ATTRIBUTES', {
@ -30,4 +30,10 @@ describe('#actions', () => {
}); });
}); });
}); });
describe('#clear attributes', () => {
it('sends correct mutations', () => {
actions.clearConversationAttributes({ commit });
expect(commit).toBeCalledWith('CLEAR_CONVERSATION_ATTRIBUTES');
});
});
}); });

View file

@ -30,4 +30,15 @@ describe('#mutations', () => {
expect(state).toEqual({ id: 1, status: 'bot' }); expect(state).toEqual({ id: 1, status: 'bot' });
}); });
}); });
describe('#CLEAR_CONVERSATION_ATTRIBUTES', () => {
it('clear status if it is same conversation', () => {
const state = { id: 1, status: 'open' };
mutations.CLEAR_CONVERSATION_ATTRIBUTES(state, {
id: 1,
status: 'open',
});
expect(state).toEqual({ id: '', status: '' });
});
});
}); });

View file

@ -1,3 +1,4 @@
export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR'; export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR';
export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES'; export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES';
export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES'; export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES';
export const CLEAR_CONVERSATION_ATTRIBUTES = 'CLEAR_CONVERSATION_ATTRIBUTES';

View file

@ -113,7 +113,11 @@ export default {
}, },
}, },
data() { data() {
return { isOnCollapsedView: false, showAttachmentError: false }; return {
isOnCollapsedView: false,
showAttachmentError: false,
isOnNewConversation: false,
};
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
@ -130,7 +134,10 @@ export default {
if (this.conversationSize) { if (this.conversationSize) {
return 'messageView'; return 'messageView';
} }
if (this.preChatFormEnabled && !currentUserEmail) { if (
this.isOnNewConversation ||
(this.preChatFormEnabled && !currentUserEmail)
) {
return 'preChatFormView'; return 'preChatFormView';
} }
return 'messageView'; return 'messageView';
@ -163,6 +170,10 @@ export default {
this.showAttachmentError = false; this.showAttachmentError = false;
}, 3000); }, 3000);
}); });
bus.$on(BUS_EVENTS.START_NEW_CONVERSATION, () => {
this.isOnCollapsedView = true;
this.isOnNewConversation = true;
});
}, },
methods: { methods: {
startConversation() { startConversation() {

View file

@ -20,6 +20,7 @@
preChatFormEnabled: <%= @web_widget.pre_chat_form_enabled %>, preChatFormEnabled: <%= @web_widget.pre_chat_form_enabled %>,
preChatFormOptions: <%= @web_widget.pre_chat_form_options.to_json.html_safe %>, preChatFormOptions: <%= @web_widget.pre_chat_form_options.to_json.html_safe %>,
workingHoursEnabled: <%= @web_widget.inbox.working_hours_enabled %>, workingHoursEnabled: <%= @web_widget.inbox.working_hours_enabled %>,
csatSurveyEnabled: <%= @web_widget.inbox.csat_survey_enabled %>,
workingHours: <%= @web_widget.inbox.working_hours.to_json.html_safe %>, workingHours: <%= @web_widget.inbox.working_hours.to_json.html_safe %>,
outOfOfficeMessage: <%= @web_widget.inbox.out_of_office_message.to_json.html_safe %>, outOfOfficeMessage: <%= @web_widget.inbox.out_of_office_message.to_json.html_safe %>,
utcOffset: '<%= ActiveSupport::TimeZone[@web_widget.inbox.timezone].formatted_offset %>' utcOffset: '<%= ActiveSupport::TimeZone[@web_widget.inbox.timezone].formatted_offset %>'