diff --git a/.changeset/many-walls-impress.md b/.changeset/many-walls-impress.md new file mode 100644 index 0000000000000..058723b41d421 --- /dev/null +++ b/.changeset/many-walls-impress.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Adds deprecation warning on `livechat:addMonitor` with new endpoint replacing it; `livechat/monitors.create` diff --git a/apps/meteor/client/views/omnichannel/monitors/MonitorsTable.tsx b/apps/meteor/client/views/omnichannel/monitors/MonitorsTable.tsx index de5020d5b7716..45bfb796d971c 100644 --- a/apps/meteor/client/views/omnichannel/monitors/MonitorsTable.tsx +++ b/apps/meteor/client/views/omnichannel/monitors/MonitorsTable.tsx @@ -49,7 +49,7 @@ const MonitorsTable = () => { // TODO: implement endpoints for monitors add/remove const removeMonitor = useMethod('livechat:removeMonitor'); - const addMonitor = useMethod('livechat:addMonitor'); + const addMonitor = useEndpoint('POST', '/v1/livechat/monitors.create'); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = pagination; const { sortBy, sortDirection, setSort } = sort; @@ -79,7 +79,7 @@ const MonitorsTable = () => { const addMutation = useMutation({ mutationFn: async (username: string) => { - await addMonitor(username); + await addMonitor({ username }); await queryClient.invalidateQueries({ queryKey: ['omnichannel', 'monitors'] }); }, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/monitors.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/monitors.ts index 5a1418799e538..65686bd849cbc 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/monitors.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/monitors.ts @@ -1,8 +1,17 @@ import type { ILivechatMonitor } from '@rocket.chat/core-typings'; +import { + isPOSTLivechatMonitorCreateRequest, + POSTLivechatMonitorsCreateSuccessResponse, + validateBadRequestErrorResponse, + validateForbiddenErrorResponse, + validateUnauthorizedErrorResponse, +} from '@rocket.chat/rest-typings'; import { findMonitors, findMonitorByUsername } from './lib/monitors'; import { API } from '../../../../../app/api/server'; +import type { ExtractRoutesFromAPI } from '../../../../../app/api/server/ApiClass'; import { getPaginationItems } from '../../../../../app/api/server/helpers/getPaginationItems'; +import { LivechatEnterprise } from '../lib/LivechatEnterprise'; API.v1.addRoute( 'livechat/monitors', @@ -50,3 +59,33 @@ API.v1.addRoute( }, }, ); + +const livechatMonitorsEndpoints = API.v1.post( + 'livechat/monitors.create', + { + response: { + 200: POSTLivechatMonitorsCreateSuccessResponse, + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + }, + authRequired: true, + permissionsRequired: ['manage-livechat-monitors'], + license: ['livechat-enterprise'], + body: isPOSTLivechatMonitorCreateRequest, + }, + async function action() { + const { username } = this.bodyParams; + + const result = await LivechatEnterprise.addMonitor(username); + + return API.v1.success(result); + }, +); + +type LivechatMonitorsEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends LivechatMonitorsEndpoints {} +} diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts index c3a810367639e..319962b6491f0 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts @@ -1,3 +1,4 @@ +import { MeteorError } from '@rocket.chat/core-services'; import type { IOmnichannelBusinessUnit, IOmnichannelServiceLevelAgreements, IUser, ILivechatTag } from '@rocket.chat/core-typings'; import { Users, OmnichannelServiceLevelAgreements, LivechatTag, LivechatUnitMonitors, LivechatUnit } from '@rocket.chat/models'; import { getUnitsFromUser } from '@rocket.chat/omni-core-ee'; @@ -17,16 +18,18 @@ export const LivechatEnterprise = { }); if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { + throw new MeteorError('error-invalid-user', 'Invalid user', { method: 'livechat:addMonitor', }); } - if (await addUserRolesAsync(user._id, ['livechat-monitor'])) { - return user; + if (!(await addUserRolesAsync(user._id, ['livechat-monitor']))) { + throw new MeteorError('error-adding-monitor-role', 'Error adding monitor role', { + method: 'livechat:addMonitor', + }); } - return false; + return user; }, async removeMonitor(username: string) { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts index 4b420da34412b..1cdd298a205d0 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts @@ -4,6 +4,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; +import { methodDeprecationLogger } from '../../../../../app/lib/server/lib/deprecationWarningLogger'; import { LivechatEnterprise } from '../lib/LivechatEnterprise'; declare module '@rocket.chat/ddp-client' { @@ -15,6 +16,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async 'livechat:addMonitor'(username) { + methodDeprecationLogger.method('livechat:addMonitor', '8.0.0', '/v1/livechat/monitors'); const uid = Meteor.userId(); if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-monitors'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:addMonitor' }); diff --git a/apps/meteor/tests/data/livechat/units.ts b/apps/meteor/tests/data/livechat/units.ts index 691d8a23fd6bf..92a5e9ed639fc 100644 --- a/apps/meteor/tests/data/livechat/units.ts +++ b/apps/meteor/tests/data/livechat/units.ts @@ -4,24 +4,18 @@ import type { IOmnichannelBusinessUnit } from '@rocket.chat/core-typings'; import { methodCall, credentials, request, api } from '../api-data'; import type { DummyResponse } from './utils'; -export const createMonitor = async (username: string): Promise<{ _id: string; username: string }> => { +export const createMonitor = async (username: string): Promise<{ _id: string; username: string; role: string[] }> => { return new Promise((resolve, reject) => { void request - .post(methodCall(`livechat:addMonitor`)) + .post(api('livechat/monitors.create')) .set(credentials) - .send({ - message: JSON.stringify({ - method: 'livechat:addMonitor', - params: [username], - id: '101', - msg: 'method', - }), - }) - .end((err: Error, res: DummyResponse) => { + .send({ username }) + .end((err: Error, res: DummyResponse<{ _id: string; username: string; role: string[] }, 'not-wrapped'>) => { if (err) { return reject(err); } - resolve(JSON.parse(res.body.message).result); + + resolve(res.body); }); }); }; diff --git a/apps/meteor/tests/e2e/utils/omnichannel/monitors.ts b/apps/meteor/tests/e2e/utils/omnichannel/monitors.ts index 5c14b4f607a90..31cc6195c7358 100644 --- a/apps/meteor/tests/e2e/utils/omnichannel/monitors.ts +++ b/apps/meteor/tests/e2e/utils/omnichannel/monitors.ts @@ -1,4 +1,3 @@ -import { parseMeteorResponse } from '../parseMeteorResponse'; import type { BaseTest } from '../test'; const removeMonitor = async (api: BaseTest['api'], id: string) => @@ -6,25 +5,18 @@ const removeMonitor = async (api: BaseTest['api'], id: string) => message: JSON.stringify({ msg: 'method', id: '33', method: 'livechat:removeMonitor', params: [id] }), }); -export const createMonitor = async (api: BaseTest['api'], id: string) => { - const response = await api.post('/method.call/livechat:addMonitor', { - message: JSON.stringify({ - msg: 'method', - id: '17', - method: 'livechat:addMonitor', - params: [id], - }), - }); +export const createMonitor = async (api: BaseTest['api'], username: string) => { + const response = await api.post('/livechat/monitors.create', { username }); if (response.status() !== 200) { throw new Error(`Failed to create monitor [http status: ${response.status()}]`); } - const monitor = await parseMeteorResponse<{ _id: string; username: string }>(response); + const data = await response.json(); return { response, - data: monitor, - delete: async () => removeMonitor(api, monitor._id), + data, + delete: async () => removeMonitor(api, data.username), }; }; diff --git a/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts b/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts index 801d3f9d65020..405a20d15035b 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts @@ -81,77 +81,33 @@ type TestUser = { user: IUser; credentials: Credentials }; }); it('should properly create a new monitor', async () => { - const { body } = await request - .post(methodCall(`livechat:addMonitor`)) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'livechat:addMonitor', - params: [user.username], - id: '101', - msg: 'method', - }), - }) - .expect(200); + const { body } = await request.post(api('livechat/monitors.create')).send({ username: user.username }).set(credentials).expect(200); expect(body.success).to.be.true; }); it('should not fail when trying to create a monitor that already exists', async () => { - const { body } = await request - .post(methodCall(`livechat:addMonitor`)) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'livechat:addMonitor', - params: [user.username], - id: '101', - msg: 'method', - }), - }) - .expect(200); + const { body } = await request.post(api('livechat/monitors.create')).send({ username: user.username }).set(credentials).expect(200); expect(body.success).to.be.true; }); it('should fail when trying to create a monitor with an invalid username', async () => { const { body } = await request - .post(methodCall(`livechat:addMonitor`)) + .post(api('livechat/monitors.create')) .set(credentials) - .send({ - message: JSON.stringify({ - method: 'livechat:addMonitor', - params: ['invalid-username'], - id: '101', - msg: 'method', - }), - }) - .expect(200); + .send({ username: 'invalid-username' }) + .expect(400); - expect(body.success).to.be.true; - const parsedBody = JSON.parse(body.message); - - expect(parsedBody.error).to.have.property('error').to.be.equal('error-invalid-user'); + expect(body.success).to.be.false; + expect(body).to.have.property('error').to.be.equal('Invalid user [error-invalid-user]'); }); it('should fail when trying to create a monitor with an empty username', async () => { - const { body } = await request - .post(methodCall(`livechat:addMonitor`)) - .set(credentials) - .send({ - message: JSON.stringify({ - method: 'livechat:addMonitor', - params: [''], - id: '101', - msg: 'method', - }), - }) - .expect(200); - - expect(body.success).to.be.true; - const parsedBody = JSON.parse(body.message); + const { body } = await request.post(api('livechat/monitors.create')).set(credentials).send({ username: '' }).expect(400); - expect(parsedBody.error).to.have.property('error').to.be.equal('error-invalid-user'); + expect(body.success).to.be.false; + expect(body).to.have.property('error').to.be.equal('Invalid user [error-invalid-user]'); }); it('should remove a monitor', async () => { diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 9636df6ef6a9e..9c98444aeba80 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -6288,6 +6288,7 @@ "error-room-is-not-closed": "Room is not closed", "error-room-not-on-hold": "Error! Room is not On Hold", "error-room-onHold": "Error! Room is On Hold", + "error-adding-monitor": "Error adding monitor", "error-saving-sla": "An error ocurred while saving the SLA", "error-selected-agent-room-agent-are-same": "The selected agent and the room agent are the same", "error-starring-message": "Message could not be stared", diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 71de7ed221b83..d09025d8b8a05 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -513,6 +513,40 @@ const LivechatMonitorsListSchema = { export const isLivechatMonitorsListProps = ajv.compile(LivechatMonitorsListSchema); +type POSTLivechatMonitorCreateRequest = { + username: string; +}; + +const POSTLivechatMonitorCreateRequestSchema = { + type: 'object', + properties: { + username: { + type: 'string', + }, + }, + required: ['username'], + additionalProperties: false, +}; + +export const isPOSTLivechatMonitorCreateRequest = ajv.compile(POSTLivechatMonitorCreateRequestSchema); + +type POSTLivechatMonitorsCreateSuccess = Pick; + +const POSTLivechatMonitorsCreateSuccessSchema = { + type: 'object', + properties: { + success: { type: 'boolean', enum: [true] }, + _id: { type: 'string' }, + username: { type: 'string' }, + roles: { type: 'array', items: { type: 'string' } }, + }, + additionalProperties: false, +}; + +export const POSTLivechatMonitorsCreateSuccessResponse = ajv.compile( + POSTLivechatMonitorsCreateSuccessSchema, +); + type POSTLivechatTagsRemoveParams = { id: string; }; @@ -4169,23 +4203,6 @@ const POSTLivechatRemoveRoomSuccessSchema = { export const POSTLivechatRemoveRoomSuccess = ajv.compile(POSTLivechatRemoveRoomSuccessSchema); -type POSTLivechatRemoveCustomFields = { - customFieldId: string; -}; - -const POSTLivechatRemoveCustomFieldsSchema = { - type: 'object', - properties: { - customFieldId: { - type: 'string', - }, - }, - required: ['customFieldId'], - additionalProperties: false, -}; - -export const isPOSTLivechatRemoveCustomFields = ajv.compile(POSTLivechatRemoveCustomFieldsSchema); - const POSTLivechatSaveCustomFieldsSchema = { type: 'object', properties: { @@ -4305,6 +4322,23 @@ export const POSTLivechatSaveCustomFieldSuccess = ajv.compile<{ customField: ILi POSTLivechatSaveCustomFieldSuccessSchema, ); +type POSTLivechatRemoveCustomFields = { + customFieldId: string; +}; + +const POSTLivechatRemoveCustomFieldsSchema = { + type: 'object', + properties: { + customFieldId: { + type: 'string', + }, + }, + required: ['customFieldId'], + additionalProperties: false, +}; + +export const isPOSTLivechatRemoveCustomFields = ajv.compile(POSTLivechatRemoveCustomFieldsSchema); + const POSTLivechatRemoveCustomFieldSuccessSchema = { type: 'object', properties: {