From 79cba772bd8ae0a1e084687b47e05f312e85078a Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Thu, 26 Dec 2024 09:32:57 -0300 Subject: [PATCH] feat: Return `departmentsAllowedToForward` property in `livechat/config` endpoint (#33060) --- .changeset/beige-kiwis-count.md | 7 ++ .../app/livechat/server/api/lib/livechat.ts | 26 +++---- .../server/models/raw/LivechatDepartment.ts | 16 ++--- .../server/models/raw/LivechatDepartment.ts | 16 ++--- apps/meteor/tests/data/livechat/department.ts | 6 ++ .../end-to-end/api/livechat/11-livechat.ts | 72 ++++++++++++++++++- .../livechat/src/definitions/departments.d.ts | 2 + .../src/models/ILivechatDepartmentModel.ts | 10 +-- 8 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 .changeset/beige-kiwis-count.md diff --git a/.changeset/beige-kiwis-count.md b/.changeset/beige-kiwis-count.md new file mode 100644 index 000000000000..a318de427a02 --- /dev/null +++ b/.changeset/beige-kiwis-count.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/livechat": minor +"@rocket.chat/model-typings": minor +--- + +Added `departmentsAllowedToForward` property to departments returned in the `livechat/config` endpoint diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index c3c22c327731..e747be2c90dd 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -29,23 +29,19 @@ async function findTriggers(): Promise[]> { +): Promise[]> { // TODO: check this function usage return ( - await ( - await LivechatDepartment.findEnabledWithAgentsAndBusinessUnit(businessUnit, { - _id: 1, - name: 1, - showOnRegistration: 1, - showOnOfflineForm: 1, - }) - ).toArray() - ).map(({ _id, name, showOnRegistration, showOnOfflineForm }) => ({ - _id, - name, - showOnRegistration, - showOnOfflineForm, - })); + await LivechatDepartment.findEnabledWithAgentsAndBusinessUnit< + Pick + >(businessUnit, { + _id: 1, + name: 1, + showOnRegistration: 1, + showOnOfflineForm: 1, + departmentsAllowedToForward: 1, + }) + ).toArray(); } export function findGuest(token: string): Promise { diff --git a/apps/meteor/ee/server/models/raw/LivechatDepartment.ts b/apps/meteor/ee/server/models/raw/LivechatDepartment.ts index b5cd4c9500f3..3586c4b836d1 100644 --- a/apps/meteor/ee/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/ee/server/models/raw/LivechatDepartment.ts @@ -28,10 +28,10 @@ declare module '@rocket.chat/model-typings' { ): Promise; unfilteredRemove(query: Filter): Promise; removeParentAndAncestorById(id: string): Promise; - findEnabledWithAgentsAndBusinessUnit( + findEnabledWithAgentsAndBusinessUnit( businessUnit: string, - projection: FindOptions['projection'], - ): Promise>; + projection: FindOptions['projection'], + ): Promise>; findByParentId(parentId: string, options?: FindOptions): FindCursor; findAgentsByBusinessHourId(businessHourId: string): AggregationCursor<{ agentIds: string[] }>; } @@ -74,19 +74,19 @@ export class LivechatDepartmentEE extends LivechatDepartmentRaw implements ILive return this.updateMany({ parentId: id }, { $unset: { parentId: 1 }, $pull: { ancestors: id } }); } - async findEnabledWithAgentsAndBusinessUnit( + async findEnabledWithAgentsAndBusinessUnit( businessUnit: string, - projection: FindOptions['projection'], - ): Promise> { + projection: FindOptions['projection'], + ): Promise> { if (!businessUnit) { - return super.findEnabledWithAgents(projection); + return super.findEnabledWithAgents(projection); } const unit = await LivechatUnit.findOneById(businessUnit, { projection: { _id: 1 } }); if (!unit) { throw new Meteor.Error('error-unit-not-found', `Error! No Active Business Unit found with id: ${businessUnit}`); } - return super.findActiveByUnitIds([businessUnit], { projection }); + return super.findActiveByUnitIds([businessUnit], { projection }); } findByParentId(parentId: string, options?: FindOptions): FindCursor { diff --git a/apps/meteor/server/models/raw/LivechatDepartment.ts b/apps/meteor/server/models/raw/LivechatDepartment.ts index 7e318ad33935..949807f7213e 100644 --- a/apps/meteor/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/server/models/raw/LivechatDepartment.ts @@ -289,23 +289,23 @@ export class LivechatDepartmentRaw extends BaseRaw implemen return this.updateMany({ _id: { $in: _ids } }, { $inc: { numAgents: -1 } }); } - findEnabledWithAgents(projection: FindOptions['projection'] = {}): FindCursor { + findEnabledWithAgents(projection: FindOptions['projection'] = {}): FindCursor { const query = { numAgents: { $gt: 0 }, enabled: true, }; - return this.find(query, projection && { projection }); + return this.find(query, projection && { projection }); } - async findEnabledWithAgentsAndBusinessUnit( + async findEnabledWithAgentsAndBusinessUnit( _: any, - projection: FindOptions['projection'] = {}, - ): Promise> { + projection: FindOptions['projection'] = {}, + ): Promise> { const query = { numAgents: { $gt: 0 }, enabled: true, }; - return this.find(query, projection && { projection }); + return this.find(query, projection && { projection }); } findOneByIdOrName(_idOrName: string, options: FindOptions = {}): Promise { @@ -338,7 +338,7 @@ export class LivechatDepartmentRaw extends BaseRaw implemen return this.countDocuments({ parentId: unitId }); } - findActiveByUnitIds(unitIds: string[], options: FindOptions = {}): FindCursor { + findActiveByUnitIds(unitIds: string[], options: FindOptions = {}): FindCursor { const query = { enabled: true, numAgents: { $gt: 0 }, @@ -348,7 +348,7 @@ export class LivechatDepartmentRaw extends BaseRaw implemen }, }; - return this.find(query, options); + return this.find(query, options); } findNotArchived(options: FindOptions = {}): FindCursor { diff --git a/apps/meteor/tests/data/livechat/department.ts b/apps/meteor/tests/data/livechat/department.ts index d7f22fca970b..5e3402df051d 100644 --- a/apps/meteor/tests/data/livechat/department.ts +++ b/apps/meteor/tests/data/livechat/department.ts @@ -46,6 +46,7 @@ export const createDepartmentWithMethod = ({ initialAgents = [], allowReceiveForwardOffline = false, fallbackForwardDepartment, + departmentsAllowedToForward, name, departmentUnit, userCredentials = credentials, @@ -54,6 +55,7 @@ export const createDepartmentWithMethod = ({ initialAgents?: { agentId: string; username: string }[]; allowReceiveForwardOffline?: boolean; fallbackForwardDepartment?: string; + departmentsAllowedToForward?: string[]; name?: string; departmentUnit?: { _id?: string }; userCredentials?: Credentials; @@ -77,6 +79,7 @@ export const createDepartmentWithMethod = ({ description: 'created from api', allowReceiveForwardOffline, fallbackForwardDepartment, + departmentsAllowedToForward, }, initialAgents, departmentUnit, @@ -149,9 +152,11 @@ export const addOrRemoveAgentFromDepartment = async ( export const createDepartmentWithAnOfflineAgent = async ({ allowReceiveForwardOffline = false, fallbackForwardDepartment, + departmentsAllowedToForward, }: { allowReceiveForwardOffline?: boolean; fallbackForwardDepartment?: string; + departmentsAllowedToForward?: string[]; }): Promise<{ department: ILivechatDepartment; agent: { @@ -164,6 +169,7 @@ export const createDepartmentWithAnOfflineAgent = async ({ const department = (await createDepartmentWithMethod({ allowReceiveForwardOffline, fallbackForwardDepartment, + departmentsAllowedToForward, })) as ILivechatDepartment; await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true); diff --git a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts index f57f7df9489c..d19b90a013e3 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts @@ -1,10 +1,17 @@ +import type { Credentials } from '@rocket.chat/api-client'; +import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { after, before, describe, it } from 'mocha'; import { sleep } from '../../../../lib/utils/sleep'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields'; -import { addOrRemoveAgentFromDepartment, createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department'; +import { + addOrRemoveAgentFromDepartment, + createDepartmentWithAnOfflineAgent, + createDepartmentWithAnOnlineAgent, + deleteDepartment, +} from '../../../data/livechat/department'; import { createVisitor, createLivechatRoom, @@ -12,9 +19,12 @@ import { closeOmnichannelRoom, sendMessage, deleteVisitor, + createDepartment, } from '../../../data/livechat/rooms'; import { createBotAgent, getRandomVisitorToken } from '../../../data/livechat/users'; +import type { WithRequiredProperty } from '../../../data/livechat/utils'; import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper'; +import { deleteUser } from '../../../data/users.helper'; import { IS_EE } from '../../../e2e/config/constants'; describe('LIVECHAT - Utils', () => { @@ -71,6 +81,38 @@ describe('LIVECHAT - Utils', () => { }); describe('livechat/config', () => { + let emptyDepartment: ILivechatDepartment; + let forwardDepartment: ILivechatDepartment; + let testDepartment: ILivechatDepartment; + let agent: { user: WithRequiredProperty; credentials: Credentials }; + let agent2: { user: WithRequiredProperty; credentials: Credentials }; + + before(async () => { + if (!IS_EE) { + return; + } + + emptyDepartment = await createDepartment(); + ({ department: forwardDepartment, agent } = await createDepartmentWithAnOnlineAgent()); + ({ department: testDepartment, agent: agent2 } = await createDepartmentWithAnOfflineAgent({ + departmentsAllowedToForward: [forwardDepartment._id], + })); + }); + + after(() => { + if (!IS_EE) { + return; + } + + return Promise.all([ + deleteDepartment(emptyDepartment._id), + deleteDepartment(forwardDepartment._id), + deleteDepartment(testDepartment._id), + deleteUser(agent.user), + deleteUser(agent2.user), + ]); + }); + it('should return enabled: false if livechat is disabled', async () => { await updateSetting('Livechat_enabled', false); const { body } = await request.get(api('livechat/config')).set(credentials); @@ -171,6 +213,34 @@ describe('LIVECHAT - Utils', () => { expect(body.config).to.have.property('room'); expect(body.config.room).to.have.property('_id', newRoom._id); }); + (IS_EE ? it : it.skip)('should return list of departments with at least one agent', async () => { + const { body } = await request.get(api('livechat/config')).set(credentials); + + expect(body).to.have.property('success', true); + expect(body).to.have.property('config'); + expect(body.config).to.have.property('departments'); + expect(body.config.departments).to.be.an('array').with.lengthOf.at.least(2); + + expect(body.config.departments).to.not.deep.include({ + _id: emptyDepartment._id, + name: emptyDepartment.name, + showOnRegistration: emptyDepartment.showOnRegistration, + showOnOfflineForm: emptyDepartment.showOnOfflineForm, + }); + expect(body.config.departments).to.deep.include({ + _id: forwardDepartment._id, + name: forwardDepartment.name, + showOnRegistration: forwardDepartment.showOnRegistration, + showOnOfflineForm: forwardDepartment.showOnOfflineForm, + }); + expect(body.config.departments).to.deep.include({ + _id: testDepartment._id, + name: testDepartment.name, + showOnRegistration: testDepartment.showOnRegistration, + showOnOfflineForm: testDepartment.showOnOfflineForm, + departmentsAllowedToForward: [forwardDepartment._id], + }); + }); }); describe('livechat/page.visited', () => { diff --git a/packages/livechat/src/definitions/departments.d.ts b/packages/livechat/src/definitions/departments.d.ts index 765cfb104631..0d4f604b56e5 100644 --- a/packages/livechat/src/definitions/departments.d.ts +++ b/packages/livechat/src/definitions/departments.d.ts @@ -3,5 +3,7 @@ export type Department = { _id: string; name: string; showOnRegistration?: boolean; + showOnOfflineForm?: boolean; + departmentsAllowedToForward?: string[]; [key: string]: unknown; }; diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index fe366256eff7..71c1f825cc25 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -52,11 +52,13 @@ export interface ILivechatDepartmentModel extends IBaseModel): Promise; updateNumAgentsById(_id: string, numAgents: number): Promise; decreaseNumberOfAgentsByIds(_ids: string[]): Promise; - findEnabledWithAgents(projection?: FindOptions['projection']): FindCursor; - findEnabledWithAgentsAndBusinessUnit( + findEnabledWithAgents( + projection?: FindOptions['projection'], + ): FindCursor; + findEnabledWithAgentsAndBusinessUnit( _: any, - projection: FindOptions['projection'], - ): Promise>; + projection: FindOptions['projection'], + ): Promise>; findOneByIdOrName(_idOrName: string, options?: FindOptions): Promise; findByUnitIds(unitIds: string[], options?: FindOptions): FindCursor; countDepartmentsInUnit(unitId: string): Promise;