From 58b14d1069b6978915ec725d30e9573ca30e6685 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 13 Apr 2020 13:32:58 -0300 Subject: [PATCH 01/31] Apply $and helper to message template (#17280) --- app/ui-message/client/message.html | 2 +- app/ui-message/client/message.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html index a69ad4ca7a71..84b56e5dbe76 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -121,7 +121,7 @@ {{/if}} - {{#if and settings.showReplyButton msg.tcount}} + {{#if $and settings.showReplyButton msg.tcount}}
{{else}} diff --git a/app/ui-admin/client/rooms/channelSettingsDefault.js b/app/ui-admin/client/rooms/channelSettingsDefault.js index e7c3cb4db00e..2b57af949ebb 100644 --- a/app/ui-admin/client/rooms/channelSettingsDefault.js +++ b/app/ui-admin/client/rooms/channelSettingsDefault.js @@ -15,22 +15,34 @@ Template.channelSettingsDefault.helpers({ return Template.instance().editing.get() === field; }, roomDefault() { - const room = Template.instance().room.get(); - - if (room) { - return room.default; - } + return Template.instance().isDefault.get(); + }, + isFavorite() { + return Template.instance().isFavorite.get(); }, defaultDescription() { - const room = Template.instance().room.get(); - if (room && room.default) { - return t('True'); + const { room, isDefault, isFavorite } = { room: Template.instance().room.get(), isDefault: Template.instance().isDefault.get(), isFavorite: Template.instance().isFavorite.get() }; + let description = t('False'); + + if (room && isDefault) { + description = t('True'); + + if (isFavorite) { + description += `, ${ t('Set_as_favorite') }`; + } + return description; } - return t('False'); + return description; }, }); Template.channelSettingsDefault.events({ + 'change input[name=default]'(e, t) { + t.isDefault.set(e.currentTarget.value === 'true'); + }, + 'change input[name=favorite]'(e, t) { + t.isFavorite.set(e.currentTarget.checked); + }, 'click [data-edit]'(e, t) { e.preventDefault(); t.editing.set($(e.currentTarget).data('edit')); @@ -45,7 +57,7 @@ Template.channelSettingsDefault.events({ 'click .save'(e, t) { e.preventDefault(); - Meteor.call('saveRoomSettings', Template.instance().room.get()._id, 'default', $('input[name=default]:checked').val(), (err/* , result*/) => { + Meteor.call('saveRoomSettings', t.room.get()._id, { default: t.isDefault.get(), favorite: { defaultValue: t.isDefault.get(), favorite: t.isFavorite.get() } }, (err/* , result*/) => { if (err) { return handleError(err); } @@ -58,11 +70,15 @@ Template.channelSettingsDefault.events({ Template.channelSettingsDefault.onCreated(function() { this.editing = new ReactiveVar(); + this.isDefault = new ReactiveVar(); + this.isFavorite = new ReactiveVar(); this.room = new ReactiveVar(); this.onSuccess = Template.currentData().onSuccess; this.autorun(() => { const { room } = Template.currentData(); + this.isDefault.set(room && room.default); + this.isFavorite.set(room && room.favorite && room.default); this.room.set(room); }); }); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 8d720436922a..65c0fe5b244f 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3039,6 +3039,7 @@ "set-readonly": "Set ReadOnly", "set-readonly_description": "Permission to set a channel to read only channel", "Set_as_leader": "Set as leader", + "Set_as_favorite": "Set as favorite", "Set_as_moderator": "Set as moderator", "Set_as_owner": "Set as owner", "Settings": "Settings", From c180a53698f92c8d7408060626f6bb810f275c9d Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 13 Apr 2020 17:57:21 -0300 Subject: [PATCH 03/31] Static props for Administration route components (#17285) --- app/ui-admin/client/components/AdministrationRouter.js | 4 ++-- app/ui-admin/client/routes.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/ui-admin/client/components/AdministrationRouter.js b/app/ui-admin/client/components/AdministrationRouter.js index 8fc0486cd444..5efe3aff39a8 100644 --- a/app/ui-admin/client/components/AdministrationRouter.js +++ b/app/ui-admin/client/components/AdministrationRouter.js @@ -3,13 +3,13 @@ import React, { lazy, useMemo, Suspense } from 'react'; import { useAdminSideNav } from '../hooks/useAdminSideNav'; import PageSkeleton from './PageSkeleton'; -function AdministrationRouter({ lazyRouteComponent }) { +function AdministrationRouter({ lazyRouteComponent, ...props }) { useAdminSideNav(); const LazyRouteComponent = useMemo(() => lazy(lazyRouteComponent), [lazyRouteComponent]); return }> - + ; } diff --git a/app/ui-admin/client/routes.js b/app/ui-admin/client/routes.js index 0cf97ff76672..910e33863006 100644 --- a/app/ui-admin/client/routes.js +++ b/app/ui-admin/client/routes.js @@ -9,7 +9,7 @@ const routeGroup = FlowRouter.group({ prefix: '/admin', }); -export const registerAdminRoute = (path, { lazyRouteComponent, action, ...options } = {}) => { +export const registerAdminRoute = (path, { lazyRouteComponent, props, action, ...options } = {}) => { routeGroup.route(path, { ...options, action: (params, queryParams) => { @@ -21,7 +21,7 @@ export const registerAdminRoute = (path, { lazyRouteComponent, action, ...option renderRouteComponent(() => import('./components/AdministrationRouter'), { template: 'main', region: 'center', - propsFn: () => ({ lazyRouteComponent, ...options, params, queryParams }), + propsFn: () => ({ lazyRouteComponent, ...options, params, queryParams, ...props }), }); }, }); From 6a94e3c9e5d4435bdfe7cb2f162c0902613c7b49 Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Mon, 13 Apr 2020 18:43:54 -0300 Subject: [PATCH 04/31] [NEW][ENTERPRISE] Auto close abandoned Omnichannel rooms (#17055) * close/freeze automatically inactive omnichannel rooms * Apply suggestions from review * Apply suggestions from review * Apply suggestions from review * Apply suggestions from review * Set livechat rooms as waiting response after visitor's messages * Remove unnecessary console.log calls. Co-authored-by: Renato Becker --- .../server/hooks/markRoomNotResponded.js | 24 +++++ .../server/hooks/processRoomAbandonment.js | 2 +- app/livechat/server/index.js | 1 + app/models/server/models/LivechatRooms.js | 17 +++- .../livechatDepartmentCustomFieldsForm.html | 12 +++ .../hooks/afterForwardChatToDepartment.js | 2 + .../setPredictedVisitorAbandonmentTime.js | 26 ++++++ ee/app/livechat-enterprise/server/index.js | 1 + .../livechat-enterprise/server/lib/Helper.js | 33 ++++++- .../server/lib/VisitorInactivityMonitor.js | 87 +++++++++++++++++++ ee/app/livechat-enterprise/server/settings.js | 15 ++++ ee/app/livechat-enterprise/server/startup.js | 15 +++- ee/app/models/server/models/LivechatRooms.js | 34 ++++++++ ee/i18n/en.i18n.json | 5 ++ ee/i18n/pt-BR.i18n.json | 5 ++ 15 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 app/livechat/server/hooks/markRoomNotResponded.js create mode 100644 ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js create mode 100644 ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js diff --git a/app/livechat/server/hooks/markRoomNotResponded.js b/app/livechat/server/hooks/markRoomNotResponded.js new file mode 100644 index 000000000000..08ab7d1d9272 --- /dev/null +++ b/app/livechat/server/hooks/markRoomNotResponded.js @@ -0,0 +1,24 @@ + +import { callbacks } from '../../../callbacks'; +import { LivechatRooms } from '../../../models'; + +callbacks.add('afterSaveMessage', function(message, room) { + // skips this callback if the message was edited + if (!message || message.editedAt) { + return message; + } + + // if the message has not a token, it was sent by the agent, so ignore it + if (!message.token) { + return message; + } + + // check if room is yet awaiting for response + if (typeof room.t !== 'undefined' && room.t === 'l' && room.waitingResponse) { + return message; + } + + LivechatRooms.setNotResponseByRoomId(room._id); + + return message; +}, callbacks.priority.LOW, 'markRoomNotResponded'); diff --git a/app/livechat/server/hooks/processRoomAbandonment.js b/app/livechat/server/hooks/processRoomAbandonment.js index 50bfeb5b469c..70f88ea7ed50 100644 --- a/app/livechat/server/hooks/processRoomAbandonment.js +++ b/app/livechat/server/hooks/processRoomAbandonment.js @@ -58,5 +58,5 @@ callbacks.add('livechat.closeRoom', (room) => { return; } const secondsSinceLastAgentResponse = getSecondsSinceLastAgentResponse(room, agentLastMessage); - LivechatRooms.setVisitorInactivityInSecondsByRoomId(room._id, secondsSinceLastAgentResponse); + LivechatRooms.setVisitorInactivityInSecondsById(room._id, secondsSinceLastAgentResponse); }, callbacks.priority.HIGH, 'process-room-abandonment'); diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js index f806301c05d8..fe61cd6f5825 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -17,6 +17,7 @@ import './hooks/sendToCRM'; import './hooks/sendToFacebook'; import './hooks/processRoomAbandonment'; import './hooks/saveLastVisitorMessageTs'; +import './hooks/markRoomNotResponded'; import './methods/addAgent'; import './methods/addManager'; import './methods/changeLivechatStatus'; diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 1a93ce690a0a..65b5b6e43047 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -15,6 +15,7 @@ export class LivechatRooms extends Base { this.tryEnsureIndex({ 'metrics.chatDuration': 1 }, { sparse: true }); this.tryEnsureIndex({ 'metrics.serviceTimeDuration': 1 }, { sparse: true }); this.tryEnsureIndex({ 'metrics.visitorInactivity': 1 }, { sparse: true }); + this.tryEnsureIndex({ 'omnichannel.predictedVisitorAbandonmentAt': 1 }, { sparse: true }); } findLivechat(filter = {}, offset = 0, limit = 20) { @@ -286,6 +287,20 @@ export class LivechatRooms extends Base { }); } + setNotResponseByRoomId(roomId) { + return this.update({ + _id: roomId, + t: 'l', + }, { + $set: { + waitingResponse: true, + }, + $unset: { + responseBy: 1, + }, + }); + } + setAgentLastMessageTs(roomId) { return this.update({ _id: roomId, @@ -566,7 +581,7 @@ export class LivechatRooms extends Base { return this.update(query, update); } - setVisitorInactivityInSecondsByRoomId(roomId, visitorInactivity) { + setVisitorInactivityInSecondsById(roomId, visitorInactivity) { const query = { _id: roomId, }; diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html index 38d41dda64d5..c0cd15e90ca8 100644 --- a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html @@ -5,6 +5,18 @@
+
+ +
+ +
+
+
+ +
+ +
+
diff --git a/ee/app/livechat-enterprise/server/hooks/afterForwardChatToDepartment.js b/ee/app/livechat-enterprise/server/hooks/afterForwardChatToDepartment.js index 66fe74c1d72f..ed8b311c8835 100644 --- a/ee/app/livechat-enterprise/server/hooks/afterForwardChatToDepartment.js +++ b/ee/app/livechat-enterprise/server/hooks/afterForwardChatToDepartment.js @@ -1,6 +1,7 @@ import { callbacks } from '../../../../../app/callbacks/server'; import LivechatRooms from '../../../../../app/models/server/models/LivechatRooms'; import LivechatDepartment from '../../../../../app/models/server/models/LivechatDepartment'; +import { setPredictedVisitorAbandonmentTime } from '../lib/Helper'; callbacks.add('livechat.afterForwardChatToDepartment', (options) => { const { rid, newDepartmentId } = options; @@ -9,6 +10,7 @@ callbacks.add('livechat.afterForwardChatToDepartment', (options) => { if (!room) { return; } + setPredictedVisitorAbandonmentTime(room); const department = LivechatDepartment.findOneById(newDepartmentId, { fields: { ancestors: 1 } }); if (!department) { diff --git a/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js new file mode 100644 index 000000000000..111e73fbccdf --- /dev/null +++ b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js @@ -0,0 +1,26 @@ +import { callbacks } from '../../../../../app/callbacks/server'; +import { settings } from '../../../../../app/settings/server'; +import { setPredictedVisitorAbandonmentTime } from '../lib/Helper'; + +callbacks.add('afterSaveMessage', function(message, room) { + if (!settings.get('Livechat_auto_close_abandoned_rooms') || settings.get('Livechat_visitor_inactivity_timeout') <= 0) { + return message; + } + // skips this callback if the message was edited + if (message.editedAt) { + return false; + } + // message valid only if it is a livechat room + if (!(typeof room.t !== 'undefined' && room.t === 'l' && room.v && room.v.token)) { + return false; + } + // if the message has a type means it is a special message (like the closing comment), so skips + if (message.t) { + return false; + } + const sentByAgent = !message.token; + if (sentByAgent) { + setPredictedVisitorAbandonmentTime(room); + } + return message; +}, callbacks.priority.HIGH, 'save-visitor-inactivity'); diff --git a/ee/app/livechat-enterprise/server/index.js b/ee/app/livechat-enterprise/server/index.js index e532db6f935b..e15a5bef4edc 100644 --- a/ee/app/livechat-enterprise/server/index.js +++ b/ee/app/livechat-enterprise/server/index.js @@ -3,6 +3,7 @@ import { Meteor } from 'meteor/meteor'; import './hooks/addDepartmentAncestors'; import './hooks/afterForwardChatToDepartment'; import './hooks/beforeListTags'; +import './hooks/setPredictedVisitorAbandonmentTime'; import './methods/addMonitor'; import './methods/getUnitsFromUserRoles'; import './methods/removeMonitor'; diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index e13dc640baed..ac4579219080 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -1,8 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; +import moment from 'moment'; import { hasRole } from '../../../../../app/authorization'; -import { LivechatDepartment, Users, LivechatInquiry } from '../../../../../app/models/server'; +import { LivechatDepartment, Users, LivechatInquiry, LivechatRooms } from '../../../../../app/models/server'; import { Rooms as RoomRaw } from '../../../../../app/models/server/raw'; import { settings } from '../../../../../app/settings'; import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; @@ -64,7 +65,7 @@ export const normalizeQueueInfo = async ({ position, queueInfo, department }) => queueInfo = await getQueueInfo(department); } - const { message, numberMostRecentChats, statistics: { avgChatDuration } = { } } = queueInfo; + const { message, numberMostRecentChats, statistics: { avgChatDuration } = {} } = queueInfo; const spot = position + 1; const estimatedWaitTimeSeconds = getSpotEstimatedWaitTime(spot, numberMostRecentChats, avgChatDuration); return { spot, message, estimatedWaitTimeSeconds }; @@ -137,3 +138,31 @@ export const allowAgentSkipQueue = (agent) => { return settings.get('Livechat_assign_new_conversation_to_bot') && hasRole(agent.agentId, 'bot'); }; + +export const setPredictedVisitorAbandonmentTime = (room) => { + if (!room.v || !room.v.lastMessageTs || !settings.get('Livechat_auto_close_abandoned_rooms')) { + return; + } + + let secondsToAdd = settings.get('Livechat_visitor_inactivity_timeout'); + + const department = room.departmentId && LivechatDepartment.findOneById(room.departmentId); + if (department && department.visitorInactivityTimeoutInSeconds) { + secondsToAdd = department.visitorInactivityTimeoutInSeconds; + } + + if (secondsToAdd <= 0) { + return; + } + + const willBeAbandonedAt = moment(room.v.lastMessageTs).add(Number(secondsToAdd), 'seconds').toDate(); + LivechatRooms.setPredictedVisitorAbandonment(room._id, willBeAbandonedAt); +}; + +export const updatePredictedVisitorAbandonment = () => { + if (settings.get('Livechat_auto_close_abandoned_rooms')) { + LivechatRooms.findLivechat({ open: true }).forEach((room) => setPredictedVisitorAbandonmentTime(room)); + } else { + LivechatRooms.unsetPredictedVisitorAbandonment(); + } +}; diff --git a/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js b/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js new file mode 100644 index 000000000000..11879836e182 --- /dev/null +++ b/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js @@ -0,0 +1,87 @@ +import { SyncedCron } from 'meteor/littledata:synced-cron'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { settings } from '../../../../../app/settings/server'; +import { LivechatRooms, LivechatDepartment, Users } from '../../../../../app/models/server'; +import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; + +export class VisitorInactivityMonitor { + constructor() { + this._started = false; + this._name = 'Omnichannel Visitor Inactivity Monitor'; + this.messageCache = new Map(); + this.userToPerformAutomaticClosing; + } + + start() { + this._startMonitoring(); + this._initializeMessageCache(); + this.userToPerformAutomaticClosing = Users.findOneById('rocket.cat'); + } + + _startMonitoring() { + if (this.isRunning()) { + return; + } + const everyMinute = '* * * * *'; + SyncedCron.add({ + name: this._name, + schedule: (parser) => parser.cron(everyMinute), + job: () => { + this.handleAbandonedRooms(); + }, + }); + this._started = true; + } + + stop() { + if (!this.isRunning()) { + return; + } + + SyncedCron.remove(this._name); + + this._started = false; + } + + isRunning() { + return this._started; + } + + _initializeMessageCache() { + this.messageCache.clear(); + this.messageCache.set('default', settings.get('Livechat_abandoned_rooms_closed_custom_message') || TAPi18n.__('Closed_automatically')); + } + + _getDepartmentAbandonedCustomMessage(departmentId) { + if (this.messageCache.has('departmentId')) { + return this.messageCache.get('departmentId'); + } + const department = LivechatDepartment.findOneById(departmentId); + if (!department) { + return; + } + this.messageCache.set(department._id, department.abandonedRoomsCloseCustomMessage); + return department.abandonedRoomsCloseCustomMessage; + } + + closeRooms(room) { + let comment = this.messageCache.get('default'); + if (room.departmentId) { + comment = this._getDepartmentAbandonedCustomMessage(room.departmentId) || comment; + } + Livechat.closeRoom({ + comment, + room, + user: this.userToPerformAutomaticClosing, + }); + } + + handleAbandonedRooms() { + if (!settings.get('Livechat_auto_close_abandoned_rooms')) { + return; + } + LivechatRooms.findAbandonedOpenRooms(new Date()).forEach((room) => this.closeRooms(room)); + this._initializeMessageCache(); + } +} diff --git a/ee/app/livechat-enterprise/server/settings.js b/ee/app/livechat-enterprise/server/settings.js index 4ad2147cb830..8d91cb92f6c2 100644 --- a/ee/app/livechat-enterprise/server/settings.js +++ b/ee/app/livechat-enterprise/server/settings.js @@ -36,5 +36,20 @@ export const createSettings = () => { enableQuery: { _id: 'Livechat_waiting_queue', value: true }, }); + settings.add('Livechat_auto_close_abandoned_rooms', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Sessions', + i18nLabel: 'Enable_omnichannel_auto_close_abandoned_rooms', + }); + + settings.add('Livechat_abandoned_rooms_closed_custom_message', '', { + type: 'string', + group: 'Omnichannel', + section: 'Sessions', + i18nLabel: 'Livechat_abandoned_rooms_closed_custom_message', + enableQuery: { _id: 'Livechat_auto_close_abandoned_rooms', value: true }, + }); + Settings.addOptionValueById('Livechat_Routing_Method', { key: 'Load_Balancing', i18nLabel: 'Load_Balancing' }); }; diff --git a/ee/app/livechat-enterprise/server/startup.js b/ee/app/livechat-enterprise/server/startup.js index 3016268255ab..91d956ad449c 100644 --- a/ee/app/livechat-enterprise/server/startup.js +++ b/ee/app/livechat-enterprise/server/startup.js @@ -1,11 +1,24 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../../../app/settings'; -import { checkWaitingQueue } from './lib/Helper'; +import { checkWaitingQueue, updatePredictedVisitorAbandonment } from './lib/Helper'; +import { VisitorInactivityMonitor } from './lib/VisitorInactivityMonitor'; import './lib/query.helper'; +const visitorActivityMonitor = new VisitorInactivityMonitor(); + Meteor.startup(function() { settings.onload('Livechat_maximum_chats_per_agent', function(/* key, value */) { checkWaitingQueue(); }); + settings.onload('Livechat_auto_close_abandoned_rooms', function(_, value) { + updatePredictedVisitorAbandonment(); + if (!value) { + return visitorActivityMonitor.stop(); + } + visitorActivityMonitor.start(); + }); + settings.onload('Livechat_visitor_inactivity_timeout', function() { + updatePredictedVisitorAbandonment(); + }); }); diff --git a/ee/app/models/server/models/LivechatRooms.js b/ee/app/models/server/models/LivechatRooms.js index 8fa684baf4b6..2fb9844d7aab 100644 --- a/ee/app/models/server/models/LivechatRooms.js +++ b/ee/app/models/server/models/LivechatRooms.js @@ -23,4 +23,38 @@ overwriteClassOnLicense('livechat-enterprise', LivechatRooms, { }, }); + +LivechatRooms.prototype.setPredictedVisitorAbandonment = function(roomId, willBeAbandonedAt) { + const query = { + _id: roomId, + }; + const update = { + $set: { + 'omnichannel.predictedVisitorAbandonmentAt': willBeAbandonedAt, + }, + }; + + return this.update(query, update); +}; + +LivechatRooms.prototype.findAbandonedOpenRooms = function(date) { + return this.find({ + 'omnichannel.predictedVisitorAbandonmentAt': { $lte: date }, + waitingResponse: { $exists: false }, + closedAt: { $exists: false }, + open: true, + }); +}; + +LivechatRooms.prototype.unsetPredictedVisitorAbandonment = function() { + return this.update({ + open: true, + t: 'l', + }, { + $unset: { 'omnichannel.predictedVisitorAbandonmentAt': 1 }, + }, { + multi: true, + }); +}; + export default LivechatRooms; diff --git a/ee/i18n/en.i18n.json b/ee/i18n/en.i18n.json index 73d194a28f1f..f9d3d4bd7b09 100644 --- a/ee/i18n/en.i18n.json +++ b/ee/i18n/en.i18n.json @@ -6,8 +6,11 @@ "Canned Responses": "Canned Responses", "Canned_Response_Removed": "Canned Response Removed", "Canned_Responses_Enable": "Enable Canned Responses", + "Closed_automatically": "Closed automatically by the system", "Edit_Tag": "Edit Tag", "Edit_Unit": "Edit Unit", + "Enable_omnichannel_auto_close_abandoned_rooms": "Enable automatic closing of rooms abandoned by the visitor", + "Enter_a_custom_message": "Enter a custom message", "Enterprise_License": "Enterprise License", "Enterprise_License_Description": "If your workspace is registered and license is provided by Rocket.Chat Cloud you don't need to manually update the license here.", "Failed_to_add_monitor": "Failed to add monitor", @@ -22,6 +25,7 @@ "LDAP_Roles_To_Rocket_Chat_Roles_Description": "Role mapping in object format where the object key must be the LDAP role and the object value must be an array of RC roles. Example: { 'ldapRole': ['rcRole', 'anotherRCRole'] }", "LDAP_Validate_Roles_For_Each_Login": "Validate mapping for each login", "LDAP_Validate_Roles_For_Each_Login_Description": "If the validation should occurs for each login (Be careful with this setting because it will overwrite the user roles in each login, otherwise this will be validated only at the moment of user creation).", + "Livechat_abandoned_rooms_closed_custom_message": "Custom message when room is automatically closed by visitor inactivity", "Livechat_Monitors": "Monitors", "Livechat_monitors": "Livechat monitors", "Max_number_of_chats_per_agent": "Max. number of simultaneous chats", @@ -35,6 +39,7 @@ "New_Tag": "New Tag", "New_Unit": "New Unit", "No_Canned_Responses": "No Canned Responses", + "Number_in_seconds": "Number in seconds", "Number_of_most_recent_chats_estimate_wait_time": "Number of recent chats to calculate estimate wait time", "Number_of_most_recent_chats_estimate_wait_time_description": "This number defines the number of last served rooms that will be used to calculate queue wait times.", "Others": "Others", diff --git a/ee/i18n/pt-BR.i18n.json b/ee/i18n/pt-BR.i18n.json index d995cda488e3..58d93186aaf0 100644 --- a/ee/i18n/pt-BR.i18n.json +++ b/ee/i18n/pt-BR.i18n.json @@ -2,8 +2,11 @@ "error-max-number-simultaneous-chats-reached": "O número máximo de bate-papos simultâneos por agente foi atingido.", "Add_monitor": "Adicionar Monitor", "Available_departments": "Departamentos disponíveis", + "Closed_automatically": "Fechado automaticamente pelo sistema", "Edit_Tag": "Editar Tag", "Edit_Unit": "Editar Unidade", + "Enable_omnichannel_auto_close_abandoned_rooms": "Habilitar o fechamento automático de salas abandonadas pelo visitante", + "Enter_a_custom_message": "Digite uma mensagem customizada", "Enterprise_License": "Licença Enterprise", "Enterprise_License_Description": "Se você registrou seu workspace e a licença foi fornecida pelo Rocket.Chat Cloud você não precisa atualizar a licença manualmente aqui.", "Invalid_Department": "Departamento inválido", @@ -16,6 +19,7 @@ "LDAP_Roles_To_Rocket_Chat_Roles_Description": "Mapeamento dos papéis que deve ser em formato de objeto onde a chave do objeto precisa ser o nome do papel LDAP e o valor deve ser um array de papéis Rocket.Chat. Exemplo: { 'ldapRole': ['rcRole', 'anotherRCRole'] }", "LDAP_Validate_Roles_For_Each_Login": "Validar o mapeamento em cada login", "LDAP_Validate_Roles_For_Each_Login_Description": " Se a validação deve ser feita a cada login(Tenha cuidado com essa configuração, pois ela vai sobrescrever os papéis de usuário a cada login, caso esteja desabilitado, a validação será feita apenas no momento da criação do usuário).", + "Livechat_abandoned_rooms_closed_custom_message": "Mensagem customizada para usar quando a sala for automaticamente fechada por abandono do visitante", "Livechat_Monitors": "Monitores", "Livechat_monitors": "Monitores de Livechat", "Max_number_of_chats_per_agent": "Número máximo de atendimentos simultâneos", @@ -26,6 +30,7 @@ "Monitors": "Monitores", "New_Tag": "Nova Tag", "New_Unit": "Nova Unidade", + "Number_in_seconds": "Number in seconds", "Number_of_most_recent_chats_estimate_wait_time": "Número de chats recentes para cálculo de tempo na fila", "Number_of_most_recent_chats_estimate_wait_time_description": "Este numero define a quantidade de últimas salas atendidas que serão usadas para calculo de estimativa de tempo de espera da fila.", "Others": "Outros", From 39c74d4b8cbd68071b2ddc774b390cc456644d9a Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 13 Apr 2020 22:15:28 -0300 Subject: [PATCH 05/31] Update Apps-Engine to stable version (#17287) --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c979d72972c4..26011ed5e407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2849,9 +2849,9 @@ } }, "@rocket.chat/apps-engine": { - "version": "1.13.0-beta.3003", - "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.13.0-beta.3003.tgz", - "integrity": "sha512-RGegax9zaDzQta7U1v05JYUgJUhrGlYBYioMdCJRvKs6NrmJNbpdeJ6A0Bu6pNKF9ppMkG17hfxI6xT0MoSd0w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.13.0.tgz", + "integrity": "sha512-gU72qk3xhk5UYGmgsp4VLnOOYeAcsc4O8K2rYiLfs1EpBA1tNY+/YtR6Crl5usdU7sRSHUq4Y86pAchepT6aJQ==", "requires": { "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", diff --git a/package.json b/package.json index cda18917ced2..6315adde6862 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "@nivo/heatmap": "^0.61.0", "@nivo/line": "^0.61.1", "@nivo/pie": "^0.61.1", - "@rocket.chat/apps-engine": "^1.13.0-beta.3003", + "@rocket.chat/apps-engine": "^1.13.0", "@rocket.chat/fuselage": "^0.7.1", "@rocket.chat/fuselage-hooks": "^0.7.1", "@rocket.chat/fuselage-polyfills": "^0.7.1", From fd69a79d748bba316561330221e2b276daccb907 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 13 Apr 2020 23:30:23 -0300 Subject: [PATCH 06/31] [FIX] Avatar on sidebar when showing real names (#17286) --- app/lib/lib/roomTypes/direct.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/lib/lib/roomTypes/direct.js b/app/lib/lib/roomTypes/direct.js index 1a36927ba561..28295be6bcde 100644 --- a/app/lib/lib/roomTypes/direct.js +++ b/app/lib/lib/roomTypes/direct.js @@ -186,16 +186,23 @@ export class DirectMessageRoomType extends RoomTypeConfig { } getAvatarPath(roomData, subData) { + if (!roomData && !subData) { + return ''; + } + if (this.isGroupChat(roomData)) { return getAvatarURL({ username: roomData.uids.length + roomData.usernames.join() }); } - if (roomData) { - return getUserAvatarURL(roomData.name || this.roomName(roomData)); + const sub = subData || Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } }); + + if (sub && sub.name) { + return getUserAvatarURL(sub.name); } - const sub = subData || Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } }); - return getUserAvatarURL(sub.name || this.roomName(roomData)); + if (roomData) { + return getUserAvatarURL(roomData.name || this.roomName(roomData)); // rooms should have no name for direct messages... + } } includeInDashboard() { From 6b303c466f8b7d6b6b416225620a08e27554c859 Mon Sep 17 00:00:00 2001 From: 1rV1N <34376228+1rV1N-git@users.noreply.github.com> Date: Tue, 14 Apr 2020 05:44:28 +0300 Subject: [PATCH 07/31] Update ru.i18n.json (#16869) --- packages/rocketchat-i18n/i18n/ru.i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index 80dee4ae0e55..c69993f826d0 100644 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -2390,7 +2390,7 @@ "Num_Agents": "# Представители", "Number_of_events": "Количество событий", "Number_of_federated_users": "Количество федеративных пользователей", - "Number_of_federated_servers": "Количество федеративных пользователей", + "Number_of_federated_servers": "Количество федеративных серверов", "Number_of_messages": "Количество сообщений", "OAuth Apps": "Приложения OAuth", "OAuth_Application": "Приложение OAuth", @@ -3559,4 +3559,4 @@ "Your_server_link": "Ссылка на ваш сервер", "Your_temporary_password_is_password": "Ваш временный пароль [password].", "Your_workspace_is_ready": "Ваше рабочее пространство готово к работе 🎉" -} \ No newline at end of file +} From d00a452daf8eeeaee773ec3746d9e94e641f48a8 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 14 Apr 2020 00:57:18 -0300 Subject: [PATCH 08/31] [FIX] Directory default tab (#17283) --- .../components/Directory/DirectoryTable.js | 22 +++++++++---------- .../views/app/components/Directory/index.js | 8 ++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/ui/client/views/app/components/Directory/DirectoryTable.js b/app/ui/client/views/app/components/Directory/DirectoryTable.js index 09925c92e407..96c73164102c 100644 --- a/app/ui/client/views/app/components/Directory/DirectoryTable.js +++ b/app/ui/client/views/app/components/Directory/DirectoryTable.js @@ -27,23 +27,23 @@ export function Markdown({ children, ...props }) { const LoadingRow = ({ cols }) => - - - - - - - - - + + + + + + + - + { Array.from({ length: cols - 1 }, (_, i) => )} ; +const style = { minHeight: '40px' }; + export function DirectoryTable({ data = {}, renderRow, @@ -79,7 +79,7 @@ export function DirectoryTable({ return <> - + } onChange={handleChange} value={text} /> {channels && !channels.length diff --git a/app/ui/client/views/app/components/Directory/index.js b/app/ui/client/views/app/components/Directory/index.js index 9788b7fb11e3..671b079f423d 100644 --- a/app/ui/client/views/app/components/Directory/index.js +++ b/app/ui/client/views/app/components/Directory/index.js @@ -13,6 +13,8 @@ const avatarBase = { baseUrl: '/avatar/' }; export function DirectoryPage() { const t = useTranslation(); + const defaultTab = useSetting('Accounts_Directory_DefaultView'); + const federationEnabled = useSetting('FEDERATION_Enabled'); const tab = useRouteParameter('tab'); @@ -22,13 +24,13 @@ export function DirectoryPage() { useEffect(() => { if (!tab || (tab === 'external' && !federationEnabled)) { - return goToDirectory.replacingState({ tab: 'channels' }); + return goToDirectory.replacingState({ tab: defaultTab }); } - }, [tab, federationEnabled]); + }, [tab, federationEnabled, defaultTab]); return - + {t('Channels')} {t('Users')} { federationEnabled && {t('External_Users')} } From 16a2b9fcbd5960c146511f95d147b9521e85a946 Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Tue, 14 Apr 2020 01:34:46 -0300 Subject: [PATCH 09/31] [FIX] Email not verified message (#16236) Co-authored-by: Guilherme Gazzo --- app/ui-login/client/login/form.html | 2 +- app/ui-login/client/login/form.js | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/ui-login/client/login/form.html b/app/ui-login/client/login/form.html index 565a3403ef2a..3a20bd66344f 100644 --- a/app/ui-login/client/login/form.html +++ b/app/ui-login/client/login/form.html @@ -109,7 +109,7 @@

{{{_ "Registration_Succeeded"}}}

+ placeholder="{{_ "Email"}}" autofocus value="{{#if state 'email-verification'}}{{typedEmail}}{{/if}}">
diff --git a/app/ui-login/client/login/form.js b/app/ui-login/client/login/form.js index a0c5bfa3ef41..cbf9fe67d25e 100644 --- a/app/ui-login/client/login/form.js +++ b/app/ui-login/client/login/form.js @@ -72,6 +72,9 @@ Template.loginForm.helpers({ manuallyApproveNewUsers() { return settings.get('Accounts_ManuallyApproveNewUsers'); }, + typedEmail() { + return s.trim(Template.instance().typedEmail); + }, }); Template.loginForm.events({ @@ -137,16 +140,18 @@ Template.loginForm.events({ return Meteor[loginMethod](s.trim(formData.emailOrUsername), formData.pass, function(error) { instance.loading.set(false); if (error != null) { - if (error.error === 'no-valid-email') { - instance.state.set('email-verification'); - } else if (error.error === 'error-user-is-not-activated') { + if (error.error === 'error-user-is-not-activated') { + return toastr.error(t('Wait_activation_warning')); + } if (error.error === 'error-invalid-email') { + instance.typedEmail = formData.emailOrUsername; + return instance.state.set('email-verification'); + } if (error.error === 'error-user-is-not-activated') { toastr.error(t('Wait_activation_warning')); } else if (error.error === 'error-app-user-is-not-allowed-to-login') { toastr.error(t('App_user_not_allowed_to_login')); } else { - toastr.error(t('User_not_found_or_incorrect_password')); + return toastr.error(t('User_not_found_or_incorrect_password')); } - return; } Session.set('forceLogin', false); }); @@ -234,7 +239,7 @@ Template.loginForm.onCreated(function() { validationObj.emailOrUsername = t('Invalid_email'); } } - if (state !== 'forgot-password') { + if (state !== 'forgot-password' && state !== 'email-verification') { if (!formObj.pass) { validationObj.pass = t('Invalid_pass'); } From 057ba18845ae194ee1134e7224150e5ed50373c1 Mon Sep 17 00:00:00 2001 From: Ashwani Yadav Date: Tue, 14 Apr 2020 10:08:41 +0530 Subject: [PATCH 10/31] [FIX] In Create a New Channel, input should be focused on channel name instead of invite users (#16405) --- app/ui/client/views/app/createChannel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/client/views/app/createChannel.js b/app/ui/client/views/app/createChannel.js index 778f2a708d40..9cb96bfc1abc 100644 --- a/app/ui/client/views/app/createChannel.js +++ b/app/ui/client/views/app/createChannel.js @@ -274,7 +274,7 @@ Template.createChannel.events({ Template.createChannel.onRendered(function() { const users = this.selectedUsers; - this.firstNode.querySelector('[name="users"]').focus(); + this.firstNode.querySelector('[name="name"]').focus(); this.ac.element = this.firstNode.querySelector('[name="users"]'); this.ac.$element = $(this.ac.element); this.ac.$element.on('autocompleteselect', function(e, { item }) { From a05a2d5649b02dd0d7cfc69cbd9c0d0c502f1293 Mon Sep 17 00:00:00 2001 From: Ashwani Yadav Date: Tue, 14 Apr 2020 10:18:00 +0530 Subject: [PATCH 11/31] [FIX] Directory users email sort button (#16606) Co-authored-by: Guilherme Gazzo --- server/methods/browseChannels.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/methods/browseChannels.js b/server/methods/browseChannels.js index 762b4662fe5e..ba720a62cbba 100644 --- a/server/methods/browseChannels.js +++ b/server/methods/browseChannels.js @@ -31,6 +31,7 @@ const sortUsers = function(field, direction) { case 'email': return { 'emails.address': direction === 'asc' ? 1 : -1, + username: direction === 'asc' ? 1 : -1, }; default: return { From 9aaf2eab75c726a0d5ddf48b23bb655828cba5d1 Mon Sep 17 00:00:00 2001 From: Ashwani Yadav Date: Tue, 14 Apr 2020 10:20:01 +0530 Subject: [PATCH 12/31] [FIX] No maxlength defined for custom user status (#16534) --- app/user-status/client/admin/userStatusEdit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/user-status/client/admin/userStatusEdit.html b/app/user-status/client/admin/userStatusEdit.html index 0c4bd9b21f42..75597ad3972f 100644 --- a/app/user-status/client/admin/userStatusEdit.html +++ b/app/user-status/client/admin/userStatusEdit.html @@ -13,7 +13,7 @@

{{_ "Custom_User_Status_Add"}}

From 01f1aac66d4dceea95f732c71e28f92215f2130e Mon Sep 17 00:00:00 2001 From: CC007 Date: Tue, 14 Apr 2020 06:54:49 +0200 Subject: [PATCH 13/31] [FIX] translation for nl (#16742) --- packages/rocketchat-i18n/i18n/nl.i18n.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index c425b6ad95a3..546b0b6c6e64 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -1115,8 +1115,8 @@ "Enter_a_username": "Voer een gebruikersnaam in", "Enter_Alternative": "Alternatieve modus (verzenden met Enter + Ctrl / Alt / Shift / CMD)", "Enter_authentication_code": "Voer verificatiecode in", - "Enter_Behaviour": "Voer sleutelgedrag in", - "Enter_Behaviour_Description": "Dit verandert als de Enter-toets een bericht verzendt of een regeleinde uitvoert", + "Enter_Behaviour": "Enter-toets gedrag", + "Enter_Behaviour_Description": "Dit verandert of de Enter-toets een bericht verstuurd of de cursor naar de volgende regel verplaatst.", "Enter_name_here": "Voer naam hier", "Enter_Normal": "Normale modus (verzenden met Enter)", "Enter_to": "Ga binnen bij", @@ -2044,7 +2044,7 @@ "OAuth_Application": "OAuth Application", "OAuth_Applications": "OAuth Applications", "Objects": "Voorwerpen", - "Off": "Af", + "Off": "Uit", "Off_the_record_conversation": "Off-the-opname Conversation", "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Off-the-opname gesprek is niet beschikbaar voor uw browser of apparaat.", "Office_Hours": "Kantoortijden", @@ -2064,7 +2064,7 @@ "Old Colors": "Oude kleuren", "Old Colors (minor)": "Oude kleuren (klein)", "Older_than": "Ouder dan", - "On": "Op", + "On": "Aan", "Online": "Online", "online": "online", "Only_authorized_users_can_write_new_messages": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven", @@ -2123,7 +2123,7 @@ "pin-message": "Pinbericht", "pin-message_description": "Toestemming om een ​​bericht in een kanaal vast te zetten", "Pin_Message": "Zet bericht vast", - "Pinned_a_message": "Speldde een bericht:", + "Pinned_a_message": "Heeft een bericht vastgezet:", "Pinned_Messages": "Vastgezette berichten", "PiwikAdditionalTrackers": "Extra Piwik-sites", "PiwikAdditionalTrackers_Description": "Voer additionele Piwik-website-URL's en SiteID's in de volgende indeling in, als u dezelfde gegevens niet op verschillende websites wilt bijhouden: [{\"trackerURL\": \"https: //my.piwik.domain2/\", \"siteId\": 42}, {\"trackerURL\": \"https: //my.piwik.domain3/\", \"siteId\": 15}]", @@ -3056,4 +3056,4 @@ "Your_push_was_sent_to_s_devices": "Je push werd verzonden naar %s apparaten", "Your_server_link": "Uw serverlink", "Your_workspace_is_ready": "Uw werkruimte is klaar voor gebruik 🎉" -} \ No newline at end of file +} From 863b9b61eb850b6b97f3937595aee81e977ee6a8 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 14 Apr 2020 10:37:47 -0300 Subject: [PATCH 14/31] [FIX] Discussions created from inside DMs were not working (#17282) --- .../server/methods/createDiscussion.js | 4 +- app/lib/lib/roomTypes/public.js | 4 + app/utils/lib/RoomTypeConfig.js | 4 + server/startup/migrations/index.js | 1 + server/startup/migrations/v183.js | 94 +++++++++++++++++++ 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 server/startup/migrations/v183.js diff --git a/app/discussion/server/methods/createDiscussion.js b/app/discussion/server/methods/createDiscussion.js index b15a057adedb..e4287b86a45e 100644 --- a/app/discussion/server/methods/createDiscussion.js +++ b/app/discussion/server/methods/createDiscussion.js @@ -5,6 +5,7 @@ import { hasAtLeastOnePermission, canAccessRoom } from '../../../authorization/s import { Messages, Rooms } from '../../../models/server'; import { createRoom, addUserToRoom, sendMessage, attachMessage } from '../../../lib/server'; import { settings } from '../../../settings/server'; +import { roomTypes } from '../../../utils/server'; const getParentRoom = (rid) => { const room = Rooms.findOne(rid); @@ -86,7 +87,8 @@ const create = ({ prid, pmid, t_name, reply, users }) => { // auto invite the replied message owner const invitedUsers = message ? [message.u.username, ...users] : users; - const discussion = createRoom(p_room.t, name, user.username, [...new Set(invitedUsers)], false, { + const type = roomTypes.getConfig(p_room.t).getDiscussionType(); + const discussion = createRoom(type, name, user.username, [...new Set(invitedUsers)], false, { fname: t_name, description: message.msg, // TODO discussions remove topic: p_room.name, // TODO discussions remove diff --git a/app/lib/lib/roomTypes/public.js b/app/lib/lib/roomTypes/public.js index 11d688731eff..8732e6d13d9c 100644 --- a/app/lib/lib/roomTypes/public.js +++ b/app/lib/lib/roomTypes/public.js @@ -133,4 +133,8 @@ export class PublicRoomType extends RoomTypeConfig { return getAvatarURL({ username: `@${ this.roomName(roomData) }` }); } + + getDiscussionType() { + return 'c'; + } } diff --git a/app/utils/lib/RoomTypeConfig.js b/app/utils/lib/RoomTypeConfig.js index da578a82402e..bc40944c6b3a 100644 --- a/app/utils/lib/RoomTypeConfig.js +++ b/app/utils/lib/RoomTypeConfig.js @@ -299,4 +299,8 @@ export class RoomTypeConfig { openCustomProfileTab() { return false; } + + getDiscussionType() { + return 'p'; + } } diff --git a/server/startup/migrations/index.js b/server/startup/migrations/index.js index 60ee4afe9979..948bc590af93 100644 --- a/server/startup/migrations/index.js +++ b/server/startup/migrations/index.js @@ -180,4 +180,5 @@ import './v179'; import './v180'; import './v181'; import './v182'; +import './v183'; import './xrun'; diff --git a/server/startup/migrations/v183.js b/server/startup/migrations/v183.js new file mode 100644 index 000000000000..fd242bd4d9ef --- /dev/null +++ b/server/startup/migrations/v183.js @@ -0,0 +1,94 @@ +import { Random } from 'meteor/random'; + +import { Migrations } from '../../../app/migrations'; +import { Rooms, Messages, Subscriptions, Uploads } from '../../../app/models/server'; + +const unifyRooms = (room) => { + // verify if other DM already exists + const other = Rooms.findOne({ + _id: { $ne: room._id }, + t: 'd', + uids: room.uids, + }); + + // we need to at least change the _id of the current room, so remove it + Rooms.remove({ _id: room._id }); + + const newId = (other && other._id) || Random.id(); + + if (!other) { + // create the same room with different _id + Rooms.insert({ + ...room, + _id: newId, + }); + + // update subscription to point to new room _id + Subscriptions.update({ rid: room._id }, { + $set: { + rid: newId, + }, + }, { multi: true }); + + return newId; + } + + // the other room exists already, so just remove the subscription of the wrong room + Subscriptions.remove({ rid: room._id }); + + return newId; +}; + +const fixSelfDMs = () => { + Rooms.find({ + t: 'd', + uids: { $size: 1 }, + }).forEach((room) => { + if (!Array.isArray(room.uids) || room._id !== room.uids[0]) { + return; + } + + const correctId = unifyRooms(room); + + // move things to correct room + Messages.update({ rid: room._id }, { + $set: { + rid: correctId, + }, + }, { multi: true }); + Uploads.update({ rid: room._id }, { + $set: { + rid: correctId, + }, + }, { multi: true }); + }); +}; + +const fixDiscussions = () => { + Rooms.find({ t: 'd', prid: { $exists: true } }, { fields: { _id: 1 } }).forEach(({ _id }) => { + const { u } = Messages.findOne({ drid: _id }, { fields: { u: 1 } }) || {}; + + Rooms.update({ _id }, { + $set: { + t: 'p', + name: Random.id(), + u, + ro: false, + default: false, + sysMes: true, + }, + $unset: { + usernames: 1, + uids: 1, + }, + }); + }); +}; + +Migrations.add({ + version: 183, + up() { + fixDiscussions(); + fixSelfDMs(); + }, +}); From 130af28500e6740f7f1bb59a2637dec1aaa1a3d0 Mon Sep 17 00:00:00 2001 From: Ashwani Yadav Date: Tue, 14 Apr 2020 19:16:01 +0530 Subject: [PATCH 15/31] [FIX] Admin panel custom sounds, multiple sound playback fix and added single play/pause button (#16215) --- .../client/admin/adminSounds.html | 7 +++++-- app/custom-sounds/client/admin/adminSounds.js | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/custom-sounds/client/admin/adminSounds.html b/app/custom-sounds/client/admin/adminSounds.html index b4ca2015025a..c9a20bd9c4fd 100644 --- a/app/custom-sounds/client/admin/adminSounds.html +++ b/app/custom-sounds/client/admin/adminSounds.html @@ -45,8 +45,11 @@
- {{>icon _id=_id icon="play" block="icon-play-circled"}} - {{>icon _id=_id icon="pause" block="icon-pause-circled"}} + {{#if isPlaying _id}} + {{>icon _id=_id icon="pause" block="icon-pause-circled"}} + {{else}} + {{>icon _id=_id icon="play" block="icon-play-circled"}} + {{/if}} {{>icon _id=_id icon="ban" block="icon-reset-circled"}}
diff --git a/app/custom-sounds/client/admin/adminSounds.js b/app/custom-sounds/client/admin/adminSounds.js index e142bc85d60d..9de092e3c143 100644 --- a/app/custom-sounds/client/admin/adminSounds.js +++ b/app/custom-sounds/client/admin/adminSounds.js @@ -16,6 +16,9 @@ Template.adminSounds.helpers({ const instance = Template.instance(); return instance.filter && instance.filter.get(); }, + isPlaying(_id) { + return Template.instance().isPlayingId.get() === _id; + }, customsounds() { return Template.instance().sounds.get(); }, @@ -62,6 +65,7 @@ Template.adminSounds.onCreated(function() { this.query = new ReactiveVar({}); this.isLoading = new ReactiveVar(false); this.filter = new ReactiveVar(''); + this.isPlayingId = new ReactiveVar(''); this.tabBar = new RocketChatTabBar(); this.tabBar.showGroup(FlowRouter.current().route.name); @@ -138,18 +142,28 @@ Template.adminSounds.events({ t.filter.set(e.currentTarget.value); t.offset.set(0); }, - 'click .icon-play-circled'(e) { + 'click .icon-play-circled'(e, t) { e.preventDefault(); e.stopPropagation(); CustomSounds.play(this._id); + const audio = document.getElementById(t.isPlayingId.get()); + if (audio) { + audio.pause(); + } + document.getElementById(this._id).onended = () => { + t.isPlayingId.set(''); + this.onended = null; + }; + t.isPlayingId.set(this._id); }, - 'click .icon-pause-circled'(e) { + 'click .icon-pause-circled'(e, t) { e.preventDefault(); e.stopPropagation(); const audio = document.getElementById(this._id); if (audio && !audio.paused) { audio.pause(); } + t.isPlayingId.set(''); }, 'click .icon-reset-circled'(e) { e.preventDefault(); From 4e055c4f07684729387a96039104522fa20da8ec Mon Sep 17 00:00:00 2001 From: ritwizsinha <43509699+ritwizsinha@users.noreply.github.com> Date: Tue, 14 Apr 2020 19:18:03 +0530 Subject: [PATCH 16/31] [FIX] Variable rendering problem on Import recent history page (#15997) --- app/importer/client/admin/importOperationSummary.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/importer/client/admin/importOperationSummary.html b/app/importer/client/admin/importOperationSummary.html index 2378a4ebddc8..c033a4ffe3b9 100644 --- a/app/importer/client/admin/importOperationSummary.html +++ b/app/importer/client/admin/importOperationSummary.html @@ -1,6 +1,6 @@