diff --git a/app/livechat/client/views/app/livechatDepartmentForm.html b/app/livechat/client/views/app/livechatDepartmentForm.html
index 3305382d02ec..6621458a767e 100644
--- a/app/livechat/client/views/app/livechatDepartmentForm.html
+++ b/app/livechat/client/views/app/livechatDepartmentForm.html
@@ -55,6 +55,50 @@
+
+
+ {{#if hasChatClosingTags}}
+
+ {{/if}}
{{#if customFieldsTemplate}}
{{> Template.dynamic template=customFieldsTemplate data=data }}
{{/if}}
diff --git a/app/livechat/client/views/app/livechatDepartmentForm.js b/app/livechat/client/views/app/livechatDepartmentForm.js
index 47458c4b0013..4af23b45ddcf 100644
--- a/app/livechat/client/views/app/livechatDepartmentForm.js
+++ b/app/livechat/client/views/app/livechatDepartmentForm.js
@@ -79,6 +79,18 @@ Template.livechatDepartmentForm.helpers({
.some((button) => button.groups
.some((group) => group.startsWith('livechat-department')));
},
+ chatClosingTags() {
+ return Template.instance().chatClosingTags.get();
+ },
+ availableDepartmentTags() {
+ return Template.instance().availableDepartmentTags.get();
+ },
+ hasAvailableTags() {
+ return [...Template.instance().availableTags.get()].length > 0;
+ },
+ hasChatClosingTags() {
+ return [...Template.instance().chatClosingTags.get()].length > 0;
+ },
});
Template.livechatDepartmentForm.events({
@@ -98,7 +110,7 @@ Template.livechatDepartmentForm.events({
const email = instance.$('input[name=email]').val();
const showOnOfflineForm = instance.$('input[name=showOnOfflineForm]:checked').val();
const requestTagBeforeClosingChat = instance.$('input[name=requestTagBeforeClosingChat]:checked').val();
-
+ const chatClosingTags = instance.chatClosingTags.get();
if (enabled !== '1' && enabled !== '0') {
return toastr.error(t('Please_select_enabled_yes_or_no'));
}
@@ -119,6 +131,7 @@ Template.livechatDepartmentForm.events({
showOnOfflineForm: showOnOfflineForm === '1',
requestTagBeforeClosingChat: requestTagBeforeClosingChat === '1',
email: email.trim(),
+ chatClosingTags,
};
}
@@ -190,6 +203,33 @@ Template.livechatDepartmentForm.events({
instance.departmentAgents.set(instance.departmentAgents.get().filter((agent) => agent.agentId !== this.agentId));
},
+
+ 'click #addTag'(e, instance) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ const isSelect = [...instance.availableTags.get()].length > 0;
+ const elId = isSelect ? '#tagSelect' : '#tagInput';
+ const elDefault = isSelect ? 'placeholder' : '';
+
+ const tag = $(elId).val();
+ const chatClosingTags = [...instance.chatClosingTags.get()];
+ if (tag === '' || chatClosingTags.indexOf(tag) > -1) {
+ return;
+ }
+
+ chatClosingTags.push(tag);
+ instance.chatClosingTags.set(chatClosingTags);
+ $(elId).val(elDefault);
+ },
+
+ 'click .remove-tag'(e, instance) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ const chatClosingTags = [...instance.chatClosingTags.get()].filter((el) => el !== this.valueOf());
+ instance.chatClosingTags.set(chatClosingTags);
+ },
});
Template.livechatDepartmentForm.onCreated(async function() {
@@ -199,21 +239,37 @@ Template.livechatDepartmentForm.onCreated(async function() {
this.tabBar = new RocketChatTabBar();
this.tabBar.showGroup(FlowRouter.current().route.name);
this.tabBarData = new ReactiveVar();
+ this.chatClosingTags = new ReactiveVar([]);
+ this.availableTags = new ReactiveVar([]);
+ this.availableDepartmentTags = new ReactiveVar([]);
this.onSelectAgents = ({ item: agent }) => {
this.selectedAgents.set([agent]);
};
- this.onClickTagAgent = ({ username }) => {
+ this.onClickTagAgents = ({ username }) => {
this.selectedAgents.set(this.selectedAgents.get().filter((user) => user.username !== username));
};
+ this.loadAvailableTags = (departmentId) => {
+ Meteor.call('livechat:getTagsList', (err, tagsList) => {
+ this.availableTags.set(tagsList || []);
+ const tags = this.availableTags.get();
+ const availableTags = tags
+ .filter(({ departments }) => departments.length === 0 || departments.indexOf(departmentId) > -1)
+ .map(({ name }) => name);
+ this.availableDepartmentTags.set(availableTags);
+ });
+ };
+
this.autorun(async () => {
const id = FlowRouter.getParam('_id');
if (id) {
const { department, agents } = await APIClient.v1.get(`livechat/department/${ FlowRouter.getParam('_id') }`);
this.department.set(department);
this.departmentAgents.set(agents);
+ this.chatClosingTags.set((department && department.chatClosingTags) || []);
+ this.loadAvailableTags(id);
}
});
});
diff --git a/app/livechat/client/views/app/tabbar/visitorInfo.js b/app/livechat/client/views/app/tabbar/visitorInfo.js
index 08b59ea10c04..3ebc6cc66a46 100644
--- a/app/livechat/client/views/app/tabbar/visitorInfo.js
+++ b/app/livechat/client/views/app/tabbar/visitorInfo.js
@@ -231,7 +231,7 @@ Template.visitorInfo.events({
'click .close-livechat'(event) {
event.preventDefault();
- const closeRoom = (comment) => Meteor.call('livechat:closeRoom', this.rid, comment, function(error/* , result*/) {
+ const closeRoom = (comment) => Meteor.call('livechat:closeRoom', this.rid, comment, { clientAction: true }, function(error/* , result*/) {
if (error) {
return handleError(error);
}
diff --git a/app/livechat/server/hooks/beforeCloseRoom.js b/app/livechat/server/hooks/beforeCloseRoom.js
index 0a2f84b489b8..de4f13a7f8c5 100644
--- a/app/livechat/server/hooks/beforeCloseRoom.js
+++ b/app/livechat/server/hooks/beforeCloseRoom.js
@@ -3,20 +3,34 @@ import { Meteor } from 'meteor/meteor';
import { callbacks } from '../../../callbacks';
import { LivechatDepartment } from '../../../models';
-callbacks.add('livechat.beforeCloseRoom', (room) => {
- const { departmentId } = room;
+const concatUnique = (...arrays) => [...new Set([].concat(...arrays.filter(Array.isArray)))];
+
+callbacks.add('livechat.beforeCloseRoom', ({ room, options }) => {
+ const { departmentId, tags: roomTags } = room;
if (!departmentId) {
- return room;
+ return;
}
const department = LivechatDepartment.findOneById(departmentId);
- if (!department || !department.requestTagBeforeClosingChat) {
- return room;
+ if (!department) {
+ return;
+ }
+
+ const { requestTagBeforeClosingChat, chatClosingTags } = department;
+ const extraData = {
+ tags: concatUnique(roomTags, chatClosingTags),
+ };
+
+ if (!requestTagBeforeClosingChat) {
+ return extraData;
}
- if (room.tags && room.tags.length > 0) {
- return room;
+ const { clientAction } = options;
+ const checkRoomTags = !clientAction || (roomTags && roomTags.length > 0);
+ const checkDepartmentTags = chatClosingTags && chatClosingTags.length > 0;
+ if (!checkRoomTags || !checkDepartmentTags) {
+ throw new Meteor.Error('error-tags-must-be-assigned-before-closing-chat', 'Tag(s) must be assigned before closing the chat', { method: 'livechat.beforeCloseRoom' });
}
- throw new Meteor.Error('error-tags-must-be-assigned-before-closing-chat', 'Tag(s) must be assigned before closing the chat', { method: 'livechat.beforeCloseRoom' });
+ return extraData;
}, callbacks.priority.HIGH, 'livechat-before-close-Room');
diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js
index 36ee6fc697c0..ac6734708319 100644
--- a/app/livechat/server/lib/Livechat.js
+++ b/app/livechat/server/lib/Livechat.js
@@ -314,18 +314,19 @@ export const Livechat = {
return ret;
},
- closeRoom({ user, visitor, room, comment }) {
+ closeRoom({ user, visitor, room, comment, options = {} }) {
if (!room || room.t !== 'l' || !room.open) {
return false;
}
- callbacks.run('livechat.beforeCloseRoom', room);
+ const extraData = callbacks.run('livechat.beforeCloseRoom', { room, options });
const now = new Date();
const closeData = {
closedAt: now,
chatDuration: (now.getTime() - room.ts) / 1000,
+ ...extraData,
};
if (user) {
@@ -807,6 +808,8 @@ export const Livechat = {
showOnRegistration: Boolean,
email: String,
showOnOfflineForm: Boolean,
+ requestTagBeforeClosingChat: Match.Optional(Boolean),
+ chatClosingTags: Match.Optional([String]),
};
// The Livechat Form department support addition/custom fields, so those fields need to be added before validating
@@ -825,6 +828,11 @@ export const Livechat = {
}),
]));
+ const { requestTagBeforeClosingChat, chatClosingTags } = departmentData;
+ if (requestTagBeforeClosingChat && (!chatClosingTags || chatClosingTags.length === 0)) {
+ throw new Meteor.Error('error-validating-department-chat-closing-tags', 'At least one closing tag is required when the department requires tag(s) on closing conversations.', { method: 'livechat:saveDepartment' });
+ }
+
if (_id) {
const department = LivechatDepartment.findOneById(_id);
if (!department) {
diff --git a/app/livechat/server/methods/closeRoom.js b/app/livechat/server/methods/closeRoom.js
index f7f2d18942d5..d4b1776da579 100644
--- a/app/livechat/server/methods/closeRoom.js
+++ b/app/livechat/server/methods/closeRoom.js
@@ -5,7 +5,7 @@ import { Subscriptions, LivechatRooms } from '../../../models';
import { Livechat } from '../lib/Livechat';
Meteor.methods({
- 'livechat:closeRoom'(roomId, comment) {
+ 'livechat:closeRoom'(roomId, comment, options = {}) {
const userId = Meteor.userId();
if (!userId || !hasPermission(userId, 'close-livechat-room')) {
throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'livechat:closeRoom' });
@@ -31,6 +31,7 @@ Meteor.methods({
user,
room: LivechatRooms.findOneById(roomId),
comment,
+ options,
});
},
});
diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js
index 91e560ea4f64..f4de7632962b 100644
--- a/app/models/server/models/LivechatRooms.js
+++ b/app/models/server/models/LivechatRooms.js
@@ -421,16 +421,19 @@ export class LivechatRooms extends Base {
closeByRoomId(roomId, closeInfo) {
+ const { closer, closedBy, closedAt, chatDuration, ...extraData } = closeInfo;
+
return this.update({
_id: roomId,
t: 'l',
}, {
$set: {
- closer: closeInfo.closer,
- closedBy: closeInfo.closedBy,
- closedAt: closeInfo.closedAt,
- 'metrics.chatDuration': closeInfo.chatDuration,
+ closer,
+ closedBy,
+ closedAt,
+ 'metrics.chatDuration': chatDuration,
'v.status': 'offline',
+ ...extraData,
},
$unset: {
open: 1,
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index bcef536dfcfc..1932dbd50e57 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -737,6 +737,8 @@
"Conversation": "Conversation",
"Conversations": "Conversations",
"Conversation_closed": "Conversation closed: __comment__.",
+ "Conversation_closing_tags": "Conversation closing tags",
+ "Conversation_closing_tags_description": "Closing tags will be automatically assigned to conversations at closing.",
"Conversation_finished": "Conversation Finished",
"Conversation_finished_message": "Conversation Finished Message",
"Conversation_finished_text": "Conversation Finished Text",
@@ -1402,6 +1404,7 @@
"error-logged-user-not-in-room": "You are not in the room `%s`",
"error-user-registration-disabled": "User registration is disabled",
"error-user-registration-secret": "User registration is only allowed via Secret URL",
+ "error-validating-department-chat-closing-tags": "At least one closing tag is required when the department requires tag(s) on closing conversations.",
"error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.",
"error-starring-message": "Message could not be stared",
"Error_404": "Error:404",
diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
index 6b18a52a876d..95d1e3091854 100644
--- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
+++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
@@ -677,6 +677,8 @@
"Conversation": "Conversa",
"Conversations": "Conversas",
"Conversation_closed": "Chat encerrado: __comment__.",
+ "Conversation_closing_tags": "Tags de encerramento de conversa",
+ "Conversation_closing_tags_description": "As tags de encerramento serão automaticamente atribuídas as conversas no seu encerramento.",
"Conversation_finished": "Conversa concluída",
"Conversation_finished_message": "Mensagem de conversa concluída",
"Conversation_finished_text": "Texto de conversa concluída",
@@ -1303,6 +1305,7 @@
"error-logged-user-not-in-room": "Você não está na sala `%s`",
"error-user-registration-disabled": "O registro do usuário está desativado",
"error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta",
+ "error-validating-department-chat-closing-tags": "Pelo menos uma tag de encerramento é necessária quando o departamento exige tags no encerramento de conversas.",
"error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.",
"Error_404": "Erro 404",
"Error_changing_password": "Erro ao alterar senha",