diff --git a/app/livechat/client/views/app/livechatReadOnly.js b/app/livechat/client/views/app/livechatReadOnly.js index adec67da50fb..765088722c26 100644 --- a/app/livechat/client/views/app/livechatReadOnly.js +++ b/app/livechat/client/views/app/livechatReadOnly.js @@ -3,7 +3,7 @@ import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import { ChatRoom } from '../../../../models'; +import { ChatRoom, CachedChatRoom } from '../../../../models'; import { call } from '../../../../ui-utils/client'; import './livechatReadOnly.html'; import { APIClient } from '../../../../utils/client'; @@ -65,6 +65,11 @@ Template.livechatReadOnly.onCreated(function() { this.updateInquiry = async ({ clientAction, ...inquiry }) => { if (clientAction === 'removed' || !await call('canAccessRoom', inquiry.rid, Meteor.userId())) { + // this will force to refresh the room + // since the client wont get notified of room changes when chats are on queue (no one assigned) + // a better approach should be performed when refactoring these templates to use react + ChatRoom.remove(this.rid); + CachedChatRoom.save(); return FlowRouter.go('/home'); } diff --git a/app/livechat/server/lib/Helper.js b/app/livechat/server/lib/Helper.js index 6ac35a141024..78e7c5e83bb4 100644 --- a/app/livechat/server/lib/Helper.js +++ b/app/livechat/server/lib/Helper.js @@ -13,7 +13,7 @@ import { Apps, AppEvents } from '../../../apps/server'; import notifications from '../../../notifications/server/lib/Notifications'; import { sendNotification } from '../../../lib/server'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; -import { queueInquiry } from './QueueManager'; +import { queueInquiry, saveQueueInquiry } from './QueueManager'; export const allowAgentSkipQueue = (agent) => { check(agent, Match.ObjectIncluding({ @@ -233,7 +233,7 @@ export const dispatchInquiryQueued = (inquiry, agent) => { } if (!agent || !allowAgentSkipQueue(agent)) { - LivechatInquiry.queueInquiry(inquiry._id); + saveQueueInquiry(inquiry); } // Alert only the online agents of the queued request diff --git a/app/livechat/server/lib/QueueManager.js b/app/livechat/server/lib/QueueManager.js index 238e4cdb235f..cc6a07495de3 100644 --- a/app/livechat/server/lib/QueueManager.js +++ b/app/livechat/server/lib/QueueManager.js @@ -6,6 +6,10 @@ import { checkServiceStatus, createLivechatRoom, createLivechatInquiry } from '. import { callbacks } from '../../../callbacks/server'; import { RoutingManager } from './RoutingManager'; +export const saveQueueInquiry = (inquiry) => { + LivechatInquiry.queueInquiry(inquiry._id); + callbacks.run('livechat.afterInquiryQueued', inquiry); +}; export const queueInquiry = async (room, inquiry, defaultAgent) => { const inquiryAgent = RoutingManager.delegateAgent(defaultAgent, inquiry); diff --git a/app/models/server/models/LivechatInquiry.js b/app/models/server/models/LivechatInquiry.js index 7b79c5322618..55fde68f2b90 100644 --- a/app/models/server/models/LivechatInquiry.js +++ b/app/models/server/models/LivechatInquiry.js @@ -37,6 +37,10 @@ export class LivechatInquiry extends Base { ); } + getQueuedInquiries(options) { + return this.find({ status: 'queued' }, options); + } + /* * mark the inquiry as taken */ @@ -45,7 +49,7 @@ export class LivechatInquiry extends Base { _id: inquiryId, }, { $set: { status: 'taken' }, - $unset: { defaultAgent: 1 }, + $unset: { defaultAgent: 1, estimatedInactivityCloseTimeAt: 1 }, }); } @@ -228,6 +232,48 @@ export class LivechatInquiry extends Base { this.remove(query); } + + getUnnatendedQueueItems(date) { + const query = { + status: 'queued', + estimatedInactivityCloseTimeAt: { $lte: new Date(date) }, + }; + return this.find(query); + } + + setEstimatedInactivityCloseTime(_id, date) { + return this.update({ _id }, { + $set: { + estimatedInactivityCloseTimeAt: new Date(date), + }, + }); + } + + unsetEstimatedInactivityCloseTime() { + return this.update({ status: 'queued' }, { + $unset: { + estimatedInactivityCloseTimeAt: 1, + }, + }, { multi: true }); + } + + // This is a better solution, but update pipelines are not supported until version 4.2 of mongo + // leaving this here for when the time comes + /* updateEstimatedInactivityCloseTime(milisecondsToAdd) { + return this.model.rawCollection().updateMany( + { status: 'queued' }, + [{ + // in case this field doesn't exists, set at the last time the item was modified (updatedAt) + $set: { estimatedInactivityCloseTimeAt: '$_updatedAt' }, + }, { + $set: { + estimatedInactivityCloseTimeAt: { + $add: ['$estimatedInactivityCloseTimeAt', milisecondsToAdd], + }, + }, + }], + ); + } */ } export default new LivechatInquiry(); diff --git a/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts b/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts new file mode 100644 index 000000000000..fbd10c5427c2 --- /dev/null +++ b/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts @@ -0,0 +1,28 @@ +import moment from 'moment'; + +import { callbacks } from '../../../../../app/callbacks/server'; +import { LivechatInquiry } from '../../../../../app/models/server'; +import { settings } from '../../../../../app/settings/server'; + +let timer = 0; + +const setQueueTimer = (inquiry: any): void => { + if (!inquiry?._id) { + return; + } + + const newQueueTime = moment(inquiry?._updatedAt).add(timer, 'minutes'); + (LivechatInquiry as any).setEstimatedInactivityCloseTime(inquiry?._id, newQueueTime); +}; + +settings.get('Livechat_max_queue_wait_time', (_, value) => { + timer = value as number; +}); + +settings.get('Livechat_max_queue_wait_time_action', (_, value) => { + if (!value || value === 'Nothing') { + callbacks.remove('livechat:afterReturnRoomAsInquiry', 'livechat-after-return-room-as-inquiry-set-queue-timer'); + return; + } + callbacks.add('livechat.afterInquiryQueued', setQueueTimer, callbacks.priority.HIGH, 'livechat-inquiry-queued-set-queue-timer'); +}); diff --git a/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts b/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts index ebbd6e6de27e..e35975f18933 100644 --- a/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts +++ b/ee/app/livechat-enterprise/server/hooks/afterReturnRoomAsInquiry.ts @@ -2,7 +2,7 @@ import { callbacks } from '../../../../../app/callbacks/server'; import { LivechatRooms } from '../../../../../app/models/server'; import { settings } from '../../../../../app/settings/server'; -const afterReturnRoomAsInquiry = ({ room }: { room: any }): void => { +const unsetPredictedVisitorAbandonment = ({ room }: { room: any }): void => { if (!room?._id || !room?.omnichannel?.predictedVisitorAbandonmentAt) { return; } @@ -15,5 +15,5 @@ settings.get('Livechat_abandoned_rooms_action', (_, value) => { callbacks.remove('livechat:afterReturnRoomAsInquiry', 'livechat-after-return-room-as-inquiry'); return; } - callbacks.add('livechat:afterReturnRoomAsInquiry', afterReturnRoomAsInquiry, callbacks.priority.HIGH, 'livechat-after-return-room-as-inquiry'); + callbacks.add('livechat:afterReturnRoomAsInquiry', unsetPredictedVisitorAbandonment, callbacks.priority.HIGH, 'livechat-after-return-room-as-inquiry'); }); diff --git a/ee/app/livechat-enterprise/server/hooks/beforeNewInquiry.js b/ee/app/livechat-enterprise/server/hooks/beforeNewInquiry.js index ea26097fed1e..178ad22ff35a 100644 --- a/ee/app/livechat-enterprise/server/hooks/beforeNewInquiry.js +++ b/ee/app/livechat-enterprise/server/hooks/beforeNewInquiry.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../../app/callbacks'; +import { settings } from '../../../../../app/settings'; import LivechatPriority from '../../../models/server/models/LivechatPriority'; callbacks.add('livechat.beforeInquiry', (extraData = {}) => { @@ -22,3 +23,15 @@ callbacks.add('livechat.beforeInquiry', (extraData = {}) => { return Object.assign({ ...props }, { ts, queueOrder, estimatedWaitingTimeQueue, estimatedServiceTimeAt }); }, callbacks.priority.MEDIUM, 'livechat-before-new-inquiry'); + +callbacks.add('livechat.beforeInquiry', (extraData = {}) => { + const queueInactivityAction = settings.get('Livechat_max_queue_wait_time_action'); + if (!queueInactivityAction || queueInactivityAction === 'Nothing') { + return extraData; + } + + const maxQueueWaitTimeMinutes = settings.get('Livechat_max_queue_wait_time'); + const estimatedInactivityCloseTimeAt = new Date(new Date().getTime() + maxQueueWaitTimeMinutes * 60000); + + return Object.assign(extraData, { estimatedInactivityCloseTimeAt }); +}, callbacks.priority.MEDIUM, 'livechat-before-new-inquiry-append-queue-timer'); diff --git a/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js b/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js index 8a9426c85fcb..a8e715a9ed56 100644 --- a/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js +++ b/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js @@ -3,6 +3,7 @@ import { settings } from '../../../../../app/settings'; import { LivechatInquiry } from '../../../../../app/models/server'; import { dispatchInquiryPosition } from '../lib/Helper'; import { allowAgentSkipQueue } from '../../../../../app/livechat/server/lib/Helper'; +import { saveQueueInquiry } from '../../../../../app/livechat/server/lib/QueueManager'; callbacks.add('livechat.beforeRouteChat', async (inquiry, agent) => { if (!settings.get('Livechat_waiting_queue')) { @@ -23,7 +24,7 @@ callbacks.add('livechat.beforeRouteChat', async (inquiry, agent) => { return inquiry; } - LivechatInquiry.queueInquiry(_id); + saveQueueInquiry(inquiry); const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id, department }); if (inq) { diff --git a/ee/app/livechat-enterprise/server/hooks/index.js b/ee/app/livechat-enterprise/server/hooks/index.js index 6bf32b045b6a..8f370a0f7c57 100644 --- a/ee/app/livechat-enterprise/server/hooks/index.js +++ b/ee/app/livechat-enterprise/server/hooks/index.js @@ -21,3 +21,4 @@ import './afterReturnRoomAsInquiry'; import './applyDepartmentRestrictions'; import './afterForwardChatToAgent'; import './applySimultaneousChatsRestrictions'; +import './afterInquiryQueued'; diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index d4c4a5a4b097..0ecab61e3b26 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -14,6 +14,9 @@ import { settings } from '../../../../../app/settings'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper'; import notifications from '../../../../../app/notifications/server/lib/Notifications'; +import { Logger } from '../../../../../app/logger'; + +const logger = new Logger('LivechatEnterpriseHelper'); export const getMaxNumberSimultaneousChat = ({ agentId, departmentId }) => { if (departmentId) { @@ -151,6 +154,21 @@ export const updatePredictedVisitorAbandonment = () => { } }; +export const updateQueueInactivityTimeout = () => { + const queueAction = settings.get('Livechat_max_queue_wait_time_action'); + const queueTimeout = settings.get('Livechat_max_queue_wait_time'); + if (!queueAction || queueAction === 'Nothing') { + logger.debug('QueueInactivityTimer: No action performed (disabled by setting)'); + return LivechatInquiry.unsetEstimatedInactivityCloseTime(); + } + + logger.debug('QueueInactivityTimer: Updating estimated inactivity time for queued items'); + LivechatInquiry.getQueuedInquiries().forEach((inq) => { + const aggregatedDate = moment(inq._updatedAt).add(queueTimeout, 'minutes'); + return LivechatInquiry.setEstimatedInactivityCloseTime(inq._id, aggregatedDate); + }); +}; + export const updateRoomPriorityHistory = (rid, user, priority) => { const history = { priorityData: { diff --git a/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts b/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts new file mode 100644 index 000000000000..d79ca422e7ca --- /dev/null +++ b/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts @@ -0,0 +1,101 @@ +import Agenda from 'agenda'; +import { MongoInternals } from 'meteor/mongo'; +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import moment from 'moment'; + +import { settings } from '../../../../../app/settings/server'; +import { Logger } from '../../../../../app/logger/server'; +import { LivechatRooms, Users, LivechatInquiry } from '../../../../../app/models/server'; +import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; +import { IUser } from '../../../../../definition/IUser'; +import { IOmnichannelRoom } from '../../../../../definition/IRoom'; + +const SCHEDULER_NAME = 'omnichannel_queue_inactivity_monitor'; + +export class OmnichannelQueueInactivityMonitorClass { + scheduler: Agenda; + + running: boolean; + + logger: any; + + _name: string; + + user: IUser; + + message: string; + + constructor() { + this.running = false; + this._name = 'Omnichannel-Queue-Inactivity-Monitor'; + this.logger = new Logger('QueueInactivityMonitor'); + this.scheduler = new Agenda({ + mongo: (MongoInternals.defaultRemoteCollectionDriver().mongo as any).client.db(), + db: { collection: SCHEDULER_NAME }, + defaultConcurrency: 1, + }); + this.user = Users.findOneById('rocket.cat'); + const language = settings.get('Language') || 'en'; + this.message = TAPi18n.__('Closed_automatically_chat_queued_too_long', { lng: language }); + } + + start(): void { + if (this.running) { + return; + } + + Promise.await(this.scheduler.start()); + this.running = true; + } + + async stop(): Promise { + if (!this.running) { + return; + } + await this.scheduler.cancel({ name: this._name }); + this.running = false; + } + + async schedule(): Promise { + this.scheduler.define(this._name, Meteor.bindEnvironment(this.job.bind(this))); + await this.scheduler.every('one minute', this._name); + } + + closeRooms(room: IOmnichannelRoom): void { + const comment = this.message; + Livechat.closeRoom({ + comment, + room, + user: this.user, + visitor: null, + }); + } + + job(): void { + const action = settings.get('Livechat_max_queue_wait_time_action'); + this.logger.debug(`Processing dangling queued items with action ${ action }`); + let counter = 0; + if (!action || action === 'Nothing') { + return; + } + + LivechatInquiry.getUnnatendedQueueItems(moment().utc()).forEach((inquiry: any) => { + switch (action) { + case 'Close_chat': { + counter++; + this.closeRooms(LivechatRooms.findOneById(inquiry.rid)); + break; + } + } + }); + + this.logger.debug(`Running succesful. Closed ${ counter } queued items because of inactivity`); + } +} + +export const OmnichannelQueueInactivityMonitor = new OmnichannelQueueInactivityMonitorClass(); + +Meteor.startup(() => { + OmnichannelQueueInactivityMonitor.start(); +}); diff --git a/ee/app/livechat-enterprise/server/settings.js b/ee/app/livechat-enterprise/server/settings.js index 1c146a101d23..1c2faa307e24 100644 --- a/ee/app/livechat-enterprise/server/settings.js +++ b/ee/app/livechat-enterprise/server/settings.js @@ -2,57 +2,6 @@ import { settings } from '../../../../app/settings'; import { Settings } from '../../../../app/models/server'; export const createSettings = () => { - settings.add('Livechat_waiting_queue', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Routing', - i18nLabel: 'Waiting_queue', - enterprise: true, - invalidValue: false, - }); - - settings.add('Livechat_waiting_queue_message', '', { - type: 'string', - group: 'Omnichannel', - section: 'Routing', - i18nLabel: 'Waiting_queue_message', - i18nDescription: 'Waiting_queue_message_description', - enableQuery: { _id: 'Livechat_waiting_queue', value: true }, - enterprise: true, - invalidValue: '', - modules: [ - 'livechat-enterprise', - ], - }); - - settings.add('Livechat_maximum_chats_per_agent', 0, { - type: 'int', - group: 'Omnichannel', - section: 'Routing', - i18nLabel: 'Max_number_of_chats_per_agent', - i18nDescription: 'Max_number_of_chats_per_agent_description', - enableQuery: { _id: 'Livechat_waiting_queue', value: true }, - enterprise: true, - invalidValue: 0, - modules: [ - 'livechat-enterprise', - ], - }); - - settings.add('Livechat_number_most_recent_chats_estimate_wait_time', 100, { - type: 'int', - group: 'Omnichannel', - section: 'Routing', - i18nLabel: 'Number_of_most_recent_chats_estimate_wait_time', - i18nDescription: 'Number_of_most_recent_chats_estimate_wait_time_description', - enableQuery: { _id: 'Livechat_waiting_queue', value: true }, - enterprise: true, - invalidValue: 100, - modules: [ - 'livechat-enterprise', - ], - }); - settings.add('Livechat_abandoned_rooms_action', 'none', { type: 'select', group: 'Omnichannel', @@ -70,7 +19,6 @@ export const createSettings = () => { ], }); - settings.add('Livechat_abandoned_rooms_closed_custom_message', '', { type: 'string', group: 'Omnichannel', @@ -115,6 +63,92 @@ export const createSettings = () => { ], }); }); + + this.section('Queue_management', function() { + this.add('Livechat_waiting_queue', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Queue_management', + i18nLabel: 'Waiting_queue', + enterprise: true, + invalidValue: false, + }); + + this.add('Livechat_waiting_queue_message', '', { + type: 'string', + group: 'Omnichannel', + section: 'Queue_management', + i18nLabel: 'Waiting_queue_message', + i18nDescription: 'Waiting_queue_message_description', + enableQuery: { _id: 'Livechat_waiting_queue', value: true }, + enterprise: true, + invalidValue: '', + modules: [ + 'livechat-enterprise', + ], + }); + + this.add('Livechat_maximum_chats_per_agent', 0, { + type: 'int', + group: 'Omnichannel', + section: 'Queue_management', + i18nLabel: 'Max_number_of_chats_per_agent', + i18nDescription: 'Max_number_of_chats_per_agent_description', + enableQuery: { _id: 'Livechat_waiting_queue', value: true }, + enterprise: true, + invalidValue: 0, + modules: [ + 'livechat-enterprise', + ], + }); + + this.add('Livechat_number_most_recent_chats_estimate_wait_time', 100, { + type: 'int', + group: 'Omnichannel', + section: 'Queue_management', + i18nLabel: 'Number_of_most_recent_chats_estimate_wait_time', + i18nDescription: 'Number_of_most_recent_chats_estimate_wait_time_description', + enableQuery: { _id: 'Livechat_waiting_queue', value: true }, + enterprise: true, + invalidValue: 100, + modules: [ + 'livechat-enterprise', + ], + }); + + this.add('Livechat_max_queue_wait_time_action', 'Nothing', { + type: 'select', + group: 'Omnichannel', + section: 'Queue_management', + i18nLabel: 'Livechat_max_queue_wait_time_action', + enterprise: true, + invalidValue: '', + modules: [ + 'livechat-enterprise', + ], + values: [{ + key: 'Nothing', + i18nLabel: 'Do_Nothing', + }, { + key: 'Close_chat', + i18nLabel: 'Livechat_close_chat', + }], + }); + + this.add('Livechat_max_queue_wait_time', 60, { + type: 'int', + group: 'Omnichannel', + section: 'Queue_management', + i18nLabel: 'Livechat_maximum_queue_wait_time', + i18nDescription: 'Time_in_minutes', + enableQuery: { _id: 'Livechat_max_queue_wait_time_action', value: 'Close_chat' }, + enterprise: true, + invalidValue: 0, + modules: [ + 'livechat-enterprise', + ], + }); + }); }); settings.add('Omnichannel_contact_manager_routing', true, { diff --git a/ee/app/livechat-enterprise/server/startup.js b/ee/app/livechat-enterprise/server/startup.js index 71724f59268e..4f8e6bf1d4f4 100644 --- a/ee/app/livechat-enterprise/server/startup.js +++ b/ee/app/livechat-enterprise/server/startup.js @@ -1,8 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../../../app/settings'; -import { updatePredictedVisitorAbandonment } from './lib/Helper'; +import { updatePredictedVisitorAbandonment, updateQueueInactivityTimeout } from './lib/Helper'; import { VisitorInactivityMonitor } from './lib/VisitorInactivityMonitor'; +import { OmnichannelQueueInactivityMonitor } from './lib/QueueInactivityMonitor'; import './lib/query.helper'; import { MultipleBusinessHoursBehavior } from './business-hour/Multiple'; import { SingleBusinessHourBehavior } from '../../../../app/livechat/server/business-hour/Single'; @@ -32,5 +33,21 @@ Meteor.startup(async function() { businessHourManager.startManager(); } }); + settings.onload('Livechat_max_queue_wait_time_action', function(_, value) { + updateQueueInactivityTimeout(); + if (!value || value === 'Nothing') { + return Promise.await(OmnichannelQueueInactivityMonitor.stop()); + } + return Promise.await(OmnichannelQueueInactivityMonitor.schedule()); + }); + + settings.onload('Livechat_max_queue_wait_time', function(_, value) { + if (value <= 0) { + return Promise.await(OmnichannelQueueInactivityMonitor.stop()); + } + updateQueueInactivityTimeout(); + Promise.await(OmnichannelQueueInactivityMonitor.schedule()); + }); + await resetDefaultBusinessHourIfNeeded(); }); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index b3c1946e7f6b..898484c25e31 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -866,6 +866,7 @@ "Clients_will_refresh_in_a_few_seconds": "Clients will refresh in a few seconds", "close": "close", "Close": "Close", + "Close_chat": "Close chat", "Close_room_description": "You are about to close this chat. Are you sure you want to continue?", "close-livechat-room": "Close Omnichannel Room", "close-livechat-room_description": "Permission to close the current Omnichannel room", @@ -874,6 +875,7 @@ "Closed": "Closed", "Closed_At": "Closed at", "Closed_automatically": "Closed automatically by the system", + "Closed_automatically_chat_queued_too_long": "Closed automatically by the system (queue maximum time exceeded)", "Closed_by_visitor": "Closed by visitor", "Closing_chat": "Closing chat", "Closing_chat_message": "Closing chat message", @@ -2601,6 +2603,8 @@ "Livechat_last_chatted_agent_routing_Description": "The Last-Chatted Agent setting allocates chats to the agent who previously interacted with the same visitor if the agent is available when the chat starts.", "Livechat_managers": "Omnichannel managers", "Livechat_Managers": "Managers", + "Livechat_max_queue_wait_time_action": "How to handle queued chats when the maximum wait time is reached", + "Livechat_maximum_queue_wait_time": "Maximum waiting time in queue", "Livechat_message_character_limit": "Livechat message character limit", "Livechat_monitors": "Livechat monitors", "Livechat_Monitors": "Monitors", @@ -3354,6 +3358,7 @@ "Query_is_not_valid_JSON": "Query is not valid JSON", "Queue": "Queue", "Queue_Time": "Queue Time", + "Queue_management": "Queue Management", "quote": "quote", "Quote": "Quote", "Random": "Random", @@ -4128,6 +4133,7 @@ "Thread_message": "Commented on *__username__'s* message: _ __msg__ _", "Threads": "Threads", "Thursday": "Thursday", + "Time_in_minutes": "Time in minutes", "Time_in_seconds": "Time in seconds", "Timeout": "Timeout", "Timeouts": "Timeouts", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index dae29472e226..c482bfee4c21 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -2127,6 +2127,8 @@ "Livechat_last_chatted_agent_routing_Description": "Agente preferido pela última conversa aloca bate-papos para o agente que interagiu anteriormente com o mesmo visitante, caso o agente esteja disponível quando o bate-papo for iniciado.", "Livechat_managers": "Gerentes do Omnichannel", "Livechat_Managers": "Gerentes", + "Livechat_max_queue_wait_time_action": "O que fazer com chats na fila quando tempo máximo de espera for atingido", + "Livechat_maximum_queue_wait_time": "Tempo máximo de espera na fila", "Livechat_message_character_limit": "Limite de caracteres da mensagem no livechat", "Livechat_monitors": "Monitores de Livechat", "Livechat_Monitors": "Monitores", @@ -2728,6 +2730,7 @@ "Query": "Query", "Query_description": "Condições adicionais para determinar para quais usuários enviar e-mail. Usuários não inscritos são automaticamente removidos a partir da consulta. Deve ser um JSON válido. Exemplo: `{\"createdAt\": {\"$gt\": {\"$date\": \"2015-01-01T00: 00: 00.000Z\"}}}`", "Queue": "Fila", + "Queue_management": "Gerenciamento de Fila", "Queued": "Na Fila", "Queue_Time": "Tempo na fila", "quote": "citação", @@ -3298,6 +3301,7 @@ "Thread_message": "Comentou em * __username__ 's * message: _ __msg__ _", "Threads": "Tópicos", "Thursday": "Quinta-feira", + "Time_in_minutes": "Tempo em minutos", "Time_in_seconds": "Tempo em segundos", "Timeouts": "Tempos limite", "Timezone": "Fuso horário",