diff --git a/app/livechat/client/stylesheets/livechat.css b/app/livechat/client/stylesheets/livechat.css index f80d646c0b5c..00eb63ef619d 100644 --- a/app/livechat/client/stylesheets/livechat.css +++ b/app/livechat/client/stylesheets/livechat.css @@ -5,6 +5,7 @@ --primary-font-color: #444444; --secondary-font-color: #7f7f7f; --info-font-color: #aaaaaa; + --color-gray: #9ea2a8; } .flex-list { @@ -639,3 +640,53 @@ width: 100%; height: 100%; } + +.chat-history-item { + cursor: pointer; + + border-bottom: 2px solid #f2f3f5; +} + +.open { + padding-left: 4% !important; +} + +.contact-chat-history-messages-list .message-actions { + visibility: hidden; +} + +.chat-history-item-count-msg { + margin-top: 1%; + + color: grey; + + font-family: Inter; + font-size: 12px; + font-weight: bold; + font-style: normal; + line-height: 16px; +} + +.closing-message-body-wrapper { + margin-top: 3%; +} + +.closing-message-title { + color: var(--color-dark-light); + + font-family: Inter; + font-size: 12px; + font-weight: bold; + font-style: normal; + line-height: 16px; +} + +.closing-message-text { + color: var(--color-gray); + + font-family: Inter; + font-size: 12px; + font-weight: 500; + font-style: italic; + line-height: 16px; +} diff --git a/app/livechat/client/ui.js b/app/livechat/client/ui.js index 23b945147195..2572503a21d1 100644 --- a/app/livechat/client/ui.js +++ b/app/livechat/client/ui.js @@ -32,10 +32,10 @@ TabBar.addButton({ TabBar.addButton({ groups: ['live'], - id: 'visitor-history', - i18nTitle: 'Past_Chats', + id: 'conatct-chat-history', + i18nTitle: 'Contact_Chat_History', icon: 'clock', - template: 'visitorHistory', + template: 'contactChatHistory', order: 11, }); diff --git a/app/livechat/client/views/app/tabbar/contactChatHistory.html b/app/livechat/client/views/app/tabbar/contactChatHistory.html new file mode 100644 index 000000000000..d5305556fe42 --- /dev/null +++ b/app/livechat/client/views/app/tabbar/contactChatHistory.html @@ -0,0 +1,44 @@ + + {{#if isReady}} + {{#if canSearch}} + + + + + + + {{> icon block="rc-input__icon-svg" icon='magnifier'}} + + + + + + + + {{/if}} + + {{#if hasChatHistory}} + + + {{#each item in history}} + {{> contactChatHistoryItem item}} + {{/each}} + + + {{else}} + {{#if isSearching}} + {{_ "No_results_found"}} + {{else}} + {{_ "No_previous_chat_found"}} + {{/if}} + {{/if}} + + {{else}} + {{> loading}} + {{/if}} + {{#if showChatHistoryMessages}} + + {{> contactChatHistoryMessages (chatHistoryMessagesContext)}} + + {{/if}} + diff --git a/app/livechat/client/views/app/tabbar/contactChatHistory.js b/app/livechat/client/views/app/tabbar/contactChatHistory.js new file mode 100644 index 000000000000..87ff3b1cee5d --- /dev/null +++ b/app/livechat/client/views/app/tabbar/contactChatHistory.js @@ -0,0 +1,150 @@ +import { Tracker } from 'meteor/tracker'; +import { Template } from 'meteor/templating'; +import moment from 'moment'; +import { ReactiveVar } from 'meteor/reactive-var'; +import './contactChatHistory.html'; +import './contactChatHistoryItem.html'; +import _ from 'underscore'; + +import { t, APIClient } from '../../../../../utils/client'; + + +const HISTORY_LIMIT = 50; + +Template.contactChatHistory.helpers({ + isReady() { + return Template.instance().isReady.get(); + }, + hasChatHistory() { + return Template.instance().history.get().length > 0; + }, + history() { + return Template.instance().history.get(); + }, + isLoading() { + return Template.instance().isLoading.get(); + }, + isSearching() { + return Template.instance().searchTerm.get().length > 0; + }, + showChatHistoryMessages() { + return Template.instance().showChatHistoryMessages.get(); + }, + chatHistoryMessagesContext() { + return { + tabBar: Template.instance().tabBar, + clear: Template.instance().returnChatHistoryList, + ...Template.instance().chatHistoryMessagesContext.get(), + }; + }, + canSearch() { + return Template.instance().canSearch.get(); + }, +}); + +Template.contactChatHistory.onCreated(async function() { + const currentData = Template.currentData(); + this.offset = new ReactiveVar(0); + this.visitorId = new ReactiveVar(); + this.history = new ReactiveVar([]); + this.searchTerm = new ReactiveVar(''); + this.hasMore = new ReactiveVar(true); + this.isLoading = new ReactiveVar(true); + this.chatHistoryMessagesContext = new ReactiveVar(); + this.showChatHistoryMessages = new ReactiveVar(false); + this.limit = new ReactiveVar(HISTORY_LIMIT); + this.isReady = new ReactiveVar(false); + this.canSearch = new ReactiveVar(false); + this.tabBar = currentData.tabBar; + + this.returnChatHistoryList = () => { + this.showChatHistoryMessages.set(false); + this.chatHistoryMessagesContext.set(); + + this.tabBar.setData({ + label: 'Contact_Chat_History', + icon: 'clock', + }); + }; + + this.autorun(async () => { + if (!this.visitorId.get() || !currentData || !currentData.rid) { + return; + } + + const limit = this.limit.get(); + const offset = this.offset.get(); + const searchTerm = this.searchTerm.get(); + + let baseUrl = `livechat/visitors.searchChats/room/${ currentData.rid }/visitor/${ this.visitorId.get() }?count=${ limit }&offset=${ offset }&closedChatsOnly=true&servedChatsOnly=true`; + if (searchTerm) { + baseUrl += `&searchText=${ searchTerm }`; + } + + this.isLoading.set(true); + const { history, total } = await APIClient.v1.get(baseUrl); + this.history.set(offset === 0 ? history : this.history.get().concat(history)); + this.hasMore.set(total > this.history.get().length); + this.isLoading.set(false); + }); + + this.autorun(async () => { + const { room } = await APIClient.v1.get(`rooms.info?roomId=${ currentData.rid }`); + if (room?.v) { + this.visitorId.set(room.v._id); + } + }); +}); + +Template.contactChatHistory.onRendered(function() { + Tracker.autorun((computation) => { + if (this.isLoading.get()) { + return; + } + + const history = this.history.get(); + this.canSearch.set(history && history.length > 0); + this.isReady.set(true); + + computation.stop(); + }); +}); + + +Template.contactChatHistory.events({ + 'scroll .js-list': _.throttle(function(e, instance) { + if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight - 10 && instance.hasMore.get()) { + instance.offset.set(instance.offset.get() + instance.limit.get()); + } + }, 200), + 'click .chat-history-item'(event, instance) { + event.preventDefault(); + event.stopPropagation(); + + const { _id: rid, v: { name, username } = { }, closedAt } = this; + + const closedAtLabel = t('Closed_At'); + const closedDay = moment(closedAt).format('MMM D YYYY'); + + instance.chatHistoryMessagesContext.set({ + label: `${ name || username }, ${ closedAtLabel } ${ closedDay }`, + rid, + }); + + instance.showChatHistoryMessages.set(true); + }, + 'keyup #chat-search': _.debounce(function(e, instance) { + if (e.keyCode === 13) { + return e.preventDefault(); + } + + const { value } = e.target; + + if (e.keyCode === 40 || e.keyCode === 38) { + return e.preventDefault(); + } + + instance.offset.set(0); + instance.searchTerm.set(value); + }, 300), +}); diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryItem.html b/app/livechat/client/views/app/tabbar/contactChatHistoryItem.html new file mode 100644 index 000000000000..2a10c9890d98 --- /dev/null +++ b/app/livechat/client/views/app/tabbar/contactChatHistoryItem.html @@ -0,0 +1,26 @@ + + + + {{> avatar username=servedBy.username}} + + + + {{servedBy.username}} + {{closedAt}} + + + {{#if $gt msgs 0}} + {{{_ 'message_counter' counter=i18nMessageCounter count=msgs }}} + {{else}} + {{_ "No_messages_yet" }} + {{/if}} + + {{#with closingMessage }} + + {{_ "Closing_chat_message"}}: + "{{msg}}" + + {{/with}} + + + diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryItem.js b/app/livechat/client/views/app/tabbar/contactChatHistoryItem.js new file mode 100644 index 000000000000..dc370050df63 --- /dev/null +++ b/app/livechat/client/views/app/tabbar/contactChatHistoryItem.js @@ -0,0 +1,28 @@ +import moment from 'moment'; +import './contactChatHistoryItem.html'; +import { Template } from 'meteor/templating'; +import { ReactiveVar } from 'meteor/reactive-var'; + +Template.contactChatHistoryItem.helpers({ + closedAt() { + const { closedAt } = Template.instance().room.get(); + return moment(closedAt).format('lll'); + }, + closingRoomMessage() { + const closingObj = Template.instance().closingRoomMessage.get(); + return closingObj.msg; + }, + i18nMessageCounter() { + const { msgs } = this; + return `${ msgs }`; + }, +}); + +Template.contactChatHistoryItem.onCreated(function() { + this.room = new ReactiveVar(); + + this.autorun(async () => { + const currentData = Template.currentData(); + this.room.set(currentData); + }); +}); diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html new file mode 100644 index 000000000000..5a938d563168 --- /dev/null +++ b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html @@ -0,0 +1,47 @@ + + + + + + + {{> icon icon="clock" block="contextual-bar__header-icon"}} + {{label}} + + + {{> icon block="contextual-bar__header-close-icon" icon="plus"}} + + + + + + + + + + {{> icon block="rc-input__icon-svg" icon='magnifier'}} + + + + + + + + {{#if $and isSearching empty }} + + {{_ "No_results_found"}} + + {{else}} + + + {{# with messageContext}} + {{#each msg in messages}}{{> message msg=msg room=room subscription=subscription settings=settings u=u}}{{/each}} + {{/with}} + + {{#if isLoading}} + {{> loading}} + {{/if}} + + + {{/if}} + + diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js new file mode 100644 index 000000000000..4e09c19dd82e --- /dev/null +++ b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js @@ -0,0 +1,103 @@ +import { Template } from 'meteor/templating'; +import './contactChatHistoryMessages.html'; +import { ReactiveVar } from 'meteor/reactive-var'; +import _ from 'underscore'; + +import { messageContext } from '../../../../../ui-utils/client/lib/messageContext'; +import { APIClient } from '../../../../../utils/client'; + +const MESSAGES_LIMIT = 10; + +Template.contactChatHistoryMessages.helpers({ + messages() { + return Template.instance().messages.get(); + }, + messageContext() { + const result = messageContext.call(this, { rid: Template.instance().rid }); + return { + ...result, + settings: { + ...result.settings, + showReplyButton: false, + showreply: false, + hideRoles: true, + }, + }; + }, + hasMore() { + return Template.instance().hasMore.get(); + }, + isLoading() { + return Template.instance().isLoading.get(); + }, + isSearching() { + return Template.instance().searchTerm.get().length > 0; + }, + empty() { + return Template.instance().messages.get().length === 0; + }, +}); + +Template.contactChatHistoryMessages.events({ + 'click .js-back'(e, instance) { + return instance.clear(); + }, + 'scroll .js-list': _.throttle(function(e, instance) { + if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) { + instance.offset.set(instance.offset.get() + instance.limit.get()); + } + }, 200), + 'keyup #message-search': _.debounce(function(e, instance) { + if (e.keyCode === 13) { + return e.preventDefault(); + } + + const { value } = e.target; + + if (e.keyCode === 40 || e.keyCode === 38) { + return e.preventDefault(); + } + + instance.offset.set(0); + instance.searchTerm.set(value); + }, 300), +}); + +Template.contactChatHistoryMessages.onCreated(function() { + const currentData = Template.currentData(); + this.rid = currentData.rid; + this.messages = new ReactiveVar([]); + this.hasMore = new ReactiveVar(true); + this.offset = new ReactiveVar(0); + this.searchTerm = new ReactiveVar(''); + this.isLoading = new ReactiveVar(true); + this.limit = new ReactiveVar(MESSAGES_LIMIT); + + this.loadMessages = async (url) => { + this.isLoading.set(true); + const offset = this.offset.get(); + + const { messages, total } = await APIClient.v1.get(url); + this.messages.set(offset === 0 ? messages : this.messages.get().concat(messages)); + this.hasMore.set(total > this.messages.get().length); + this.isLoading.set(false); + }; + + this.autorun(() => { + const limit = this.limit.get(); + const offset = this.offset.get(); + const searchTerm = this.searchTerm.get(); + + if (searchTerm !== '') { + return this.loadMessages(`chat.search/?roomId=${ this.rid }&searchText=${ searchTerm }&count=${ limit }&offset=${ offset }&sort={"ts": 1}`); + } + + this.loadMessages(`channels.messages/?roomId=${ this.rid }&count=${ limit }&offset=${ offset }&sort={"ts": 1}&query={"$or": [ {"t": {"$exists": false} }, {"t": "livechat-close"} ] }`); + }); + + this.autorun(() => { + if (currentData.clear != null) { + this.clear = currentData.clear; + } + }); +}); diff --git a/app/livechat/client/views/app/tabbar/visitorHistory.html b/app/livechat/client/views/app/tabbar/visitorHistory.html deleted file mode 100644 index 25b744f51cc4..000000000000 --- a/app/livechat/client/views/app/tabbar/visitorHistory.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {{_ "Past_Chats"}} - - - {{#if isLoading}} - {{_ "Loading..."}} - {{else}} - - - {{#each previousChats}} - {{title}} - {{/each}} - - - {{/if}} - - - - diff --git a/app/livechat/client/views/app/tabbar/visitorHistory.js b/app/livechat/client/views/app/tabbar/visitorHistory.js deleted file mode 100644 index 84d3c00b447c..000000000000 --- a/app/livechat/client/views/app/tabbar/visitorHistory.js +++ /dev/null @@ -1,70 +0,0 @@ -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import moment from 'moment'; -import _ from 'underscore'; - -import './visitorHistory.html'; -import { APIClient } from '../../../../../utils/client'; - -const ITEMS_COUNT = 50; - -Template.visitorHistory.helpers({ - isLoading() { - return Template.instance().isLoading.get(); - }, - - previousChats() { - return Template.instance().history.get(); - }, - - title() { - let title = moment(this.ts).format('L LTS'); - - if (this.label) { - title += ` - ${ this.label }`; - } - - return title; - }, -}); - -Template.visitorHistory.onCreated(function() { - const currentData = Template.currentData(); - this.visitorId = new ReactiveVar(); - this.isLoading = new ReactiveVar(false); - this.history = new ReactiveVar([]); - this.offset = new ReactiveVar(0); - this.total = new ReactiveVar(0); - - this.autorun(async () => { - const { room } = await APIClient.v1.get(`rooms.info?roomId=${ currentData.rid }`); - if (room && room.v) { - this.visitorId.set(room.v._id); - } - }); - - this.autorun(async () => { - if (!this.visitorId.get() || !currentData || !currentData.rid) { - return; - } - - const offset = this.offset.get(); - this.isLoading.set(true); - const { history, total } = await APIClient.v1.get(`livechat/visitors.chatHistory/room/${ currentData.rid }/visitor/${ this.visitorId.get() }?count=${ ITEMS_COUNT }&offset=${ offset }`); - this.isLoading.set(false); - this.total.set(total); - this.history.set(this.history.get().concat(history)); - }); -}); - -Template.visitorHistory.events({ - 'scroll .visitor-scroll': _.throttle(function(e, instance) { - if (e.target.scrollTop >= (e.target.scrollHeight - e.target.clientHeight)) { - const history = instance.history.get(); - if (instance.total.get() <= history.length) { - return; - } - return instance.offset.set(instance.offset.get() + ITEMS_COUNT); - } - }, 200), -}); diff --git a/app/livechat/client/views/regular.js b/app/livechat/client/views/regular.js index e2a3f3b8b6fd..00ce6f4184b2 100644 --- a/app/livechat/client/views/regular.js +++ b/app/livechat/client/views/regular.js @@ -8,6 +8,8 @@ import './app/tabbar/agentInfo'; import './app/tabbar/visitorEdit'; import './app/tabbar/visitorEditCustomField'; import './app/tabbar/visitorForward'; -import './app/tabbar/visitorHistory'; +import './app/tabbar/contactChatHistory'; +import './app/tabbar/contactChatHistoryItem'; +import './app/tabbar/contactChatHistoryMessages'; import './app/tabbar/visitorInfo'; import './app/tabbar/visitorNavigation'; diff --git a/app/livechat/imports/server/rest/visitors.js b/app/livechat/imports/server/rest/visitors.js index cc38c3bc4008..423950ff3d02 100644 --- a/app/livechat/imports/server/rest/visitors.js +++ b/app/livechat/imports/server/rest/visitors.js @@ -2,7 +2,7 @@ import { check } from 'meteor/check'; import { API } from '../../../../api/server'; -import { findVisitorInfo, findVisitedPages, findChatHistory, findVisitorsToAutocomplete } from '../../../server/api/lib/visitors'; +import { findVisitorInfo, findVisitedPages, findChatHistory, searchChats, findVisitorsToAutocomplete } from '../../../server/api/lib/visitors'; API.v1.addRoute('livechat/visitors.info', { authRequired: true }, { get() { @@ -47,7 +47,6 @@ API.v1.addRoute('livechat/visitors.chatHistory/room/:roomId/visitor/:visitorId', }); const { offset, count } = this.getPaginationItems(); const { sort } = this.parseJsonQuery(); - const history = Promise.await(findChatHistory({ userId: this.userId, roomId: this.urlParams.roomId, @@ -63,6 +62,33 @@ API.v1.addRoute('livechat/visitors.chatHistory/room/:roomId/visitor/:visitorId', }, }); +API.v1.addRoute('livechat/visitors.searchChats/room/:roomId/visitor/:visitorId', { authRequired: true }, { + get() { + check(this.urlParams, { + visitorId: String, + roomId: String, + }); + const { roomId, visitorId } = this.urlParams; + const { searchText, closedChatsOnly, servedChatsOnly } = this.queryParams; + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const history = Promise.await(searchChats({ + userId: this.userId, + roomId, + visitorId, + searchText, + closedChatsOnly, + servedChatsOnly, + pagination: { + offset, + count, + sort, + }, + })); + return API.v1.success(history); + }, +}); + API.v1.addRoute('livechat/visitors.autocomplete', { authRequired: true }, { get() { const { selector } = this.queryParams; diff --git a/app/livechat/server/api/lib/visitors.js b/app/livechat/server/api/lib/visitors.js index d1c7477fdd56..39fca9d90c8a 100644 --- a/app/livechat/server/api/lib/visitors.js +++ b/app/livechat/server/api/lib/visitors.js @@ -72,6 +72,37 @@ export async function findChatHistory({ userId, roomId, visitorId, pagination: { total, }; } +export async function searchChats({ userId, roomId, visitorId, searchText, closedChatsOnly, servedChatsOnly: served, pagination: { offset, count, sort } }) { + if (!await hasPermissionAsync(userId, 'view-l-room')) { + throw new Error('error-not-authorized'); + } + const room = await LivechatRooms.findOneById(roomId); + if (!room) { + throw new Error('invalid-room'); + } + + if (!await canAccessRoomAsync(room, { _id: userId })) { + throw new Error('error-not-allowed'); + } + + const options = { + sort: sort || { ts: -1 }, + skip: offset, + limit: count, + }; + + const [total] = await LivechatRooms.findRoomsByVisitorIdAndMessageWithCriteria({ visitorId, open: !closedChatsOnly, served, searchText, onlyCount: true }).toArray(); + const cursor = await LivechatRooms.findRoomsByVisitorIdAndMessageWithCriteria({ visitorId, open: !closedChatsOnly, served, searchText, options }); + + const history = await cursor.toArray(); + + return { + history, + count: history.length, + offset, + total: (total && total.count) || 0, + }; +} export async function findVisitorsToAutocomplete({ userId, selector }) { if (!await hasPermissionAsync(userId, 'view-l-room')) { diff --git a/app/livechat/server/api/v1/message.js b/app/livechat/server/api/v1/message.js index 0811bc8324fb..68a83706fc18 100644 --- a/app/livechat/server/api/v1/message.js +++ b/app/livechat/server/api/v1/message.js @@ -204,8 +204,10 @@ API.v1.addRoute('livechat/messages.history/:rid', { rid: String, }); + const { offset } = this.getPaginationItems(); + const { searchText: text, token } = this.queryParams; const { rid } = this.urlParams; - const { token } = this.queryParams; + const { sort } = this.parseJsonQuery(); if (!token) { throw new Meteor.Error('error-token-param-not-provided', 'The required "token" query param is missing.'); @@ -236,7 +238,7 @@ API.v1.addRoute('livechat/messages.history/:rid', { limit = parseInt(this.queryParams.limit); } - const messages = loadMessageHistory({ userId: guest._id, rid, end, limit, ls }) + const messages = loadMessageHistory({ userId: guest._id, rid, end, limit, ls, sort, offset, text }) .messages .map(normalizeMessageFileUpload); return API.v1.success({ messages }); diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 06707539e47f..1ff1886caa03 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -19,6 +19,7 @@ export class LivechatRooms extends Base { this.tryEnsureIndex({ closedAt: 1 }, { sparse: true }); this.tryEnsureIndex({ servedBy: 1 }, { sparse: true }); this.tryEnsureIndex({ 'v.token': 1 }, { sparse: true }); + this.tryEnsureIndex({ 'v._id': 1 }, { sparse: true }); } findLivechat(filter = {}, offset = 0, limit = 20) { diff --git a/app/models/server/models/Messages.js b/app/models/server/models/Messages.js index 285161850efa..862207d1f971 100644 --- a/app/models/server/models/Messages.js +++ b/app/models/server/models/Messages.js @@ -251,7 +251,6 @@ export class Messages extends Base { _hidden: { $ne: true, }, - rid: roomId, }; diff --git a/app/models/server/raw/LivechatRooms.js b/app/models/server/raw/LivechatRooms.js index 39fa94546222..0c07e12add2b 100644 --- a/app/models/server/raw/LivechatRooms.js +++ b/app/models/server/raw/LivechatRooms.js @@ -834,11 +834,69 @@ export class LivechatRoomsRaw extends BaseRaw { t: 'l', 'v._id': visitorId, }; - return this.find(query, options); } - findRoomsWithCriteria({ agents, roomName, departmentId, open, createdAt, closedAt, tags, customFields, options = {} }) { + findRoomsByVisitorIdAndMessageWithCriteria({ visitorId, searchText, open, served, onlyCount = false, options = {} }) { + const match = { + $match: { + 'v._id': visitorId, + ...open !== undefined && { open: { $exists: open } }, + ...served !== undefined && { servedBy: { $exists: served } }, + }, + }; + const lookup = { $lookup: { from: 'rocketchat_message', localField: '_id', foreignField: 'rid', as: 'messages' } }; + const matchMessages = searchText && { $match: { 'messages.msg': { $regex: `.*${ searchText }.*` } } }; + + const params = [match, lookup]; + + if (matchMessages) { + params.push(matchMessages); + } + + const project = { + $project: { + fname: 1, + ts: 1, + v: 1, + msgs: 1, + servedBy: 1, + closedAt: 1, + closedBy: 1, + closer: 1, + tags: 1, + closingMessage: { + $filter: { + input: '$messages', + as: 'messages', + cond: { $eq: ['$$messages.t', 'livechat-close'] }, + }, + }, + }, + }; + + const unwindClosingMsg = { $unwind: { path: '$closingMessage', preserveNullAndEmptyArrays: true } }; + const sort = { $sort: options.sort || { ts: -1 } }; + + params.push(project, unwindClosingMsg, sort); + + if (onlyCount) { + params.push({ $count: 'count' }); + return this.col.aggregate(params); + } + + if (options.skip) { + params.push({ $skip: options.skip }); + } + + if (options.limit) { + params.push({ $limit: options.limit }); + } + + return this.col.aggregate(params); + } + + findRoomsWithCriteria({ agents, roomName, departmentId, open, served, createdAt, closedAt, tags, customFields, visitorId, roomIds, options = {} }) { const query = { t: 'l', }; @@ -854,6 +912,12 @@ export class LivechatRoomsRaw extends BaseRaw { if (open !== undefined) { query.open = { $exists: open }; } + if (served !== undefined) { + query.servedBy = { $exists: served }; + } + if (visitorId && visitorId !== 'undefined') { + query['v._id'] = visitorId; + } if (createdAt) { query.ts = {}; if (createdAt.start) { @@ -878,6 +942,11 @@ export class LivechatRoomsRaw extends BaseRaw { if (customFields) { query.$and = Object.keys(customFields).map((key) => ({ [`livechatData.${ key }`]: new RegExp(customFields[key], 'i') })); } + + if (roomIds) { + query._id = { $in: roomIds }; + } + return this.find(query, { sort: options.sort || { name: 1 }, skip: options.offset, limit: options.count }); } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index fb3223682d64..fc5841841f26 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -732,6 +732,7 @@ "Closed_At": "Closed at", "Closed_by_visitor": "Closed by visitor", "Closing_chat": "Closing chat", + "Closing_chat_message": "Closing chat message", "Cloud": "Cloud", "Cloud_Register_manually": "Register Manually", "Cloud_click_here": "After copy the text, go to [cloud console (click here)](__cloudConsoleUrl__).", @@ -786,6 +787,7 @@ "Consumer_Goods": "Consumer Goods", "Contains_Security_Fixes": "Contains Security Fixes", "Contact": "Contact", + "Contact_Chat_History": "Contact Chat History", "Content": "Content", "Continue": "Continue", "Continuous_sound_notifications_for_new_livechat_room": "Continuous sound notifications for new omnichannel room", @@ -2544,6 +2546,7 @@ "No_messages_yet": "No messages yet", "No_pages_yet_Try_hitting_Reload_Pages_button": "No pages yet. Try hitting \"Reload Pages\" button.", "No_pinned_messages": "No pinned messages", + "No_previous_chat_found": "No previous chat found", "No_results_found": "No results found", "No_results_found_for": "No results found for:", "No_snippet_messages": "No snippet", @@ -3049,6 +3052,7 @@ "Search_by_file_name": "Search by file name", "Search_by_username": "Search by username", "Search_Channels": "Search Channels", + "Search_Chat_History": "Search Chat History", "Search_current_provider_not_active": "Current Search Provider is not active", "Search_Integrations": "Search Integrations", "Search_message_search_failed": "Search request failed", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 7c7ad857ea07..747779fa2edf 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -676,6 +676,7 @@ "Closed_At": "Encerrado em", "Closed_by_visitor": "Encerrado pelo visitante", "Closing_chat": "Encerrando chat", + "Closing_chat_message": "Mensagem de encerramento de chat", "Cloud": "Nuvem", "Cloud_Register_manually": "Registre manualmente", "Cloud_register_success": "A sua área de trabalho foi registrada com sucesso!", @@ -716,7 +717,8 @@ "Consulting": "Consultar", "Consumer_Goods": "Bens de consumo", "Contains_Security_Fixes": "Contém correções de segurança", - "Contact": "Contacto", + "Contact": "Contato", + "Contact_Chat_History": "Histórico do bate-papo do contato", "Content": "Conteúdo", "Continue": "Continuar", "Continuous_sound_notifications_for_new_livechat_room": "Notificações sonoras contínuas, para nova sala de omnichannel", @@ -2276,6 +2278,7 @@ "No_messages_yet": "Ainda não há mensagens", "No_pages_yet_Try_hitting_Reload_Pages_button": "Ainda não há páginas. Tente acertar o botão \"Recarregar páginas\".", "No_pinned_messages": "Não há mensagens fixadas", + "No_previous_chat_found": "Nenhum bate-papo anterior encontrado", "No_results_found": "Nenhum resultado encontrado", "No_results_found_for": "Nenhum resultado encontrado para:", "No_snippet_messages": "Sem trecho", @@ -2718,6 +2721,7 @@ "Search_by_file_name": "Pesquisar por nome de arquivo", "Search_by_username": "Busca por nome de usuário", "Search_Channels": "Pesquisar Canais", + "Search_Chats": "Pesquisar Histórico Bate-Papos", "Search_current_provider_not_active": "Provedor de pesquisa atual não está ativo", "Search_message_search_failed": "Falha na solicitação de pesquisa", "Search_Messages": "Pesquisar Mensagens",
{{{_ 'message_counter' counter=i18nMessageCounter count=msgs }}}
{{_ "No_messages_yet" }}
{{_ "Closing_chat_message"}}:
"{{msg}}"