From 7e2facc979488b059dd4ecf49fe53c48d3387141 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 20 Aug 2024 01:33:07 +0530 Subject: [PATCH] fix: customFields ignored in livechat room creation (#33047) --- .changeset/twelve-windows-train.md | 5 ++ .../server/hooks/beforeNewRoom.ts | 9 ++-- apps/meteor/lib/utils/isPlainObject.ts | 3 ++ .../livechat/hooks/beforeNewRoom.spec.ts | 52 +++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 .changeset/twelve-windows-train.md create mode 100644 apps/meteor/lib/utils/isPlainObject.ts create mode 100644 apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts diff --git a/.changeset/twelve-windows-train.md b/.changeset/twelve-windows-train.md new file mode 100644 index 000000000000..4c6ef548e650 --- /dev/null +++ b/.changeset/twelve-windows-train.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed: Custom fields in extraData now correctly added to extraRoomInfo by livechat.beforeRoom callback during livechat room creation. diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts index 35219fc6e03b..4b0db6814bf2 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts @@ -2,6 +2,7 @@ import { OmnichannelServiceLevelAgreements } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../../lib/callbacks'; +import { isPlainObject } from '../../../../../lib/utils/isPlainObject'; callbacks.add( 'livechat.beforeRoom', @@ -10,9 +11,11 @@ callbacks.add( return roomInfo; } - const { sla: searchTerm } = extraData; + const { sla: searchTerm, customFields } = extraData; + const roomInfoWithExtraData = { ...roomInfo, ...(isPlainObject(customFields) && { customFields }) }; + if (!searchTerm) { - return roomInfo; + return roomInfoWithExtraData; } const sla = await OmnichannelServiceLevelAgreements.findOneByIdOrName(searchTerm); @@ -23,7 +26,7 @@ callbacks.add( } const { _id: slaId } = sla; - return { ...roomInfo, slaId }; + return { ...roomInfoWithExtraData, slaId }; }, callbacks.priority.MEDIUM, 'livechat-before-new-room', diff --git a/apps/meteor/lib/utils/isPlainObject.ts b/apps/meteor/lib/utils/isPlainObject.ts new file mode 100644 index 000000000000..a2bcf15cc590 --- /dev/null +++ b/apps/meteor/lib/utils/isPlainObject.ts @@ -0,0 +1,3 @@ +export function isPlainObject(value: unknown) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} diff --git a/apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts b/apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts new file mode 100644 index 000000000000..9ba9ae73fe57 --- /dev/null +++ b/apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import { describe, it, beforeEach } from 'mocha'; +import proxyquire from 'proxyquire'; +import sinon from 'sinon'; + +import { callbacks } from '../../../../../lib/callbacks'; + +const findStub = sinon.stub(); + +proxyquire.noCallThru().load('../../../../../ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts', { + 'meteor/meteor': { + Meteor: { + Error, + }, + }, + '@rocket.chat/models': { + OmnichannelServiceLevelAgreements: { + findOneByIdOrName: findStub, + }, + }, +}); + +describe('livechat.beforeRoom', () => { + beforeEach(() => findStub.withArgs('high').resolves({ _id: 'high' }).withArgs('invalid').resolves(null)); + + it('should return roomInfo with customFields when provided', async () => { + const roomInfo = { name: 'test' }; + const extraData = { customFields: { test: 'test' } }; + const result = await callbacks.run('livechat.beforeRoom', roomInfo, extraData); + expect(result).to.deep.equal({ ...roomInfo, customFields: extraData.customFields }); + }); + + it('should throw an error when provided with an invalid sla', async () => { + const roomInfo = { name: 'test' }; + const extraData = { customFields: { test: 'test' }, sla: 'invalid' }; + await expect(callbacks.run('livechat.beforeRoom', roomInfo, extraData)).to.be.rejectedWith(Error, 'error-invalid-sla'); + }); + + it('should not include field in roomInfo when extraData has field other than customFields, sla', async () => { + const roomInfo = { name: 'test' }; + const extraData = { customFields: { test: 'test' }, sla: 'high' }; + const result = await callbacks.run('livechat.beforeRoom', roomInfo, extraData); + expect(result).to.deep.equal({ ...roomInfo, customFields: extraData.customFields, slaId: 'high' }); + }); + + it('should return roomInfo with no customFields when customFields is not an object', async () => { + const roomInfo = { name: 'test' }; + const extraData = { customFields: 'not an object' }; + const result = await callbacks.run('livechat.beforeRoom', roomInfo, extraData); + expect(result).to.deep.equal({ ...roomInfo }); + }); +});