Skip to content

Commit

Permalink
feat(Omnichannel): Queued Status option Current Chats (#32800)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Aleman <[email protected]>
  • Loading branch information
MartinSchoeler and KevLehman authored Jul 23, 2024
1 parent be1f1a2 commit 264d7d5
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 4 deletions.
7 changes: 7 additions & 0 deletions .changeset/weak-tigers-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/model-typings": minor
"@rocket.chat/rest-typings": minor
---

Added the ability to filter chats by `queued` on the Current Chats Omnichannel page
3 changes: 2 additions & 1 deletion apps/meteor/app/livechat/imports/server/rest/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ API.v1.addRoute(
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields } = await this.parseJsonQuery();
const { agents, departmentId, open, tags, roomName, onhold } = this.queryParams;
const { agents, departmentId, open, tags, roomName, onhold, queued } = this.queryParams;
const { createdAt, customFields, closedAt } = this.queryParams;

const createdAtParam = validateDateParams('createdAt', createdAt);
Expand Down Expand Up @@ -69,6 +69,7 @@ API.v1.addRoute(
tags,
customFields: parsedCf,
onhold,
queued,
options: { offset, count, sort, fields },
}),
);
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/app/livechat/server/api/lib/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export async function findRooms({
tags,
customFields,
onhold,
queued,
options: { offset, count, fields, sort },
}: {
agents?: Array<string>;
Expand All @@ -31,6 +32,7 @@ export async function findRooms({
tags?: Array<string>;
customFields?: Record<string, string>;
onhold?: string | boolean;
queued?: string | boolean;
options: { offset: number; count: number; fields: Record<string, number>; sort: Record<string, number> };
}): Promise<PaginatedResult<{ rooms: Array<IOmnichannelRoom> }>> {
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {});
Expand All @@ -44,6 +46,7 @@ export async function findRooms({
tags,
customFields,
onhold: ['t', 'true', '1'].includes(`${onhold}`),
queued: ['t', 'true', '1'].includes(`${queued}`),
options: {
sort: sort || { ts: -1 },
offset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type CurrentChatQuery = {
customFields?: string;
sort: string;
count?: number;
queued?: boolean;
};

type useQueryType = (
Expand Down Expand Up @@ -95,8 +96,9 @@ const currentChatQuery: useQueryType = (
}

if (status !== 'all') {
query.open = status === 'opened' || status === 'onhold';
query.open = status === 'opened' || status === 'onhold' || status === 'queued';
query.onhold = status === 'onhold';
query.queued = status === 'queued';
}
if (servedBy && servedBy !== 'all') {
query.agents = [servedBy];
Expand Down Expand Up @@ -170,8 +172,9 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s
const renderRow = useCallback(
(room) => {
const { _id, fname, servedBy, ts, lm, department, open, onHold, priorityWeight } = room;
const getStatusText = (open: boolean, onHold: boolean): string => {
const getStatusText = (open: boolean, onHold: boolean, servedBy: boolean): string => {
if (!open) return t('Closed');
if (open && !servedBy) return t('Queued');
return onHold ? t('On_Hold_Chats') : t('Room_Status_Open');
};

Expand All @@ -198,7 +201,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s
{moment(lm).format('L LTS')}
</GenericTableCell>
<GenericTableCell withTruncatedText data-qa='current-chats-cell-status'>
<RoomActivityIcon room={room} /> {getStatusText(open, onHold)}
<RoomActivityIcon room={room} /> {getStatusText(open, onHold, !!servedBy?.username)}
</GenericTableCell>
{canRemoveClosedChats && !open && <RemoveChatButton _id={_id} />}
</GenericTableRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const FilterByText = ({ setFilter, reload, customFields, setCustomFields, hasCus
['closed', t('Closed')],
['opened', t('Room_Status_Open')],
['onhold', t('On_Hold_Chats')],
['queued', t('Queued')],
];

const [guest, setGuest] = useLocalStorage('guest', '');
Expand Down
12 changes: 12 additions & 0 deletions apps/meteor/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,7 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
visitorId,
roomIds,
onhold,
queued,
options = {},
extraQuery = {},
}: {
Expand All @@ -1226,6 +1227,7 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
visitorId?: string;
roomIds?: string[];
onhold?: boolean;
queued?: boolean;
options?: { offset?: number; count?: number; sort?: { [k: string]: SortDirection } };
extraQuery?: Filter<IOmnichannelRoom>;
}) {
Expand All @@ -1242,6 +1244,10 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
...(visitorId && visitorId !== 'undefined' && { 'v._id': visitorId }),
};

if (open) {
query.servedBy = { $exists: true };
}

if (createdAt) {
query.ts = {};
if (createdAt.start) {
Expand Down Expand Up @@ -1280,6 +1286,12 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
};
}

if (queued) {
query.servedBy = { $exists: false };
query.open = true;
query.onHold = { $ne: true };
}

return this.findPaginated(query, {
sort: options.sort || { name: 1 },
skip: options.offset,
Expand Down
72 changes: 72 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
fetchMessages,
deleteVisitor,
makeAgentUnavailable,
sendAgentMessage,
} from '../../../data/livechat/rooms';
import { saveTags } from '../../../data/livechat/tags';
import type { DummyResponse } from '../../../data/livechat/utils';
Expand Down Expand Up @@ -341,6 +342,77 @@ describe('LIVECHAT - rooms', () => {
expect(body.rooms.some((room: IOmnichannelRoom) => !!room.closedAt)).to.be.true;
expect(body.rooms.some((room: IOmnichannelRoom) => room.open)).to.be.true;
});
it('should return queued rooms when `queued` param is passed', async () => {
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);

const { body } = await request.get(api('livechat/rooms')).query({ queued: true }).set(credentials).expect(200);

expect(body.rooms.every((room: IOmnichannelRoom) => room.open)).to.be.true;
expect(body.rooms.every((room: IOmnichannelRoom) => !room.servedBy)).to.be.true;
expect(body.rooms.find((froom: IOmnichannelRoom) => froom._id === room._id)).to.be.not.undefined;
});
it('should return queued rooms when `queued` and `open` params are passed', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);

const { body } = await request.get(api('livechat/rooms')).query({ queued: true, open: true }).set(credentials).expect(200);

expect(body.rooms.every((room: IOmnichannelRoom) => room.open)).to.be.true;
expect(body.rooms.every((room: IOmnichannelRoom) => !room.servedBy)).to.be.true;
expect(body.rooms.find((froom: IOmnichannelRoom) => froom._id === room._id)).to.be.not.undefined;
});
it('should return open rooms when `open` is param is passed. Open rooms should not include queued conversations', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);

const { room: room2 } = await startANewLivechatRoomAndTakeIt();

const { body } = await request.get(api('livechat/rooms')).query({ open: true }).set(credentials).expect(200);

expect(body.rooms.every((room: IOmnichannelRoom) => room.open)).to.be.true;
expect(body.rooms.find((froom: IOmnichannelRoom) => froom._id === room2._id)).to.be.not.undefined;
expect(body.rooms.find((froom: IOmnichannelRoom) => froom._id === room._id)).to.be.undefined;

await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
});
(IS_EE ? describe : describe.skip)('Queued and OnHold chats', () => {
before(async () => {
await updateSetting('Livechat_allow_manual_on_hold', true);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
});

after(async () => {
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
await updateSetting('Livechat_allow_manual_on_hold', false);
});

it('should not return on hold rooms along with queued rooms when `queued` is true and `onHold` is true', async () => {
const { room } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(200);

expect(response.body.success).to.be.true;

const visitor = await createVisitor();
const room2 = await createLivechatRoom(visitor.token);

const { body } = await request.get(api('livechat/rooms')).query({ queued: true, onhold: true }).set(credentials).expect(200);

expect(body.rooms.every((room: IOmnichannelRoom) => room.open)).to.be.true;
expect(body.rooms.every((room: IOmnichannelRoom) => !room.servedBy)).to.be.true;
expect(body.rooms.every((room: IOmnichannelRoom) => !room.onHold)).to.be.true;
expect(body.rooms.find((froom: IOmnichannelRoom) => froom._id === room._id)).to.be.undefined;
expect(body.rooms.find((froom: IOmnichannelRoom) => froom._id === room2._id)).to.be.not.undefined;
});
});
(IS_EE ? it : it.skip)('should return only rooms with the given department', async () => {
const { department } = await createDepartmentWithAnOnlineAgent();

Expand Down
2 changes: 2 additions & 0 deletions packages/model-typings/src/models/ILivechatRoomsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type WithOptions = {
options?: any;
};

// TODO: Fix types of model
export interface ILivechatRoomsModel extends IBaseModel<IOmnichannelRoom> {
getQueueMetrics(params: { departmentId: any; agentId: any; includeOfflineAgents: any; options?: any }): any;

Expand Down Expand Up @@ -96,6 +97,7 @@ export interface ILivechatRoomsModel extends IBaseModel<IOmnichannelRoom> {
visitorId?: any;
roomIds?: any;
onhold: any;
queued: any;
options?: any;
extraQuery?: any;
}): FindPaginated<FindCursor<IOmnichannelRoom>>;
Expand Down
7 changes: 7 additions & 0 deletions packages/rest-typings/src/v1/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,7 @@ export type GETLivechatRoomsParams = PaginatedRequest<{
departmentId?: string;
open?: string | boolean;
onhold?: string | boolean;
queued?: string | boolean;
tags?: string[];
}>;

Expand Down Expand Up @@ -2617,6 +2618,12 @@ const GETLivechatRoomsParamsSchema = {
{ type: 'boolean', nullable: true },
],
},
queued: {
anyOf: [
{ type: 'string', nullable: true },
{ type: 'boolean', nullable: true },
],
},
tags: {
type: 'array',
items: {
Expand Down

0 comments on commit 264d7d5

Please sign in to comment.