From e500e55f1c5b42b6df4560d48708184ca901d3a7 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Thu, 22 Dec 2022 13:38:11 +0530 Subject: [PATCH 1/9] [FIX] Black screen when try to open a chat with a non-existent department --- .../chats/contextualBar/DepartmentField.js | 26 ----------------- .../chats/contextualBar/DepartmentField.tsx | 28 +++++++++++++++++++ .../directory/hooks/useDepartmentInfo.ts | 10 +++++++ 3 files changed, 38 insertions(+), 26 deletions(-) delete mode 100644 apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js create mode 100644 apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.tsx create mode 100644 apps/meteor/client/views/omnichannel/directory/hooks/useDepartmentInfo.ts diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js deleted file mode 100644 index 5e68b7abb586..000000000000 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js +++ /dev/null @@ -1,26 +0,0 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; - -import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; -import { useEndpointData } from '../../../../../hooks/useEndpointData'; -import Field from '../../../components/Field'; -import Info from '../../../components/Info'; -import Label from '../../../components/Label'; -import { FormSkeleton } from '../../Skeleton'; - -const DepartmentField = ({ departmentId }) => { - const t = useTranslation(); - const { value: data, phase: state } = useEndpointData(`/v1/livechat/department/${departmentId}`); - if (state === AsyncStatePhase.LOADING) { - return ; - } - const { department: { name } = {} } = data || { department: {} }; - return ( - - - {name || t('Department_not_found')} - - ); -}; - -export default DepartmentField; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.tsx b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.tsx new file mode 100644 index 000000000000..a12bba236965 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.tsx @@ -0,0 +1,28 @@ +import { Box, Skeleton } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import Field from '../../../components/Field'; +import Info from '../../../components/Info'; +import Label from '../../../components/Label'; +import { useDepartmentInfo } from '../../hooks/useDepartmentInfo'; + +type DepartmentFieldProps = { + departmentId: string; +}; + +const DepartmentField = ({ departmentId }: DepartmentFieldProps) => { + const t = useTranslation(); + const { data, isLoading, isError } = useDepartmentInfo(departmentId); + + return ( + + + {isLoading && } + {isError && {t('Something_went_wrong')}} + {!isLoading && !isError && {data?.department?.name || t('Department_not_found')}} + + ); +}; + +export default DepartmentField; diff --git a/apps/meteor/client/views/omnichannel/directory/hooks/useDepartmentInfo.ts b/apps/meteor/client/views/omnichannel/directory/hooks/useDepartmentInfo.ts new file mode 100644 index 000000000000..11167694757a --- /dev/null +++ b/apps/meteor/client/views/omnichannel/directory/hooks/useDepartmentInfo.ts @@ -0,0 +1,10 @@ +import type { OperationResult } from '@rocket.chat/rest-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; + +export const useDepartmentInfo = (departmentId: string): UseQueryResult> => { + const deptInfo = useEndpoint('GET', `/v1/livechat/department/${departmentId}`); + + return useQuery(['livechat/department', departmentId], () => deptInfo({})); +}; From ecff71a40572b51071a83e2e5254f6d52600bc97 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Tue, 27 Dec 2022 13:09:50 +0530 Subject: [PATCH 2/9] Cleanup department props from room once a department is removed --- .../imports/server/rest/departments.ts | 10 ++-- .../livechat/server/lib/DepartmentsHelper.ts | 49 +++++++++++++++++++ .../app/livechat/server/lib/Livechat.js | 24 --------- .../server/methods/removeDepartment.js | 7 ++- .../server/models/LivechatDepartment.js | 12 ----- .../server/models/LivechatDepartmentAgents.js | 4 -- .../rocketchat-i18n/i18n/en.i18n.json | 3 +- .../server/models/raw/LivechatDepartment.ts | 4 ++ .../models/raw/LivechatDepartmentAgents.ts | 6 ++- .../meteor/server/models/raw/LivechatRooms.js | 4 ++ .../models/ILivechatDepartmentAgentsModel.ts | 3 +- .../src/models/ILivechatDepartmentModel.ts | 2 + .../src/models/ILivechatRoomsModel.ts | 2 + 13 files changed, 80 insertions(+), 50 deletions(-) create mode 100644 apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index 3a76c7ce06fa..53c5d058381e 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -12,6 +12,7 @@ import { findDepartmentsBetweenIds, findDepartmentAgents, } from '../../../server/api/lib/departments'; +import { DepartmentHelper } from '../../../server/lib/DepartmentsHelper'; API.v1.addRoute( 'livechat/department', @@ -133,15 +134,14 @@ API.v1.addRoute( return API.v1.failure(); }, - delete() { + async delete() { check(this.urlParams, { _id: String, }); - if (Livechat.removeDepartment(this.urlParams._id)) { - return API.v1.success(); - } - return API.v1.failure(); + await DepartmentHelper.removeDepartment(this.urlParams._id); + + return API.v1.success(); }, }, ); diff --git a/apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts b/apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts new file mode 100644 index 000000000000..f050da8bb48f --- /dev/null +++ b/apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts @@ -0,0 +1,49 @@ +import { LivechatDepartment, LivechatDepartmentAgents, LivechatRooms } from '@rocket.chat/models'; + +import { callbacks } from '../../../../lib/callbacks'; +import { Logger } from '../../../logger/server'; + +class DepartmentHelperClass { + logger = new Logger('Omnichannel:DepartmentHelper'); + + async removeDepartment(departmentId: string) { + this.logger.debug(`Removing department: ${departmentId}`); + + const department = await LivechatDepartment.findOneById(departmentId, { projection: { _id: 1 } }); + if (!department) { + this.logger.debug(`Department not found: ${departmentId}`); + throw new Error('error-department-not-found'); + } + + const { _id } = department; + + const ret = await LivechatDepartment.removeById(_id); + if (ret.acknowledged !== true) { + this.logger.error(`Department record not removed: ${_id}. Result from db: ${ret}`); + throw new Error('error-failed-to-delete-department'); + } + this.logger.debug(`Department record removed: ${_id}`); + + const agentsIds = LivechatDepartmentAgents.findAgentsByDepartmentId(department._id).cursor.map((agent) => agent.agentId); + + this.logger.debug( + `Performing post-department-removal actions: ${_id}. Removing department agents, unsetting fallback department and removing department from rooms`, + ); + + await Promise.all([ + LivechatDepartmentAgents.removeByDepartmentId(_id), + LivechatDepartment.unsetFallbackDepartmentByDepartmentId(_id), + LivechatRooms.bulkRemoveDepartmentFromRooms(_id), + ]); + + this.logger.debug(`Post-department-removal actions completed: ${_id}. Notifying callbacks with department and agentsIds`); + + Meteor.defer(() => { + callbacks.run('livechat.afterRemoveDepartment', { department, agentsIds }); + }); + + return ret; + } +} + +export const DepartmentHelper = new DepartmentHelperClass(); diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 47ae2de29551..6d32b0c2e834 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -1129,30 +1129,6 @@ export const Livechat = { return true; }, - removeDepartment(_id) { - check(_id, String); - - const department = LivechatDepartment.findOneById(_id, { fields: { _id: 1 } }); - - if (!department) { - throw new Meteor.Error('department-not-found', 'Department not found', { - method: 'livechat:removeDepartment', - }); - } - const ret = LivechatDepartment.removeById(_id); - const agentsIds = LivechatDepartmentAgents.findByDepartmentId(_id) - .fetch() - .map((agent) => agent.agentId); - LivechatDepartmentAgents.removeByDepartmentId(_id); - LivechatDepartment.unsetFallbackDepartmentByDepartmentId(_id); - if (ret) { - Meteor.defer(() => { - callbacks.run('livechat.afterRemoveDepartment', { department, agentsIds }); - }); - } - return ret; - }, - showConnecting() { const { showConnecting } = RoutingManager.getConfig(); return showConnecting; diff --git a/apps/meteor/app/livechat/server/methods/removeDepartment.js b/apps/meteor/app/livechat/server/methods/removeDepartment.js index 226fb1153376..bf6a38f63059 100644 --- a/apps/meteor/app/livechat/server/methods/removeDepartment.js +++ b/apps/meteor/app/livechat/server/methods/removeDepartment.js @@ -1,19 +1,22 @@ import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; import { hasPermission } from '../../../authorization'; -import { Livechat } from '../lib/Livechat'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; +import { DepartmentHelper } from '../lib/DepartmentsHelper'; Meteor.methods({ 'livechat:removeDepartment'(_id) { methodDeprecationLogger.warn('livechat:removeDepartment will be deprecated in future versions of Rocket.Chat'); + check(_id, String); + if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'manage-livechat-departments')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeDepartment', }); } - return Livechat.removeDepartment(_id); + return DepartmentHelper.removeDepartment(_id); }, }); diff --git a/apps/meteor/app/models/server/models/LivechatDepartment.js b/apps/meteor/app/models/server/models/LivechatDepartment.js index 4f8a22fa04b0..4bed4d175dfd 100644 --- a/apps/meteor/app/models/server/models/LivechatDepartment.js +++ b/apps/meteor/app/models/server/models/LivechatDepartment.js @@ -152,18 +152,6 @@ export class LivechatDepartment extends Base { return this.find(query, options); } - - unsetFallbackDepartmentByDepartmentId(_id) { - return this.update( - { fallbackForwardDepartment: _id }, - { - $unset: { - fallbackForwardDepartment: 1, - }, - }, - { multi: true }, - ); - } } export default new LivechatDepartment(); diff --git a/apps/meteor/app/models/server/models/LivechatDepartmentAgents.js b/apps/meteor/app/models/server/models/LivechatDepartmentAgents.js index d3e9111b33f5..5ec1494a70fd 100644 --- a/apps/meteor/app/models/server/models/LivechatDepartmentAgents.js +++ b/apps/meteor/app/models/server/models/LivechatDepartmentAgents.js @@ -52,10 +52,6 @@ export class LivechatDepartmentAgents extends Base { this.remove({ departmentId, agentId }); } - removeByDepartmentId(departmentId) { - this.remove({ departmentId }); - } - getNextAgentForDepartment(departmentId, ignoreAgentId, extraQuery) { const agents = this.findByDepartmentId(departmentId).fetch(); diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 4fb24dac3515..8ecd2fe353d7 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1880,6 +1880,7 @@ "error-email-domain-blacklisted": "The email domain is blacklisted", "error-email-send-failed": "Error trying to send email: __message__", "error-essential-app-disabled": "Error: a Rocket.Chat App that is essential for this is disabled. Please contact your administrator", + "error-failed-to-delete-department": "Failed to delete department", "error-field-unavailable": "__field__ is already in use :(", "error-file-too-large": "File is too large", "error-forwarding-chat": "Something went wrong while forwarding the chat, Please try again later.", @@ -5550,4 +5551,4 @@ "Theme_dark": "Dark", "Join_your_team": "Join your team", "Create_an_account": "Create an account" -} \ No newline at end of file +} diff --git a/apps/meteor/server/models/raw/LivechatDepartment.ts b/apps/meteor/server/models/raw/LivechatDepartment.ts index ee03b2ab31a8..b4b1ede2e7c8 100644 --- a/apps/meteor/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/server/models/raw/LivechatDepartment.ts @@ -110,4 +110,8 @@ export class LivechatDepartmentRaw extends BaseRaw im return this.updateMany(query, update); } + + unsetFallbackDepartmentByDepartmentId(departmentId: string): Promise { + return this.updateMany({ fallbackDepartment: departmentId }, { $unset: { fallbackDepartment: 1 } }); + } } diff --git a/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts b/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts index a55dea5601e1..5e9a278b2db7 100644 --- a/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts +++ b/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts @@ -1,6 +1,6 @@ import type { ILivechatDepartmentAgents, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { FindPaginated, ILivechatDepartmentAgentsModel } from '@rocket.chat/model-typings'; -import type { Collection, FindCursor, Db, Filter, FindOptions } from 'mongodb'; +import type { Collection, FindCursor, Db, Filter, FindOptions, DeleteResult } from 'mongodb'; import { BaseRaw } from './BaseRaw'; @@ -105,4 +105,8 @@ export class LivechatDepartmentAgentsRaw extends BaseRaw { + return this.deleteOne({ departmentId }); + } } diff --git a/apps/meteor/server/models/raw/LivechatRooms.js b/apps/meteor/server/models/raw/LivechatRooms.js index 5e0f9f2af9f5..3f1791d3290b 100644 --- a/apps/meteor/server/models/raw/LivechatRooms.js +++ b/apps/meteor/server/models/raw/LivechatRooms.js @@ -1288,4 +1288,8 @@ export class LivechatRoomsRaw extends BaseRaw { }, ]); } + + bulkRemoveDepartmentFromRooms(departmentId) { + return this.updateMany({ departmentId }, { $unset: { departmentId: 1 } }); + } } diff --git a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts index 05963ea57396..95e4f51234f0 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts @@ -1,4 +1,4 @@ -import type { FindCursor, FindOptions } from 'mongodb'; +import type { DeleteResult, FindCursor, FindOptions } from 'mongodb'; import type { ILivechatDepartmentAgents } from '@rocket.chat/core-typings'; import type { FindPaginated, IBaseModel } from './IBaseModel'; @@ -58,4 +58,5 @@ export interface ILivechatDepartmentAgentsModel extends IBaseModel): FindCursor; findAgentsByAgentIdAndBusinessHourId(_agentId: string, _businessHourId: string): []; + removeByDepartmentId(departmentId: string): Promise; } diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index b75f93007982..41c97bd2ce7e 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -30,4 +30,6 @@ export interface ILivechatDepartmentModel extends IBaseModel; removeBusinessHourFromDepartmentsByBusinessHourId(businessHourId: string): Promise; + + unsetFallbackDepartmentByDepartmentId(departmentId: string): Promise; } diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index b3607e29b930..934e4e2df940 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -109,4 +109,6 @@ export interface ILivechatRoomsModel extends IBaseModel { setAutoTransferredAtById(roomId: string): Promise; findAvailableSources(): AggregationCursor; + + bulkRemoveDepartmentFromRooms(departmentId: string): Promise; } From eea19b76210b6eaf47e112461501d7d486f478d2 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Tue, 27 Dec 2022 15:25:41 +0530 Subject: [PATCH 3/9] Fix tests --- packages/model-typings/src/models/ILivechatRoomsModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index 934e4e2df940..a7b48c21f0eb 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -110,5 +110,5 @@ export interface ILivechatRoomsModel extends IBaseModel { findAvailableSources(): AggregationCursor; - bulkRemoveDepartmentFromRooms(departmentId: string): Promise; + bulkRemoveDepartmentFromRooms(departmentId: string): Promise; } From 192d425b3f45c1fbc6b60942a5f0850adb022bf0 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Tue, 27 Dec 2022 15:51:16 +0530 Subject: [PATCH 4/9] Add tests --- .../end-to-end/api/livechat/10-departments.ts | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts index df182eee81c7..511643c72203 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -5,7 +5,15 @@ import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; import { updatePermission, updateSetting } from '../../../data/permissions.helper'; -import { makeAgentAvailable, createAgent, createDepartment } from '../../../data/livechat/rooms'; +import { + makeAgentAvailable, + createAgent, + createDepartment, + createVisitor, + createLivechatRoom, + getLivechatRoomInfo, +} from '../../../data/livechat/rooms'; +import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department'; describe('LIVECHAT - Departments', function () { before((done) => getCredentials(done)); @@ -153,6 +161,61 @@ describe('LIVECHAT - Departments', function () { }); }); + describe('DELETE livechat/department/:_id', () => { + it('should return unauthorized error when the user does not have the necessary permission', async () => { + await updatePermission('manage-livechat-departments', []); + await updatePermission('remove-livechat-department', []); + + await request + .delete(api('livechat/department/testetetetstetete')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403); + }); + + it('should return an error when the department does not exist', async () => { + await updatePermission('manage-livechat-departments', ['admin']); + + const resp: Response = await request + .delete(api('livechat/department/testesteteste')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400); + + expect(resp.body).to.have.property('success', false); + expect(resp.body).to.have.property('error', 'error-department-not-found'); + }); + + it('it should remove the department', async () => { + const department = await createDepartment(); + + const resp: Response = await request + .delete(api(`livechat/department/${department._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(resp.body).to.have.property('success', true); + }); + + it('it should remove the department and disassociate the rooms from it', async () => { + const { department } = await createDepartmentWithAnOnlineAgent(); + const newVisitor = await createVisitor(department._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + const resp: Response = await request + .delete(api(`livechat/department/${department._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(resp.body).to.have.property('success', true); + + const latestRoom = await getLivechatRoomInfo(newRoom._id); + expect(latestRoom.departmentId).to.be.undefined; + }); + }); + describe('GET livechat/department.autocomplete', () => { it('should return an error when the user does not have the necessary permission', (done) => { updatePermission('view-livechat-departments', []) From 76ab64647eb70f3c6739ef0b1c7d1e7278f7ba5a Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Tue, 27 Dec 2022 19:52:08 +0530 Subject: [PATCH 5/9] Code review changes --- .../meteor/app/livechat/imports/server/rest/departments.ts | 2 +- .../server/lib/{DepartmentsHelper.ts => Departments.ts} | 7 ++++++- .../meteor/app/livechat/server/methods/removeDepartment.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) rename apps/meteor/app/livechat/server/lib/{DepartmentsHelper.ts => Departments.ts} (85%) diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index 53c5d058381e..cc32d2909f21 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -12,7 +12,7 @@ import { findDepartmentsBetweenIds, findDepartmentAgents, } from '../../../server/api/lib/departments'; -import { DepartmentHelper } from '../../../server/lib/DepartmentsHelper'; +import { DepartmentHelper } from '../../../server/lib/Departments'; API.v1.addRoute( 'livechat/department', diff --git a/apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts b/apps/meteor/app/livechat/server/lib/Departments.ts similarity index 85% rename from apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts rename to apps/meteor/app/livechat/server/lib/Departments.ts index f050da8bb48f..1e5cc1d2a8c3 100644 --- a/apps/meteor/app/livechat/server/lib/DepartmentsHelper.ts +++ b/apps/meteor/app/livechat/server/lib/Departments.ts @@ -30,11 +30,16 @@ class DepartmentHelperClass { `Performing post-department-removal actions: ${_id}. Removing department agents, unsetting fallback department and removing department from rooms`, ); - await Promise.all([ + const promiseResponses = await Promise.allSettled([ LivechatDepartmentAgents.removeByDepartmentId(_id), LivechatDepartment.unsetFallbackDepartmentByDepartmentId(_id), LivechatRooms.bulkRemoveDepartmentFromRooms(_id), ]); + promiseResponses.forEach((response, index) => { + if (response.status === 'rejected') { + this.logger.error(`Error while performing post-department-removal actions: ${_id}. Action No: ${index}. Error:`, response.reason); + } + }); this.logger.debug(`Post-department-removal actions completed: ${_id}. Notifying callbacks with department and agentsIds`); diff --git a/apps/meteor/app/livechat/server/methods/removeDepartment.js b/apps/meteor/app/livechat/server/methods/removeDepartment.js index bf6a38f63059..c4d64ee25c12 100644 --- a/apps/meteor/app/livechat/server/methods/removeDepartment.js +++ b/apps/meteor/app/livechat/server/methods/removeDepartment.js @@ -3,7 +3,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../../authorization'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { DepartmentHelper } from '../lib/DepartmentsHelper'; +import { DepartmentHelper } from '../lib/Departments'; Meteor.methods({ 'livechat:removeDepartment'(_id) { From 338b3cd4513ea194baf148f157c278a75af07346 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Mon, 2 Jan 2023 17:12:51 +0530 Subject: [PATCH 6/9] cleanup units associated to room after its linked department is removed --- .../app/livechat/server/lib/Departments.ts | 6 ++- .../server/hooks/afterRemoveDepartment.js | 16 -------- .../server/hooks/afterRemoveDepartment.ts | 40 +++++++++++++++++++ .../server/models/LivechatDepartment.js | 4 -- .../ee/server/models/LivechatDepartment.ts | 6 +++ .../server/models/raw/LivechatDepartment.ts | 15 +++++++ .../ee/server/models/raw/LivechatRooms.ts | 19 ++++++++- apps/meteor/ee/server/models/startup.ts | 1 + apps/meteor/lib/callbacks.ts | 2 +- .../server/models/raw/LivechatDepartment.ts | 4 ++ packages/core-typings/src/IRoom.ts | 2 + 11 files changed, 91 insertions(+), 24 deletions(-) delete mode 100644 apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js create mode 100644 apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts create mode 100644 apps/meteor/ee/server/models/LivechatDepartment.ts create mode 100644 apps/meteor/ee/server/models/raw/LivechatDepartment.ts diff --git a/apps/meteor/app/livechat/server/lib/Departments.ts b/apps/meteor/app/livechat/server/lib/Departments.ts index 1e5cc1d2a8c3..c69d96f0305a 100644 --- a/apps/meteor/app/livechat/server/lib/Departments.ts +++ b/apps/meteor/app/livechat/server/lib/Departments.ts @@ -9,7 +9,7 @@ class DepartmentHelperClass { async removeDepartment(departmentId: string) { this.logger.debug(`Removing department: ${departmentId}`); - const department = await LivechatDepartment.findOneById(departmentId, { projection: { _id: 1 } }); + const department = await LivechatDepartment.findOneById(departmentId); if (!department) { this.logger.debug(`Department not found: ${departmentId}`); throw new Error('error-department-not-found'); @@ -24,7 +24,9 @@ class DepartmentHelperClass { } this.logger.debug(`Department record removed: ${_id}`); - const agentsIds = LivechatDepartmentAgents.findAgentsByDepartmentId(department._id).cursor.map((agent) => agent.agentId); + const agentsIds: string[] = await LivechatDepartmentAgents.findAgentsByDepartmentId(department._id) + .cursor.map((agent) => agent.agentId) + .toArray(); this.logger.debug( `Performing post-department-removal actions: ${_id}. Removing department agents, unsetting fallback department and removing department from rooms`, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js deleted file mode 100644 index 8a17be5f27fd..000000000000 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js +++ /dev/null @@ -1,16 +0,0 @@ -import { callbacks } from '../../../../../lib/callbacks'; -import { LivechatDepartment } from '../../../../../app/models/server'; - -callbacks.add( - 'livechat.afterRemoveDepartment', - (options = {}) => { - const { department } = options; - if (!department) { - return options; - } - LivechatDepartment.removeDepartmentFromForwardListById(department._id); - return options; - }, - callbacks.priority.HIGH, - 'livechat-after-remove-department', -); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts new file mode 100644 index 000000000000..27e7617f6ec2 --- /dev/null +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts @@ -0,0 +1,40 @@ +import type { ILivechatAgent, ILivechatDepartmentRecord } from '@rocket.chat/core-typings'; +import { LivechatDepartment, LivechatRooms } from '@rocket.chat/models'; + +import { callbacks } from '../../../../../lib/callbacks'; +import { cbLogger } from '../lib/logger'; + +const afterRemoveDepartment = async (options: { department: ILivechatDepartmentRecord; agentsId: ILivechatAgent['_id'][] }) => { + cbLogger.debug( + `Performing post-department-removal actions in EE: ${options?.department?._id}. Removing department from forward list and removing unit association from rooms`, + ); + if (!options || !options.department) { + cbLogger.warn('No department found in options', options); + return options; + } + + const { department } = options; + + cbLogger.debug(`Removing department from forward list: ${department._id}`); + await LivechatDepartment.removeDepartmentFromForwardListById(department._id); + cbLogger.debug(`Removed department from forward list: ${department._id}`); + + const { parentId } = department; + if (parentId) { + cbLogger.debug(`Removing unit association from rooms: ${parentId}`); + // If the department has a parentId set (aka is a unit), then we need to dissociate all the rooms from this unit + await LivechatRooms.bulkRemoveUnitAssociationFromRooms(parentId); + cbLogger.debug(`Removed unit association from rooms: ${parentId}`); + } + + cbLogger.debug(`Post-department-removal actions completed in EE: ${department._id}`); + + return options; +}; + +callbacks.add( + 'livechat.afterRemoveDepartment', + (options) => Promise.await(afterRemoveDepartment(options)), + callbacks.priority.HIGH, + 'livechat-after-remove-department', +); diff --git a/apps/meteor/ee/app/models/server/models/LivechatDepartment.js b/apps/meteor/ee/app/models/server/models/LivechatDepartment.js index cfa218c9db51..163ae5e72d1d 100644 --- a/apps/meteor/ee/app/models/server/models/LivechatDepartment.js +++ b/apps/meteor/ee/app/models/server/models/LivechatDepartment.js @@ -55,8 +55,4 @@ overwriteClassOnLicense('livechat-enterprise', LivechatDepartment, { }, }); -LivechatDepartment.prototype.removeDepartmentFromForwardListById = function (_id) { - return this.update({ departmentsAllowedToForward: _id }, { $pull: { departmentsAllowedToForward: _id } }, { multi: true }); -}; - export default LivechatDepartment; diff --git a/apps/meteor/ee/server/models/LivechatDepartment.ts b/apps/meteor/ee/server/models/LivechatDepartment.ts new file mode 100644 index 000000000000..620983c7711d --- /dev/null +++ b/apps/meteor/ee/server/models/LivechatDepartment.ts @@ -0,0 +1,6 @@ +import { registerModel } from '@rocket.chat/models'; + +import { db } from '../../../server/database/utils'; +import { LivechatDepartmentEE } from './raw/LivechatDepartment'; + +registerModel('ILivechatDepartmentModel', new LivechatDepartmentEE(db)); diff --git a/apps/meteor/ee/server/models/raw/LivechatDepartment.ts b/apps/meteor/ee/server/models/raw/LivechatDepartment.ts new file mode 100644 index 000000000000..589f47216ccd --- /dev/null +++ b/apps/meteor/ee/server/models/raw/LivechatDepartment.ts @@ -0,0 +1,15 @@ +import type { ILivechatDepartmentModel } from '@rocket.chat/model-typings'; + +import { LivechatDepartmentRaw } from '../../../../server/models/raw/LivechatDepartment'; + +declare module '@rocket.chat/model-typings' { + export interface ILivechatDepartmentModel { + removeDepartmentFromForwardListById(departmentId: string): Promise; + } +} + +export class LivechatDepartmentEE extends LivechatDepartmentRaw implements ILivechatDepartmentModel { + async removeDepartmentFromForwardListById(departmentId: string): Promise { + await this.updateMany({ departmentsAllowedToForward: departmentId }, { $pull: { departmentsAllowedToForward: departmentId } }); + } +} diff --git a/apps/meteor/ee/server/models/raw/LivechatRooms.ts b/apps/meteor/ee/server/models/raw/LivechatRooms.ts index f0513cb51a01..e7e82bff73a0 100644 --- a/apps/meteor/ee/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/ee/server/models/raw/LivechatRooms.ts @@ -1,6 +1,6 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import type { ILivechatRoomsModel } from '@rocket.chat/model-typings'; -import type { FindCursor, UpdateResult } from 'mongodb'; +import type { FindCursor, UpdateResult, Document } from 'mongodb'; import { LivechatRoomsRaw } from '../../../../server/models/raw/LivechatRooms'; import { queriesLogger } from '../../../app/livechat-enterprise/server/lib/logger'; @@ -20,10 +20,27 @@ declare module '@rocket.chat/model-typings' { unsetOnHoldAndPredictedVisitorAbandonmentByRoomId(roomId: string): Promise; unsetPriorityByIdFromAllOpenRooms(priorityId: string): Promise; findOpenRoomsByPriorityId(priorityId: string): FindCursor; + bulkRemoveUnitAssociationFromRooms(unit: string): Promise; } } export class LivechatRoomsRawEE extends LivechatRoomsRaw implements ILivechatRoomsModel { + async bulkRemoveUnitAssociationFromRooms(unit: string): Promise { + const query = { + departmentAncestors: unit, + }; + + const update = { + $pull: { + departmentAncestors: unit, + }, + }; + + queriesLogger.debug('bulkRemoveUnitAssociationFromRooms', { query, update }); + + return this.updateMany(query, update); + } + async unsetAllPredictedVisitorAbandonment(): Promise { return this.updateMany( { diff --git a/apps/meteor/ee/server/models/startup.ts b/apps/meteor/ee/server/models/startup.ts index 9645c9cd0501..7b803b4c9521 100644 --- a/apps/meteor/ee/server/models/startup.ts +++ b/apps/meteor/ee/server/models/startup.ts @@ -7,4 +7,5 @@ onLicense('livechat-enterprise', () => { import('./LivechatUnit'); import('./LivechatUnitMonitors'); import('./LivechatRooms'); + import('./LivechatDepartment'); }); diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index f381c9b9fc39..7a295ed7be56 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -107,7 +107,7 @@ type ChainedCallbackSignatures = { oldDepartmentId: ILivechatDepartmentRecord['_id']; }; 'livechat.afterInquiryQueued': (inquiry: ILivechatInquiryRecord) => ILivechatInquiryRecord; - 'livechat.afterRemoveDepartment': (params: { departmentId: ILivechatDepartmentRecord['_id']; agentsId: ILivechatAgent['_id'][] }) => { + 'livechat.afterRemoveDepartment': (params: { department: ILivechatDepartmentRecord; agentsId: ILivechatAgent['_id'][] }) => { departmentId: ILivechatDepartmentRecord['_id']; agentsId: ILivechatAgent['_id'][]; }; diff --git a/apps/meteor/server/models/raw/LivechatDepartment.ts b/apps/meteor/server/models/raw/LivechatDepartment.ts index b4b1ede2e7c8..6338e98b1222 100644 --- a/apps/meteor/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/server/models/raw/LivechatDepartment.ts @@ -114,4 +114,8 @@ export class LivechatDepartmentRaw extends BaseRaw im unsetFallbackDepartmentByDepartmentId(departmentId: string): Promise { return this.updateMany({ fallbackDepartment: departmentId }, { $unset: { fallbackDepartment: 1 } }); } + + removeDepartmentFromForwardListById(_departmentId: string): Promise { + throw new Error('Method not implemented in Community Edition.'); + } } diff --git a/packages/core-typings/src/IRoom.ts b/packages/core-typings/src/IRoom.ts index b44fd8b8b5be..3250254a0946 100644 --- a/packages/core-typings/src/IRoom.ts +++ b/packages/core-typings/src/IRoom.ts @@ -188,6 +188,8 @@ export interface IOmnichannelGenericRoom extends Omit Date: Mon, 2 Jan 2023 17:28:06 +0530 Subject: [PATCH 7/9] Add tests for the case in previous commit --- .../end-to-end/api/livechat/10-departments.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts index 511643c72203..8374887be6b9 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -14,6 +14,9 @@ import { getLivechatRoomInfo, } from '../../../data/livechat/rooms'; import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department'; +import { IS_EE } from '../../../e2e/config/constants'; +import { createUser } from '../../../data/users.helper'; +import { createMonitor, createUnit } from '../../../data/livechat/units'; describe('LIVECHAT - Departments', function () { before((done) => getCredentials(done)); @@ -214,6 +217,33 @@ describe('LIVECHAT - Departments', function () { const latestRoom = await getLivechatRoomInfo(newRoom._id); expect(latestRoom.departmentId).to.be.undefined; }); + + (IS_EE ? it : it.skip)('it should remove the department and disassociate the rooms from it which have its units', async () => { + const { department } = await createDepartmentWithAnOnlineAgent(); + const newVisitor = await createVisitor(department._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + const monitor = await createUser(); + await createMonitor(monitor.username); + const unit = await createUnit(monitor._id, monitor.username, department._id); + + // except the room to have the unit + let latestRoom = await getLivechatRoomInfo(newRoom._id); + expect(latestRoom.departmentId).to.be.equal(department._id); + expect(latestRoom.departmentAncestors).to.be.an('array').that.includes(unit._id); + + const resp: Response = await request + .delete(api(`livechat/department/${department._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(resp.body).to.have.property('success', true); + + latestRoom = await getLivechatRoomInfo(newRoom._id); + expect(latestRoom.departmentId).to.be.undefined; + expect(latestRoom.departmentAncestors).to.be.an('array').that.does.not.include(unit._id); + }); }); describe('GET livechat/department.autocomplete', () => { From cceeb56771be1d04f97dfa634e6bfaab53881377 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Tue, 3 Jan 2023 19:54:01 +0530 Subject: [PATCH 8/9] Fix incorrect units getting dis-associated from rooms --- .../app/livechat/server/lib/Departments.ts | 2 +- .../server/hooks/afterRemoveDepartment.ts | 14 ++------------ .../ee/server/models/raw/LivechatRooms.ts | 19 +------------------ .../meteor/server/models/raw/LivechatRooms.js | 4 ++-- .../src/models/ILivechatRoomsModel.ts | 2 +- 5 files changed, 7 insertions(+), 34 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Departments.ts b/apps/meteor/app/livechat/server/lib/Departments.ts index c69d96f0305a..ec9e66093470 100644 --- a/apps/meteor/app/livechat/server/lib/Departments.ts +++ b/apps/meteor/app/livechat/server/lib/Departments.ts @@ -35,7 +35,7 @@ class DepartmentHelperClass { const promiseResponses = await Promise.allSettled([ LivechatDepartmentAgents.removeByDepartmentId(_id), LivechatDepartment.unsetFallbackDepartmentByDepartmentId(_id), - LivechatRooms.bulkRemoveDepartmentFromRooms(_id), + LivechatRooms.bulkRemoveDepartmentAndUnitsFromRooms(_id), ]); promiseResponses.forEach((response, index) => { if (response.status === 'rejected') { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts index 27e7617f6ec2..6f4adbf557d6 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.ts @@ -1,13 +1,11 @@ import type { ILivechatAgent, ILivechatDepartmentRecord } from '@rocket.chat/core-typings'; -import { LivechatDepartment, LivechatRooms } from '@rocket.chat/models'; +import { LivechatDepartment } from '@rocket.chat/models'; import { callbacks } from '../../../../../lib/callbacks'; import { cbLogger } from '../lib/logger'; const afterRemoveDepartment = async (options: { department: ILivechatDepartmentRecord; agentsId: ILivechatAgent['_id'][] }) => { - cbLogger.debug( - `Performing post-department-removal actions in EE: ${options?.department?._id}. Removing department from forward list and removing unit association from rooms`, - ); + cbLogger.debug(`Performing post-department-removal actions in EE: ${options?.department?._id}. Removing department from forward list`); if (!options || !options.department) { cbLogger.warn('No department found in options', options); return options; @@ -19,14 +17,6 @@ const afterRemoveDepartment = async (options: { department: ILivechatDepartmentR await LivechatDepartment.removeDepartmentFromForwardListById(department._id); cbLogger.debug(`Removed department from forward list: ${department._id}`); - const { parentId } = department; - if (parentId) { - cbLogger.debug(`Removing unit association from rooms: ${parentId}`); - // If the department has a parentId set (aka is a unit), then we need to dissociate all the rooms from this unit - await LivechatRooms.bulkRemoveUnitAssociationFromRooms(parentId); - cbLogger.debug(`Removed unit association from rooms: ${parentId}`); - } - cbLogger.debug(`Post-department-removal actions completed in EE: ${department._id}`); return options; diff --git a/apps/meteor/ee/server/models/raw/LivechatRooms.ts b/apps/meteor/ee/server/models/raw/LivechatRooms.ts index e7e82bff73a0..f0513cb51a01 100644 --- a/apps/meteor/ee/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/ee/server/models/raw/LivechatRooms.ts @@ -1,6 +1,6 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import type { ILivechatRoomsModel } from '@rocket.chat/model-typings'; -import type { FindCursor, UpdateResult, Document } from 'mongodb'; +import type { FindCursor, UpdateResult } from 'mongodb'; import { LivechatRoomsRaw } from '../../../../server/models/raw/LivechatRooms'; import { queriesLogger } from '../../../app/livechat-enterprise/server/lib/logger'; @@ -20,27 +20,10 @@ declare module '@rocket.chat/model-typings' { unsetOnHoldAndPredictedVisitorAbandonmentByRoomId(roomId: string): Promise; unsetPriorityByIdFromAllOpenRooms(priorityId: string): Promise; findOpenRoomsByPriorityId(priorityId: string): FindCursor; - bulkRemoveUnitAssociationFromRooms(unit: string): Promise; } } export class LivechatRoomsRawEE extends LivechatRoomsRaw implements ILivechatRoomsModel { - async bulkRemoveUnitAssociationFromRooms(unit: string): Promise { - const query = { - departmentAncestors: unit, - }; - - const update = { - $pull: { - departmentAncestors: unit, - }, - }; - - queriesLogger.debug('bulkRemoveUnitAssociationFromRooms', { query, update }); - - return this.updateMany(query, update); - } - async unsetAllPredictedVisitorAbandonment(): Promise { return this.updateMany( { diff --git a/apps/meteor/server/models/raw/LivechatRooms.js b/apps/meteor/server/models/raw/LivechatRooms.js index 3f1791d3290b..542f05ddbb02 100644 --- a/apps/meteor/server/models/raw/LivechatRooms.js +++ b/apps/meteor/server/models/raw/LivechatRooms.js @@ -1289,7 +1289,7 @@ export class LivechatRoomsRaw extends BaseRaw { ]); } - bulkRemoveDepartmentFromRooms(departmentId) { - return this.updateMany({ departmentId }, { $unset: { departmentId: 1 } }); + bulkRemoveDepartmentAndUnitsFromRooms(departmentId) { + return this.updateMany({ departmentId }, { $unset: { departmentId: 1, departmentAncestors: 1 } }); } } diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index a7b48c21f0eb..4e7f36b30044 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -110,5 +110,5 @@ export interface ILivechatRoomsModel extends IBaseModel { findAvailableSources(): AggregationCursor; - bulkRemoveDepartmentFromRooms(departmentId: string): Promise; + bulkRemoveDepartmentAndUnitsFromRooms(departmentId: string): Promise; } From c0e0467607cae832a7c105dc4b7ad52d15f7e95f Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Tue, 3 Jan 2023 20:07:20 +0530 Subject: [PATCH 9/9] Add tests for the edge case in previous commit --- apps/meteor/tests/data/livechat/units.ts | 4 +- .../end-to-end/api/livechat/10-departments.ts | 45 ++++++++++++++++++- .../tests/end-to-end/api/livechat/14-units.ts | 14 +++--- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/apps/meteor/tests/data/livechat/units.ts b/apps/meteor/tests/data/livechat/units.ts index 07f11cfc3ca2..3b5a94c0132b 100644 --- a/apps/meteor/tests/data/livechat/units.ts +++ b/apps/meteor/tests/data/livechat/units.ts @@ -25,7 +25,7 @@ export const createMonitor = async (username: string): Promise<{ _id: string; us }); }; -export const createUnit = async (monitorId: string, username: string, departmentId: string): Promise => { +export const createUnit = async (monitorId: string, username: string, departmentIds: string[]): Promise => { return new Promise((resolve, reject) => { request .post(methodCall(`livechat:saveUnit`)) @@ -33,7 +33,7 @@ export const createUnit = async (monitorId: string, username: string, department .send({ message: JSON.stringify({ method: 'livechat:saveUnit', - params: [null, { name: faker.name.firstName(), visibility: faker.helpers.arrayElement(['public', 'private']) }, [{ monitorId, username }], [{ departmentId }]], + params: [null, { name: faker.name.firstName(), visibility: faker.helpers.arrayElement(['public', 'private']) }, [{ monitorId, username }], departmentIds.map((departmentId) => ({ departmentId }))], id: '101', msg: 'method', }), diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts index 8374887be6b9..88e5e6167a5d 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -225,7 +225,7 @@ describe('LIVECHAT - Departments', function () { const monitor = await createUser(); await createMonitor(monitor.username); - const unit = await createUnit(monitor._id, monitor.username, department._id); + const unit = await createUnit(monitor._id, monitor.username, [department._id]); // except the room to have the unit let latestRoom = await getLivechatRoomInfo(newRoom._id); @@ -242,8 +242,49 @@ describe('LIVECHAT - Departments', function () { latestRoom = await getLivechatRoomInfo(newRoom._id); expect(latestRoom.departmentId).to.be.undefined; - expect(latestRoom.departmentAncestors).to.be.an('array').that.does.not.include(unit._id); + expect(latestRoom.departmentAncestors).to.be.undefined; }); + + (IS_EE ? it : it.skip)( + 'contd from above test case: if a unit has more than 1 dept, then it should not disassociate rooms from other dept when any one dept is removed', + async () => { + const { department: department1 } = await createDepartmentWithAnOnlineAgent(); + const newVisitor1 = await createVisitor(department1._id); + const newRoom1 = await createLivechatRoom(newVisitor1.token); + + const { department: department2 } = await createDepartmentWithAnOnlineAgent(); + const newVisitor2 = await createVisitor(department2._id); + const newRoom2 = await createLivechatRoom(newVisitor2.token); + + const monitor = await createUser(); + await createMonitor(monitor.username); + const unit = await createUnit(monitor._id, monitor.username, [department1._id, department2._id]); + + // except the room to have the unit + let latestRoom1 = await getLivechatRoomInfo(newRoom1._id); + let latestRoom2 = await getLivechatRoomInfo(newRoom2._id); + expect(latestRoom1.departmentId).to.be.equal(department1._id); + expect(latestRoom1.departmentAncestors).to.be.an('array').that.includes(unit._id); + expect(latestRoom2.departmentId).to.be.equal(department2._id); + expect(latestRoom2.departmentAncestors).to.be.an('array').that.includes(unit._id); + + const resp: Response = await request + .delete(api(`livechat/department/${department1._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(resp.body).to.have.property('success', true); + + latestRoom1 = await getLivechatRoomInfo(newRoom1._id); + expect(latestRoom1.departmentId).to.be.undefined; + expect(latestRoom1.departmentAncestors).to.be.undefined; + + latestRoom2 = await getLivechatRoomInfo(newRoom2._id); + expect(latestRoom2.departmentId).to.be.equal(department2._id); + expect(latestRoom2.departmentAncestors).to.be.an('array').that.includes(unit._id); + }, + ); }); describe('GET livechat/department.autocomplete', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/14-units.ts b/apps/meteor/tests/end-to-end/api/livechat/14-units.ts index 76bbe33001c2..ef624febce29 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/14-units.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/14-units.ts @@ -40,7 +40,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request.get(api('livechat/units')).set(credentials).expect(200); expect(body.units).to.be.an('array').with.lengthOf.greaterThan(0); @@ -104,7 +104,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request .get(api(`livechat/units/${unit._id}`)) @@ -128,7 +128,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request .post(api(`livechat/units/${unit._id}`)) @@ -159,7 +159,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request .delete(api(`livechat/units/${unit._id}`)) @@ -180,7 +180,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request .get(api(`livechat/units/${unit._id}/departments`)) @@ -204,7 +204,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request .get(api(`livechat/units/${unit._id}/departments/available`)) @@ -229,7 +229,7 @@ import { IS_EE } from '../../../e2e/config/constants'; const user = await createUser(); await createMonitor(user.username); const department = await createDepartment(); - const unit = await createUnit(user._id, user.username, department._id); + const unit = await createUnit(user._id, user.username, [department._id]); const { body } = await request .get(api(`livechat/units/${unit._id}/monitors`))