Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f915074
chore: adds a new endpoint to replace deprecated method
lucas-a-pelegrino Sep 17, 2025
c34fd1d
chore: replaces deprecated method by the new endpoint
lucas-a-pelegrino Sep 17, 2025
ab17083
tests: updates tests to use the new endpoint
lucas-a-pelegrino Sep 17, 2025
dbb86b0
chore: adds deprecation warning
lucas-a-pelegrino Sep 17, 2025
6c23a2a
fix: lint import errors
lucas-a-pelegrino Sep 17, 2025
e137035
fix: import paths
lucas-a-pelegrino Sep 17, 2025
46fe174
docs: adds changeset
lucas-a-pelegrino Sep 17, 2025
19ac582
chore: adds trycatch to handle meteor errors thrown by service function
lucas-a-pelegrino Sep 17, 2025
7d045c6
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into chor…
lucas-a-pelegrino Sep 22, 2025
0a255f7
chore: adds minor improvements to returnAsInquiry
lucas-a-pelegrino Sep 22, 2025
f2f7684
fix: lint errors
lucas-a-pelegrino Sep 22, 2025
5b01542
fix: lint errors
lucas-a-pelegrino Sep 22, 2025
d784c51
Merge remote-tracking branch 'origin/chore/v7/CTZ-61' into chore/v7/C…
lucas-a-pelegrino Sep 22, 2025
505efbc
fix: error message validation
lucas-a-pelegrino Sep 22, 2025
a669247
fix: invalid message in api test
lucas-a-pelegrino Sep 22, 2025
25bab34
fix: error message patterns
lucas-a-pelegrino Sep 23, 2025
8dcbf1b
chore: adds minor changes requested from code review
lucas-a-pelegrino Sep 23, 2025
db9b1ba
Merge branch 'develop' into chore/v7/CTZ-61
lucas-a-pelegrino Sep 23, 2025
5be06b6
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into chor…
lucas-a-pelegrino Sep 25, 2025
a5f4743
fix: import order
lucas-a-pelegrino Sep 25, 2025
ec7b110
Merge branch 'develop' into chore/v7/CTZ-61
kodiakhq[bot] Sep 26, 2025
1037b2b
Merge branch 'develop' into chore/v7/CTZ-61
kodiakhq[bot] Sep 29, 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/calm-hounds-look.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:returnAsInquiry` with new endpoint replacing it; `livechat/inquiries.returnAsInquiry`
51 changes: 50 additions & 1 deletion apps/meteor/app/livechat/imports/server/rest/inquiries.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { LivechatInquiryStatus } from '@rocket.chat/core-typings';
import { LivechatInquiry, LivechatDepartment, Users } from '@rocket.chat/models';
import { LivechatInquiry, LivechatDepartment, Users, LivechatRooms } from '@rocket.chat/models';
import {
isGETLivechatInquiriesListParams,
isPOSTLivechatInquiriesTakeParams,
isGETLivechatInquiriesQueuedForUserParams,
isGETLivechatInquiriesGetOneParams,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
isPOSTLivechatInquiriesReturnAsInquiry,
POSTLivechatInquiriesReturnAsInquirySuccessResponse,
} from '@rocket.chat/rest-typings';

import { API } from '../../../../api/server';
import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass';
import { getPaginationItems } from '../../../../api/server/helpers/getPaginationItems';
import { findInquiries, findOneInquiryByRoomId } from '../../../server/api/lib/inquiries';
import { returnRoomAsInquiry } from '../../../server/lib/rooms';
import { takeInquiry } from '../../../server/lib/takeInquiry';

API.v1.addRoute(
Expand Down Expand Up @@ -108,3 +115,45 @@ API.v1.addRoute(
},
},
);

const livechatInquiriesEndpoints = API.v1.post(
'livechat/inquiries.returnAsInquiry',
{
response: {
200: POSTLivechatInquiriesReturnAsInquirySuccessResponse,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
authRequired: true,
permissionsRequired: ['view-l-room'],
body: isPOSTLivechatInquiriesReturnAsInquiry,
},
async function action() {
const { roomId, departmentId } = this.bodyParams;

try {
const room = await LivechatRooms.findOneById(roomId);
if (!room) {
return API.v1.failure('error-room-not-found');
}

const result = await returnRoomAsInquiry(room, departmentId);

return API.v1.success({ result });
} catch (error) {
if (error instanceof Meteor.Error && typeof error.error === 'string') {
return API.v1.failure(error.error as string);
}

return API.v1.failure('error-returning-inquiry');
}
},
);

type LivechatInquiriesEndpoints = ExtractRoutesFromAPI<typeof livechatInquiriesEndpoints>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends LivechatInquiriesEndpoints {}
}
7 changes: 6 additions & 1 deletion apps/meteor/app/livechat/server/lib/rooms.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AppEvents, Apps } from '@rocket.chat/apps';
import { Omnichannel } from '@rocket.chat/core-services';
import type {
ILivechatVisitor,
IMessage,
Expand Down Expand Up @@ -211,13 +212,17 @@ export async function saveRoomInfo(
export async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?: string, overrideTransferData: Partial<TransferData> = {}) {
livechatLogger.debug({ msg: `Transfering room to ${departmentId ? 'department' : ''} queue`, room });
if (!room.open) {
throw new Meteor.Error('room-closed');
throw new Meteor.Error('room-closed', 'Room closed');
}

if (room.onHold) {
throw new Meteor.Error('error-room-onHold');
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Meteor.Error('error-mac-limit-reached');
}

if (!room.servedBy) {
return false;
}
Expand Down
11 changes: 2 additions & 9 deletions apps/meteor/app/livechat/server/methods/returnAsInquiry.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { ILivechatDepartment, IRoom } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { LivechatRooms } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { returnRoomAsInquiry } from '../lib/rooms';

declare module '@rocket.chat/ddp-client' {
Expand All @@ -16,6 +16,7 @@ declare module '@rocket.chat/ddp-client' {

Meteor.methods<ServerMethods>({
async 'livechat:returnAsInquiry'(rid, departmentId) {
methodDeprecationLogger.method('livechat:returnAsInquiry', '8.0.0', '/v1/livechat/inquiries.returnAsInquiry');
const uid = Meteor.userId();
if (!uid || !(await hasPermissionAsync(uid, 'view-l-room'))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
Expand All @@ -30,14 +31,6 @@ Meteor.methods<ServerMethods>({
});
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:returnAsInquiry' });
}

if (!room.open) {
throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:returnAsInquiry' });
}

return returnRoomAsInquiry(room, departmentId);
},
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useMethod } from '@rocket.chat/ui-contexts';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';

Expand All @@ -8,13 +8,13 @@ import { roomsQueryKeys, subscriptionsQueryKeys } from '../../../../../../lib/qu
export const useReturnChatToQueueMutation = (
options?: Omit<UseMutationOptions<void, Error, IRoom['_id']>, 'mutationFn'>,
): UseMutationResult<void, Error, IRoom['_id']> => {
const returnChatToQueue = useMethod('livechat:returnAsInquiry');
const returnChatToQueue = useEndpoint('POST', '/v1/livechat/inquiries.returnAsInquiry');

const queryClient = useQueryClient();

return useMutation({
mutationFn: async (rid) => {
await returnChatToQueue(rid);
await returnChatToQueue({ roomId: rid });
},
...options,
onSuccess: async (data, rid, context) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useMethod } from '@rocket.chat/ui-contexts';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';

Expand All @@ -8,13 +8,13 @@ import { roomsQueryKeys, subscriptionsQueryKeys } from '../../../../../../lib/qu
export const useReturnChatToQueueMutation = (
options?: Omit<UseMutationOptions<void, Error, IRoom['_id']>, 'mutationFn'>,
): UseMutationResult<void, Error, IRoom['_id']> => {
const returnChatToQueue = useMethod('livechat:returnAsInquiry');
const returnChatToQueue = useEndpoint('POST', '/v1/livechat/inquiries.returnAsInquiry');

const queryClient = useQueryClient();

return useMutation({
mutationFn: async (rid) => {
await returnChatToQueue(rid);
await returnChatToQueue({ roomId: rid });
},
...options,
onSuccess: async (data, rid, context) => {
Expand Down
114 changes: 33 additions & 81 deletions apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import type { Response } from 'supertest';

import { getCredentials, api, request, credentials, methodCall } from '../../../data/api-data';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { deleteDepartment } from '../../../data/livechat/department';
import {
closeOmnichannelRoom,
Expand All @@ -19,7 +19,7 @@ import {
startANewLivechatRoomAndTakeIt,
takeInquiry,
} from '../../../data/livechat/rooms';
import { parseMethodResponse, sleep } from '../../../data/livechat/utils';
import { sleep } from '../../../data/livechat/utils';
import {
removePermissionFromAllRoles,
restorePermissionToRoles,
Expand Down Expand Up @@ -324,7 +324,7 @@ describe('LIVECHAT - inquiries', () => {
});
});

describe('livechat:returnAsInquiry', () => {
describe('livechat/inquiries.returnAsInquiry', () => {
let testUser: { user: IUser; credentials: Credentials };
before(async () => {
const user = await createUser();
Expand All @@ -344,81 +344,54 @@ describe('LIVECHAT - inquiries', () => {
it('should throw an error if user doesnt have view-l-room permission', async () => {
await removePermissionFromAllRoles('view-l-room');
const { body } = await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: ['test'],
id: 'id',
msg: 'method',
}),
})
.send({ roomId: 'test' })
.expect('Content-Type', 'application/json')
.expect(200);

const response = parseMethodResponse(body);
.expect(403);

expect(response.error.error).to.be.equal('error-not-allowed');
expect(body).to.have.property('success', false);
expect(body.error).to.have.equal('User does not have the permissions required for this action [error-unauthorized]');
});
it('should fail if provided room doesnt exists', async () => {
await restorePermissionToRoles('view-l-room');
const { body } = await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: ['test'],
id: 'id',
msg: 'method',
}),
roomId: 'test',
})
.expect('Content-Type', 'application/json')
.expect(200);
.expect(400);

const response = parseMethodResponse(body);
expect(response.error.error).to.be.equal('error-invalid-room');
expect(body).to.have.property('success', false);
expect(body).to.have.property('error', 'error-room-not-found');
});
it('should fail if room is not a livechat room', async () => {
const { body } = await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: ['GENERAL'],
id: 'id',
msg: 'method',
}),
})
.send({ roomId: 'GENERAL' })
.expect('Content-Type', 'application/json')
.expect(200);
.expect(400);

const response = parseMethodResponse(body);
expect(response.error.error).to.be.equal('error-invalid-room');
expect(body).to.have.property('success', false);
expect(body).to.have.property('error', 'error-room-not-found');
});
it('should fail if room is closed', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);

const { body } = await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: [room._id],
id: 'id',
msg: 'method',
}),
})
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);
.expect(400);

const response = parseMethodResponse(body);
expect(response.error.error).to.be.equal('room-closed');
expect(body).to.have.property('success', false);
expect(body).to.have.property('error', 'room-closed');
});
describe('no serving', () => {
let room: IOmnichannelRoom;
Expand All @@ -431,21 +404,14 @@ describe('LIVECHAT - inquiries', () => {
});
it('should fail if no one is serving the room', async () => {
const { body } = await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: [room._id],
id: 'id',
msg: 'method',
}),
})
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);

const response = parseMethodResponse(body);
expect(response.result).to.be.false;
expect(body).to.have.property('success', true);
expect(body).to.have.property('result', false);
});
});

Expand All @@ -460,21 +426,14 @@ describe('LIVECHAT - inquiries', () => {
await takeInquiry(inq._id, testUser.credentials);

const { body } = await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(testUser.credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: [room._id],
id: 'id',
msg: 'method',
}),
})
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);

const response = parseMethodResponse(body);
expect(response.result).to.be.true;
expect(body).to.have.property('success', true);
expect(body).to.have.property('result', true);
});
(IS_EE ? it : it.skip)('should appear on users queued elements', async () => {
const { body } = await request
Expand Down Expand Up @@ -554,16 +513,9 @@ describe('LIVECHAT - inquiries', () => {
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);

await request
.post(methodCall('livechat:returnAsInquiry'))
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: [room._id],
id: 'id',
msg: 'method',
}),
})
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);

Expand Down
Loading
Loading