Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7b20fa8
chore: adds deprecation warning to livechat:addMonitor
lucas-a-pelegrino Sep 25, 2025
d30f6dc
chore: adds new endpoint for adding monitors
lucas-a-pelegrino Sep 25, 2025
34f96d7
tests: updates tests to use monitors.save endpoint
lucas-a-pelegrino Sep 25, 2025
28ce1bf
chore: updates client to use monitors.save endpoint instead of the de…
lucas-a-pelegrino Sep 25, 2025
ec6ff33
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into chor…
lucas-a-pelegrino Sep 25, 2025
673eebd
docs: adds changeset
lucas-a-pelegrino Sep 25, 2025
22a7719
fix: lint error
lucas-a-pelegrino Sep 25, 2025
a927186
Merge branch 'chore/v7/CTZ-74' of github.com:RocketChat/Rocket.Chat i…
lucas-a-pelegrino Sep 25, 2025
4c4ab4b
fix: import order
lucas-a-pelegrino Sep 25, 2025
85d7a42
tests: updates createMonitor helper
lucas-a-pelegrino Sep 25, 2025
b8a4520
tests: fixes createMonitor helper url
lucas-a-pelegrino Oct 3, 2025
4b63861
chore: adds i18n for the error-adding-monitor message
lucas-a-pelegrino Oct 8, 2025
368d6ea
fix: merge conflicts
lucas-a-pelegrino Oct 22, 2025
cb5d823
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into chor…
lucas-a-pelegrino Nov 6, 2025
a5ffac4
chore: adds improvements to LivechatEnterprise.addMonitor() function …
lucas-a-pelegrino Nov 6, 2025
4dc4eea
chore: fix wrong message assertion on integration test
lucas-a-pelegrino Nov 7, 2025
497677f
chore: renames livechat/monitors.save to livechat/monitors.create
lucas-a-pelegrino Nov 21, 2025
b0fd14a
chore: renames leftovers from monitors.save action suffix
lucas-a-pelegrino Nov 21, 2025
7b08a37
Merge branch 'develop' into chore/v7/CTZ-74
kodiakhq[bot] Nov 25, 2025
4049425
Merge branch 'develop' into chore/v7/CTZ-74
kodiakhq[bot] Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/many-walls-impress.md
Original file line number Diff line number Diff line change
@@ -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`
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'] });
},
Expand Down
39 changes: 39 additions & 0 deletions apps/meteor/ee/app/livechat-enterprise/server/api/monitors.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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<typeof livechatMonitorsEndpoints>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends LivechatMonitorsEndpoints {}
}
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' {
Expand All @@ -15,6 +16,7 @@ declare module '@rocket.chat/ddp-client' {

Meteor.methods<ServerMethods>({
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' });
Expand Down
18 changes: 6 additions & 12 deletions apps/meteor/tests/data/livechat/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, 'wrapped'>) => {
.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);
});
});
};
Expand Down
18 changes: 5 additions & 13 deletions apps/meteor/tests/e2e/utils/omnichannel/monitors.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
import { parseMeteorResponse } from '../parseMeteorResponse';
import type { BaseTest } from '../test';

const removeMonitor = async (api: BaseTest['api'], id: string) =>
api.post('/method.call/livechat:removeMonitor', {
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),
};
};
64 changes: 10 additions & 54 deletions apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
68 changes: 51 additions & 17 deletions packages/rest-typings/src/v1/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,40 @@ const LivechatMonitorsListSchema = {

export const isLivechatMonitorsListProps = ajv.compile<LivechatMonitorsListProps>(LivechatMonitorsListSchema);

type POSTLivechatMonitorCreateRequest = {
username: string;
};

const POSTLivechatMonitorCreateRequestSchema = {
type: 'object',
properties: {
username: {
type: 'string',
},
},
required: ['username'],
additionalProperties: false,
};

export const isPOSTLivechatMonitorCreateRequest = ajv.compile<POSTLivechatMonitorCreateRequest>(POSTLivechatMonitorCreateRequestSchema);

type POSTLivechatMonitorsCreateSuccess = Pick<IUser, '_id' | 'username' | 'roles'>;

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<POSTLivechatMonitorsCreateSuccess>(
POSTLivechatMonitorsCreateSuccessSchema,
);

type POSTLivechatTagsRemoveParams = {
id: string;
};
Expand Down Expand Up @@ -4169,23 +4203,6 @@ const POSTLivechatRemoveRoomSuccessSchema = {

export const POSTLivechatRemoveRoomSuccess = ajv.compile<void>(POSTLivechatRemoveRoomSuccessSchema);

type POSTLivechatRemoveCustomFields = {
customFieldId: string;
};

const POSTLivechatRemoveCustomFieldsSchema = {
type: 'object',
properties: {
customFieldId: {
type: 'string',
},
},
required: ['customFieldId'],
additionalProperties: false,
};

export const isPOSTLivechatRemoveCustomFields = ajv.compile<POSTLivechatRemoveCustomFields>(POSTLivechatRemoveCustomFieldsSchema);

const POSTLivechatSaveCustomFieldsSchema = {
type: 'object',
properties: {
Expand Down Expand Up @@ -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<POSTLivechatRemoveCustomFields>(POSTLivechatRemoveCustomFieldsSchema);

const POSTLivechatRemoveCustomFieldSuccessSchema = {
type: 'object',
properties: {
Expand Down
Loading