From 920f0a151a16b76024b2409f06988686c5e4ea70 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Fri, 22 Mar 2019 14:10:16 -0300 Subject: [PATCH 01/24] [IMPROVE] Ignore agent status when queuing incoming livechats via Guest Pool (#13818) * Several improvements regarding Guest Pool routing method. * rename const `agents` to `allAgents`. --- app/livechat/server/api/lib/livechat.js | 4 +++- app/livechat/server/lib/Livechat.js | 7 +----- app/livechat/server/lib/QueueMethods.js | 32 +++++++++++++++++-------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/livechat/server/api/lib/livechat.js b/app/livechat/server/api/lib/livechat.js index d4fa9ac5599f..24b906dfdec9 100644 --- a/app/livechat/server/api/lib/livechat.js +++ b/app/livechat/server/api/lib/livechat.js @@ -3,9 +3,11 @@ import { Random } from 'meteor/random'; import { Users, Rooms, LivechatVisitors, LivechatDepartment, LivechatTrigger } from '../../../../models'; import _ from 'underscore'; import { Livechat } from '../../lib/Livechat'; +import { settings as rcSettings } from '../../../../settings'; export function online() { - return Users.findOnlineAgents().count() > 0; + const onlineAgents = Livechat.getOnlineAgents(); + return (onlineAgents && onlineAgents.count() > 0) || rcSettings.get('Livechat_guest_pool_with_no_agents'); } export function findTriggers() { diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 4fbebf13a155..b66eb6d9feb4 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -533,12 +533,7 @@ export const Livechat = { const agentIds = []; // get the agents of the department if (departmentId) { - let agents = Livechat.getOnlineAgents(departmentId); - - if (agents.count() === 0 && settings.get('Livechat_guest_pool_with_no_agents')) { - agents = Livechat.getAgents(departmentId); - } - + const agents = Livechat.getAgents(departmentId); if (agents.count() === 0) { return false; } diff --git a/app/livechat/server/lib/QueueMethods.js b/app/livechat/server/lib/QueueMethods.js index e03aa2c93e36..6dd4642913c2 100644 --- a/app/livechat/server/lib/QueueMethods.js +++ b/app/livechat/server/lib/QueueMethods.js @@ -91,21 +91,23 @@ export const QueueMethods = { * only the client until paired with an agent */ 'Guest_Pool'(guest, message, roomInfo) { - let agents = Livechat.getOnlineAgents(guest.department); - - if (agents.count() === 0 && settings.get('Livechat_guest_pool_with_no_agents')) { - agents = Livechat.getAgents(guest.department); + const onlineAgents = Livechat.getOnlineAgents(guest.department); + if (settings.get('Livechat_guest_pool_with_no_agents') === false) { + if (!onlineAgents || onlineAgents.count() === 0) { + throw new Meteor.Error('no-agent-online', 'Sorry, no online agents'); + } } - if (agents.count() === 0) { - throw new Meteor.Error('no-agent-online', 'Sorry, no online agents'); + const allAgents = Livechat.getAgents(guest.department); + if (allAgents.count() === 0) { + throw new Meteor.Error('no-agent-available', 'Sorry, no available agents.'); } Rooms.updateLivechatRoomCount(); const agentIds = []; - agents.forEach((agent) => { + allAgents.forEach((agent) => { if (guest.department) { agentIds.push(agent.agentId); } else { @@ -157,16 +159,26 @@ export const QueueMethods = { LivechatInquiry.insert(inquiry); Rooms.insert(room); - // Alert the agents of the queued request - agentIds.forEach((agentId) => { + // Alert only the online agents of the queued request + onlineAgents.forEach((agent) => { + const { _id, active, emails, language, status, statusConnection, username } = agent; + sendNotification({ // fake a subscription in order to make use of the function defined above subscription: { rid: room._id, t : room.t, u: { - _id : agentId, + _id, }, + receiver: [{ + active, + emails, + language, + status, + statusConnection, + username, + }], }, sender: room.v, hasMentionToAll: true, // consider all agents to be in the room From ceb653921ba7f80f40208731de40a514c4914f03 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Fri, 22 Mar 2019 14:37:10 -0300 Subject: [PATCH 02/24] [IMPROVE] Filter agents with autocomplete input instead of select element (#13730) Closes #13728 --- .../views/app/livechatCurrentChats.html | 15 +++---- .../client/views/app/livechatCurrentChats.js | 44 +++++++++++++++---- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/app/livechat/client/views/app/livechatCurrentChats.html b/app/livechat/client/views/app/livechatCurrentChats.html index 4606ad8e4f09..7771bbcb1888 100644 --- a/app/livechat/client/views/app/livechatCurrentChats.html +++ b/app/livechat/client/views/app/livechatCurrentChats.html @@ -9,12 +9,7 @@
- + {{> inputAutocomplete settings=agentAutocompleteSettings id="agent" class="rc-input__element" autocomplete="off"}}
@@ -57,11 +52,11 @@ {{#if isClosed}} {{else}} -   - {{/if}} - {{else}} +   + {{/if}} + {{else}}   - {{/requiresPermission}} + {{/requiresPermission}} {{/each}} diff --git a/app/livechat/client/views/app/livechatCurrentChats.js b/app/livechat/client/views/app/livechatCurrentChats.js index b0f7147ceab7..b73c32cb4365 100644 --- a/app/livechat/client/views/app/livechatCurrentChats.js +++ b/app/livechat/client/views/app/livechatCurrentChats.js @@ -3,9 +3,8 @@ import { Mongo } from 'meteor/mongo'; import { ReactiveVar } from 'meteor/reactive-var'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Template } from 'meteor/templating'; -import { modal } from '../../../../ui-utils'; -import { t, handleError } from '../../../../utils'; -import { AgentUsers } from '../../collections/AgentUsers'; +import { modal } from '/app/ui-utils'; +import { t, handleError } from '/app/utils'; import _ from 'underscore'; import moment from 'moment'; @@ -27,12 +26,27 @@ Template.livechatCurrentChats.helpers({ status() { return this.open ? t('Opened') : t('Closed'); }, - agents() { - return AgentUsers.find({}, { sort: { name: 1 } }); - }, isClosed() { return !this.open; }, + agentAutocompleteSettings() { + return { + limit: 10, + inputDelay: 300, + rules: [{ + collection: 'UserAndRoom', + subscription: 'userAutocomplete', + field: 'username', + template: Template.userSearch, + noMatchTemplate: Template.userSearchEmpty, + matchAll: true, + selector(match) { + return { term: match }; + }, + sort: 'username', + }], + }; + }, }); Template.livechatCurrentChats.events({ @@ -64,6 +78,10 @@ Template.livechatCurrentChats.events({ delete filter.to; } + if (!_.isEmpty(instance.selectedAgent.get())) { + filter.agent = instance.selectedAgent.get(); + } + instance.filter.set(filter); instance.limit.set(20); }, @@ -95,14 +113,22 @@ Template.livechatCurrentChats.events({ }); }); }, + 'autocompleteselect input[id=agent]'(event, template, agent) { + template.selectedAgent.set(agent._id); + }, + + 'input [id=agent]'(event, template) { + const input = event.currentTarget; + if (input.value === '') { + template.selectedAgent.set(); + } + }, }); Template.livechatCurrentChats.onCreated(function() { this.limit = new ReactiveVar(20); this.filter = new ReactiveVar({}); - - this.subscribe('livechat:agents'); - + this.selectedAgent = new ReactiveVar; this.autorun(() => { this.subscribe('livechat:rooms', this.filter.get(), 0, this.limit.get()); }); From be18e1898dcfe16a8699f53e40e737de066debe7 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 22 Mar 2019 16:51:06 -0300 Subject: [PATCH 03/24] Rename Threads to Discussion (#13782) --- .../server/methods/saveRoomSettings.js | 2 +- .../client/createDiscussionMessageAction.js | 53 ++++++ .../client/discussionFromMessageBox.js | 36 ++++ app/discussion/client/index.js | 16 ++ .../client/lib/discussionsOfRoom.js | 3 + .../lib/messageTypes/discussionMessage.js | 17 ++ .../client/public/stylesheets/discussion.css | 22 +++ .../client/tabBar.js | 11 +- .../client/views/DiscussionList.html} | 4 +- .../client/views/DiscussionList.js} | 12 +- .../client/views/DiscussionTabbar.html} | 8 +- .../client/views/DiscussionTabbar.js} | 25 ++- .../creationDialog/CreateDiscussion.html} | 63 +++---- .../views/creationDialog/CreateDiscussion.js} | 68 ++++---- app/{threading => discussion}/index.js | 0 .../lib/discussionRoomType.js} | 12 +- .../server/authorization.js | 0 app/discussion/server/config.js | 38 ++++ .../server/hooks/joinDiscussionOnMessage.js} | 9 +- .../hooks/propagateDiscussionMetadata.js | 27 +++ app/discussion/server/index.js | 14 ++ .../server/methods/createDiscussion.js | 133 ++++++++++++++ .../server/permissions.js | 6 +- .../discussionParentAutocomplete.js} | 8 +- .../server/publications/discussionsOfRoom.js | 32 ++++ app/lib/lib/roomTypes/private.js | 2 +- app/lib/lib/roomTypes/public.js | 2 +- app/lib/server/functions/addUserToRoom.js | 2 +- app/lib/server/functions/cleanRoomHistory.js | 14 +- app/lib/server/methods/cleanRoomHistory.js | 4 +- .../client/messageAttachment.html | 5 +- .../client/stylesheets/messageAttachments.css | 2 + app/models/server/models/Messages.js | 46 ++--- app/models/server/models/Rooms.js | 6 +- .../server/cronPruneMessages.js | 6 +- .../client/imports/components/header.css | 2 + app/theme/client/imports/components/modal.css | 2 - .../components/modal/create-channel.css | 4 +- .../client/imports/components/userInfo.css | 2 + app/theme/client/imports/forms/button.css | 7 +- app/theme/client/imports/forms/input.css | 10 +- app/theme/client/imports/general/base_old.css | 8 - .../client/imports/general/variables.css | 4 +- .../client/createThreadMessageAction.js | 49 ------ app/threading/client/index.js | 20 --- .../client/lib/messageTypes/threadMessage.js | 32 ---- app/threading/client/lib/threadsOfRoom.js | 3 - .../client/public/stylesheets/threading.css | 34 ---- app/threading/client/threadFromMessageBox.js | 33 ---- .../views/fieldTypeThreadLastMessageAge.html | 7 - .../views/fieldTypeThreadLastMessageAge.js | 12 -- .../views/fieldTypeThreadReplyCounter.html | 19 -- .../views/fieldTypeThreadReplyCounter.js | 30 ---- app/threading/server/config.js | 42 ----- .../server/hooks/propagateThreadMetadata.js | 28 --- app/threading/server/index.js | 14 -- app/threading/server/methods/createThread.js | 162 ------------------ .../server/publications/threadsOfRoom.js | 31 ---- app/ui-account/client/accountPreferences.html | 8 +- app/ui-account/client/accountPreferences.js | 2 +- .../client/views/cleanHistory.html | 4 +- .../client/views/cleanHistory.js | 14 +- app/ui-message/client/message.html | 19 +- app/ui-message/client/message.js | 11 ++ app/ui-message/client/messageBox.js | 2 +- app/ui-sidenav/client/chatRoomItem.js | 2 +- app/ui-sidenav/client/roomList.js | 8 +- app/ui-sidenav/client/sidebarHeader.js | 50 +++--- app/ui-sidenav/client/sortlist.html | 9 + app/ui-sidenav/client/sortlist.js | 4 + app/ui-utils/client/lib/RoomManager.js | 6 +- app/ui-utils/client/lib/modal.html | 12 +- app/ui-utils/client/lib/modal.js | 5 + .../client/components/header/headerRoom.html | 2 +- app/ui/client/components/header/headerRoom.js | 8 +- app/ui/client/lib/chatMessages.js | 6 +- app/ui/client/views/app/room.js | 5 + client/importPackages.js | 2 +- client/importsCss.js | 1 - packages/rocketchat-i18n/i18n/de.i18n.json | 29 +--- packages/rocketchat-i18n/i18n/en.i18n.json | 53 +++--- private/client/imports/general/variables.css | 4 +- private/public/icons.svg | 3 + server/importPackages.js | 2 +- server/startup/migrations/index.js | 1 + server/startup/migrations/v139.js | 118 +++++++++++++ tests/end-to-end/api/00-miscellaneous.js | 2 +- .../ui/{15-threading.js => 15-discussion.js} | 16 +- .../{threading.page.js => discussion.page.js} | 46 ++--- tests/pageobjects/side-nav.page.js | 2 +- 90 files changed, 873 insertions(+), 846 deletions(-) create mode 100644 app/discussion/client/createDiscussionMessageAction.js create mode 100644 app/discussion/client/discussionFromMessageBox.js create mode 100644 app/discussion/client/index.js create mode 100644 app/discussion/client/lib/discussionsOfRoom.js create mode 100644 app/discussion/client/lib/messageTypes/discussionMessage.js create mode 100644 app/discussion/client/public/stylesheets/discussion.css rename app/{threading => discussion}/client/tabBar.js (51%) rename app/{threading/client/views/ThreadList.html => discussion/client/views/DiscussionList.html} (76%) rename app/{threading/client/views/ThreadList.js => discussion/client/views/DiscussionList.js} (70%) rename app/{threading/client/views/ThreadsTabbar.html => discussion/client/views/DiscussionTabbar.html} (62%) rename app/{threading/client/views/ThreadsTabbar.js => discussion/client/views/DiscussionTabbar.js} (66%) rename app/{threading/client/views/creationDialog/CreateThread.html => discussion/client/views/creationDialog/CreateDiscussion.html} (59%) rename app/{threading/client/views/creationDialog/CreateThread.js => discussion/client/views/creationDialog/CreateDiscussion.js} (79%) rename app/{threading => discussion}/index.js (100%) rename app/{threading/lib/threadRoomType.js => discussion/lib/discussionRoomType.js} (50%) rename app/{threading => discussion}/server/authorization.js (100%) create mode 100644 app/discussion/server/config.js rename app/{threading/server/hooks/joinThreadOnMessage.js => discussion/server/hooks/joinDiscussionOnMessage.js} (67%) create mode 100644 app/discussion/server/hooks/propagateDiscussionMetadata.js create mode 100644 app/discussion/server/index.js create mode 100644 app/discussion/server/methods/createDiscussion.js rename app/{threading => discussion}/server/permissions.js (60%) rename app/{threading/server/publications/threadParentAutocomplete.js => discussion/server/publications/discussionParentAutocomplete.js} (69%) create mode 100644 app/discussion/server/publications/discussionsOfRoom.js delete mode 100644 app/threading/client/createThreadMessageAction.js delete mode 100644 app/threading/client/index.js delete mode 100644 app/threading/client/lib/messageTypes/threadMessage.js delete mode 100644 app/threading/client/lib/threadsOfRoom.js delete mode 100644 app/threading/client/public/stylesheets/threading.css delete mode 100644 app/threading/client/threadFromMessageBox.js delete mode 100644 app/threading/client/views/fieldTypeThreadLastMessageAge.html delete mode 100644 app/threading/client/views/fieldTypeThreadLastMessageAge.js delete mode 100644 app/threading/client/views/fieldTypeThreadReplyCounter.html delete mode 100644 app/threading/client/views/fieldTypeThreadReplyCounter.js delete mode 100644 app/threading/server/config.js delete mode 100644 app/threading/server/hooks/propagateThreadMetadata.js delete mode 100644 app/threading/server/index.js delete mode 100644 app/threading/server/methods/createThread.js delete mode 100644 app/threading/server/publications/threadsOfRoom.js create mode 100644 server/startup/migrations/v139.js rename tests/end-to-end/ui/{15-threading.js => 15-discussion.js} (78%) rename tests/pageobjects/{threading.page.js => discussion.page.js} (50%) diff --git a/app/channel-settings/server/methods/saveRoomSettings.js b/app/channel-settings/server/methods/saveRoomSettings.js index ee21ae0523c7..3223584951e4 100644 --- a/app/channel-settings/server/methods/saveRoomSettings.js +++ b/app/channel-settings/server/methods/saveRoomSettings.js @@ -60,7 +60,7 @@ Meteor.methods({ } if (room.prid) { - throw new Meteor.Error('error-action-not-allowed', 'Editing thread room is not allowed', { + throw new Meteor.Error('error-action-not-allowed', 'Editing discussion room is not allowed', { method: 'saveRoomSettings', action: 'Editing_room', }); diff --git a/app/discussion/client/createDiscussionMessageAction.js b/app/discussion/client/createDiscussionMessageAction.js new file mode 100644 index 000000000000..eebf5853d009 --- /dev/null +++ b/app/discussion/client/createDiscussionMessageAction.js @@ -0,0 +1,53 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; + +import { Subscriptions } from '../../models/client'; +import { settings } from '../../settings/client'; +import { hasPermission } from '../../authorization/client'; +import { MessageAction, modal } from '../../ui-utils/client'; +import { t } from '../../utils/client'; + +const condition = (rid, uid) => { + if (!Subscriptions.findOne({ rid })) { + return false; + } + return uid !== Meteor.userId() ? hasPermission('start-discussion-other-user') : hasPermission('start-discussion'); +}; + +Meteor.startup(function() { + Tracker.autorun(() => { + if (!settings.get('Discussion_enabled')) { + return MessageAction.removeButton('start-discussion'); + } + + MessageAction.addButton({ + id: 'start-discussion', + icon: 'discussion', + label: 'Discussion_start', + context: ['message', 'message-mobile'], + async action() { + const [, message] = this._arguments; + + modal.open({ + title: t('Discussion_title'), + modifier: 'modal', + content: 'CreateDiscussion', + data: { rid: message.rid, message, onCreate() { + modal.close(); + } }, + confirmOnEnter: false, + showConfirmButton: false, + showCancelButton: false, + }); + }, + condition({ rid, u: { _id: uid }, drid, dcount }) { + if (drid || !isNaN(dcount)) { + return false; + } + return condition(rid, uid); + }, + order: 0, + group: 'menu', + }); + }); +}); diff --git a/app/discussion/client/discussionFromMessageBox.js b/app/discussion/client/discussionFromMessageBox.js new file mode 100644 index 000000000000..dbade46fc93d --- /dev/null +++ b/app/discussion/client/discussionFromMessageBox.js @@ -0,0 +1,36 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; + +import { messageBox, modal } from '../../ui-utils/client'; +import { t } from '../../utils/client'; +import { settings } from '../../settings/client'; + +Meteor.startup(function() { + Tracker.autorun(() => { + if (!settings.get('Discussion_enabled')) { + return messageBox.actions.remove('Create_new', /start-discussion/); + } + messageBox.actions.add('Create_new', 'Discussion', { + id: 'start-discussion', + icon: 'discussion', + condition: () => true, + action(data) { + modal.open({ + title: t('Discussion_title'), + modifier: 'modal', + content: 'CreateDiscussion', + data: { + ...data, + onCreate() { + modal.close(); + }, + }, + showConfirmButton: false, + showCancelButton: false, + confirmOnEnter: false, + }); + }, + }); + + }); +}); diff --git a/app/discussion/client/index.js b/app/discussion/client/index.js new file mode 100644 index 000000000000..9a7bdc13cd5d --- /dev/null +++ b/app/discussion/client/index.js @@ -0,0 +1,16 @@ +// Templates +import './views/creationDialog/CreateDiscussion'; +import './views/DiscussionList'; +import './views/DiscussionTabbar'; + +// Other UI extensions +import './lib/messageTypes/discussionMessage'; +import './lib/discussionsOfRoom'; +import './createDiscussionMessageAction'; +import './discussionFromMessageBox'; +import './tabBar'; + +import '../lib/discussionRoomType'; + +// Style +import './public/stylesheets/discussion.css'; diff --git a/app/discussion/client/lib/discussionsOfRoom.js b/app/discussion/client/lib/discussionsOfRoom.js new file mode 100644 index 000000000000..a620af48a42d --- /dev/null +++ b/app/discussion/client/lib/discussionsOfRoom.js @@ -0,0 +1,3 @@ +import { Mongo } from 'meteor/mongo'; + +export const DiscussionOfRoom = new Mongo.Collection('rocketchat_discussions_of_room'); diff --git a/app/discussion/client/lib/messageTypes/discussionMessage.js b/app/discussion/client/lib/messageTypes/discussionMessage.js new file mode 100644 index 000000000000..5dc928a7f38e --- /dev/null +++ b/app/discussion/client/lib/messageTypes/discussionMessage.js @@ -0,0 +1,17 @@ +import { Meteor } from 'meteor/meteor'; + +import { MessageTypes } from '../../../../ui-utils/client'; + +Meteor.startup(function() { + MessageTypes.registerType({ + id: 'discussion-created', + system: false, + message: 'discussion-created', + data(message) { + return { + // channelLink: `${ TAPi18n.__('discussion') }`, + message: ` ${ message.msg }`, + }; + }, + }); +}); diff --git a/app/discussion/client/public/stylesheets/discussion.css b/app/discussion/client/public/stylesheets/discussion.css new file mode 100644 index 000000000000..93c6d4e8e20f --- /dev/null +++ b/app/discussion/client/public/stylesheets/discussion.css @@ -0,0 +1,22 @@ +.message-discussion { + display: flex; + + padding: 0.5rem 0; + align-items: center; +} + +.discussion-reply-lm { + padding: 4px 8px; + + color: var(--color-gray); + + font-size: 12px; +} + +.discussions-list .load-more { + text-align: center; + text-transform: lowercase; + + font-style: italic; + line-height: 40px; +} diff --git a/app/threading/client/tabBar.js b/app/discussion/client/tabBar.js similarity index 51% rename from app/threading/client/tabBar.js rename to app/discussion/client/tabBar.js index ccff24215665..8ab8d47bda70 100644 --- a/app/threading/client/tabBar.js +++ b/app/discussion/client/tabBar.js @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; -import { TabBar } from '../../ui-utils'; + +import { TabBar } from '../../ui-utils/client'; Meteor.startup(function() { return TabBar.addButton({ groups: ['channel', 'group', 'direct'], - id: 'threads', - i18nTitle: 'Threads', - icon: 'thread', - template: 'threadsTabbar', + id: 'discussions', + i18nTitle: 'Discussions', + icon: 'discussion', + template: 'discussionsTabbar', order: 10, }); }); diff --git a/app/threading/client/views/ThreadList.html b/app/discussion/client/views/DiscussionList.html similarity index 76% rename from app/threading/client/views/ThreadList.html rename to app/discussion/client/views/DiscussionList.html index 35df571080ba..4792f63418ce 100644 --- a/app/threading/client/views/ThreadList.html +++ b/app/discussion/client/views/DiscussionList.html @@ -1,7 +1,7 @@ -