From 4fbe78297fe92c85a850640c1906a2fe96df4d01 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 24 Jun 2022 11:19:00 -0300 Subject: [PATCH 1/6] Chore: VoIP Context (#25994) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/client/contexts/CallContext.ts | 39 ++++++++++------------ 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/apps/meteor/client/contexts/CallContext.ts b/apps/meteor/client/contexts/CallContext.ts index b28a325e97cf..bb739416a74c 100644 --- a/apps/meteor/client/contexts/CallContext.ts +++ b/apps/meteor/client/contexts/CallContext.ts @@ -1,6 +1,6 @@ import type { IVoipRoom } from '@rocket.chat/core-typings'; import { ICallerInfo, VoIpCallerInfo } from '@rocket.chat/core-typings'; -import { createContext, useCallback, useContext } from 'react'; +import { createContext, useContext, useMemo } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { VoIPUser } from '../lib/voip/VoIPUser'; @@ -62,18 +62,11 @@ export const useIsCallEnabled = (): boolean => { return enabled; }; -let callerInfo: VoIpCallerInfo; - export const useIsCallReady = (): boolean => { - const context = useContext(CallContext); + const { ready } = useContext(CallContext); - if (isCallContextReady(context)) { - callerInfo = context.voipClient.callerInfo; - } - - return !!context.ready; + return Boolean(ready); }; - export const useIsCallError = (): boolean => { const context = useContext(CallContext); return Boolean(isCallContextError(context)); @@ -94,19 +87,21 @@ export const useCallerInfo = (): VoIpCallerInfo => { if (!isCallContextReady(context)) { throw new Error('useCallerInfo only if Calls are enabled and ready'); } + const { voipClient } = context; - const subscribe = useCallback( - (callback: () => void): (() => void) => { - voipClient.on('stateChanged', callback); - - return (): void => { - voipClient.off('stateChanged', callback); - }; - }, - [voipClient], - ); - - const getSnapshot = (): VoIpCallerInfo => callerInfo; + + const [subscribe, getSnapshot] = useMemo(() => { + let caller: VoIpCallerInfo = voipClient.callerInfo; + + const callback = (cb: () => void): (() => void) => + voipClient.on('stateChanged', () => { + caller = voipClient.callerInfo; + cb(); + }); + + const getSnapshot = (): VoIpCallerInfo => caller; + return [callback, getSnapshot]; + }, [voipClient]); return useSyncExternalStore(subscribe, getSnapshot); }; From 24ffc28a6fe889b86d794799432c7b879e1facd9 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 24 Jun 2022 10:41:43 -0600 Subject: [PATCH 2/6] Chore: Migrate from meteor model to raw (#25756) Co-authored-by: Diego Sampaio --- .../app/apps/server/bridges/livechat.ts | 24 +- .../app/apps/server/converters/rooms.js | 5 +- .../app/apps/server/converters/visitors.js | 8 +- .../livechat/imports/server/rest/facebook.js | 11 +- .../app/livechat/imports/server/rest/sms.js | 9 +- .../livechat/imports/server/rest/upload.js | 19 +- .../app/livechat/server/api/lib/livechat.js | 6 +- .../app/livechat/server/api/v1/agent.js | 4 +- .../app/livechat/server/api/v1/contact.js | 14 +- .../app/livechat/server/api/v1/customField.js | 26 +- .../app/livechat/server/api/v1/message.js | 39 +-- .../meteor/app/livechat/server/api/v1/room.js | 22 +- .../app/livechat/server/api/v1/transcript.js | 4 +- .../app/livechat/server/api/v1/videoCall.js | 8 +- .../app/livechat/server/api/v1/visitor.ts | 13 +- .../app/livechat/server/hooks/leadCapture.js | 5 +- .../server/hooks/saveContactLastChat.js | 2 +- .../app/livechat/server/hooks/sendToCRM.js | 2 +- .../server/hooks/sendTranscriptOnClose.js | 3 +- .../app/livechat/server/lib/Contacts.js | 18 +- apps/meteor/app/livechat/server/lib/Helper.js | 2 +- .../app/livechat/server/lib/Livechat.js | 60 ++--- .../livechat/server/methods/closeByVisitor.js | 7 +- .../livechat/server/methods/getAgentData.js | 7 +- .../livechat/server/methods/getInitialData.js | 6 +- .../livechat/server/methods/loadHistory.js | 7 +- .../livechat/server/methods/loginByToken.js | 7 +- .../livechat/server/methods/registerGuest.js | 14 +- .../app/livechat/server/methods/saveInfo.js | 4 +- .../server/methods/saveSurveyFeedback.js | 9 +- .../server/methods/sendFileLivechatMessage.js | 5 +- .../server/methods/sendMessageLivechat.js | 8 +- .../livechat/server/methods/setCustomField.js | 5 +- .../server/methods/setDepartmentForVisitor.js | 7 +- .../server/methods/startFileUploadRoom.js | 7 +- .../app/livechat/server/methods/transfer.js | 9 +- .../app/livechat/server/sendMessageBySMS.js | 5 +- apps/meteor/app/models/server/index.js | 2 - .../models/server/models/LivechatVisitors.js | 253 ------------------ .../app/statistics/server/lib/statistics.ts | 5 +- .../hooks/onMessageSentParsePlaceholder.ts | 9 +- .../hooks/handleNextAgentPreferredEvents.js | 14 +- .../server/lib/VisitorInactivityMonitor.js | 9 +- .../server/methods/resumeOnHold.ts | 36 +-- .../server/lib/ReadReceipt.js | 18 +- .../EmailInbox/EmailInbox_Incoming.ts | 24 +- .../server/lib/rooms/roomTypes/livechat.ts | 5 +- .../server/models/raw/LivechatVisitors.ts | 208 +++++++++++++- apps/meteor/server/startup/migrations/v260.ts | 11 +- packages/core-typings/src/ILivechatVisitor.ts | 10 + .../src/models/ILivechatVisitorsModel.ts | 20 +- yarn.lock | 2 +- 52 files changed, 526 insertions(+), 511 deletions(-) delete mode 100644 apps/meteor/app/models/server/models/LivechatVisitors.js diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index 23a0e91411da..28f28a9ae4c9 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -11,10 +11,11 @@ import { IUser } from '@rocket.chat/apps-engine/definition/users'; import { IMessage } from '@rocket.chat/apps-engine/definition/messages'; import { IExtraRoomParams } from '@rocket.chat/apps-engine/definition/accessors/ILivechatCreator'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; import { getRoom } from '../../../livechat/server/api/lib/livechat'; import { Livechat } from '../../../livechat/server/lib/Livechat'; -import { Users, LivechatDepartment, LivechatVisitors, LivechatRooms } from '../../../models/server'; +import { Users, LivechatDepartment, LivechatRooms } from '../../../models/server'; import { AppServerOrchestrator } from '../orchestrator'; export class AppLivechatBridge extends LivechatBridge { @@ -216,9 +217,9 @@ export class AppLivechatBridge extends LivechatBridge { console.warn('The method AppLivechatBridge.findVisitors is deprecated. Please consider using its alternatives'); } - return LivechatVisitors.find(query) - .fetch() - .map((visitor: IVisitor) => this.orch.getConverters()?.get('visitors').convertVisitor(visitor)); + return (await LivechatVisitors.find(query).toArray()).map( + (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor), + ); } protected async findVisitorById(id: string, appId: string): Promise { @@ -230,19 +231,28 @@ export class AppLivechatBridge extends LivechatBridge { protected async findVisitorByEmail(email: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`); - return this.orch.getConverters()?.get('visitors').convertVisitor(LivechatVisitors.findOneGuestByEmailAddress(email)); + return this.orch + .getConverters() + ?.get('visitors') + .convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email)); } protected async findVisitorByToken(token: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`); - return this.orch.getConverters()?.get('visitors').convertVisitor(LivechatVisitors.getVisitorByToken(token, {})); + return this.orch + .getConverters() + ?.get('visitors') + .convertVisitor(await LivechatVisitors.getVisitorByToken(token, {})); } protected async findVisitorByPhoneNumber(phoneNumber: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`); - return this.orch.getConverters()?.get('visitors').convertVisitor(LivechatVisitors.findOneVisitorByPhone(phoneNumber)); + return this.orch + .getConverters() + ?.get('visitors') + .convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber)); } protected async findDepartmentByIdOrName(value: string, appId: string): Promise { diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js index 32214be6372f..4a9f6225af15 100644 --- a/apps/meteor/app/apps/server/converters/rooms.js +++ b/apps/meteor/app/apps/server/converters/rooms.js @@ -1,6 +1,7 @@ import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { Rooms, Users, LivechatVisitors, LivechatDepartment } from '../../../models/server'; +import { Rooms, Users, LivechatDepartment } from '../../../models/server'; import { transformMappedData } from '../../lib/misc/transformMappedData'; export class AppRoomsConverter { @@ -36,7 +37,7 @@ export class AppRoomsConverter { let v; if (room.visitor) { - const visitor = LivechatVisitors.findOneById(room.visitor.id); + const visitor = Promise.await(LivechatVisitors.findOneById(room.visitor.id)); v = { _id: visitor._id, username: visitor.username, diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js index 40c29e1c59a8..361aa3758c6a 100644 --- a/apps/meteor/app/apps/server/converters/visitors.js +++ b/apps/meteor/app/apps/server/converters/visitors.js @@ -1,19 +1,21 @@ -import LivechatVisitors from '../../../models/server/models/LivechatVisitors'; +import { LivechatVisitors } from '@rocket.chat/models'; + import { transformMappedData } from '../../lib/misc/transformMappedData'; +// TODO: check if functions from this converter can be async export class AppVisitorsConverter { constructor(orch) { this.orch = orch; } convertById(id) { - const visitor = LivechatVisitors.findOneById(id); + const visitor = Promise.await(LivechatVisitors.findOneById(id)); return this.convertVisitor(visitor); } convertByToken(token) { - const visitor = LivechatVisitors.getVisitorByToken(token); + const visitor = Promise.await(LivechatVisitors.getVisitorByToken(token)); return this.convertVisitor(visitor); } diff --git a/apps/meteor/app/livechat/imports/server/rest/facebook.js b/apps/meteor/app/livechat/imports/server/rest/facebook.js index 829e92d56930..db8f74ea264c 100644 --- a/apps/meteor/app/livechat/imports/server/rest/facebook.js +++ b/apps/meteor/app/livechat/imports/server/rest/facebook.js @@ -1,9 +1,10 @@ import crypto from 'crypto'; import { Random } from 'meteor/random'; +import { LivechatVisitors } from '@rocket.chat/models'; import { API } from '../../../../api/server'; -import { LivechatRooms, LivechatVisitors } from '../../../../models/server'; +import { LivechatRooms } from '../../../../models/server'; import { settings } from '../../../../settings/server'; import { Livechat } from '../../../server/lib/Livechat'; @@ -21,7 +22,7 @@ import { Livechat } from '../../../server/lib/Livechat'; * @apiParam {String} [attachments] Facebook message attachments */ API.v1.addRoute('livechat/facebook', { - post() { + async post() { if (!this.bodyParams.text && !this.bodyParams.attachments) { return { success: false, @@ -63,7 +64,7 @@ API.v1.addRoute('livechat/facebook', { }, }, }; - let visitor = LivechatVisitors.getVisitorByToken(this.bodyParams.token); + let visitor = await LivechatVisitors.getVisitorByToken(this.bodyParams.token); if (visitor) { const rooms = LivechatRooms.findOpenByVisitorToken(visitor.token).fetch(); if (rooms && rooms.length > 0) { @@ -76,12 +77,12 @@ API.v1.addRoute('livechat/facebook', { sendMessage.message.rid = Random.id(); sendMessage.message.token = this.bodyParams.token; - const userId = Livechat.registerGuest({ + const userId = await Livechat.registerGuest({ token: sendMessage.message.token, name: `${this.bodyParams.first_name} ${this.bodyParams.last_name}`, }); - visitor = LivechatVisitors.findOneById(userId); + visitor = await LivechatVisitors.findOneById(userId); } sendMessage.message.msg = this.bodyParams.text; diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.js b/apps/meteor/app/livechat/imports/server/rest/sms.js index 3447870082b8..1756325757df 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.js +++ b/apps/meteor/app/livechat/imports/server/rest/sms.js @@ -1,9 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; import { FileUpload } from '../../../../file-upload/server'; -import { LivechatRooms, LivechatVisitors, LivechatDepartment } from '../../../../models/server'; +import { LivechatRooms, LivechatDepartment } from '../../../../models/server'; import { API } from '../../../../api/server'; import { fetch } from '../../../../../server/lib/http/fetch'; import { SMS } from '../../../../sms'; @@ -34,7 +35,7 @@ const defineDepartment = (idOrName) => { return department && department._id; }; -const defineVisitor = (smsNumber, targetDepartment) => { +const defineVisitor = async (smsNumber, targetDepartment) => { const visitor = LivechatVisitors.findOneVisitorByPhone(smsNumber); let data = { token: (visitor && visitor.token) || Random.id(), @@ -53,7 +54,7 @@ const defineVisitor = (smsNumber, targetDepartment) => { data.department = targetDepartment; } - const id = Livechat.registerGuest(data); + const id = await Livechat.registerGuest(data); return LivechatVisitors.findOneById(id); }; @@ -79,7 +80,7 @@ API.v1.addRoute('livechat/sms-incoming/:service', { targetDepartment = defineDepartment(SMS.department); } - const visitor = defineVisitor(sms.from, targetDepartment); + const visitor = await defineVisitor(sms.from, targetDepartment); const { token } = visitor; const room = LivechatRooms.findOneOpenByVisitorTokenAndDepartmentId(token, targetDepartment); const roomExists = !!room; diff --git a/apps/meteor/app/livechat/imports/server/rest/upload.js b/apps/meteor/app/livechat/imports/server/rest/upload.js index 7947ac2d9826..5057c5e7e006 100644 --- a/apps/meteor/app/livechat/imports/server/rest/upload.js +++ b/apps/meteor/app/livechat/imports/server/rest/upload.js @@ -1,8 +1,9 @@ import { Meteor } from 'meteor/meteor'; import filesize from 'filesize'; +import { LivechatVisitors } from '@rocket.chat/models'; import { settings } from '../../../../settings/server'; -import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models/server'; +import { Settings, LivechatRooms } from '../../../../models/server'; import { fileUploadIsValidContentType } from '../../../../utils/server'; import { FileUpload } from '../../../../file-upload'; import { API } from '../../../../api/server'; @@ -19,13 +20,13 @@ settings.watch('FileUpload_MaxFileSize', function (value) { }); API.v1.addRoute('livechat/upload/:rid', { - post() { + async post() { if (!this.request.headers['x-visitor-token']) { return API.v1.unauthorized(); } const visitorToken = this.request.headers['x-visitor-token']; - const visitor = LivechatVisitors.getVisitorByToken(visitorToken); + const visitor = await LivechatVisitors.getVisitorByToken(visitorToken); if (!visitor) { return API.v1.unauthorized(); @@ -36,13 +37,11 @@ API.v1.addRoute('livechat/upload/:rid', { return API.v1.unauthorized(); } - const [file, fields] = Promise.await( - getUploadFormData( - { - request: this.request, - }, - { field: 'file' }, - ), + const [file, fields] = await getUploadFormData( + { + request: this.request, + }, + { field: 'file' }, ); if (!fileUploadIsValidContentType(file.mimetype)) { diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.js b/apps/meteor/app/livechat/server/api/lib/livechat.js index b1d45ac58892..92de8a4ffa9b 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.js +++ b/apps/meteor/app/livechat/server/api/lib/livechat.js @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { EmojiCustom, LivechatTrigger } from '@rocket.chat/models'; +import { EmojiCustom, LivechatTrigger, LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, LivechatVisitors, LivechatDepartment } from '../../../../models/server'; +import { LivechatRooms, LivechatDepartment } from '../../../../models/server'; import { Livechat } from '../../lib/Livechat'; import { callbacks } from '../../../../../lib/callbacks'; import { normalizeAgent } from '../../lib/Helper'; @@ -40,7 +40,7 @@ export function findDepartments(businessUnit) { export function findGuest(token) { return LivechatVisitors.getVisitorByToken(token, { - fields: { + projection: { name: 1, username: 1, token: 1, diff --git a/apps/meteor/app/livechat/server/api/v1/agent.js b/apps/meteor/app/livechat/server/api/v1/agent.js index 7e2329058542..104f0bc012e3 100644 --- a/apps/meteor/app/livechat/server/api/v1/agent.js +++ b/apps/meteor/app/livechat/server/api/v1/agent.js @@ -6,14 +6,14 @@ import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; API.v1.addRoute('livechat/agent.info/:rid/:token', { - get() { + async get() { try { check(this.urlParams, { rid: String, token: String, }); - const visitor = findGuest(this.urlParams.token); + const visitor = await findGuest(this.urlParams.token); if (!visitor) { throw new Meteor.Error('invalid-token'); } diff --git a/apps/meteor/app/livechat/server/api/v1/contact.js b/apps/meteor/app/livechat/server/api/v1/contact.js index be4e5670338d..af43442bdcf7 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.js +++ b/apps/meteor/app/livechat/server/api/v1/contact.js @@ -1,15 +1,15 @@ import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { LivechatVisitors } from '@rocket.chat/models'; import { API } from '../../../../api/server'; import { Contacts } from '../../lib/Contacts'; -import { LivechatVisitors } from '../../../../models/server'; API.v1.addRoute( 'omnichannel/contact', { authRequired: true }, { - post() { + async post() { try { check(this.bodyParams, { _id: Match.Maybe(String), @@ -23,19 +23,19 @@ API.v1.addRoute( }), }); - const contact = Contacts.registerContact(this.bodyParams); + const contact = await Contacts.registerContact(this.bodyParams); return API.v1.success({ contact }); } catch (e) { return API.v1.failure(e); } }, - get() { + async get() { check(this.queryParams, { contactId: String, }); - const contact = Promise.await(LivechatVisitors.findOneById(this.queryParams.contactId)); + const contact = await LivechatVisitors.findOneById(this.queryParams.contactId); return API.v1.success({ contact }); }, @@ -46,7 +46,7 @@ API.v1.addRoute( 'omnichannel/contact.search', { authRequired: true }, { - get() { + async get() { try { check(this.queryParams, { email: Match.Maybe(String), @@ -67,7 +67,7 @@ API.v1.addRoute( }, ); - const contact = Promise.await(LivechatVisitors.findOne(query)); + const contact = await LivechatVisitors.findOne(query); return API.v1.success({ contact }); } catch (e) { return API.v1.failure(e); diff --git a/apps/meteor/app/livechat/server/api/v1/customField.js b/apps/meteor/app/livechat/server/api/v1/customField.js index b15ded04a5d0..5d242f3d00ad 100644 --- a/apps/meteor/app/livechat/server/api/v1/customField.js +++ b/apps/meteor/app/livechat/server/api/v1/customField.js @@ -7,7 +7,7 @@ import { Livechat } from '../../lib/Livechat'; import { findLivechatCustomFields, findCustomFieldById } from '../lib/customFields'; API.v1.addRoute('livechat/custom.field', { - post() { + async post() { try { check(this.bodyParams, { token: String, @@ -18,12 +18,12 @@ API.v1.addRoute('livechat/custom.field', { const { token, key, value, overwrite } = this.bodyParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } - if (!Livechat.setCustomFields({ token, key, value, overwrite })) { + if (!(await Livechat.setCustomFields({ token, key, value, overwrite }))) { return API.v1.failure(); } @@ -35,7 +35,7 @@ API.v1.addRoute('livechat/custom.field', { }); API.v1.addRoute('livechat/custom.fields', { - post() { + async post() { check(this.bodyParams, { token: String, customFields: [ @@ -48,19 +48,21 @@ API.v1.addRoute('livechat/custom.fields', { }); const { token } = this.bodyParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } - const fields = this.bodyParams.customFields.map((customField) => { - const data = Object.assign({ token }, customField); - if (!Livechat.setCustomFields(data)) { - return API.v1.failure(); - } + const fields = await Promise.all( + this.bodyParams.customFields.map(async (customField) => { + const data = Object.assign({ token }, customField); + if (!(await Livechat.setCustomFields(data))) { + return API.v1.failure(); + } - return { Key: customField.key, value: customField.value, overwrite: customField.overwrite }; - }); + return { Key: customField.key, value: customField.value, overwrite: customField.overwrite }; + }), + ); return API.v1.success({ fields }); }, diff --git a/apps/meteor/app/livechat/server/api/v1/message.js b/apps/meteor/app/livechat/server/api/v1/message.js index 78fda9d95d60..2d57ca7f5724 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.js +++ b/apps/meteor/app/livechat/server/api/v1/message.js @@ -2,8 +2,9 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Random } from 'meteor/random'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { Messages, LivechatRooms, LivechatVisitors } from '../../../../models/server'; +import { Messages, LivechatRooms } from '../../../../models/server'; import { hasPermission } from '../../../../authorization'; import { API } from '../../../../api/server'; import { loadMessageHistory } from '../../../../lib'; @@ -13,7 +14,7 @@ import { normalizeMessageFileUpload } from '../../../../utils/server/functions/n import { settings } from '../../../../settings/server'; API.v1.addRoute('livechat/message', { - post() { + async post() { try { check(this.bodyParams, { _id: Match.Maybe(String), @@ -28,7 +29,7 @@ API.v1.addRoute('livechat/message', { const { token, rid, agent, msg } = this.bodyParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -67,7 +68,7 @@ API.v1.addRoute('livechat/message', { }, }; - const result = Promise.await(Livechat.sendMessage(sendMessage)); + const result = await Livechat.sendMessage(sendMessage); if (result) { const message = Messages.findOneById(_id); return API.v1.success({ message }); @@ -81,7 +82,7 @@ API.v1.addRoute('livechat/message', { }); API.v1.addRoute('livechat/message/:_id', { - get() { + async get() { try { check(this.urlParams, { _id: String, @@ -95,7 +96,7 @@ API.v1.addRoute('livechat/message/:_id', { const { token, rid } = this.queryParams; const { _id } = this.urlParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -111,7 +112,7 @@ API.v1.addRoute('livechat/message/:_id', { } if (message.file) { - message = Promise.await(normalizeMessageFileUpload(message)); + message = await normalizeMessageFileUpload(message); } return API.v1.success({ message }); @@ -120,7 +121,7 @@ API.v1.addRoute('livechat/message/:_id', { } }, - put() { + async put() { try { check(this.urlParams, { _id: String, @@ -135,7 +136,7 @@ API.v1.addRoute('livechat/message/:_id', { const { token, rid } = this.bodyParams; const { _id } = this.urlParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -157,7 +158,7 @@ API.v1.addRoute('livechat/message/:_id', { if (result) { let message = Messages.findOneById(_id); if (message.file) { - message = Promise.await(normalizeMessageFileUpload(message)); + message = await normalizeMessageFileUpload(message); } return API.v1.success({ message }); @@ -168,7 +169,7 @@ API.v1.addRoute('livechat/message/:_id', { return API.v1.failure(e); } }, - delete() { + async delete() { try { check(this.urlParams, { _id: String, @@ -182,7 +183,7 @@ API.v1.addRoute('livechat/message/:_id', { const { token, rid } = this.bodyParams; const { _id } = this.urlParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -197,7 +198,7 @@ API.v1.addRoute('livechat/message/:_id', { throw new Meteor.Error('invalid-message'); } - const result = Promise.await(Livechat.deleteMessage({ guest, message })); + const result = await Livechat.deleteMessage({ guest, message }); if (result) { return API.v1.success({ message: { @@ -215,7 +216,7 @@ API.v1.addRoute('livechat/message/:_id', { }); API.v1.addRoute('livechat/messages.history/:rid', { - get() { + async get() { try { check(this.urlParams, { rid: String, @@ -230,7 +231,7 @@ API.v1.addRoute('livechat/messages.history/:rid', { throw new Meteor.Error('error-token-param-not-provided', 'The required "token" query param is missing.'); } - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -276,7 +277,7 @@ API.v1.addRoute( 'livechat/messages', { authRequired: true }, { - post() { + async post() { if (!hasPermission(this.userId, 'view-livechat-manager')) { return API.v1.unauthorized(); } @@ -299,7 +300,7 @@ API.v1.addRoute( const visitorToken = this.bodyParams.visitor.token; - let visitor = LivechatVisitors.getVisitorByToken(visitorToken); + let visitor = await LivechatVisitors.getVisitorByToken(visitorToken); let rid; if (visitor) { const rooms = LivechatRooms.findOpenByVisitorToken(visitorToken).fetch(); @@ -314,8 +315,8 @@ API.v1.addRoute( const guest = this.bodyParams.visitor; guest.connectionData = normalizeHttpHeaderData(this.request.headers); - const visitorId = Livechat.registerGuest(guest); - visitor = LivechatVisitors.findOneById(visitorId); + const visitorId = await Livechat.registerGuest(guest); + visitor = await LivechatVisitors.findOneById(visitorId); } const sentMessages = this.bodyParams.messages.map((message) => { diff --git a/apps/meteor/app/livechat/server/api/v1/room.js b/apps/meteor/app/livechat/server/api/v1/room.js index 81a6ebfa9a87..6a0f991795ae 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.js +++ b/apps/meteor/app/livechat/server/api/v1/room.js @@ -15,7 +15,7 @@ import { canAccessRoom } from '../../../../authorization/server'; import { addUserToRoom } from '../../../../lib/server/functions'; API.v1.addRoute('livechat/room', { - get() { + async get() { const defaultCheckParams = { token: String, rid: Match.Maybe(String), @@ -28,7 +28,7 @@ API.v1.addRoute('livechat/room', { const { token, rid: roomId, agentId, ...extraParams } = this.queryParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -54,7 +54,7 @@ API.v1.addRoute('livechat/room', { }, }; - room = Promise.await(getRoom({ guest, rid, agent, roomInfo, extraParams })); + room = await getRoom({ guest, rid, agent, roomInfo, extraParams }); return API.v1.success(room); } @@ -68,7 +68,7 @@ API.v1.addRoute('livechat/room', { }); API.v1.addRoute('livechat/room.close', { - post() { + async post() { try { check(this.bodyParams, { rid: String, @@ -77,7 +77,7 @@ API.v1.addRoute('livechat/room.close', { const { rid, token } = this.bodyParams; - const visitor = findGuest(token); + const visitor = await findGuest(token); if (!visitor) { throw new Meteor.Error('invalid-token'); } @@ -106,7 +106,7 @@ API.v1.addRoute('livechat/room.close', { }); API.v1.addRoute('livechat/room.transfer', { - post() { + async post() { try { check(this.bodyParams, { rid: String, @@ -116,7 +116,7 @@ API.v1.addRoute('livechat/room.transfer', { const { rid, token, department } = this.bodyParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -132,7 +132,7 @@ API.v1.addRoute('livechat/room.transfer', { const { _id, username, name } = guest; const transferredBy = normalizeTransferredByData({ _id, username, name, userType: 'visitor' }, room); - if (!Promise.await(Livechat.transfer(room, guest, { roomId: rid, departmentId: department, transferredBy }))) { + if (!(await Livechat.transfer(room, guest, { roomId: rid, departmentId: department, transferredBy }))) { return API.v1.failure(); } @@ -145,7 +145,7 @@ API.v1.addRoute('livechat/room.transfer', { }); API.v1.addRoute('livechat/room.survey', { - post() { + async post() { try { check(this.bodyParams, { rid: String, @@ -160,7 +160,7 @@ API.v1.addRoute('livechat/room.survey', { const { rid, token, data } = this.bodyParams; - const visitor = findGuest(token); + const visitor = await findGuest(token); if (!visitor) { throw new Meteor.Error('invalid-token'); } @@ -170,7 +170,7 @@ API.v1.addRoute('livechat/room.survey', { throw new Meteor.Error('invalid-room'); } - const config = Promise.await(settings()); + const config = await settings(); if (!config.survey || !config.survey.items || !config.survey.values) { throw new Meteor.Error('invalid-livechat-config'); } diff --git a/apps/meteor/app/livechat/server/api/v1/transcript.js b/apps/meteor/app/livechat/server/api/v1/transcript.js index f8f3c923d25e..040bb51a0f65 100644 --- a/apps/meteor/app/livechat/server/api/v1/transcript.js +++ b/apps/meteor/app/livechat/server/api/v1/transcript.js @@ -5,7 +5,7 @@ import { API } from '../../../../api/server'; import { Livechat } from '../../lib/Livechat'; API.v1.addRoute('livechat/transcript', { - post() { + async post() { try { check(this.bodyParams, { token: String, @@ -14,7 +14,7 @@ API.v1.addRoute('livechat/transcript', { }); const { token, rid, email } = this.bodyParams; - if (!Livechat.sendTranscript({ token, rid, email })) { + if (!(await Livechat.sendTranscript({ token, rid, email }))) { return API.v1.failure({ message: TAPi18n.__('Error_sending_livechat_transcript') }); } diff --git a/apps/meteor/app/livechat/server/api/v1/videoCall.js b/apps/meteor/app/livechat/server/api/v1/videoCall.js index 74dce685b66d..efca6c1a06ff 100644 --- a/apps/meteor/app/livechat/server/api/v1/videoCall.js +++ b/apps/meteor/app/livechat/server/api/v1/videoCall.js @@ -15,7 +15,7 @@ import { Logger } from '../../../../logger'; const logger = new Logger('LivechatVideoCallApi'); API.v1.addRoute('livechat/video.call/:token', { - get() { + async get() { try { check(this.urlParams, { token: String, @@ -27,7 +27,7 @@ API.v1.addRoute('livechat/video.call/:token', { const { token } = this.urlParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -40,8 +40,8 @@ API.v1.addRoute('livechat/video.call/:token', { alias: 'video-call', }, }; - const { room } = getRoom({ guest, rid, roomInfo }); - const config = Promise.await(settings()); + const { room } = await getRoom({ guest, rid, roomInfo }); + const config = await settings(); if (!config.theme || !config.theme.actionLinks || !config.theme.actionLinks.jitsi) { throw new Meteor.Error('invalid-livechat-config'); } diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index ca21e3ee26d3..9226926f94a9 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -3,7 +3,7 @@ import { Match, check } from 'meteor/check'; import type { ILivechatVisitorDTO, IRoom } from '@rocket.chat/core-typings'; import { LivechatVisitors as VisitorsRaw } from '@rocket.chat/models'; -import { LivechatRooms, LivechatVisitors, LivechatCustomField } from '../../../../models/server'; +import { LivechatRooms, LivechatCustomField } from '../../../../models/server'; import { API } from '../../../../api/server'; import { findGuest, normalizeHttpHeaderData } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; @@ -37,7 +37,7 @@ API.v1.addRoute('livechat/visitor', { } guest.connectionData = normalizeHttpHeaderData(this.request.headers); - const visitorId = Livechat.registerGuest(guest as any); // TODO: Rewrite Livechat to TS + const visitorId = await Livechat.registerGuest(guest as any); // TODO: Rewrite Livechat to TS let visitor = await VisitorsRaw.findOneById(visitorId, {}); // If it's updating an existing visitor, it must also update the roomInfo @@ -55,7 +55,8 @@ API.v1.addRoute('livechat/visitor', { return; } const { key, value, overwrite } = field; - if (customField.scope === 'visitor' && !LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite)) { + // TODO: refactor this to use normal await + if (customField.scope === 'visitor' && !Promise.await(VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { return API.v1.failure(); } }); @@ -112,7 +113,7 @@ API.v1.addRoute('livechat/visitor/:token', { } const { _id } = visitor; - const result = Livechat.removeGuest(_id); + const result = await Livechat.removeGuest(_id); if (!result) { throw new Meteor.Error('error-removing-visitor', 'An error ocurred while deleting visitor'); } @@ -156,7 +157,7 @@ API.v1.addRoute('livechat/visitor.callStatus', { }); const { token, callStatus, rid, callId } = this.bodyParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } @@ -174,7 +175,7 @@ API.v1.addRoute('livechat/visitor.status', { const { token, status } = this.bodyParams; - const guest = findGuest(token); + const guest = await findGuest(token); if (!guest) { throw new Meteor.Error('invalid-token'); } diff --git a/apps/meteor/app/livechat/server/hooks/leadCapture.js b/apps/meteor/app/livechat/server/hooks/leadCapture.js index 156edc32c6f2..6a1d3b5b67b9 100644 --- a/apps/meteor/app/livechat/server/hooks/leadCapture.js +++ b/apps/meteor/app/livechat/server/hooks/leadCapture.js @@ -1,6 +1,7 @@ +import { LivechatVisitors } from '@rocket.chat/models'; + import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; -import { LivechatVisitors } from '../../../models/server'; function validateMessage(message, room) { // skips this callback if the message was edited @@ -40,7 +41,7 @@ callbacks.add( const msgEmails = message.msg.match(emailRegexp); if (msgEmails || msgPhones) { - LivechatVisitors.saveGuestEmailPhoneById(room.v._id, msgEmails, msgPhones); + Promise.await(LivechatVisitors.saveGuestEmailPhoneById(room.v._id, msgEmails, msgPhones)); callbacks.run('livechat.leadCapture', room); } diff --git a/apps/meteor/app/livechat/server/hooks/saveContactLastChat.js b/apps/meteor/app/livechat/server/hooks/saveContactLastChat.js index 0d87723b7079..9745b09930e3 100644 --- a/apps/meteor/app/livechat/server/hooks/saveContactLastChat.js +++ b/apps/meteor/app/livechat/server/hooks/saveContactLastChat.js @@ -13,7 +13,7 @@ callbacks.add( _id, ts: new Date(), }; - Livechat.updateLastChat(guestId, lastChat); + Promise.await(Livechat.updateLastChat(guestId, lastChat)); }, callbacks.priority.MEDIUM, 'livechat-save-last-chat', diff --git a/apps/meteor/app/livechat/server/hooks/sendToCRM.js b/apps/meteor/app/livechat/server/hooks/sendToCRM.js index 3a6ffce3a64c..c54b761717c9 100644 --- a/apps/meteor/app/livechat/server/hooks/sendToCRM.js +++ b/apps/meteor/app/livechat/server/hooks/sendToCRM.js @@ -41,7 +41,7 @@ function sendToCRM(type, room, includeMessages = true) { return room; } - const postData = Livechat.getLivechatRoomGuestInfo(room); + const postData = Promise.await(Livechat.getLivechatRoomGuestInfo(room)); postData.type = type; diff --git a/apps/meteor/app/livechat/server/hooks/sendTranscriptOnClose.js b/apps/meteor/app/livechat/server/hooks/sendTranscriptOnClose.js index cfa17e4ac039..597d656b2f0e 100644 --- a/apps/meteor/app/livechat/server/hooks/sendTranscriptOnClose.js +++ b/apps/meteor/app/livechat/server/hooks/sendTranscriptOnClose.js @@ -9,7 +9,8 @@ const sendTranscriptOnClose = (room) => { } const { email, subject, requestedBy: user } = transcriptRequest; - Livechat.sendTranscript({ token, rid, email, subject, user }); + // TODO: refactor this to use normal await + Promise.await(Livechat.sendTranscript({ token, rid, email, subject, user })); LivechatRooms.removeTranscriptRequestByRoomId(rid); diff --git a/apps/meteor/app/livechat/server/lib/Contacts.js b/apps/meteor/app/livechat/server/lib/Contacts.js index 27fc010786a0..df4b05a9904f 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.js +++ b/apps/meteor/app/livechat/server/lib/Contacts.js @@ -1,19 +1,19 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; -import { Users } from '@rocket.chat/models'; +import { LivechatVisitors, Users } from '@rocket.chat/models'; -import { LivechatVisitors, LivechatCustomField, LivechatRooms, Rooms, LivechatInquiry, Subscriptions } from '../../../models/server'; +import { LivechatCustomField, LivechatRooms, Rooms, LivechatInquiry, Subscriptions } from '../../../models/server'; export const Contacts = { - registerContact({ token, name, email, phone, username, customFields = {}, contactManager = {} } = {}) { + async registerContact({ token, name, email, phone, username, customFields = {}, contactManager = {} } = {}) { check(token, String); const visitorEmail = s.trim(email).toLowerCase(); if (contactManager?.username) { // verify if the user exists with this username and has a livechat-agent role - const user = Promise.await(Users.findOneByUsername(contactManager.username, { projection: { roles: 1 } })); + const user = await Users.findOneByUsername(contactManager.username, { projection: { roles: 1 } }); if (!user) { throw new Meteor.Error('error-contact-manager-not-found', `No user found with username ${contactManager.username}`); } @@ -29,18 +29,18 @@ export const Contacts = { }, }; - const user = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); + const user = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); if (user) { contactId = user._id; } else { if (!username) { - username = LivechatVisitors.getNextVisitorUsername(); + username = await LivechatVisitors.getNextVisitorUsername(); } let existingUser = null; - if (visitorEmail !== '' && (existingUser = LivechatVisitors.findOneGuestByEmailAddress(visitorEmail))) { + if (visitorEmail !== '' && (existingUser = await LivechatVisitors.findOneGuestByEmailAddress(visitorEmail))) { contactId = existingUser._id; } else { const userData = { @@ -48,7 +48,7 @@ export const Contacts = { ts: new Date(), }; - contactId = LivechatVisitors.insert(userData); + contactId = await LivechatVisitors.insertOne(userData); } } @@ -68,7 +68,7 @@ export const Contacts = { updateUser.$set.livechatData = livechatData; updateUser.$set.contactManager = (contactManager?.username && { username: contactManager.username }) || null; - LivechatVisitors.updateById(contactId, updateUser); + await LivechatVisitors.updateById(contactId, updateUser); const rooms = LivechatRooms.findByVisitorId(contactId).fetch(); diff --git a/apps/meteor/app/livechat/server/lib/Helper.js b/apps/meteor/app/livechat/server/lib/Helper.js index 2a050560c10b..3a51f2343d1e 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.js +++ b/apps/meteor/app/livechat/server/lib/Helper.js @@ -531,7 +531,7 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => { } const { token } = guest; - Livechat.setDepartmentForGuest({ token, department: departmentId }); + await Livechat.setDepartmentForGuest({ token, department: departmentId }); logger.debug(`Department for visitor with token ${token} was updated to ${departmentId}`); return true; diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 93ffbffd5c2a..424847dddb70 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -9,7 +9,7 @@ import _ from 'underscore'; import s from 'underscore.string'; import moment from 'moment-timezone'; import UAParser from 'ua-parser-js'; -import { Users as UsersRaw, LivechatVisitors as LivechatVisitorsRaw } from '@rocket.chat/models'; +import { Users as UsersRaw, LivechatVisitors } from '@rocket.chat/models'; import { QueueManager } from './QueueManager'; import { RoutingManager } from './RoutingManager'; @@ -27,7 +27,6 @@ import { LivechatDepartmentAgents, LivechatDepartment, LivechatCustomField, - LivechatVisitors, LivechatInquiry, } from '../../../models/server'; import { Logger } from '../../../logger/server'; @@ -172,8 +171,8 @@ export const Livechat = { } if (guest.department && !LivechatDepartment.findOneById(guest.department)) { - LivechatVisitors.removeDepartmentById(guest._id); - guest = LivechatVisitors.findOneById(guest._id); + await LivechatVisitors.removeDepartmentById(guest._id); + guest = await LivechatVisitors.findOneById(guest._id); } if (room == null) { @@ -272,7 +271,7 @@ export const Livechat = { return true; }, - registerGuest({ id, token, name, email, department, phone, username, connectionData, status = 'online' } = {}) { + async registerGuest({ id, token, name, email, department, phone, username, connectionData, status = 'online' } = {}) { check(token, String); check(id, Match.Maybe(String)); @@ -307,24 +306,24 @@ export const Livechat = { updateUser.$set.department = dep._id; } - const user = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); + const user = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); let existingUser = null; if (user) { Livechat.logger.debug('Found matching user by token'); userId = user._id; - } else if (phone?.number && (existingUser = LivechatVisitors.findOneVisitorByPhone(phone.number))) { + } else if (phone?.number && (existingUser = await LivechatVisitors.findOneVisitorByPhone(phone.number))) { Livechat.logger.debug('Found matching user by phone number'); userId = existingUser._id; // Don't change token when matching by phone number, use current visitor token updateUser.$set.token = existingUser.token; - } else if (email && (existingUser = LivechatVisitors.findOneGuestByEmailAddress(email))) { + } else if (email && (existingUser = await LivechatVisitors.findOneGuestByEmailAddress(email))) { Livechat.logger.debug('Found matching user by email'); userId = existingUser._id; } else { Livechat.logger.debug(`No matches found. Attempting to create new user with token ${token}`); if (!username) { - username = LivechatVisitors.getNextVisitorUsername(); + username = await LivechatVisitors.getNextVisitorUsername(); } const userData = { @@ -344,15 +343,15 @@ export const Livechat = { } } - userId = LivechatVisitors.insert(userData); + userId = (await LivechatVisitors.insertOne(userData)).insertedId; } - LivechatVisitors.updateById(userId, updateUser); + await LivechatVisitors.updateById(userId, updateUser); return userId; }, - setDepartmentForGuest({ token, department } = {}) { + async setDepartmentForGuest({ token, department } = {}) { check(token, String); check(department, String); @@ -371,14 +370,14 @@ export const Livechat = { }); } - const user = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); + const user = await LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); if (user) { return LivechatVisitors.updateById(user._id, updateUser); } return false; }, - saveGuest({ _id, name, email, phone, livechatData = {} }, userId) { + async saveGuest({ _id, name, email, phone, livechatData = {} }, userId) { Livechat.logger.debug(`Saving data for visitor ${_id}`); const updateData = {}; @@ -411,7 +410,7 @@ export const Livechat = { }); updateData.livechatData = customFields; } - const ret = LivechatVisitors.saveGuestById(_id, updateData); + const ret = await LivechatVisitors.saveGuestById(_id, updateData); Meteor.defer(() => { Apps.triggerEvent(AppEvents.IPostLivechatGuestSaved, _id); @@ -506,7 +505,7 @@ export const Livechat = { return LivechatRooms.removeById(rid); }, - setCustomFields({ token, key, value, overwrite } = {}) { + async setCustomFields({ token, key, value, overwrite } = {}) { check(token, String); check(key, String); check(value, String); @@ -636,7 +635,8 @@ export const Livechat = { forwardOpenChats(userId) { Livechat.logger.debug(`Transferring open chats for user ${userId}`); LivechatRooms.findOpenByAgent(userId).forEach((room) => { - const guest = LivechatVisitors.findOneById(room.v._id); + // TODO: refactor to use normal await + const guest = Promise.await(LivechatVisitors.findOneById(room.v._id)); const user = Users.findOneById(userId); const { _id, username, name } = user; const transferredBy = normalizeTransferredByData({ _id, username, name }, room); @@ -811,8 +811,8 @@ export const Livechat = { } }, - getLivechatRoomGuestInfo(room) { - const visitor = LivechatVisitors.findOneById(room.v._id); + async getLivechatRoomGuestInfo(room) { + const visitor = await LivechatVisitors.findOneById(room.v._id); const agent = Users.findOneById(room.servedBy && room.servedBy._id); const ua = new UAParser(); @@ -925,7 +925,7 @@ export const Livechat = { Users.removeLivechatData(_id); this.setUserStatusLivechat(_id, 'not-available'); LivechatDepartmentAgents.removeByAgentId(_id); - Promise.await(LivechatVisitorsRaw.removeContactManagerByUsername(username)); + Promise.await(LivechatVisitors.removeContactManagerByUsername(username)); return true; } @@ -946,16 +946,16 @@ export const Livechat = { return removeUserFromRoles(user._id, ['livechat-manager']); }, - removeGuest(_id) { + async removeGuest(_id) { check(_id, String); - const guest = LivechatVisitors.findOneById(_id); + const guest = await LivechatVisitors.findOneById(_id, { projection: { _id: 1 } }); if (!guest) { throw new Meteor.Error('error-invalid-guest', 'Invalid guest', { method: 'livechat:removeGuest', }); } - this.cleanGuestHistory(_id); + await this.cleanGuestHistory(_id); return LivechatVisitors.removeById(_id); }, @@ -971,8 +971,8 @@ export const Livechat = { return user; }, - cleanGuestHistory(_id) { - const guest = LivechatVisitors.findOneById(_id); + async cleanGuestHistory(_id) { + const guest = await LivechatVisitors.findOneById(_id); if (!guest) { throw new Meteor.Error('error-invalid-guest', 'Invalid guest', { method: 'livechat:cleanGuestHistory', @@ -1144,15 +1144,15 @@ export const Livechat = { }); }, - sendTranscript({ token, rid, email, subject, user }) { + async sendTranscript({ token, rid, email, subject, user }) { check(rid, String); check(email, String); Livechat.logger.debug(`Sending conversation transcript of room ${rid} to user with token ${token}`); const room = LivechatRooms.findOneById(rid); - const visitor = LivechatVisitors.getVisitorByToken(token, { - fields: { _id: 1, token: 1, language: 1, username: 1, name: 1 }, + const visitor = await LivechatVisitors.getVisitorByToken(token, { + projection: { _id: 1, token: 1, language: 1, username: 1, name: 1 }, }); const userLanguage = (visitor && visitor.language) || settings.get('Language') || 'en'; const timezone = getTimezone(user); @@ -1402,13 +1402,13 @@ export const Livechat = { return LivechatRooms.findOneById(roomId); }, - updateLastChat(contactId, lastChat) { + async updateLastChat(contactId, lastChat) { const updateUser = { $set: { lastChat, }, }; - LivechatVisitors.updateById(contactId, updateUser); + await LivechatVisitors.updateById(contactId, updateUser); }, updateCallStatus(callId, rid, status, user) { Rooms.setCallStatus(rid, status); diff --git a/apps/meteor/app/livechat/server/methods/closeByVisitor.js b/apps/meteor/app/livechat/server/methods/closeByVisitor.js index 7f7bb952e38c..022e614bb7f0 100644 --- a/apps/meteor/app/livechat/server/methods/closeByVisitor.js +++ b/apps/meteor/app/livechat/server/methods/closeByVisitor.js @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import { LivechatVisitors } from '@rocket.chat/models'; import { settings } from '../../../settings/server'; -import { LivechatRooms, LivechatVisitors } from '../../../models/server'; +import { LivechatRooms } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; Meteor.methods({ - 'livechat:closeByVisitor'({ roomId, token }) { - const visitor = LivechatVisitors.getVisitorByToken(token); + async 'livechat:closeByVisitor'({ roomId, token }) { + const visitor = await LivechatVisitors.getVisitorByToken(token); const language = (visitor && visitor.language) || settings.get('Language') || 'en'; diff --git a/apps/meteor/app/livechat/server/methods/getAgentData.js b/apps/meteor/app/livechat/server/methods/getAgentData.js index cf1b393ecedd..1cc6993974c6 100644 --- a/apps/meteor/app/livechat/server/methods/getAgentData.js +++ b/apps/meteor/app/livechat/server/methods/getAgentData.js @@ -1,15 +1,16 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { Users, LivechatRooms, LivechatVisitors } from '../../../models/server'; +import { Users, LivechatRooms } from '../../../models/server'; Meteor.methods({ - 'livechat:getAgentData'({ roomId, token }) { + async 'livechat:getAgentData'({ roomId, token }) { check(roomId, String); check(token, String); const room = LivechatRooms.findOneById(roomId); - const visitor = LivechatVisitors.getVisitorByToken(token); + const visitor = await LivechatVisitors.getVisitorByToken(token); if (!room || room.t !== 'l' || !room.v || room.v.token !== visitor.token) { throw new Meteor.Error('error-invalid-room', 'Invalid room'); diff --git a/apps/meteor/app/livechat/server/methods/getInitialData.js b/apps/meteor/app/livechat/server/methods/getInitialData.js index 9d39298b634a..b6eab00f57bd 100644 --- a/apps/meteor/app/livechat/server/methods/getInitialData.js +++ b/apps/meteor/app/livechat/server/methods/getInitialData.js @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { LivechatTrigger } from '@rocket.chat/models'; +import { LivechatTrigger, LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, Users, LivechatDepartment, LivechatVisitors } from '../../../models/server'; +import { LivechatRooms, Users, LivechatDepartment } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; import { deprecationWarning } from '../../../api/server/helpers/deprecationWarning'; @@ -53,7 +53,7 @@ Meteor.methods({ info.room = room[0]; } - const visitor = LivechatVisitors.getVisitorByToken(visitorToken, { + const visitor = await LivechatVisitors.getVisitorByToken(visitorToken, { fields: { name: 1, username: 1, diff --git a/apps/meteor/app/livechat/server/methods/loadHistory.js b/apps/meteor/app/livechat/server/methods/loadHistory.js index 2b81226f304a..7e2fd97d5329 100644 --- a/apps/meteor/app/livechat/server/methods/loadHistory.js +++ b/apps/meteor/app/livechat/server/methods/loadHistory.js @@ -1,15 +1,16 @@ import { Meteor } from 'meteor/meteor'; +import { LivechatVisitors } from '@rocket.chat/models'; import { loadMessageHistory } from '../../../lib'; -import { LivechatVisitors, LivechatRooms } from '../../../models/server'; +import { LivechatRooms } from '../../../models/server'; Meteor.methods({ - 'livechat:loadHistory'({ token, rid, end, limit = 20, ls }) { + async 'livechat:loadHistory'({ token, rid, end, limit = 20, ls }) { if (!token || typeof token !== 'string') { return; } - const visitor = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); + const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); if (!visitor) { throw new Meteor.Error('invalid-visitor', 'Invalid Visitor', { diff --git a/apps/meteor/app/livechat/server/methods/loginByToken.js b/apps/meteor/app/livechat/server/methods/loginByToken.js index 5b4c15d75ad8..4c2c7c658365 100644 --- a/apps/meteor/app/livechat/server/methods/loginByToken.js +++ b/apps/meteor/app/livechat/server/methods/loginByToken.js @@ -1,10 +1,9 @@ import { Meteor } from 'meteor/meteor'; - -import { LivechatVisitors } from '../../../models/server'; +import { LivechatVisitors } from '@rocket.chat/models'; Meteor.methods({ - 'livechat:loginByToken'(token) { - const visitor = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); + async 'livechat:loginByToken'(token) { + const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); if (!visitor) { return; diff --git a/apps/meteor/app/livechat/server/methods/registerGuest.js b/apps/meteor/app/livechat/server/methods/registerGuest.js index 364507de6893..b6c7b7f14f32 100644 --- a/apps/meteor/app/livechat/server/methods/registerGuest.js +++ b/apps/meteor/app/livechat/server/methods/registerGuest.js @@ -1,11 +1,12 @@ import { Meteor } from 'meteor/meteor'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { Messages, LivechatRooms, LivechatVisitors } from '../../../models/server'; +import { Messages, LivechatRooms } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; Meteor.methods({ - 'livechat:registerGuest'({ token, name, email, department, customFields } = {}) { - const userId = Livechat.registerGuest.call(this, { + async 'livechat:registerGuest'({ token, name, email, department, customFields } = {}) { + const userId = await Livechat.registerGuest.call(this, { token, name, email, @@ -15,8 +16,8 @@ Meteor.methods({ // update visited page history to not expire Messages.keepHistoryForToken(token); - const visitor = LivechatVisitors.getVisitorByToken(token, { - fields: { + const visitor = await LivechatVisitors.getVisitorByToken(token, { + projection: { token: 1, name: 1, username: 1, @@ -32,6 +33,7 @@ Meteor.methods({ }); if (customFields && customFields instanceof Array) { + // TODO: refactor to use normal await customFields.forEach((customField) => { if (typeof customField !== 'object') { return; @@ -39,7 +41,7 @@ Meteor.methods({ if (!customField.scope || customField.scope !== 'room') { const { key, value, overwrite } = customField; - LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite); + Promise.await(LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite)); } }); } diff --git a/apps/meteor/app/livechat/server/methods/saveInfo.js b/apps/meteor/app/livechat/server/methods/saveInfo.js index 76fa8c28915e..c5b4a164d6ca 100644 --- a/apps/meteor/app/livechat/server/methods/saveInfo.js +++ b/apps/meteor/app/livechat/server/methods/saveInfo.js @@ -7,7 +7,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { Livechat } from '../lib/Livechat'; Meteor.methods({ - 'livechat:saveInfo'(guestData, roomData) { + async 'livechat:saveInfo'(guestData, roomData) { const userId = Meteor.userId(); if (!userId || !hasPermission(userId, 'view-l-room')) { @@ -49,7 +49,7 @@ Meteor.methods({ delete guestData.phone; } - const ret = Livechat.saveGuest(guestData, userId) && Livechat.saveRoomInfo(roomData, guestData, userId); + const ret = (await Livechat.saveGuest(guestData, userId)) && Livechat.saveRoomInfo(roomData, guestData, userId); const user = Meteor.users.findOne({ _id: userId }, { fields: { _id: 1, username: 1 } }); diff --git a/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.js b/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.js index bcae8f2402ef..7d908cdc7a90 100644 --- a/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.js +++ b/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.js @@ -1,17 +1,16 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import _ from 'underscore'; - -import { LivechatRooms, LivechatVisitors } from '../../../models/server'; +import { LivechatRooms, LivechatVisitors } from '@rocket.chat/models'; Meteor.methods({ - 'livechat:saveSurveyFeedback'(visitorToken, visitorRoom, formData) { + async 'livechat:saveSurveyFeedback'(visitorToken, visitorRoom, formData) { check(visitorToken, String); check(visitorRoom, String); check(formData, [Match.ObjectIncluding({ name: String, value: String })]); - const visitor = LivechatVisitors.getVisitorByToken(visitorToken); - const room = LivechatRooms.findOneById(visitorRoom); + const visitor = await LivechatVisitors.getVisitorByToken(visitorToken); + const room = await LivechatRooms.findOneById(visitorRoom); if (visitor !== undefined && room !== undefined && room.v !== undefined && room.v.token === visitor.token) { const updateData = {}; diff --git a/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.js b/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.js index 5d647815b439..3f2a3e7c98bb 100644 --- a/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.js +++ b/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.js @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Random } from 'meteor/random'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, LivechatVisitors } from '../../../models/server'; +import { LivechatRooms } from '../../../models/server'; import { FileUpload } from '../../../file-upload/server'; Meteor.methods({ async sendFileLivechatMessage(roomId, visitorToken, file, msgData = {}) { - const visitor = LivechatVisitors.getVisitorByToken(visitorToken); + const visitor = await LivechatVisitors.getVisitorByToken(visitorToken); if (!visitor) { return false; diff --git a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.js b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.js index 51887e90c660..7b6d2d0608b9 100644 --- a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.js +++ b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.js @@ -1,13 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatVisitors } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; import { settings } from '../../../settings/server'; Meteor.methods({ - sendMessageLivechat({ token, _id, rid, msg, file, attachments }, agent) { + async sendMessageLivechat({ token, _id, rid, msg, file, attachments }, agent) { check(token, String); check(_id, String); check(rid, String); @@ -21,8 +21,8 @@ Meteor.methods({ }), ); - const guest = LivechatVisitors.getVisitorByToken(token, { - fields: { + const guest = await LivechatVisitors.getVisitorByToken(token, { + projection: { name: 1, username: 1, department: 1, diff --git a/apps/meteor/app/livechat/server/methods/setCustomField.js b/apps/meteor/app/livechat/server/methods/setCustomField.js index 73d3efe71418..cd8576bcd0aa 100644 --- a/apps/meteor/app/livechat/server/methods/setCustomField.js +++ b/apps/meteor/app/livechat/server/methods/setCustomField.js @@ -1,9 +1,10 @@ import { Meteor } from 'meteor/meteor'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, LivechatVisitors, LivechatCustomField } from '../../../models/server'; +import { LivechatRooms, LivechatCustomField } from '../../../models/server'; Meteor.methods({ - 'livechat:setCustomField'(token, key, value, overwrite = true) { + async 'livechat:setCustomField'(token, key, value, overwrite = true) { const customField = LivechatCustomField.findOneById(key); if (customField) { if (customField.scope === 'room') { diff --git a/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.js b/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.js index 0b1cf0e3d081..ada1703b34c6 100644 --- a/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.js +++ b/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.js @@ -1,18 +1,19 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, Messages, LivechatVisitors } from '../../../models/server'; +import { LivechatRooms, Messages } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; import { normalizeTransferredByData } from '../lib/Helper'; Meteor.methods({ - 'livechat:setDepartmentForVisitor'({ roomId, visitorToken, departmentId } = {}) { + async 'livechat:setDepartmentForVisitor'({ roomId, visitorToken, departmentId } = {}) { check(roomId, String); check(visitorToken, String); check(departmentId, String); const room = LivechatRooms.findOneById(roomId); - const visitor = LivechatVisitors.getVisitorByToken(visitorToken); + const visitor = await LivechatVisitors.getVisitorByToken(visitorToken); if (!room || room.t !== 'l' || !room.v || room.v.token !== visitor.token) { throw new Meteor.Error('error-invalid-room', 'Invalid room'); diff --git a/apps/meteor/app/livechat/server/methods/startFileUploadRoom.js b/apps/meteor/app/livechat/server/methods/startFileUploadRoom.js index fbbe2b6673f1..5f8f6a7c8889 100644 --- a/apps/meteor/app/livechat/server/methods/startFileUploadRoom.js +++ b/apps/meteor/app/livechat/server/methods/startFileUploadRoom.js @@ -1,15 +1,16 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatVisitors } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; +// TODO: check if this is still in use Meteor.methods({ - 'livechat:startFileUploadRoom'(roomId, token) { + async 'livechat:startFileUploadRoom'(roomId, token) { methodDeprecationLogger.warn('livechat:startFileUploadRoom will be deprecated in future versions of Rocket.Chat'); - const guest = LivechatVisitors.getVisitorByToken(token); + const guest = await LivechatVisitors.getVisitorByToken(token); const message = { _id: Random.id(), diff --git a/apps/meteor/app/livechat/server/methods/transfer.js b/apps/meteor/app/livechat/server/methods/transfer.js index f825d29327a4..52a189c97e5c 100644 --- a/apps/meteor/app/livechat/server/methods/transfer.js +++ b/apps/meteor/app/livechat/server/methods/transfer.js @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { hasPermission } from '../../../authorization'; -import { LivechatRooms, Subscriptions, LivechatVisitors, Users } from '../../../models/server'; +import { hasPermission } from '../../../authorization/server'; +import { LivechatRooms, Subscriptions, Users } from '../../../models/server'; import { Livechat } from '../lib/Livechat'; import { normalizeTransferredByData } from '../lib/Helper'; Meteor.methods({ - 'livechat:transfer'(transferData) { + async 'livechat:transfer'(transferData) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'view-l-room')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:transfer' }); } @@ -38,7 +39,7 @@ Meteor.methods({ }); } - const guest = LivechatVisitors.findOneById(room.v && room.v._id); + const guest = await LivechatVisitors.findOneById(room.v && room.v._id); transferData.transferredBy = normalizeTransferredByData(Meteor.user() || {}, room); if (transferData.userId) { const userToTransfer = Users.findOneById(transferData.userId); diff --git a/apps/meteor/app/livechat/server/sendMessageBySMS.js b/apps/meteor/app/livechat/server/sendMessageBySMS.js index 22ab45bc9866..587dd2663a29 100644 --- a/apps/meteor/app/livechat/server/sendMessageBySMS.js +++ b/apps/meteor/app/livechat/server/sendMessageBySMS.js @@ -1,7 +1,8 @@ +import { LivechatVisitors } from '@rocket.chat/models'; + import { callbacks } from '../../../lib/callbacks'; import { settings } from '../../settings/server'; import { SMS } from '../../sms'; -import { LivechatVisitors } from '../../models/server'; import { normalizeMessageFileUpload } from '../../utils/server/functions/normalizeMessageFileUpload'; callbacks.add( @@ -49,7 +50,7 @@ callbacks.add( return message; } - const visitor = LivechatVisitors.getVisitorByToken(room.v.token); + const visitor = Promise.await(LivechatVisitors.getVisitorByToken(room.v.token)); if (!visitor || !visitor.phone || visitor.phone.length === 0) { return message; diff --git a/apps/meteor/app/models/server/index.js b/apps/meteor/app/models/server/index.js index cb275dcff133..697bad8152da 100644 --- a/apps/meteor/app/models/server/index.js +++ b/apps/meteor/app/models/server/index.js @@ -10,7 +10,6 @@ import LivechatCustomField from './models/LivechatCustomField'; import LivechatDepartment from './models/LivechatDepartment'; import LivechatDepartmentAgents from './models/LivechatDepartmentAgents'; import LivechatRooms from './models/LivechatRooms'; -import LivechatVisitors from './models/LivechatVisitors'; import LivechatInquiry from './models/LivechatInquiry'; import OmnichannelQueue from './models/OmnichannelQueue'; import ImportData from './models/ImportData'; @@ -36,7 +35,6 @@ export { LivechatDepartment, LivechatDepartmentAgents, LivechatRooms, - LivechatVisitors, LivechatInquiry, OmnichannelQueue, ImportData, diff --git a/apps/meteor/app/models/server/models/LivechatVisitors.js b/apps/meteor/app/models/server/models/LivechatVisitors.js deleted file mode 100644 index ffffe1095fcd..000000000000 --- a/apps/meteor/app/models/server/models/LivechatVisitors.js +++ /dev/null @@ -1,253 +0,0 @@ -import _ from 'underscore'; -import s from 'underscore.string'; - -import { Base } from './_Base'; -import Settings from './Settings'; - -export class LivechatVisitors extends Base { - constructor() { - super('livechat_visitor'); - - this.tryEnsureIndex({ token: 1 }); - this.tryEnsureIndex({ 'phone.phoneNumber': 1 }, { sparse: true }); - this.tryEnsureIndex({ 'visitorEmails.address': 1 }, { sparse: true }); - this.tryEnsureIndex({ name: 1 }, { sparse: true }); - this.tryEnsureIndex({ username: 1 }); - this.tryEnsureIndex({ 'contactManager.username': 1 }, { sparse: true }); - } - - /** - * Gets visitor by token - * @param {string} token - Visitor token - */ - getVisitorByToken(token, options) { - const query = { - token, - }; - - return this.findOne(query, options); - } - - /** - * Find visitors by _id - * @param {string} token - Visitor token - */ - findById(_id, options) { - const query = { - _id, - }; - - return this.find(query, options); - } - - /** - * Find One visitor by _id - */ - findOneById(_id, options) { - const query = { - _id, - }; - - return this.findOne(query, options); - } - - /** - * Gets visitor by token - * @param {string} token - Visitor token - */ - findVisitorByToken(token) { - const query = { - token, - }; - - return this.find(query); - } - - updateLivechatDataByToken(token, key, value, overwrite = true) { - const query = { - token, - }; - - if (!overwrite) { - const user = this.findOne(query, { fields: { livechatData: 1 } }); - if (user.livechatData && typeof user.livechatData[key] !== 'undefined') { - return true; - } - } - - const update = { - $set: { - [`livechatData.${key}`]: value, - }, - }; - - return this.update(query, update); - } - - updateLastAgentByToken(token, lastAgent) { - const query = { - token, - }; - - const update = { - $set: { - lastAgent, - }, - }; - - return this.update(query, update); - } - - /** - * Find a visitor by their phone number - * @return {object} User from db - */ - findOneVisitorByPhone(phone) { - const query = { - 'phone.phoneNumber': phone, - }; - - return this.findOne(query); - } - - getVisitorsBetweenDate(date) { - const query = { - _updatedAt: { - $gte: date.gte, // ISO Date, ts >= date.gte - $lt: date.lt, // ISODate, ts < date.lt - }, - }; - - return this.find(query, { fields: { _id: 1 } }); - } - - /** - * Get the next visitor name - * @return {string} The next visitor name - */ - getNextVisitorUsername() { - const query = { - _id: 'Livechat_guest_count', - }; - - const update = { - $inc: { - value: 1, - }, - }; - - const livechatCount = Settings.findAndModify(query, null, update); - - return `guest-${livechatCount.value.value + 1}`; - } - - updateById(_id, update) { - return this.update({ _id }, update); - } - - saveGuestById(_id, data) { - const setData = {}; - const unsetData = {}; - - if (data.name) { - if (!_.isEmpty(s.trim(data.name))) { - setData.name = s.trim(data.name); - } else { - unsetData.name = 1; - } - } - - if (data.email) { - if (!_.isEmpty(s.trim(data.email))) { - setData.visitorEmails = [{ address: s.trim(data.email) }]; - } else { - unsetData.visitorEmails = 1; - } - } - - if (data.phone) { - if (!_.isEmpty(s.trim(data.phone))) { - setData.phone = [{ phoneNumber: s.trim(data.phone) }]; - } else { - unsetData.phone = 1; - } - } - - if (data.livechatData) { - Object.keys(data.livechatData).forEach((key) => { - const value = s.trim(data.livechatData[key]); - if (value) { - setData[`livechatData.${key}`] = value; - } else { - unsetData[`livechatData.${key}`] = 1; - } - }); - } - - const update = {}; - - if (!_.isEmpty(setData)) { - update.$set = setData; - } - - if (!_.isEmpty(unsetData)) { - update.$unset = unsetData; - } - - if (_.isEmpty(update)) { - return true; - } - - return this.update({ _id }, update); - } - - findOneGuestByEmailAddress(emailAddress) { - const query = { - 'visitorEmails.address': String(emailAddress).toLowerCase(), - }; - - return this.findOne(query); - } - - saveGuestEmailPhoneById(_id, emails, phones) { - const update = { - $addToSet: {}, - }; - - const saveEmail = [] - .concat(emails) - .filter((email) => email && email.trim()) - .map((email) => ({ address: email })); - - if (saveEmail.length > 0) { - update.$addToSet.visitorEmails = { $each: saveEmail }; - } - - const savePhone = [] - .concat(phones) - .filter((phone) => phone && phone.trim().replace(/[^\d]/g, '')) - .map((phone) => ({ phoneNumber: phone })); - - if (savePhone.length > 0) { - update.$addToSet.phone = { $each: savePhone }; - } - - if (!update.$addToSet.visitorEmails && !update.$addToSet.phone) { - return; - } - - return this.update({ _id }, update); - } - - // REMOVE - removeDepartmentById(_id) { - return this.update({ _id }, { $unset: { department: 1 } }); - } - - removeById(_id) { - const query = { _id }; - return this.remove(query); - } -} - -export default new LivechatVisitors(); diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 0a89174d8c99..3efc88d46d0d 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -15,13 +15,14 @@ import { Invites, Uploads, LivechatDepartment, + LivechatVisitors, EmailInbox, LivechatBusinessHours, Messages as MessagesRaw, InstanceStatus, } from '@rocket.chat/models'; -import { Settings, Users, Rooms, Subscriptions, Messages, LivechatVisitors } from '../../../models/server'; +import { Settings, Users, Rooms, Subscriptions, Messages } from '../../../models/server'; import { settings } from '../../../settings/server'; import { Info, getMongoInfo } from '../../../utils/server'; import { getControl } from '../../../../server/lib/migrations'; @@ -112,7 +113,7 @@ export const statistics = { statistics.totalThreads = Messages.countThreads(); // livechat visitors - statistics.totalLivechatVisitors = LivechatVisitors.find().count(); + statistics.totalLivechatVisitors = await LivechatVisitors.find().count(); // livechat agents statistics.totalLivechatAgents = Users.findAgents().count(); diff --git a/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts b/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts index 94035c20d7cc..e732adbb6696 100644 --- a/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts +++ b/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts @@ -1,10 +1,11 @@ import get from 'lodash.get'; -import type { IMessage } from '@rocket.chat/core-typings'; -import { IOmnichannelRoom, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { IMessage, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; import { settings } from '../../../../../app/settings/server'; import { callbacks } from '../../../../../lib/callbacks'; -import { Users, LivechatVisitors, Rooms } from '../../../../../app/models/server'; +import { Users, Rooms } from '../../../../../app/models/server'; const placeholderFields = { 'contact.name': { @@ -45,7 +46,7 @@ const handleBeforeSaveMessage = (message: IMessage, room?: IOmnichannelRoom): IM const agentId = room?.servedBy?._id; const visitorId = room?.v?._id; const agent = Users.findOneById(agentId, { fields: { name: 1, _id: 1, emails: 1 } }) || {}; - const visitor = LivechatVisitors.findOneById(visitorId) || {}; + const visitor = visitorId && (Promise.await(LivechatVisitors.findOneById(visitorId, {})) || {}); Object.keys(placeholderFields).map((field) => { const templateKey = `{{${field}}}`; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js b/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js index 7d02964e79fb..6ffb215450af 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.js @@ -1,7 +1,9 @@ +import { LivechatVisitors } from '@rocket.chat/models'; + import { callbacks } from '../../../../../lib/callbacks'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { settings } from '../../../../../app/settings/server'; -import { LivechatRooms, LivechatInquiry, LivechatVisitors, Users } from '../../../../../app/models/server'; +import { LivechatRooms, LivechatInquiry, Users } from '../../../../../app/models/server'; let contactManagerPreferred = false; let lastChattedAgentPreferred = false; @@ -24,9 +26,11 @@ const checkDefaultAgentOnNewRoom = (defaultAgent, defaultGuest) => { } const { _id: guestId } = defaultGuest; - const guest = LivechatVisitors.findOneById(guestId, { - fields: { lastAgent: 1, token: 1, contactManager: 1 }, - }); + const guest = Promise.await( + LivechatVisitors.findOneById(guestId, { + projection: { lastAgent: 1, token: 1, contactManager: 1 }, + }), + ); if (!guest) { return defaultAgent; } @@ -89,7 +93,7 @@ const afterTakeInquiry = (inquiry, agent) => { return inquiry; } - LivechatVisitors.updateLastAgentByToken(token, { ...agent, ts: new Date() }); + Promise.await(LivechatVisitors.updateLastAgentByToken(token, { ...agent, ts: new Date() })); return inquiry; }; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js b/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js index c272c0f516eb..e4b229803133 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.js @@ -1,9 +1,10 @@ import { SyncedCron } from 'meteor/littledata:synced-cron'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Meteor } from 'meteor/meteor'; +import { LivechatVisitors } from '@rocket.chat/models'; import { settings } from '../../../../../app/settings/server'; -import { LivechatRooms, LivechatDepartment, Users, LivechatVisitors } from '../../../../../app/models/server'; +import { LivechatRooms, LivechatDepartment, Users } from '../../../../../app/models/server'; import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; import { LivechatEnterprise } from './LivechatEnterprise'; @@ -78,11 +79,11 @@ export class VisitorInactivityMonitor { }); } - placeRoomOnHold(room) { + async placeRoomOnHold(room) { const timeout = settings.get('Livechat_visitor_inactivity_timeout'); const { v: { _id: visitorId } = {} } = room; - const visitor = LivechatVisitors.findOneById(visitorId); + const visitor = await LivechatVisitors.findOneById(visitorId); if (!visitor) { throw new Meteor.Error('error-invalid_visitor', 'Visitor Not found'); } @@ -105,7 +106,7 @@ export class VisitorInactivityMonitor { break; } case 'on-hold': { - this.placeRoomOnHold(room); + Promise.await(this.placeRoomOnHold(room)); break; } } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts index caa2c1ea0d70..716a4babaf68 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts @@ -1,30 +1,32 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import { ILivechatVisitor } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, LivechatInquiry, Messages, Users, LivechatVisitors } from '../../../../../app/models/server'; +import { LivechatRooms, LivechatInquiry, Messages, Users } from '../../../../../app/models/server'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { callbacks } from '../../../../../lib/callbacks'; -const resolveOnHoldCommentInfo = (options: { clientAction: boolean }, room: any, onHoldChatResumedBy: any): string => { - let comment = ''; +async function resolveOnHoldCommentInfo(options: { clientAction: boolean }, room: any, onHoldChatResumedBy: any): Promise { if (options.clientAction) { - comment = TAPi18n.__('Omnichannel_on_hold_chat_manually', { + return TAPi18n.__('Omnichannel_on_hold_chat_manually', { user: onHoldChatResumedBy.name || onHoldChatResumedBy.username, }); - } else { - const { - v: { _id: visitorId }, - } = room; - const visitor = LivechatVisitors.findOneById(visitorId, { name: 1, username: 1 }); - if (!visitor) { - throw new Meteor.Error('error-invalid_visitor', 'Visitor Not found'); - } - const guest = visitor.name || visitor.username; - comment = TAPi18n.__('Omnichannel_on_hold_chat_automatically', { guest }); } + const { + v: { _id: visitorId }, + } = room; + const visitor = await LivechatVisitors.findOneById>(visitorId, { + projection: { name: 1, username: 1 }, + }); + if (!visitor) { + throw new Meteor.Error('error-invalid_visitor', 'Visitor Not found'); + } + + const guest = visitor.name || visitor.username; - return comment; -}; + return TAPi18n.__('Omnichannel_on_hold_chat_automatically', { guest }); +} Meteor.methods({ async 'livechat:resumeOnHold'(roomId, options = { clientAction: false }) { @@ -55,7 +57,7 @@ Meteor.methods({ const onHoldChatResumedBy = options.clientAction ? Meteor.user() : Users.findOneById('rocket.cat'); - const comment = resolveOnHoldCommentInfo(options, room, onHoldChatResumedBy); + const comment = await resolveOnHoldCommentInfo(options, room, onHoldChatResumedBy); (Messages as any).createOnHoldResumedHistoryWithRoomIdMessageAndUser(roomId, comment, onHoldChatResumedBy); const updatedRoom = LivechatRooms.findOneById(roomId); diff --git a/apps/meteor/imports/message-read-receipt/server/lib/ReadReceipt.js b/apps/meteor/imports/message-read-receipt/server/lib/ReadReceipt.js index 625ee3d88c94..158d3ddc3365 100644 --- a/apps/meteor/imports/message-read-receipt/server/lib/ReadReceipt.js +++ b/apps/meteor/imports/message-read-receipt/server/lib/ReadReceipt.js @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; -import { ReadReceipts } from '@rocket.chat/models'; +import { LivechatVisitors, ReadReceipts } from '@rocket.chat/models'; -import { Subscriptions, Messages, Rooms, Users, LivechatVisitors } from '../../../../app/models/server'; +import { Subscriptions, Messages, Rooms, Users } from '../../../../app/models/server'; import { settings } from '../../../../app/settings/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; @@ -98,11 +98,13 @@ export const ReadReceipt = { async getReceipts(message) { const receipts = await ReadReceipts.findByMessageId(message._id).toArray(); - return receipts.map((receipt) => ({ - ...receipt, - user: receipt.token - ? LivechatVisitors.getVisitorByToken(receipt.token, { fields: { username: 1, name: 1 } }) - : Users.findOneById(receipt.userId, { fields: { username: 1, name: 1 } }), - })); + return Promise.all( + receipts.map(async (receipt) => ({ + ...receipt, + user: receipt.token + ? await LivechatVisitors.getVisitorByToken(receipt.token, { projection: { username: 1, name: 1 } }) + : Users.findOneById(receipt.userId, { fields: { username: 1, name: 1 } }), + })), + ); }, }; diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 6c5e498cb1ee..a767be02b7c2 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -3,10 +3,11 @@ import stripHtml from 'string-strip-html'; import { Random } from 'meteor/random'; import { ParsedMail, Attachment } from 'mailparser'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { ILivechatVisitor, OmnichannelSourceType } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; import { Livechat } from '../../../app/livechat/server/lib/Livechat'; -import { LivechatRooms, LivechatVisitors, Messages } from '../../../app/models/server'; +import { LivechatRooms, Messages } from '../../../app/models/server'; import { FileUpload } from '../../../app/file-upload/server'; import { QueueManager } from '../../../app/livechat/server/lib/QueueManager'; import { settings } from '../../../app/settings/server'; @@ -30,9 +31,9 @@ type FileAttachment = { const language = settings.get('Language') || 'en'; const t = (s: string): string => TAPi18n.__(s, { lng: language }); -function getGuestByEmail(email: string, name: string, department = ''): any { +async function getGuestByEmail(email: string, name: string, department = ''): Promise { logger.debug(`Attempt to register a guest for ${email} on department: ${department}`); - const guest = LivechatVisitors.findOneGuestByEmailAddress(email); + const guest = await LivechatVisitors.findOneGuestByEmailAddress(email); if (guest) { logger.debug(`Guest with email ${email} found with id ${guest._id}`); @@ -44,11 +45,11 @@ function getGuestByEmail(email: string, name: string, department = ''): any { newDepartment: department, }); if (!department) { - LivechatVisitors.removeDepartmentById(guest._id); + await LivechatVisitors.removeDepartmentById(guest._id); delete guest.department; return guest; } - Livechat.setDepartmentForGuest({ token: guest.token, department }); + await Livechat.setDepartmentForGuest({ token: guest.token, department }); return LivechatVisitors.findOneById(guest._id, {}); } return guest; @@ -58,7 +59,7 @@ function getGuestByEmail(email: string, name: string, department = ''): any { msg: 'Creating a new Omnichannel guest for visitor with email', email, }); - const userId = Livechat.registerGuest({ + const userId = await Livechat.registerGuest({ token: Random.id(), name: name || email, email, @@ -69,7 +70,7 @@ function getGuestByEmail(email: string, name: string, department = ''): any { id: undefined, }); - const newGuest = LivechatVisitors.findOneById(userId, {}); + const newGuest = await LivechatVisitors.findOneById(userId); logger.debug(`Guest ${userId} for visitor ${email} created`); if (newGuest) { return newGuest; @@ -136,7 +137,12 @@ export async function onEmailReceived(email: ParsedMail, inbox: string, departme const thread = references?.[0] ?? email.messageId; logger.debug(`Fetching guest for visitor ${email.from.value[0].address}`); - const guest = getGuestByEmail(email.from.value[0].address, email.from.value[0].name, department); + const guest = await getGuestByEmail(email.from.value[0].address, email.from.value[0].name, department); + + if (!guest) { + logger.debug(`No visitor found for ${email.from.value[0].address}`); + return; + } logger.debug(`Guest ${guest._id} obtained. Attempting to find or create a room on department ${department}`); diff --git a/apps/meteor/server/lib/rooms/roomTypes/livechat.ts b/apps/meteor/server/lib/rooms/roomTypes/livechat.ts index fe69aacc7217..1dadd91ee27d 100644 --- a/apps/meteor/server/lib/rooms/roomTypes/livechat.ts +++ b/apps/meteor/server/lib/rooms/roomTypes/livechat.ts @@ -1,6 +1,7 @@ import type { AtLeast, ValueOf } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; -import { LivechatRooms, LivechatVisitors } from '../../../../app/models/server'; +import { LivechatRooms } from '../../../../app/models/server'; import { RoomSettingsEnum, RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; import type { IRoomTypeServerDirectives } from '../../../../definition/IRoomTypeConfig'; import { getLivechatRoomType } from '../../../../lib/rooms/roomTypes/livechat'; @@ -38,7 +39,7 @@ roomCoordinator.add(LivechatRoomType, { }, getMsgSender(senderId) { - return LivechatVisitors.findOneById(senderId); + return Promise.await(LivechatVisitors.findOneById(senderId)); }, getReadReceiptsExtraData(message) { diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index dbf81fb94d38..ec6373d2bd13 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, ISetting, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { ILivechatVisitorsModel } from '@rocket.chat/model-typings'; import type { AggregationCursor, @@ -8,9 +8,12 @@ import type { FilterQuery, FindOneOptions, UpdateWriteOpResult, - WithoutProjection, + IndexSpecification, + DeleteWriteOpResultObject, + UpdateQuery, + WriteOpResult, } from 'mongodb'; -import { getCollectionName } from '@rocket.chat/models'; +import { getCollectionName, Settings } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { BaseRaw } from './BaseRaw'; @@ -20,15 +23,54 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL super(db, getCollectionName('livechat_visitor'), trash); } - findOneById(_id: string, options: WithoutProjection>): Promise { + protected modelIndexes(): IndexSpecification[] { + return [ + { key: { token: 1 } }, + { key: { 'phone.phoneNumber': 1 }, sparse: true }, + { key: { 'visitorEmails.address': 1 }, sparse: true }, + { key: { name: 1 }, sparse: true }, + { key: { username: 1 } }, + { key: { 'contactMananger.username': 1 }, sparse: true }, + ]; + } + + findOneVisitorByPhone(phone: string): Promise { + const query = { + 'phone.phoneNumber': phone, + }; + + return this.findOne(query); + } + + findOneGuestByEmailAddress(emailAddress: string): Promise { + const query = { + 'visitorEmails.address': String(emailAddress).toLowerCase(), + }; + + return this.findOne(query); + } + + /** + * Find visitors by _id + * @param {string} token - Visitor token + */ + findById(_id: string, options: FindOneOptions): Cursor { const query = { _id, }; - return this.findOne(query, options); + return this.find(query, options); + } + + findVisitorByToken(token: string): Cursor { + const query = { + token, + }; + + return this.find(query); } - getVisitorByToken(token: string, options: WithoutProjection>): Promise { + getVisitorByToken(token: string, options: FindOneOptions): Promise { const query = { token, }; @@ -48,6 +90,27 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.find(query, { projection: { _id: 1 } }); } + async getNextVisitorUsername(): Promise { + const query = { + _id: 'Livechat_guest_count', + }; + + const update: UpdateQuery = { + $inc: { + // @ts-expect-error looks like the typings of ISetting.value conflict with this type of update + value: 1, + }, + }; + + const livechatCount = await Settings.findOneAndUpdate(query, update, { returnDocument: 'after' }); + + if (!livechatCount.value) { + throw new Error("Can't find Livechat_guest_count setting"); + } + + return `guest-${livechatCount.value.value}`; + } + findByNameRegexWithExceptionsAndConditions

( searchTerm: string, exceptions: string[] = [], @@ -121,6 +184,137 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.find(query, options); } + async updateLivechatDataByToken(token: string, key: string, value: unknown, overwrite = true): Promise { + const query = { + token, + }; + + if (!overwrite) { + const user = await this.getVisitorByToken(token, { projection: { livechatData: 1 } }); + if (user?.livechatData && typeof user.livechatData[key] !== 'undefined') { + return true; + } + } + + const update = { + $set: { + [`livechatData.${key}`]: value, + }, + }; + + return this.update(query, update); + } + + updateLastAgentByToken(token: string, lastAgent: ILivechatVisitor['lastAgent']): Promise { + const query = { + token, + }; + + const update = { + $set: { + lastAgent, + }, + }; + + return this.update(query, update); + } + + updateById(_id: string, update: UpdateQuery): Promise { + return this.update({ _id }, update); + } + + saveGuestById( + _id: string, + data: { name?: string; username?: string; email?: string; phone?: string; livechatData: { [k: string]: any } }, + ): Promise { + const setData: DeepWriteable['$set']> = {}; + const unsetData: DeepWriteable['$unset']> = {}; + + if (data.name) { + if (data.name?.trim()) { + setData.name = data.name.trim(); + } else { + unsetData.name = 1; + } + } + + if (data.email) { + if (data.email?.trim()) { + setData.visitorEmails = [{ address: data.email.trim() }]; + } else { + unsetData.visitorEmails = 1; + } + } + + if (data.phone) { + if (data.phone?.trim()) { + setData.phone = [{ phoneNumber: data.phone.trim() }]; + } else { + unsetData.phone = 1; + } + } + + if (data.livechatData) { + Object.keys(data.livechatData).forEach((key) => { + const value = data.livechatData[key]?.trim(); + if (value) { + setData[`livechatData.${key}`] = value; + } else { + unsetData[`livechatData.${key}`] = 1; + } + }); + } + + const update: UpdateQuery = { + ...(Object.keys(setData).length && { $set: setData as UpdateQuery['$set'] }), + ...(Object.keys(unsetData).length && { $unset: unsetData as UpdateQuery['$unset'] }), + }; + + if (!Object.keys(update).length) { + return Promise.resolve(true); + } + + return this.update({ _id }, update); + } + + removeDepartmentById(_id: string): Promise { + return this.update({ _id }, { $unset: { department: 1 } }); + } + + removeById(_id: string): Promise { + return this.removeById(_id); + } + + saveGuestEmailPhoneById(_id: string, emails: string[], phones: string[]): Promise { + const update: DeepWriteable> = { + $addToSet: {}, + }; + + const saveEmail = ([] as string[]) + .concat(emails) + .filter((email) => email?.trim()) + .map((email) => ({ address: email })); + + if (update.$addToSet && saveEmail.length > 0) { + update.$addToSet.visitorEmails = { $each: saveEmail }; + } + + const savePhone = ([] as string[]) + .concat(phones) + .filter((phone) => phone?.trim().replace(/[^\d]/g, '')) + .map((phone) => ({ phoneNumber: phone })); + + if (update.$addToSet && savePhone.length > 0) { + update.$addToSet.phone = { $each: savePhone }; + } + + if (!Object.keys(update).length) { + return Promise.resolve(); + } + + return this.update({ _id }, update as UpdateQuery); + } + removeContactManagerByUsername(manager: string): Promise { return this.updateMany( { @@ -136,3 +330,5 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL ); } } + +type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; diff --git a/apps/meteor/server/startup/migrations/v260.ts b/apps/meteor/server/startup/migrations/v260.ts index ee2ad17beab9..b92e10bc7173 100644 --- a/apps/meteor/server/startup/migrations/v260.ts +++ b/apps/meteor/server/startup/migrations/v260.ts @@ -1,9 +1,9 @@ import { ILivechatVisitor } from '@rocket.chat/core-typings'; import { BulkWriteOperation, Cursor } from 'mongodb'; -import { LivechatVisitors as VisitorsRaw } from '@rocket.chat/models'; +import { LivechatVisitors } from '@rocket.chat/models'; import { addMigration } from '../../lib/migrations'; -import { LivechatVisitors, Users } from '../../../app/models/server'; +import { Users } from '../../../app/models/server'; const getNextPageCursor = (skip: number, limit: number): Cursor => { return LivechatVisitors.find({ 'visitorEmails.address': /[A-Z]/ }, { skip, limit, sort: { _id: 1 } }); @@ -12,9 +12,9 @@ const getNextPageCursor = (skip: number, limit: number): Cursor[] = []; - const count = LivechatVisitors.find({ 'visitorEmails.address': /[A-Z]/ }).count(); + const count = await LivechatVisitors.find({ 'visitorEmails.address': /[A-Z]/ }).count(); const limit = 5000; let skip = 0; @@ -32,7 +32,8 @@ addMigration({ }); if (updates.length) { - Promise.await(VisitorsRaw.col.bulkWrite(updates)); + // eslint-disable-next-line no-await-in-loop + await LivechatVisitors.col.bulkWrite(updates); } incrementSkip(limit); diff --git a/packages/core-typings/src/ILivechatVisitor.ts b/packages/core-typings/src/ILivechatVisitor.ts index d7e2772721e6..2759b96099c9 100644 --- a/packages/core-typings/src/ILivechatVisitor.ts +++ b/packages/core-typings/src/ILivechatVisitor.ts @@ -20,6 +20,10 @@ export interface IVisitorEmail { address: string; } +interface ILivechatData { + [k: string]: unknown; +} + export interface ILivechatVisitor extends IRocketChatRecord { username: string; ts: Date; @@ -32,6 +36,12 @@ export interface ILivechatVisitor extends IRocketChatRecord { ip?: string; host?: string; visitorEmails?: IVisitorEmail[]; + lastAgent?: { + username: string; + agentId: string; + ts: Date; + }; + livechatData?: ILivechatData; contactManager?: { username: string; }; diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index 988faba46790..a355ecc57a2d 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -1,10 +1,18 @@ -import type { AggregationCursor, Cursor, FilterQuery, FindOneOptions, WithoutProjection, UpdateWriteOpResult } from 'mongodb'; +import type { + AggregationCursor, + Cursor, + FilterQuery, + FindOneOptions, + WithoutProjection, + UpdateWriteOpResult, + WriteOpResult, +} from 'mongodb'; import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import type { IBaseModel } from './IBaseModel'; export interface ILivechatVisitorsModel extends IBaseModel { - findOneById(_id: string, options: WithoutProjection>): Promise; + findById(_id: string, options: FindOneOptions): Cursor; getVisitorByToken(token: string, options: WithoutProjection>): Promise; getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department: string }): Cursor; findByNameRegexWithExceptionsAndConditions

( @@ -22,4 +30,12 @@ export interface ILivechatVisitorsModel extends IBaseModel { options: FindOneOptions, ): Cursor; removeContactManagerByUsername(manager: string): Promise; + + updateLivechatDataByToken(token: string, key: string, value: unknown, overwrite: boolean): Promise; + + findOneGuestByEmailAddress(emailAddress: string): Promise; + + findOneVisitorByPhone(phone: string): Promise; + + removeDepartmentById(_id: string): Promise; } diff --git a/yarn.lock b/yarn.lock index ea334bb70a38..e18b3ed257e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7805,7 +7805,7 @@ __metadata: human-interval: ~1.0.0 moment-timezone: ~0.5.27 mongodb: ~3.5.0 - checksum: acb4ebb7e7356f6e53e810d821eb6aa3d88bbfb9e85183e707517bee6d1eea1f189f38bdf0dd2b91360492ab7643134d510c320d2523d86596498ab98e59735b + checksum: f5f68008298f9482631f1f494e392cd6b8ba7971a3b0ece81ae2abe60f53d67973ff4476156fa5c9c41b8b58c4ccd284e95c545e0523996dfd05f9a80b843e07 languageName: node linkType: hard From d9ffbd6dd4cf6555e2041e080915d7140270a762 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 24 Jun 2022 14:21:02 -0300 Subject: [PATCH 3/6] [FIX] Initial members value on Create Channel Modal (#26000) ## Proposed changes (including videos or screenshots) #### before ![Screen Shot 2022-06-24 at 11 58 22](https://user-images.githubusercontent.com/27704687/175562315-221dbc9a-5695-4259-a8f7-644e2ff0ab36.png) #### after ![Screen Shot 2022-06-24 at 11 59 38](https://user-images.githubusercontent.com/27704687/175562510-a4a6be49-bbd2-4aeb-aedb-a5a7a6f1159d.png) ## Issue(s) ## Steps to test or reproduce ## Further comments --- .../components/UserAutoComplete/UserAutoComplete.tsx | 4 ++-- .../UserAutoCompleteMultiple.tsx | 10 +++++----- .../client/sidebar/header/CreateChannelWithData.tsx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx b/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx index b8e23f6e44d3..1b876fc99b2d 100644 --- a/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx +++ b/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx @@ -38,9 +38,9 @@ const UserAutoComplete = ({ value, ...props }: UserAutoCompleteProps): ReactElem onChange={props.onChange as any} filter={filter} setFilter={setFilter} - renderSelected={({ value, label }): ReactElement => { + renderSelected={({ value, label }): ReactElement | null => { if (!value) { - undefined; + return null; } return ( diff --git a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx index b27c6d6034f9..9efda652b61a 100644 --- a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx +++ b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx @@ -1,4 +1,4 @@ -import { AutoComplete, Box, Option, Chip } from '@rocket.chat/fuselage'; +import { AutoComplete, Box, Option, OptionAvatar, OptionContent, Chip } from '@rocket.chat/fuselage'; import { useMutableCallback, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import React, { ComponentProps, memo, ReactElement, useMemo, useState } from 'react'; @@ -52,12 +52,12 @@ const UserAutoCompleteMultiple = ({ onChange, ...props }: UserAutoCompleteMultip } renderItem={({ value, label, ...props }): ReactElement => ( )} options={options} diff --git a/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx b/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx index 467f6c23be36..eeb1c46f34cb 100644 --- a/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx +++ b/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx @@ -41,7 +41,7 @@ const CreateChannelWithData = ({ onClose, teamId = '', reload }: CreateChannelWi }, [canCreateChannel, canCreatePrivateChannel]); const initialValues = { - users: [''], + users: [], name: '', description: '', type: canOnlyCreateOneType ? canOnlyCreateOneType === 'p' : true, From 65a100008032e781a1ce52706f757eb310271eed Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Fri, 24 Jun 2022 17:26:56 -0300 Subject: [PATCH 4/6] Chore: Small fix on callProvider (#25963) --- apps/meteor/client/contexts/CallContext.ts | 3 + .../client/contexts/VoIPAgentContext.ts | 23 ++++ .../providers/CallProvider/CallProvider.tsx | 71 +++++----- .../client/providers/MeteorProvider.tsx | 8 +- .../client/providers/VoIPAgentProvider.tsx | 102 ++++++++++++++ .../client/sidebar/footer/voip/VoipFooter.tsx | 2 - .../components/OmnichannelCallToggleReady.tsx | 124 +++--------------- .../sidebar/sections/hooks/useVoipAgent.ts | 5 + 8 files changed, 195 insertions(+), 143 deletions(-) create mode 100644 apps/meteor/client/contexts/VoIPAgentContext.ts create mode 100644 apps/meteor/client/providers/VoIPAgentProvider.tsx create mode 100644 apps/meteor/client/sidebar/sections/hooks/useVoipAgent.ts diff --git a/apps/meteor/client/contexts/CallContext.ts b/apps/meteor/client/contexts/CallContext.ts index bb739416a74c..47ec8a25bc6d 100644 --- a/apps/meteor/client/contexts/CallContext.ts +++ b/apps/meteor/client/contexts/CallContext.ts @@ -72,6 +72,8 @@ export const useIsCallError = (): boolean => { return Boolean(isCallContextError(context)); }; +export const useCallContext = (): CallContextValue => useContext(CallContext); + export const useCallActions = (): CallActionsType => { const context = useContext(CallContext); @@ -142,6 +144,7 @@ export const useCallClient = (): VoIPUser => { if (!isCallContextReady(context)) { throw new Error('useClient only if Calls are enabled and ready'); } + return context.voipClient; }; diff --git a/apps/meteor/client/contexts/VoIPAgentContext.ts b/apps/meteor/client/contexts/VoIPAgentContext.ts new file mode 100644 index 000000000000..ef27a32e09c3 --- /dev/null +++ b/apps/meteor/client/contexts/VoIPAgentContext.ts @@ -0,0 +1,23 @@ +import { createContext, Dispatch, SetStateAction } from 'react'; + +export type VoIPAgentContextValue = { + agentEnabled: boolean; + registered: boolean; + networkStatus: 'online' | 'offline'; + voipButtonEnabled: boolean; + setAgentEnabled: Dispatch>; + setRegistered: Dispatch>; + setNetworkStatus: Dispatch>; + setVoipButtonEnabled: Dispatch>; +}; + +export const VoIPAgentContext = createContext({ + agentEnabled: false, + registered: false, + networkStatus: 'offline', + voipButtonEnabled: false, + setAgentEnabled: () => undefined, + setRegistered: () => undefined, + setNetworkStatus: () => undefined, + setVoipButtonEnabled: () => undefined, +}); diff --git a/apps/meteor/client/providers/CallProvider/CallProvider.tsx b/apps/meteor/client/providers/CallProvider/CallProvider.tsx index ba1e359e4acd..72756543a250 100644 --- a/apps/meteor/client/providers/CallProvider/CallProvider.tsx +++ b/apps/meteor/client/providers/CallProvider/CallProvider.tsx @@ -21,9 +21,10 @@ import { OutgoingByeRequest } from 'sip.js/lib/core'; import { CustomSounds } from '../../../app/custom-sounds/client'; import { getUserPreference } from '../../../app/utils/client'; import { WrapUpCallModal } from '../../components/voip/modal/WrapUpCallModal'; -import { CallContext, CallContextValue, useCallCloseRoom } from '../../contexts/CallContext'; +import { CallContext, CallContextValue } from '../../contexts/CallContext'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; import { QueueAggregator } from '../../lib/voip/QueueAggregator'; +import VoIPAgentProvider from '../VoIPAgentProvider'; import { useVoipClient } from './hooks/useVoipClient'; const startRingback = (user: IUser): void => { @@ -44,6 +45,9 @@ export const CallProvider: FC = ({ children }) => { const voipEnabled = useSetting('VoIP_Enabled'); const subscribeToNotifyUser = useStream('notify-user'); const dispatchEvent = useEndpoint('POST', '/v1/voip/events'); + const visitorEndpoint = useEndpoint('POST', '/v1/livechat/visitor'); + const voipEndpoint = useEndpoint('GET', '/v1/voip/room'); + const voipCloseRoomEndpoint = useEndpoint('POST', '/v1/voip/room.close'); const setModal = useSetModal(); const result = useVoipClient(); @@ -54,10 +58,29 @@ export const CallProvider: FC = ({ children }) => { const [queueCounter, setQueueCounter] = useState(0); const [queueName, setQueueName] = useState(''); + const [roomInfo, setRoomInfo] = useState<{ v: { token?: string }; rid: string }>(); + + const closeRoom = useCallback( + async (data): Promise => { + roomInfo && + (await voipCloseRoomEndpoint({ + rid: roomInfo.rid, + token: roomInfo.v.token || '', + options: { comment: data?.comment, tags: data?.tags }, + })); + homeRoute.push({}); + + const queueAggregator = result.voipClient?.getAggregator(); + if (queueAggregator) { + queueAggregator.callEnded(); + } + }, + [homeRoute, result?.voipClient, roomInfo, voipCloseRoomEndpoint], + ); const openWrapUpModal = useCallback((): void => { - setModal(() => ); - }, [setModal]); + setModal(() => ); + }, [closeRoom, setModal]); const [queueAggregator, setQueueAggregator] = useState(); @@ -234,12 +257,6 @@ export const CallProvider: FC = ({ children }) => { }; }, [onNetworkConnected, onNetworkDisconnected, result.voipClient]); - const visitorEndpoint = useEndpoint('POST', '/v1/livechat/visitor'); - const voipEndpoint = useEndpoint('GET', '/v1/voip/room'); - const voipCloseRoomEndpoint = useEndpoint('POST', '/v1/voip/room.close'); - - const [roomInfo, setRoomInfo] = useState<{ v: { token?: string }; rid: string }>(); - const openRoom = (rid: IVoipRoom['_id']): void => { roomCoordinator.openRouteLink('v', { rid }); }; @@ -319,34 +336,24 @@ export const CallProvider: FC = ({ children }) => { } return ''; }, - closeRoom: async ({ comment, tags }: { comment?: string; tags?: string[] }): Promise => { - roomInfo && (await voipCloseRoomEndpoint({ rid: roomInfo.rid, token: roomInfo.v.token || '', options: { comment, tags } })); - homeRoute.push({}); - const queueAggregator = voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.callEnded(); - } - }, + closeRoom, openWrapUpModal, }; - }, [ - voipEnabled, - user, - result, - roomInfo, - queueCounter, - queueName, - openWrapUpModal, - visitorEndpoint, - voipEndpoint, - voipCloseRoomEndpoint, - homeRoute, - ]); + }, [voipEnabled, user, result, roomInfo, queueCounter, queueName, closeRoom, openWrapUpModal, visitorEndpoint, voipEndpoint]); return ( - {children} - {contextValue.enabled && createPortal( ); }; diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index ef0e5885cf7c..56273b2f854a 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -31,15 +31,15 @@ const MeteorProvider: FC = ({ children }) => ( - - + + {children} - - + + diff --git a/apps/meteor/client/providers/VoIPAgentProvider.tsx b/apps/meteor/client/providers/VoIPAgentProvider.tsx new file mode 100644 index 000000000000..79f45cf32dfd --- /dev/null +++ b/apps/meteor/client/providers/VoIPAgentProvider.tsx @@ -0,0 +1,102 @@ +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; + +import { useCallActions, useCallClient } from '../contexts/CallContext'; +import { VoIPAgentContext } from '../contexts/VoIPAgentContext'; + +const VoIPAgentProvider: FC = ({ children }) => { + const [agentEnabled, setAgentEnabled] = useState(false); + const [registered, setRegistered] = useState(false); + const [networkStatus, setNetworkStatus] = useState<'online' | 'offline'>('online'); + const [voipButtonEnabled, setVoipButtonEnabled] = useState(false); + const callActions = useCallActions(); + + const voipClient = useCallClient(); + const registerState = useMemo(() => voipClient.getRegistrarState(), [voipClient]); + + const toggleRegistered = useCallback((): void => { + setRegistered((registered) => !registered); + }, []); + + const toggleRegistrationError = useCallback((): void => { + setRegistered(false); + setAgentEnabled(false); + }, []); + + const onNetworkConnected = useCallback((): void => { + setVoipButtonEnabled(['IN_CALL', 'ON_HOLD'].includes(voipClient.callerInfo.state)); + setNetworkStatus('online'); + }, [setNetworkStatus, setVoipButtonEnabled, voipClient.callerInfo.state]); + + const onNetworkDisconnected = useCallback((): void => { + setVoipButtonEnabled(true); + setNetworkStatus('offline'); + }, [setNetworkStatus, setVoipButtonEnabled]); + + useEffect(() => { + if (!agentEnabled) { + return; + } + + voipClient.register(); + + return (): void => voipClient.unregister(); + }, [agentEnabled, voipClient]); + + useEffect(() => { + setVoipButtonEnabled(['IN_CALL', 'ON_HOLD'].includes(voipClient.callerInfo.state)); + }, [setVoipButtonEnabled, voipClient.callerInfo.state]); + + useEffect(() => { + setRegistered(registerState === 'registered'); + }, [registerState]); + + useEffect(() => { + if (voipButtonEnabled) { + return; + } + + voipClient.callerInfo.state === 'OFFER_RECEIVED' && callActions.reject(); + }, [callActions, voipButtonEnabled, voipClient.callerInfo.state]); + + useEffect(() => { + voipClient.on('registered', toggleRegistered); + voipClient.on('unregistered', toggleRegistered); + voipClient.on('registrationerror', toggleRegistrationError); + voipClient.on('unregistrationerror', toggleRegistrationError); + voipClient.onNetworkEvent('connected', onNetworkConnected); + voipClient.onNetworkEvent('disconnected', onNetworkDisconnected); + voipClient.onNetworkEvent('connectionerror', onNetworkDisconnected); + voipClient.onNetworkEvent('localnetworkonline', onNetworkConnected); + voipClient.onNetworkEvent('localnetworkoffline', onNetworkDisconnected); + + return (): void => { + voipClient.off('registered', toggleRegistered); + voipClient.off('unregistered', toggleRegistered); + voipClient.off('registrationerror', toggleRegistrationError); + voipClient.off('unregistrationerror', toggleRegistrationError); + voipClient.offNetworkEvent('connected', onNetworkConnected); + voipClient.offNetworkEvent('disconnected', onNetworkDisconnected); + voipClient.offNetworkEvent('connectionerror', onNetworkDisconnected); + voipClient.offNetworkEvent('localnetworkonline', onNetworkConnected); + voipClient.offNetworkEvent('localnetworkoffline', onNetworkDisconnected); + }; + }, [voipClient, onNetworkConnected, onNetworkDisconnected, toggleRegistered, toggleRegistrationError]); + + return ( + + ); +}; + +export default VoIPAgentProvider; diff --git a/apps/meteor/client/sidebar/footer/voip/VoipFooter.tsx b/apps/meteor/client/sidebar/footer/voip/VoipFooter.tsx index 6f4ca4df40f9..98e2b9474534 100644 --- a/apps/meteor/client/sidebar/footer/voip/VoipFooter.tsx +++ b/apps/meteor/client/sidebar/footer/voip/VoipFooter.tsx @@ -130,7 +130,6 @@ export const VoipFooter = ({ small square danger - primary onClick={(e): unknown => { e.stopPropagation(); toggleMic(false); @@ -152,7 +151,6 @@ export const VoipFooter = ({ small square success - primary onClick={async (): Promise => { callActions.pickUp(); const rid = await createRoom(caller); diff --git a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx index 97b75a42ccac..08b917ea58e2 100644 --- a/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx +++ b/apps/meteor/client/sidebar/sections/components/OmnichannelCallToggleReady.tsx @@ -1,29 +1,30 @@ import { Sidebar } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { ReactElement, useEffect, useState } from 'react'; +import React, { ReactElement, useCallback } from 'react'; -import { useCallClient, useCallerInfo, useCallActions } from '../../../contexts/CallContext'; +// import { useCallActions, useCallClient } from '../../../contexts/CallContext'; +import { useVoipAgent } from '../hooks/useVoipAgent'; -type NetworkState = 'online' | 'offline'; export const OmnichannelCallToggleReady = (): ReactElement => { - const [agentEnabled, setAgentEnabled] = useState(false); // TODO: get from AgentInfo const t = useTranslation(); - const [registered, setRegistered] = useState(false); - const voipClient = useCallClient(); - const [disableButtonClick, setDisableButtonClick] = useState(false); - const [networkStatus, setNetworkStatus] = useState('online'); - const callerInfo = useCallerInfo(); - const callActions = useCallActions(); - const getTooltip = (): string => { + const { agentEnabled, networkStatus, registered, voipButtonEnabled, setAgentEnabled } = useVoipAgent(); + const onClickVoipButton = useCallback((): void => { + if (voipButtonEnabled) { + return; + } + + setAgentEnabled(!agentEnabled); + }, [agentEnabled, setAgentEnabled, voipButtonEnabled]); + + const getTitle = (): string => { if (networkStatus === 'offline') { return t('Signaling_connection_disconnected'); } - if (!registered) { + if (registered) { return t('Enable'); } - if (!disableButtonClick) { + if (!voipButtonEnabled) { // Color for this state still not defined return t('Disable'); } @@ -46,97 +47,10 @@ export const OmnichannelCallToggleReady = (): ReactElement => { }; const voipCallIcon = { - title: getTooltip(), - color: getColor(), + title: getTitle(), icon: getIcon(), - } as const; - - useEffect(() => { - // Any of the 2 states means the user is already talking - setDisableButtonClick(['IN_CALL', 'ON_HOLD'].includes(callerInfo.state)); - }, [callerInfo]); - - useEffect(() => { - let agentEnabled = false; - const state = voipClient.getRegistrarState(); - if (state === 'registered') { - agentEnabled = true; - } - setAgentEnabled(agentEnabled); - setRegistered(agentEnabled); - }, [voipClient]); - - // TODO: move registration flow to context provider - const handleVoipCallStatusChange = useMutableCallback((): void => { - if (disableButtonClick) { - return; - } - // TODO: backend set voip call status - // voipClient.setVoipCallStatus(!registered); - if (agentEnabled) { - callerInfo.state === 'OFFER_RECEIVED' && callActions.reject(); - setAgentEnabled(false); - voipClient.unregister(); - return; - } - setAgentEnabled(true); - voipClient.register(); - }); - - const onUnregistrationError = useMutableCallback((): void => { - setRegistered(false); - setAgentEnabled(false); - }); - - const onUnregistered = useMutableCallback((): void => { - setRegistered(!registered); - }); - - const onRegistrationError = useMutableCallback((): void => { - setRegistered(false); - setAgentEnabled(false); - }); - - const onRegistered = useMutableCallback((): void => { - setRegistered(!registered); - }); - - const onNetworkConnected = useMutableCallback((): void => { - setDisableButtonClick(['IN_CALL', 'ON_HOLD'].includes(callerInfo.state)); - setNetworkStatus('online'); - }); - - const onNetworkDisconnected = useMutableCallback((): void => { - setDisableButtonClick(true); - setNetworkStatus('offline'); - }); - - useEffect(() => { - if (!voipClient) { - return; - } - voipClient.on('registered', onRegistered); - voipClient.on('registrationerror', onRegistrationError); - voipClient.on('unregistered', onUnregistered); - voipClient.on('unregistrationerror', onUnregistrationError); - voipClient.onNetworkEvent('connected', onNetworkConnected); - voipClient.onNetworkEvent('disconnected', onNetworkDisconnected); - voipClient.onNetworkEvent('connectionerror', onNetworkDisconnected); - voipClient.onNetworkEvent('localnetworkonline', onNetworkConnected); - voipClient.onNetworkEvent('localnetworkoffline', onNetworkDisconnected); - - return (): void => { - voipClient.off('registered', onRegistered); - voipClient.off('registrationerror', onRegistrationError); - voipClient.off('unregistered', onUnregistered); - voipClient.off('unregistrationerror', onUnregistrationError); - voipClient.offNetworkEvent('connected', onNetworkConnected); - voipClient.offNetworkEvent('disconnected', onNetworkDisconnected); - voipClient.offNetworkEvent('connectionerror', onNetworkDisconnected); - voipClient.offNetworkEvent('localnetworkonline', onNetworkConnected); - voipClient.offNetworkEvent('localnetworkoffline', onNetworkDisconnected); - }; - }, [onRegistered, onRegistrationError, onUnregistered, onUnregistrationError, voipClient, onNetworkConnected, onNetworkDisconnected]); + color: getColor(), + }; - return ; + return ; }; diff --git a/apps/meteor/client/sidebar/sections/hooks/useVoipAgent.ts b/apps/meteor/client/sidebar/sections/hooks/useVoipAgent.ts new file mode 100644 index 000000000000..c61b65bc237e --- /dev/null +++ b/apps/meteor/client/sidebar/sections/hooks/useVoipAgent.ts @@ -0,0 +1,5 @@ +import { useContext } from 'react'; + +import { VoIPAgentContext, VoIPAgentContextValue } from '../../../contexts/VoIPAgentContext'; + +export const useVoipAgent = (): VoIPAgentContextValue => useContext(VoIPAgentContext); From 75240f13ba2b140aac8e14a266d76ff95bc30f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Fri, 24 Jun 2022 19:30:23 -0300 Subject: [PATCH 5/6] Chore: Fuselage update (#26004) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/package.json | 2 +- yarn.lock | 82 ++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 7d9d892741fe..5124cd7b9c81 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -197,7 +197,7 @@ "@rocket.chat/favicon": "workspace:^", "@rocket.chat/forked-matrix-appservice-bridge": "^4.0.1", "@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.2", - "@rocket.chat/fuselage": "^0.32.0-dev.61", + "@rocket.chat/fuselage": "^0.32.0-dev.65", "@rocket.chat/fuselage-hooks": "~0.31.14-dev.9", "@rocket.chat/fuselage-polyfills": "~0.31.12", "@rocket.chat/fuselage-toastbar": "^0.32.0-dev.22", diff --git a/yarn.lock b/yarn.lock index e18b3ed257e2..7668931cf7ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3331,16 +3331,16 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-in-js@npm:~0.31.14-dev.14": - version: 0.31.14-dev.14 - resolution: "@rocket.chat/css-in-js@npm:0.31.14-dev.14" +"@rocket.chat/css-in-js@npm:~0.31.14-dev.18": + version: 0.31.14-dev.18 + resolution: "@rocket.chat/css-in-js@npm:0.31.14-dev.18" dependencies: "@emotion/hash": ^0.8.0 - "@rocket.chat/css-supports": ~0.31.14-dev.14 - "@rocket.chat/memo": ~0.31.14-dev.14 - "@rocket.chat/stylis-logical-props-middleware": ~0.31.14-dev.14 + "@rocket.chat/css-supports": ~0.31.14-dev.18 + "@rocket.chat/memo": ~0.31.14-dev.18 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.14-dev.18 stylis: ~4.0.13 - checksum: f1bb71204aa8f54c31ef40cefd1e995cf525b21e433b69e4a4dc99dad90d68e4590d37778833e1f5c053d1382ca4dba9cce63467bfb8166e6a2c06aaa96606b1 + checksum: a25bb25a5343419a67d4b353e26ecd2db8e78d77ac4a6a880af2a847647fbc5bb816cc5a4d4bdf5db3cc3fb33a2f5bf3c5fb2ded8817410e44756e5762191e59 languageName: node linkType: hard @@ -3362,12 +3362,12 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:~0.31.14-dev.14": - version: 0.31.14-dev.14 - resolution: "@rocket.chat/css-supports@npm:0.31.14-dev.14" +"@rocket.chat/css-supports@npm:~0.31.14-dev.18": + version: 0.31.14-dev.18 + resolution: "@rocket.chat/css-supports@npm:0.31.14-dev.18" dependencies: - "@rocket.chat/memo": ~0.31.14-dev.14 - checksum: dc23db05e237d72a52f7fa4909cdcf34fd2e16464d48f62bee87dc9b46adbdc51b4c8aec2946fe39c0b597c9ac3a11b02bc27c148f836d9d5e37a65c07195ecd + "@rocket.chat/memo": ~0.31.14-dev.18 + checksum: e65e9252b6848029b04ec2b9250f508e7863828ae1061f57b745b63a31bee244f06641fb6eaece92fd3e05dc92b71e3c0ad66f0a83b150f9d4ad0942e0cdbf02 languageName: node linkType: hard @@ -3595,10 +3595,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.11": - version: 0.32.0-dev.11 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.11" - checksum: f94438c4d307a204216bc49106e1714628488eef9f27f8c3e9716410895544241e46b04637ba011258135b617e3e103609b55cfbcf093f90066058b93b98c442 +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.15": + version: 0.32.0-dev.15 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.15" + checksum: 35c3e0323742d4252a14ecb453ada73b155b38a374656c183cb4f4c0a4b86f6c71823aaee37710c34ecee7e745e2568ff9d27a950e044f2ab29079baa4b5407e languageName: node linkType: hard @@ -3642,15 +3642,15 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage@npm:^0.32.0-dev.61": - version: 0.32.0-dev.61 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.61" +"@rocket.chat/fuselage@npm:^0.32.0-dev.65": + version: 0.32.0-dev.65 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.65" dependencies: - "@rocket.chat/css-in-js": ~0.31.14-dev.14 - "@rocket.chat/css-supports": ~0.31.14-dev.14 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.11 - "@rocket.chat/memo": ~0.31.14-dev.14 - "@rocket.chat/styled": ~0.31.14-dev.14 + "@rocket.chat/css-in-js": ~0.31.14-dev.18 + "@rocket.chat/css-supports": ~0.31.14-dev.18 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.15 + "@rocket.chat/memo": ~0.31.14-dev.18 + "@rocket.chat/styled": ~0.31.14-dev.18 invariant: ^2.2.4 react-keyed-flatten-children: ^1.3.0 peerDependencies: @@ -3660,7 +3660,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 3c4be69b1ed189a8e30a046977f7bbc382f71644df347db8dc6d76dc30a229515340a55fe8aef1ef119b9584cfa05cc642d63d2e117f3ac9e4de8d1e1f3e7aae + checksum: 0306d39f2e5a731a613ab64c0b01844c1d9ca470a55b743a83195accab559614cf9e66a3a205549a8fbcb18e524a404055327f1f92bf095ab948c3cbf8892a9e languageName: node linkType: hard @@ -3777,10 +3777,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:~0.31.14-dev.14": - version: 0.31.14-dev.14 - resolution: "@rocket.chat/memo@npm:0.31.14-dev.14" - checksum: 8daca0bb12028d07109079a4acbf4153b0a00837ca3e1d1671ae52214a1d7d551ff63d9499c090733300bf3874603414dc4e30656604418d9f3dffd7dc97e942 +"@rocket.chat/memo@npm:~0.31.14-dev.18": + version: 0.31.14-dev.18 + resolution: "@rocket.chat/memo@npm:0.31.14-dev.18" + checksum: e49cbe0e12211ba113ebee47cb972a3f4b9c6d1347fe6c7c21bdb12d5b73761ef7b1b1771b1ae2ccef7ce06d057f95a2b9ac1365e42bebf07361f56732d6fece languageName: node linkType: hard @@ -3827,7 +3827,7 @@ __metadata: "@rocket.chat/favicon": "workspace:^" "@rocket.chat/forked-matrix-appservice-bridge": ^4.0.1 "@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.2 - "@rocket.chat/fuselage": ^0.32.0-dev.61 + "@rocket.chat/fuselage": ^0.32.0-dev.65 "@rocket.chat/fuselage-hooks": ~0.31.14-dev.9 "@rocket.chat/fuselage-polyfills": ~0.31.12 "@rocket.chat/fuselage-toastbar": ^0.32.0-dev.22 @@ -4238,13 +4238,13 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.14-dev.14": - version: 0.31.14-dev.14 - resolution: "@rocket.chat/styled@npm:0.31.14-dev.14" +"@rocket.chat/styled@npm:~0.31.14-dev.18": + version: 0.31.14-dev.18 + resolution: "@rocket.chat/styled@npm:0.31.14-dev.18" dependencies: - "@rocket.chat/css-in-js": ~0.31.14-dev.14 + "@rocket.chat/css-in-js": ~0.31.14-dev.18 tslib: ^2.3.1 - checksum: 7ec05eb257910499f3fc998535f874d7e3aca9edd4047e44bc723023e7f34705a419f291dc3b3c6bcdb38a818728e2644d2c31024ab37cc2cab86e7b36193a4e + checksum: 59643a094025e71f7fa493374714d229eae0ad8fceaab8361543f3c8cf6a537a99508fae10e0a7f70fa62ed96427b95e624abdd25477f0ed0d380a1d421d2b4f languageName: node linkType: hard @@ -4272,15 +4272,15 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.14-dev.14": - version: 0.31.14-dev.14 - resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.14-dev.14" +"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.14-dev.18": + version: 0.31.14-dev.18 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.14-dev.18" dependencies: - "@rocket.chat/css-supports": ~0.31.14-dev.14 + "@rocket.chat/css-supports": ~0.31.14-dev.18 tslib: ^2.3.1 peerDependencies: stylis: 4.0.10 - checksum: d4f3b928740cfd8c47f51f5546aade0568f3517b40d3ef6874321d342b39993bb2ae18de1462af71646ccb74799eb9c12df86c7645beb93cb582f9966e8fae92 + checksum: c6e177d3767584d717c90bec189b27beab74cc554e2dd8e3b116bcadd70370647ac80ae4d1af60156c7604becd6099b8f8603fa20b9212b8a2e8d6ad6f023bc5 languageName: node linkType: hard @@ -7805,7 +7805,7 @@ __metadata: human-interval: ~1.0.0 moment-timezone: ~0.5.27 mongodb: ~3.5.0 - checksum: f5f68008298f9482631f1f494e392cd6b8ba7971a3b0ece81ae2abe60f53d67973ff4476156fa5c9c41b8b58c4ccd284e95c545e0523996dfd05f9a80b843e07 + checksum: acb4ebb7e7356f6e53e810d821eb6aa3d88bbfb9e85183e707517bee6d1eea1f189f38bdf0dd2b91360492ab7643134d510c320d2523d86596498ab98e59735b languageName: node linkType: hard From 579bd812c871740b7a3347d9c7834f55b0e74a4e Mon Sep 17 00:00:00 2001 From: souzaramon Date: Fri, 24 Jun 2022 20:24:11 -0300 Subject: [PATCH 6/6] Chore: Fixes e2e playwright intermittences (#25984) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Co-authored-by: Weslley Campos <30299972+weslley543@users.noreply.github.com> --- .../components/Header/ToolBoxAction.tsx | 1 + .../views/room/Header/ToolBox/ToolBox.tsx | 1 + .../RoomMembers/List/RoomMembers.js | 2 +- apps/meteor/tests/e2e/00-wizard.spec.ts | 6 +- apps/meteor/tests/e2e/03-login.spec.ts | 4 +- .../tests/e2e/04-main-elements-render.spec.ts | 166 ++++--------- .../tests/e2e/05-channel-creation.spec.ts | 34 +-- apps/meteor/tests/e2e/06-messaging.spec.ts | 16 +- apps/meteor/tests/e2e/07-emoji.spec.ts | 4 +- apps/meteor/tests/e2e/08-resolutions.spec.ts | 4 +- apps/meteor/tests/e2e/09-channel.spec.ts | 79 +++--- .../tests/e2e/10-user-preferences.spec.ts | 5 +- apps/meteor/tests/e2e/11-admin.spec.ts | 2 +- apps/meteor/tests/e2e/12-settings.spec.ts | 4 +- apps/meteor/tests/e2e/13-permissions.spec.ts | 4 +- .../tests/e2e/14-setting-permissions.spec.ts | 17 +- .../meteor/tests/e2e/15-message-popup.spec.ts | 4 +- apps/meteor/tests/e2e/16-discussion.spec.ts | 12 +- .../tests/e2e/omnichannel-agents.spec.ts | 16 +- .../e2e/omnichannel-departaments.spec.ts | 12 +- apps/meteor/tests/e2e/pageobjects/Agents.ts | 6 +- .../tests/e2e/pageobjects/ChannelCreation.ts | 74 +----- .../tests/e2e/pageobjects/Departments.ts | 2 +- .../tests/e2e/pageobjects/Discussion.ts | 2 +- apps/meteor/tests/e2e/pageobjects/FlexTab.ts | 229 +++++------------- .../meteor/tests/e2e/pageobjects/LoginPage.ts | 6 +- apps/meteor/tests/e2e/pageobjects/SideNav.ts | 39 +-- 27 files changed, 250 insertions(+), 501 deletions(-) diff --git a/apps/meteor/client/components/Header/ToolBoxAction.tsx b/apps/meteor/client/components/Header/ToolBoxAction.tsx index 1154c878257d..7e06d38f90cb 100644 --- a/apps/meteor/client/components/Header/ToolBoxAction.tsx +++ b/apps/meteor/client/components/Header/ToolBoxAction.tsx @@ -3,6 +3,7 @@ import React, { FC } from 'react'; const ToolBoxAction: FC = ({ id, icon, color, title, action, className, index, ...props }) => ( { })} {actions.length > 6 && (

- + {isTeam ? t('Teams_members') : t('Members')} {onClickClose && } diff --git a/apps/meteor/tests/e2e/00-wizard.spec.ts b/apps/meteor/tests/e2e/00-wizard.spec.ts index ee404312d180..35ae763753cc 100644 --- a/apps/meteor/tests/e2e/00-wizard.spec.ts +++ b/apps/meteor/tests/e2e/00-wizard.spec.ts @@ -18,7 +18,7 @@ test.describe('[Wizard]', () => { test.beforeEach(async ({ baseURL }) => { const baseUrl = baseURL; await setupWizard.goto(baseUrl as string); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin, false); }); test('expect required field alert showed when user dont inform data', async () => { @@ -34,7 +34,7 @@ test.describe('[Wizard]', () => { test.describe('[Step 3]', async () => { test.beforeEach(async () => { await setupWizard.goto(''); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin, false); await setupWizard.stepTwoSuccess(); }); @@ -64,7 +64,7 @@ test.describe('[Wizard]', () => { test.describe('[Final Step]', async () => { test.beforeEach(async () => { await setupWizard.goto(''); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin, false); await setupWizard.stepTwoSuccess(); await setupWizard.stepThreeSuccess(); }); diff --git a/apps/meteor/tests/e2e/03-login.spec.ts b/apps/meteor/tests/e2e/03-login.spec.ts index 17b8a558e8ba..8dbb8293700e 100644 --- a/apps/meteor/tests/e2e/03-login.spec.ts +++ b/apps/meteor/tests/e2e/03-login.spec.ts @@ -20,12 +20,12 @@ test.describe('[Login]', () => { email: validUser.email, password: 'any_password1', }; - await loginPage.login(invalidUserPassword); + await loginPage.doLogin(invalidUserPassword, false); await expect(global.getToastBarError).toBeVisible(); }); test('expect user make login', async () => { - await loginPage.login(validUser); + await loginPage.doLogin(validUser); await loginPage.waitForSelector(HOME_SELECTOR); }); }); diff --git a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts index 50e3712e5ae6..577da9f0aa8f 100644 --- a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts +++ b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts @@ -1,25 +1,25 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, Page } from '@playwright/test'; import { LoginPage, FlexTab, SideNav, MainContent } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; test.describe('[Main Elements Render]', function () { + let page: Page; let loginPage: LoginPage; let mainContent: MainContent; let sideNav: SideNav; let flexTab: FlexTab; - test.beforeAll(async ({ browser, baseURL }) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const URL = baseURL; - loginPage = new LoginPage(page); - await loginPage.goto(URL as string); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - await loginPage.login(adminLogin); + loginPage = new LoginPage(page); sideNav = new SideNav(page); mainContent = new MainContent(page); flexTab = new FlexTab(page); + + await loginPage.goto('/'); + await loginPage.doLogin(adminLogin); }); test.describe('[Side Nav Bar]', () => { @@ -47,7 +47,7 @@ test.describe('[Main Elements Render]', function () { test('expect add text to the spotlight and show the channel list', async () => { await sideNav.spotlightSearch.type('rocket.cat'); await expect(sideNav.spotlightSearchPopUp).toBeVisible(); - await sideNav.page.locator('//*[@data-qa="sidebar-search-result"]//*[@data-index="0"]').click(); + await page.locator('//*[@data-qa="sidebar-search-result"]//*[@data-index="0"]').click(); }); }); }); @@ -90,7 +90,7 @@ test.describe('[Main Elements Render]', function () { test.describe('[Main Content]', () => { test.describe('[Render]', () => { test.beforeAll(async () => { - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); }); test('expect show the title of the channel', async () => { @@ -141,135 +141,71 @@ test.describe('[Main Elements Render]', function () { test.describe('[FlexTab]', () => { test.describe('[Render]', () => { test.beforeAll(async () => { - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); }); - test.describe('[Room tab info]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('info', true); - }); - - test.afterAll(async () => { - await flexTab.operateFlexTab('info', false); - }); - - test('expect show the room info button', async () => { - await expect(flexTab.channelTab).toBeVisible(); - }); - - test('expect show the room info tab content', async () => { - await expect(flexTab.channelSettings).toBeVisible(); - }); + test('expect to show tab info content', async () => { + await flexTab.btnTabInfo.click(); + await expect(flexTab.contentTabInfo).toBeVisible(); + await flexTab.btnTabInfo.click(); }); - test.describe('[Search Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('search', true); - }); - - test.afterAll(async () => { - await flexTab.operateFlexTab('search', false); - }); - - test('expect show the message search button', async () => { - await expect(flexTab.searchTab).toBeVisible(); - }); - - test('expect show the message tab content', async () => { - await expect(flexTab.searchTabContent).toBeVisible(); - }); + test('expect to show tab thread content', async () => { + await flexTab.btnTabSearch.click(); + await expect(flexTab.contentTabSearch).toBeVisible(); + await flexTab.btnTabSearch.click(); }); - test.describe('[Members Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('members', true); - }); - - test.afterAll(async () => { - await flexTab.operateFlexTab('members', false); - }); - - test('expect show the members tab button', () => { - expect(flexTab.membersTab.isVisible()).toBeTruthy(); - }); - - test('expect show the members content', async () => { - expect(flexTab.membersTabContent.isVisible()).toBeTruthy(); - }); + test('expect to show tab members content', async () => { + await flexTab.btnTabMembers.click(); + await expect(flexTab.contentTabMembers).toBeVisible(); + await flexTab.btnTabMembers.click(); }); - test.describe('[Notifications Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('notifications', true); - }); + test('expect to show tab notifications content', async () => { + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabNotifications.click(); - test.afterAll(async () => { - await flexTab.operateFlexTab('notifications', false); - }); + await expect(flexTab.contentTabNotifications).toBeVisible(); - test('expect not show the notifications button', async () => { - await expect(flexTab.notificationsTab).not.toBeVisible(); - }); - - test('expect show the notifications Tab content', async () => { - await expect(flexTab.notificationsSettings).toBeVisible(); - }); + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabNotifications.click(); }); - test.describe('[Files Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('files', true); - }); - - test.afterAll(async () => { - await flexTab.operateFlexTab('files', false); - }); - - test('expect show the files Tab content', async () => { - await expect(flexTab.filesTabContent).toBeVisible(); - }); + test('expect to show tab files content', async () => { + await flexTab.btnTabFiles.click(); + await expect(flexTab.contentTabFiles).toBeVisible(); + await flexTab.btnTabFiles.click(); }); - test.describe('[Mentions Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('mentions', true); - }); + test('expect to show tab mentions content', async () => { + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabMentions.click(); - test.afterAll(async () => { - await flexTab.operateFlexTab('mentions', false); - }); + await expect(flexTab.contentTabMentions).toBeVisible(); - test('expect show the mentions Tab content', async () => { - await expect(flexTab.mentionsTabContent).toBeVisible(); - }); + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabMentions.click(); }); - test.describe('[Starred Messages Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('starred', true); - }); + test('expect to show tab stared content', async () => { + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabStared.click(); - test.afterAll(async () => { - await flexTab.operateFlexTab('starred', false); - }); + await expect(flexTab.contentTabStared).toBeVisible(); - test('expect show the starred messages Tab content', async () => { - await expect(flexTab.starredTabContent).toBeVisible(); - }); + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabStared.click(); }); - test.describe('[Pinned Messages Tab]', () => { - test.beforeAll(async () => { - await flexTab.operateFlexTab('pinned', true); - }); + test('expect to show tab pinned content', async () => { + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabPinned.click(); - test.afterAll(async () => { - await flexTab.operateFlexTab('pinned', false); - }); + await expect(flexTab.contentTabPinned).toBeVisible(); - test('expect show the pinned messages Tab content', async () => { - await expect(flexTab.pinnedTabContent).toBeVisible(); - }); + await flexTab.doOpenMoreOptionMenu(); + await flexTab.btnTabPinned.click(); }); }); }); diff --git a/apps/meteor/tests/e2e/05-channel-creation.spec.ts b/apps/meteor/tests/e2e/05-channel-creation.spec.ts index 8a5ca373d87a..d7beb268b1c7 100644 --- a/apps/meteor/tests/e2e/05-channel-creation.spec.ts +++ b/apps/meteor/tests/e2e/05-channel-creation.spec.ts @@ -2,38 +2,26 @@ import { test } from '@playwright/test'; import { faker } from '@faker-js/faker'; import { LoginPage, ChannelCreation } from './pageobjects'; -import { validUserInserted, ROCKET_CAT } from './utils/mocks/userAndPasswordMock'; +import { adminLogin } from './utils/mocks/userAndPasswordMock'; test.describe('[Channel]', async () => { let channelCreation: ChannelCreation; let loginPage: LoginPage; - const HELLO = 'Hello'; - - test.beforeEach(async ({ page, baseURL }) => { - const baseUrl = baseURL as string; + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); loginPage = new LoginPage(page); - await loginPage.goto(baseUrl); - await loginPage.login(validUserInserted); - channelCreation = new ChannelCreation(page); - }); - test.describe('[Public and private channel creation]', () => { - let channelName: string; - test.beforeEach(async () => { - channelName = faker.animal.type(); - }); - - test('expect create privateChannel channel', async () => { - await channelCreation.createChannel(channelName, true); - }); + await loginPage.goto('/'); + await loginPage.doLogin(adminLogin); + }); - test('expect create public channel', async () => { - await channelCreation.createChannel(channelName, false); - }); + test('expect create private channel', async () => { + await channelCreation.doCreateChannel(faker.animal.type() + Date.now(), true); }); - test('expect send message to channel created', async () => { - await channelCreation.sendMessage(ROCKET_CAT, HELLO); + + test('expect create public channel', async () => { + await channelCreation.doCreateChannel(faker.animal.type() + Date.now()); }); }); diff --git a/apps/meteor/tests/e2e/06-messaging.spec.ts b/apps/meteor/tests/e2e/06-messaging.spec.ts index 694465574c6c..32d88125bb27 100644 --- a/apps/meteor/tests/e2e/06-messaging.spec.ts +++ b/apps/meteor/tests/e2e/06-messaging.spec.ts @@ -17,7 +17,7 @@ const createBrowserContextForChat = async ( const sideNav = new SideNav(page); await loginPage.goto(baseURL); - await loginPage.login(validUserInserted); + await loginPage.doLogin(validUserInserted); return { mainContent, sideNav }; }; @@ -38,7 +38,7 @@ test.describe('[Messaging]', () => { await loginPage.goto(baseURL as string); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); }); test.describe('[Normal messaging]', async () => { @@ -69,9 +69,9 @@ test.describe('[Messaging]', () => { test.describe('[Public channel]', async () => { test.beforeAll(async ({ browser, baseURL }) => { anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.findForChat('public channel'); + await anotherContext.sideNav.doOpenChat('public channel'); await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.findForChat('public channel'); + await sideNav.doOpenChat('public channel'); await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { @@ -89,9 +89,9 @@ test.describe('[Messaging]', () => { test.describe('[Private channel]', async () => { test.beforeAll(async ({ browser, baseURL }) => { anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.findForChat('private channel'); + await anotherContext.sideNav.doOpenChat('private channel'); await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.findForChat('private channel'); + await sideNav.doOpenChat('private channel'); await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { @@ -109,9 +109,9 @@ test.describe('[Messaging]', () => { test.describe('[Direct Message]', async () => { test.beforeAll(async ({ browser, baseURL }) => { anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.findForChat('rocketchat.internal.admin.test'); + await anotherContext.sideNav.doOpenChat('rocketchat.internal.admin.test'); await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.findForChat('user.name.test'); + await sideNav.doOpenChat('user.name.test'); await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { diff --git a/apps/meteor/tests/e2e/07-emoji.spec.ts b/apps/meteor/tests/e2e/07-emoji.spec.ts index 473b4c2f640e..61d20489ac30 100644 --- a/apps/meteor/tests/e2e/07-emoji.spec.ts +++ b/apps/meteor/tests/e2e/07-emoji.spec.ts @@ -15,11 +15,11 @@ test.describe('[Emoji]', () => { loginPage = new LoginPage(page); await loginPage.goto(URL); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); sideNav = new SideNav(page); mainContent = new MainContent(page); - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); }); test.describe('Render:', () => { diff --git a/apps/meteor/tests/e2e/08-resolutions.spec.ts b/apps/meteor/tests/e2e/08-resolutions.spec.ts index d7e5dd9648e1..18d4b45b0fed 100644 --- a/apps/meteor/tests/e2e/08-resolutions.spec.ts +++ b/apps/meteor/tests/e2e/08-resolutions.spec.ts @@ -19,7 +19,7 @@ async function initConfig( loginPage = new LoginPage(page); await loginPage.goto(URL); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); sideNav = new SideNav(page); mainContent = new MainContent(page); global = new Global(page); @@ -69,7 +69,7 @@ test.describe('[Resolution]', function () { }); test('expect close the sidenav when open general channel', async () => { - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); await expect(await sideNav.isSideBarOpen()).toBeFalsy; }); diff --git a/apps/meteor/tests/e2e/09-channel.spec.ts b/apps/meteor/tests/e2e/09-channel.spec.ts index cc5d989e98bf..342a9834ffc8 100644 --- a/apps/meteor/tests/e2e/09-channel.spec.ts +++ b/apps/meteor/tests/e2e/09-channel.spec.ts @@ -23,7 +23,7 @@ test.describe('[Channel]', () => { loginPage = new LoginPage(page); await loginPage.goto(URL); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); sideNav = new SideNav(page); mainContent = new MainContent(page); flexTab = new FlexTab(page); @@ -31,32 +31,22 @@ test.describe('[Channel]', () => { if (!publicChannelCreated) { await sideNav.createChannel(publicChannelName, false); - await setPublicChannelCreated(true); + setPublicChannelCreated(true); } - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); }); test.describe('[Search]', () => { test.describe('[SpotlightSearch]', async () => { test.describe('general:', () => { - test('expect search general', async () => { - await sideNav.spotlightSearchIcon.click(); - await sideNav.searchChannel('general'); - }); - test('expect go to general', async () => { - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); await expect(mainContent.channelTitle('general')).toContainText('general'); }); }); test.describe('user created channel:', () => { - test('expect search the user created channel', async () => { - await sideNav.spotlightSearchIcon.click(); - await sideNav.searchChannel(publicChannelName); - }); - test('expect go to the user created channel', async () => { - await sideNav.openChannel(publicChannelName); + await sideNav.doOpenChat(publicChannelName); await expect(mainContent.channelTitle(publicChannelName)).toContainText(publicChannelName); }); }); @@ -68,24 +58,14 @@ test.describe('[Channel]', () => { }); test.describe('general:', async () => { - test('expect show the general in the channel list', async () => { - await sideNav.getChannelFromList('general').scrollIntoViewIfNeeded(); - await expect(sideNav.getChannelFromList('general')).toBeVisible(); - }); - test('expect go to the general channel', async () => { - await sideNav.openChannel('general'); + await sideNav.doOpenChat('general'); }); }); test.describe('user created channel:', async () => { - test('expect show the user created channel in the channel list', async () => { - await sideNav.getChannelFromList(publicChannelName).scrollIntoViewIfNeeded(); - await expect(sideNav.getChannelFromList(publicChannelName)).toBeVisible(); - }); - test('expect go to the user created channel', async () => { - await sideNav.openChannel(publicChannelName); + await sideNav.doOpenChat(publicChannelName); }); }); }); @@ -93,7 +73,7 @@ test.describe('[Channel]', () => { test.describe('[Usage]', () => { test.beforeAll(async () => { - await sideNav.openChannel(publicChannelName); + await sideNav.doOpenChat(publicChannelName); }); test.describe('Adding a user to the room:', async () => { @@ -101,14 +81,14 @@ test.describe('[Channel]', () => { if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); }); test.afterAll(async () => { if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); }); test('expect add people to the room', async () => { @@ -121,7 +101,7 @@ test.describe('[Channel]', () => { test.describe('[Channel settings]:', async () => { test.describe('[Channel topic edit]', async () => { test.beforeAll(async () => { - await flexTab.operateFlexTab('info', true); + await flexTab.btnTabInfo.click(); await flexTab.editNameBtn.click(); }); @@ -149,7 +129,7 @@ test.describe('[Channel]', () => { test.describe('[Channel announcement edit]', async () => { test.beforeAll(async () => { - await flexTab.operateFlexTab('info', true); + await flexTab.btnTabInfo.click(); await flexTab.editNameBtn.click(); }); @@ -177,7 +157,7 @@ test.describe('[Channel]', () => { test.describe('[Channel description edit]', async () => { test.beforeAll(async () => { - await flexTab.operateFlexTab('info', true); + await flexTab.btnTabInfo.click(); await flexTab.editNameBtn.click(); }); @@ -209,18 +189,18 @@ test.describe('[Channel]', () => { test.describe('User muted', async () => { test.beforeAll(async () => { if (!hasUserAddedInChannel) { - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); await flexTab.addPeopleToChannel(targetUser); - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); } - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); }); test.afterAll(async () => { if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); }); test('expect mute rocket cat', async () => { @@ -231,18 +211,18 @@ test.describe('[Channel]', () => { test.describe('[Owner added]', async () => { test.beforeAll(async () => { if (!hasUserAddedInChannel) { - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); await flexTab.addPeopleToChannel(targetUser); - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); } - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); }); test.afterAll(async () => { if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); }); test('expect set rocket cat as owner', async () => { @@ -267,18 +247,18 @@ test.describe('[Channel]', () => { test.describe('[Moderator added]', async () => { test.beforeAll(async () => { if (!hasUserAddedInChannel) { - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); await flexTab.addPeopleToChannel(targetUser); - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); } - await flexTab.operateFlexTab('members', true); + await flexTab.btnTabMembers.click(); }); test.afterAll(async () => { if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - await flexTab.operateFlexTab('members', false); + await flexTab.btnTabMembers.click(); }); test('expect set rocket cat as moderator', async () => { @@ -295,7 +275,7 @@ test.describe('[Channel]', () => { if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - await flexTab.operateFlexTab('info', true); + await flexTab.btnTabInfo.click(); }); test.afterAll(async () => { @@ -304,7 +284,7 @@ test.describe('[Channel]', () => { } if (await flexTab.mainSideBar.isVisible()) { - await flexTab.operateFlexTab('info', false); + await flexTab.btnTabInfo.click(); } }); @@ -324,9 +304,8 @@ test.describe('[Channel]', () => { await flexTab.editNameSave.click(); }); - test('expect show the new name', async () => { - const channelName = sideNav.getChannelFromList(`NAME-EDITED-${publicChannelName}`); - await expect(channelName).toHaveText(`NAME-EDITED-${publicChannelName}`); + test('expect to find and open with new name', async () => { + await sideNav.doOpenChat(`NAME-EDITED-${publicChannelName}`); }); }); }); diff --git a/apps/meteor/tests/e2e/10-user-preferences.spec.ts b/apps/meteor/tests/e2e/10-user-preferences.spec.ts index 864cbcd6c238..aeea9b8d96c6 100644 --- a/apps/meteor/tests/e2e/10-user-preferences.spec.ts +++ b/apps/meteor/tests/e2e/10-user-preferences.spec.ts @@ -27,7 +27,7 @@ test.describe('[User Preferences]', () => { loginPage = new LoginPage(page); await loginPage.goto(URL); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); sideNav = new SideNav(page); mainContent = new MainContent(page); preferencesMainContent = new PreferencesMainContent(page); @@ -94,8 +94,7 @@ test.describe('[User Preferences]', () => { test('expect close the preferences menu', async () => { await sideNav.preferencesClose.click(); - await sideNav.getChannelFromList('general').scrollIntoViewIfNeeded(); - await sideNav.getChannelFromList('general').click(); + await sideNav.doOpenChat('general'); }); test('send message with different user name', async () => { diff --git a/apps/meteor/tests/e2e/11-admin.spec.ts b/apps/meteor/tests/e2e/11-admin.spec.ts index d422dfb180d4..f56e9b402e32 100644 --- a/apps/meteor/tests/e2e/11-admin.spec.ts +++ b/apps/meteor/tests/e2e/11-admin.spec.ts @@ -19,7 +19,7 @@ test.describe('[Administration]', () => { flexTab = new FlexTab(page); admin = new Administration(page); await loginPage.goto(baseURL as string); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); }); test.describe('[Admin View]', () => { test.beforeAll(async () => { diff --git a/apps/meteor/tests/e2e/12-settings.spec.ts b/apps/meteor/tests/e2e/12-settings.spec.ts index 4acbbcffcf0d..d5ad4940fb91 100644 --- a/apps/meteor/tests/e2e/12-settings.spec.ts +++ b/apps/meteor/tests/e2e/12-settings.spec.ts @@ -24,7 +24,7 @@ test.describe.skip('[Settings]', async () => { userPreferences = new PreferencesMainContent(page); await loginPage.goto('/'); - await loginPage.login(validUserInserted); + await loginPage.doLogin(validUserInserted); await sideNav.general.click(); }); @@ -407,7 +407,7 @@ test.describe.skip('[Settings (admin)]', async () => { admin = new Administration(page); await loginPage.goto('/'); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); await sideNav.general.click(); }); diff --git a/apps/meteor/tests/e2e/13-permissions.spec.ts b/apps/meteor/tests/e2e/13-permissions.spec.ts index 1e9a84499c81..c082a5c34cf3 100644 --- a/apps/meteor/tests/e2e/13-permissions.spec.ts +++ b/apps/meteor/tests/e2e/13-permissions.spec.ts @@ -27,7 +27,7 @@ test.describe('[Permissions]', () => { mainContent = new MainContent(page); await page.goto('/'); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); await sideNav.sidebarUserMenu.click(); await sideNav.admin.click(); await sideNav.users.click(); @@ -77,7 +77,7 @@ test.describe('[Permissions]', () => { test.beforeAll(async () => { await sideNav.doLogout(); await loginPage.goto('/'); - await loginPage.login(userToBeCreated); + await loginPage.doLogin(userToBeCreated); await sideNav.general.click(); }); diff --git a/apps/meteor/tests/e2e/14-setting-permissions.spec.ts b/apps/meteor/tests/e2e/14-setting-permissions.spec.ts index f4cd066ea0de..cfdc65f5cd0c 100644 --- a/apps/meteor/tests/e2e/14-setting-permissions.spec.ts +++ b/apps/meteor/tests/e2e/14-setting-permissions.spec.ts @@ -8,10 +8,12 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { let admin: Administration; let sideNav: SideNav; let loginPage: LoginPage; + const newHomeTitle = faker.animal.type(); + test.beforeAll(async ({ browser }) => { - const context = await browser.newContext(); - const page = await context.newPage(); + const page = await browser.newPage(); + sideNav = new SideNav(page); admin = new Administration(page); loginPage = new LoginPage(page); @@ -20,7 +22,7 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { test.describe('[Give User Permissions]', async () => { test.beforeAll(async () => { await loginPage.goto('/'); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); await sideNav.sidebarUserMenu.click(); await sideNav.admin.click(); await admin.permissionsLink.click(); @@ -35,6 +37,7 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { await admin.rolesSettingsFindInput.type('settings'); await admin.page.locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); const isOptionChecked = await admin.page.isChecked('table tbody tr:first-child td:nth-child(6) label input'); + if (!isOptionChecked) { await admin.page.click('table tbody tr:first-child td:nth-child(6) label'); } @@ -53,15 +56,17 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { }); }); - test.describe('Test new user setting permissions', async () => { + test.describe('[Test new user setting permissions]', async () => { test.beforeAll(async () => { await loginPage.goto('/'); - await loginPage.login(validUserInserted); + await loginPage.doLogin(validUserInserted); + await sideNav.sidebarUserMenu.click(); await sideNav.admin.click(); await admin.settingsLink.click(); await admin.layoutSettingsButton.click(); }); + test.afterAll(async () => { await loginPage.goto('/home'); await loginPage.logout(); @@ -76,7 +81,7 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { test.describe('[Verify settings change and cleanup]', async () => { test.beforeAll(async () => { await loginPage.goto('/'); - await loginPage.login(adminLogin); + await loginPage.doLogin(adminLogin); await sideNav.sidebarUserMenu.click(); await sideNav.admin.click(); await admin.settingsLink.click(); diff --git a/apps/meteor/tests/e2e/15-message-popup.spec.ts b/apps/meteor/tests/e2e/15-message-popup.spec.ts index b7a1e6b33593..7de60a43d274 100644 --- a/apps/meteor/tests/e2e/15-message-popup.spec.ts +++ b/apps/meteor/tests/e2e/15-message-popup.spec.ts @@ -19,8 +19,8 @@ test.describe('[Message Popup]', () => { sideNav = new SideNav(page); await loginPage.goto('/'); - await loginPage.login(adminLogin); - await sideNav.openChannel('public channel'); + await loginPage.doLogin(adminLogin); + await sideNav.doOpenChat('public channel'); }); test.describe('User mentions', () => { diff --git a/apps/meteor/tests/e2e/16-discussion.spec.ts b/apps/meteor/tests/e2e/16-discussion.spec.ts index 50721afe9159..5746c13f3e31 100644 --- a/apps/meteor/tests/e2e/16-discussion.spec.ts +++ b/apps/meteor/tests/e2e/16-discussion.spec.ts @@ -12,34 +12,32 @@ test.describe('[Discussion]', () => { let sideNav: SideNav; let mainContent: MainContent; - let discussionName: string; let message: string; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - await page.goto('/'); - await page.waitForLoadState('load'); loginPage = new LoginPage(page); discussion = new Discussion(page); sideNav = new SideNav(page); mainContent = new MainContent(page); - await loginPage.login(adminLogin); + await page.goto('/'); + await loginPage.doLogin(adminLogin); }); test.describe('[Create discussion from screen]', () => { test('expect discussion is created', async () => { - discussionName = faker.animal.type(); + const discussionName = faker.animal.type() + Date.now(); message = faker.animal.type(); await sideNav.newChannelBtnToolbar.click(); - await discussion.createDiscussion('public channel', discussionName, message); + await discussion.doCreateDiscussion('public channel', discussionName, message); }); }); test.describe.skip('[Create discussion from context menu]', () => { test.beforeAll(async () => { message = faker.animal.type() + uuid(); - await sideNav.findForChat('public channel'); + await sideNav.doOpenChat('public channel'); await mainContent.sendMessage(message); }); diff --git a/apps/meteor/tests/e2e/omnichannel-agents.spec.ts b/apps/meteor/tests/e2e/omnichannel-agents.spec.ts index 1765371b131b..c844d5a8b3d5 100644 --- a/apps/meteor/tests/e2e/omnichannel-agents.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-agents.spec.ts @@ -12,14 +12,13 @@ test.describe('[Agents]', () => { test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - const rootPath = '/'; - await page.goto(rootPath); loginPage = new LoginPage(page); sideNav = new SideNav(page); agents = new Agents(page); global = new Global(page); - await loginPage.login(adminLogin); + await page.goto('/'); + await loginPage.doLogin(adminLogin); await sideNav.sidebarUserMenu.click(); await sideNav.omnichannel.click(); await agents.agentsLink.click(); @@ -30,11 +29,13 @@ test.describe('[Agents]', () => { await expect(agents.agentAdded).toBeVisible(); await expect(agents.agentAdded).toHaveText('Rocket.Cat'); }); + test('expect open new agent info on tab', async () => { await agents.agentAdded.click(); await expect(agents.userInfoTab).toBeVisible(); await expect(agents.agentInfo).toBeVisible(); }); + test('expect close agent info on tab', async () => { await agents.btnClose.click(); await expect(agents.userInfoTab).not.toBeVisible(); @@ -46,6 +47,7 @@ test.describe('[Agents]', () => { test('expect show profile image', async () => { await expect(agents.userAvatar).toBeVisible(); }); + test('expect show action buttons', async () => { await expect(agents.btnClose).toBeVisible(); await expect(agents.btnEdit).toBeVisible(); @@ -56,11 +58,13 @@ test.describe('[Agents]', () => { await expect(agents.agentInfoUserInfoLabel).toBeVisible(); }); }); + test.describe('[Edit button]', async () => { test.describe('[Render]', async () => { test.beforeAll(async () => { await agents.btnEdit.click(); }); + test('expect show fields', async () => { await agents.getListOfExpectedInputs(); }); @@ -71,14 +75,17 @@ test.describe('[Agents]', () => { await agents.doChangeUserStatus('not-available'); await expect(agents.agentListStatus).toHaveText('Not Available'); }); + test.describe('[Modal Actions]', async () => { test.beforeEach(async () => { await agents.doRemoveAgent(); }); + test('expect modal is not visible after cancel delete agent', async () => { await global.btnModalCancel.click(); await expect(global.modal).not.toBeVisible(); }); + test('expect agent is removed from user info tab', async () => { await global.btnModalRemove.click(); await expect(global.modal).not.toBeVisible(); @@ -90,13 +97,16 @@ test.describe('[Agents]', () => { test.beforeAll(async () => { await agents.doAddAgent(); }); + test.beforeEach(async () => { await agents.btnTableRemove.click(); }); + test('expect modal is not visible after cancel delete agent', async () => { await global.btnModalCancel.click(); await expect(global.modal).not.toBeVisible(); }); + test('expect agent is removed from agents table', async () => { await global.btnModalRemove.click(); await expect(global.modal).not.toBeVisible(); diff --git a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts index ba74234189fc..f38afebcff7d 100644 --- a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts @@ -12,15 +12,13 @@ test.describe('[Department]', () => { test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - const basePath = '/'; - - await page.goto(basePath); loginPage = new LoginPage(page); sideNav = new SideNav(page); departments = new Departments(page); global = new Global(page); - await loginPage.login(adminLogin); + await page.goto('/'); + await loginPage.doLogin(adminLogin); await sideNav.sidebarUserMenu.click(); await sideNav.omnichannel.click(); }); @@ -30,14 +28,17 @@ test.describe('[Department]', () => { await departments.departmentsLink.click(); await departments.btnNewDepartment.click(); }); + test('expect show all inputs', async () => { await departments.getAddScreen(); }); }); + test.describe('[Actions]', async () => { test.beforeEach(async () => { await departments.departmentsLink.click(); }); + test.describe('[Create and Edit]', async () => { test.afterEach(async () => { await global.dismissToastBar(); @@ -55,15 +56,18 @@ test.describe('[Department]', () => { await expect(departments.departmentAdded).toHaveText('any_name_edit'); }); }); + test.describe('[Delete department]', () => { test.beforeEach(async () => { await departments.btnTableDeleteDepartment.click(); }); + test('expect dont show dialog on cancel delete department', async () => { await departments.btnModalCancelDeleteDepartment.click(); await expect(departments.modalDepartment).not.toBeVisible(); await expect(departments.departmentAdded).toBeVisible(); }); + test('expect delete departments', async () => { await departments.btnModalDeleteDepartment.click(); await expect(departments.modalDepartment).not.toBeVisible(); diff --git a/apps/meteor/tests/e2e/pageobjects/Agents.ts b/apps/meteor/tests/e2e/pageobjects/Agents.ts index 784f5c9cee7e..62980052e6f2 100644 --- a/apps/meteor/tests/e2e/pageobjects/Agents.ts +++ b/apps/meteor/tests/e2e/pageobjects/Agents.ts @@ -1,5 +1,6 @@ import { Locator, expect } from '@playwright/test'; +import { BACKSPACE } from '../utils/mocks/keyboardKeyMock'; import { BasePage } from './BasePage'; export class Agents extends BasePage { @@ -83,7 +84,10 @@ export class Agents extends BasePage { public async doAddAgent(): Promise { await this.textAgentsTitle.waitFor(); - await this.inputAgentsUserName.type('Rocket.Cat', { delay: 50 }); + await this.inputAgentsUserName.type('rocket.cat', { delay: 100 }); + // FIXME: temp solution for rocket.chat instability + await this.page.waitForTimeout(2000); + await this.keyboardPress(BACKSPACE); await this.userOption.click(); await this.btnAddAgents.click(); diff --git a/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts b/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts index b853bda1dd95..8a2d31bd5803 100644 --- a/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts +++ b/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts @@ -1,75 +1,17 @@ -import { Locator, expect } from '@playwright/test'; - import { BasePage } from './BasePage'; -import { ENTER } from '../utils/mocks/keyboardKeyMock'; export class ChannelCreation extends BasePage { - private get buttonCreate(): Locator { - return this.page.locator('[data-qa="sidebar-create"]'); - } - - private get inputChannelName(): Locator { - return this.page.locator('[placeholder="Channel Name"]'); - } - - private get inputChannelDescription(): Locator { - return this.page.locator('[placeholder="What is this channel about?"]'); - } - - private get buttonCreateChannel(): Locator { - return this.page.locator('//ul[@class="rc-popover__list"]//li[@class="rcx-option"][1]'); - } - - private get channelName(): Locator { - return this.page.locator('//header//div//div//div//div[2]'); - } - - private get buttonConfirmCreation(): Locator { - return this.page.locator('//button[contains(text(), "Create" )]'); - } - - private get privateChannel(): Locator { - return this.page.locator('//label[contains(text(),"Private")]/../following-sibling::label/i'); - } - - private get searchChannel(): Locator { - return this.page.locator('[data-qa="sidebar-search"]'); - } - - private get searchChannelInput(): Locator { - return this.page.locator('[data-qa="sidebar-search-input"]'); - } - - private get textArea(): Locator { - return this.page.locator('.rc-message-box__textarea'); - } + // TODO: replace old selectors with data-qa-id + async doCreateChannel(name: string, isPrivate = false): Promise { + await this.page.locator('[data-qa="sidebar-create"]').click(); + await this.page.locator('//ul[@class="rc-popover__list"]//li[@class="rcx-option"][1]').click(); + await this.page.locator('[placeholder="Channel Name"]').type(name); + await this.page.locator('[placeholder="What is this channel about?"]').type('any_description'); - private get lastMessage(): Locator { - return this.page.locator('.message:last-child .body'); - } - - public async createChannel(name: string, isPrivate: boolean): Promise { - await this.buttonCreate.click(); - await this.buttonCreateChannel.click(); - await this.inputChannelName.type(name); - await this.inputChannelDescription.type('any_description'); if (!isPrivate) { - await this.privateChannel.click(); + await this.page.locator('//label[contains(text(),"Private")]/../following-sibling::label/i').click(); } - await this.buttonConfirmCreation.click(); - - await expect(this.channelName).toHaveText(name); - } - - public async sendMessage(targetUser: string, message: string): Promise { - await this.searchChannel.click(); - await this.searchChannelInput.type(targetUser, { delay: 200 }); - await this.keyboardPress(ENTER); - - await this.textArea.type(message); - await this.keyboardPress(ENTER); - await expect(this.lastMessage).toBeVisible(); - await expect(this.lastMessage).toHaveText(message); + await this.page.locator('//button[contains(text(), "Create" )]').click(); } } diff --git a/apps/meteor/tests/e2e/pageobjects/Departments.ts b/apps/meteor/tests/e2e/pageobjects/Departments.ts index 210d3247b5b2..c3f15aa5e146 100644 --- a/apps/meteor/tests/e2e/pageobjects/Departments.ts +++ b/apps/meteor/tests/e2e/pageobjects/Departments.ts @@ -20,7 +20,6 @@ export class Departments extends BasePage { } get enabledToggle(): Locator { - // temporary selector return this.page.locator('[data-qa="DepartmentEditToggle-Enabled"] span label'); } @@ -107,6 +106,7 @@ export class Departments extends BasePage { await this.emailInput.type('any_email@mail.com'); await this.showOnRegistrationPage.click(); await this.selectLiveChatDepartmentOfflineMessageToChannel.click(); + await this.selectLiveChatDepartmentOfflineMessageToChannel.type('general'); await this.virtuosoOptions('general').click(); await this.selectAgentsTable.click(); await this.btnSaveDepartment.click(); diff --git a/apps/meteor/tests/e2e/pageobjects/Discussion.ts b/apps/meteor/tests/e2e/pageobjects/Discussion.ts index 859aea57c1e1..618bbdd42b1c 100644 --- a/apps/meteor/tests/e2e/pageobjects/Discussion.ts +++ b/apps/meteor/tests/e2e/pageobjects/Discussion.ts @@ -31,7 +31,7 @@ export class Discussion extends BasePage { return this.page.locator(`[data-qa="sidebar-item-title"] >> text='${discussionName}'`); } - async createDiscussion(channelName: string, discussionName: string, message: string): Promise { + async doCreateDiscussion(channelName: string, discussionName: string, message: string): Promise { await this.createDiscussionBtn.click(); await this.channelName.type(channelName); await this.page.keyboard.press('Enter'); diff --git a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts index 7471e99abfed..8440eac56bf4 100644 --- a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts +++ b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts @@ -1,16 +1,8 @@ -import { expect, Locator, Page } from '@playwright/test'; +import { Locator } from '@playwright/test'; import { BasePage } from './BasePage'; -import { Global } from './Global'; export class FlexTab extends BasePage { - private global: Global; - - constructor(page: Page) { - super(page); - this.global = new Global(page); - } - get mainSideBar(): Locator { return this.page.locator('//main//aside'); } @@ -23,24 +15,76 @@ export class FlexTab extends BasePage { return this.page.locator('//main//aside/h3//i[contains(@class, "rcx-icon--name-cross")]/..'); } - get headerMoreActions(): Locator { - return this.page.locator('//main/header//*[contains(@class, "rcx-icon--name-kebab")]/..'); - } - get messageInput(): Locator { return this.page.locator('.rcx-vertical-bar .js-input-message'); } - get channelTab(): Locator { - return this.page.locator('(//main//*[contains(@class, "rcx-icon--name-info-circled")])[1]/..'); + get btnTabInfo(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-info-circled]'); + } + + get btnTabSearch(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-magnifier]'); + } + + get btnTabMembers(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-members]'); + } + + get btnTabNotifications(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Notifications Preferences")]'); + } + + get btnTabFiles(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-clip]'); + } + + get btnTabMentions(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Mentions")]'); + } + + get btnTabStared(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Starred Messages")]'); + } + + get btnTabPinned(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Pinned Messages")]'); } - get channelSettings(): Locator { + get contentTabInfo(): Locator { return this.page.locator( '//aside/h3/div/i[contains(@class,"rcx-icon--name-info-circled") and contains(@class,"rcx-icon--name-info-circled")]', ); } + get contentTabMembers(): Locator { + return this.page.locator('[data-qa-id=RoomHeader-Members]'); + } + + get contentTabNotifications(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-bell'); + } + + get contentTabSearch(): Locator { + return this.page.locator('#message-search'); + } + + get contentTabFiles(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-attachment'); + } + + get contentTabMentions(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-at'); + } + + get contentTabStared(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-star'); + } + + get contentTabPinned(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-pin'); + } + get editNameBtn(): Locator { return this.page.locator('//aside//button[contains(text(), "Edit")]'); } @@ -69,14 +113,6 @@ export class FlexTab extends BasePage { return this.page.locator('//aside//button[contains(text(), "Save")]'); } - get membersTab(): Locator { - return this.page.locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-members'); - } - - get membersTabContent(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-members'); - } - get setOwnerBtn(): Locator { return this.page.locator('//main//aside//button[contains(text(), "Set as owner")]'); } @@ -89,46 +125,14 @@ export class FlexTab extends BasePage { return this.page.locator('[value="muteUser"]'); } - get avatarImage(): Locator { - return this.page.locator('(//aside[contains(@class, "rcx-vertical-bar")]//*[contains(@class, "avatar")])[1]'); - } - get memberRealName(): Locator { return this.page.locator('[data-qa="UserInfoUserName"]'); } - get searchTab(): Locator { - return this.page.locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-magnifier'); - } - - get searchTabContent(): Locator { - return this.page.locator('.rocket-search-result'); - } - - get messageSearchBar(): Locator { - return this.page.locator('#message-search'); - } - get searchResult(): Locator { return this.page.locator('.new-day'); } - get notificationsTab(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Notifications Preferences")]'); - } - - get notificationsSettings(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-bell'); - } - - get filesTab(): Locator { - return this.page.locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-clip'); - } - - get filesTabContent(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-attachment'); - } - get fileDownload(): Locator { return this.page.locator('.uploaded-files-list ul:first-child .file-download'); } @@ -137,30 +141,6 @@ export class FlexTab extends BasePage { return this.page.locator('.uploaded-files-list ul:first-child .room-file-item'); } - get mentionsTab(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Mentions")]'); - } - - get mentionsTabContent(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-at'); - } - - get starredTab(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Starred Messages")]'); - } - - get starredTabContent(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-star'); - } - - get pinnedTab(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Pinned Messages")]'); - } - - get pinnedTabContent(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-pin'); - } - get firstSetting(): Locator { return this.page.locator('//aside//i[contains(@class, "rcx-icon--name-hashtag")]/../div'); } @@ -289,7 +269,7 @@ export class FlexTab extends BasePage { await this.userMoreActions.click(); await this.muteUserBtn.waitFor(); await this.muteUserBtn.click(); - await this.global.confirmPopup(); + await this.page.locator('.rcx-modal .rcx-button--danger').click(); await this.mainSideBarBack.click(); } @@ -307,89 +287,6 @@ export class FlexTab extends BasePage { await this.addUserButtonAfterChoose.click(); } - public async operateFlexTab(desiredTab: string, desiredState: boolean): Promise { - // desiredState true=open false=closed - const locator: { [K: string]: Locator } = { - channelSettings: this.channelSettings, - messageSearchBar: this.messageSearchBar, - avatarImage: this.avatarImage, - notificationsSettings: this.notificationsSettings, - filesTabContent: this.filesTabContent, - mentionsTabContent: this.mentionsTabContent, - starredTabContent: this.starredTabContent, - pinnedTabContent: this.pinnedTabContent, - channelTab: this.channelTab, - searchTab: this.searchTab, - membersTab: this.membersTab, - notificationsTab: this.notificationsTab, - filesTab: this.filesTab, - mentionsTab: this.mentionsTab, - starredTab: this.starredTab, - pinnedTab: this.pinnedTab, - }; - - const operate = async (tab: string, panel: string, more: boolean): Promise => { - // this[panel].should(!desiredState ? 'be.visible' : 'not.exist'); - if (!desiredState) { - await expect(locator[panel]).toBeVisible(); - } else { - await expect(locator[panel]).not.toBeVisible(); - } - - if (more) { - await this.headerMoreActions.click(); - } - - await locator[tab].click(); - - if (desiredState) { - await expect(locator[panel]).toBeVisible(); - } else { - await expect(locator[panel]).not.toBeVisible(); - } - }; - - const tabs: { [K: string]: Function } = { - info: async (): Promise => { - await operate('channelTab', 'channelSettings', false); - }, - - search: async (): Promise => { - await operate('searchTab', 'messageSearchBar', false); - }, - - members: async (): Promise => { - await operate('membersTab', 'avatarImage', false); - }, - - notifications: async (): Promise => { - await operate('notificationsTab', 'notificationsSettings', true); - }, - - files: async (): Promise => { - await operate('filesTab', 'filesTabContent', false); - }, - - mentions: async (): Promise => { - await operate('mentionsTab', 'mentionsTabContent', true); - }, - - starred: async (): Promise => { - await operate('starredTab', 'starredTabContent', true); - }, - - pinned: async (): Promise => { - await operate('pinnedTab', 'pinnedTabContent', true); - }, - }; - - const callFunctionTabs = async (name: string): Promise => { - return tabs[name](); - }; - - await callFunctionTabs(desiredTab); - } - get flexTabViewThreadMessage(): Locator { return this.page.locator( 'div.thread-list.js-scroll-thread ul.thread [data-qa-type="message"]:last-child div.message-body-wrapper [data-qa-type="message-body"]', @@ -400,4 +297,8 @@ export class FlexTab extends BasePage { await this.usersAddUserRoleList.click(); await this.page.locator(`li[value=${role}]`).click(); } + + async doOpenMoreOptionMenu(): Promise { + await this.page.locator('[data-qa-id=ToolBox-Menu]').click({ force: true, clickCount: 3 }); + } } diff --git a/apps/meteor/tests/e2e/pageobjects/LoginPage.ts b/apps/meteor/tests/e2e/pageobjects/LoginPage.ts index 4c1ea2e92325..ec2d07e20f5d 100644 --- a/apps/meteor/tests/e2e/pageobjects/LoginPage.ts +++ b/apps/meteor/tests/e2e/pageobjects/LoginPage.ts @@ -81,10 +81,14 @@ export class LoginPage extends BasePage { await this.waitForSelector(HOME_SELECTOR); } - public async login({ email, password }: ILogin): Promise { + async doLogin({ email, password }: ILogin, shouldWaitForHomeScreen = true): Promise { await this.emailOrUsernameField.type(email); await this.passwordField.type(password); await this.submitButton.click(); + + if (shouldWaitForHomeScreen) { + await this.waitForSelector('.main-content'); + } } public async submit(): Promise { diff --git a/apps/meteor/tests/e2e/pageobjects/SideNav.ts b/apps/meteor/tests/e2e/pageobjects/SideNav.ts index cc80467929ab..37da198e9804 100644 --- a/apps/meteor/tests/e2e/pageobjects/SideNav.ts +++ b/apps/meteor/tests/e2e/pageobjects/SideNav.ts @@ -1,7 +1,6 @@ import { expect, Locator } from '@playwright/test'; import { BasePage } from './BasePage'; -import { ENTER } from '../utils/mocks/keyboardKeyMock'; export class SideNav extends BasePage { get channelType(): Locator { @@ -118,25 +117,6 @@ export class SideNav extends BasePage { return !!(await this.sideNavBar.getAttribute('style')); } - public async openChannel(channelName: string): Promise { - await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).scrollIntoViewIfNeeded(); - await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).click(); - await expect(this.page.locator('.rcx-room-header')).toContainText(channelName); - } - - public async searchChannel(channelName: string): Promise { - await expect(this.spotlightSearch).toBeVisible(); - - await this.spotlightSearch.click(); - - await expect(this.spotlightSearch).toBeFocused(); - await this.spotlightSearch.type(channelName); - - await expect(this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).first()).toContainText(channelName); - - await this.spotlightSearchPopUp.click(); - } - public getChannelFromList(channelName: any): Locator { return this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }); } @@ -149,9 +129,16 @@ export class SideNav extends BasePage { return this.page.locator('[data-qa="sidebar-search-input"]'); } + async doOpenChat(name: string): Promise { + await expect(this.page.locator('[data-qa="sidebar-search"]')).toBeVisible(); + + await this.page.locator('[data-qa="sidebar-search"]').click(); + await this.page.locator('[data-qa="sidebar-search-input"]').type(name); + await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: name }).first().click(); + } + public async createChannel(channelName: any, isPrivate: any /* isReadOnly*/): Promise { await this.newChannelBtnToolbar.click(); - await this.newChannelBtn.click(); if (!isPrivate) { @@ -159,17 +146,7 @@ export class SideNav extends BasePage { } await this.channelName.type(channelName); - - await expect(this.saveChannelBtn).toBeEnabled(); - await this.saveChannelBtn.click(); - await expect(this.channelType).not.toBeVisible(); - } - - public async findForChat(target: string): Promise { - await this.searchUser.click(); - await this.searchInput.type(target, { delay: 100 }); - await this.page.keyboard.press(ENTER); } public async doLogout(): Promise {