Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVE] Differ Voip calls from Incoming and Outgoing #25643

Merged
merged 23 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8a58e62
[IMPROVE] Differ Voip calls from Incoming and Outgoing
murtaza98 May 26, 2022
55898a1
update tests based on new direction param
murtaza98 May 27, 2022
9ef2e76
Apply CRS
murtaza98 May 27, 2022
16806d2
allow filtering by call direction on voip/rooms endpoint
murtaza98 May 27, 2022
8f332de
Fix type suggestions & add migrations for existing calls to mark them…
murtaza98 May 30, 2022
cab0355
CR suggestions about avoiding unnecessary null check
murtaza98 Jun 3, 2022
bd46f56
Adapt endpoints to use new AJV validations
murtaza98 Jun 6, 2022
28f718a
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 10, 2022
39b085d
Remove wrong comment
murtaza98 Jun 13, 2022
4b48f43
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 14, 2022
bf311da
Merge branch 'voip/ib_ob' of https://github.com/RocketChat/Rocket.Cha…
murtaza98 Jun 14, 2022
7d1b56d
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 21, 2022
92062db
CR suggestions
murtaza98 Jun 21, 2022
038bda2
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 23, 2022
8aa0558
CRS
murtaza98 Jun 23, 2022
82e2f74
Add missing definition in new models package
murtaza98 Jun 23, 2022
080ca71
another model migration
murtaza98 Jun 23, 2022
12f56d2
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 23, 2022
50f6901
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 24, 2022
c793cbe
fix invalid query in migration
murtaza98 Jun 27, 2022
a224803
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 27, 2022
956b2a7
Merge branch 'develop' into voip/ib_ob
murtaza98 Jun 27, 2022
54c2fda
Fix typechecks
KevLehman Jun 27, 2022
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
38 changes: 19 additions & 19 deletions apps/meteor/app/api/server/v1/voip/rooms.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Match, check } from 'meteor/check';
import { Random } from 'meteor/random';
import type { ILivechatAgent } from '@rocket.chat/core-typings';
import type { ILivechatAgent, IVoipRoom } from '@rocket.chat/core-typings';
import { isVoipRoomProps, isVoipRoomsProps, isVoipRoomCloseProps } from '@rocket.chat/rest-typings/dist/v1/voip';
import { VoipRoom, LivechatVisitors, Users } from '@rocket.chat/models';
import { isVoipRoomCloseProps } from '@rocket.chat/rest-typings/dist/v1/voip';

import { API } from '../../api';
import { LivechatVoip } from '../../../../../server/sdk';
Expand All @@ -25,6 +24,7 @@ const validateDateParams = (property: string, date: DateParam = {}): DateParam =
const parseAndValidate = (property: string, date?: string): DateParam => {
return validateDateParams(property, parseDateParams(date));
};

/**
* @openapi
* /voip/server/api/v1/voip/room
Expand Down Expand Up @@ -87,17 +87,16 @@ API.v1.addRoute(
authRequired: true,
rateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 60000 },
permissionsRequired: ['inbound-voip-calls'],
validateParams: isVoipRoomProps,
},
{
async get() {
const defaultCheckParams = {
token: String,
agentId: Match.Maybe(String),
rid: Match.Maybe(String),
const { token, agentId, direction } = this.queryParams as {
token: string;
agentId: ILivechatAgent['_id'];
direction: IVoipRoom['direction'];
};
check(this.queryParams, defaultCheckParams);

const { token, rid, agentId } = this.queryParams;
const { rid } = this.queryParams as { rid: string; token: string };
const guest = await LivechatVisitors.getVisitorByToken(token, {});
if (!guest) {
return API.v1.failure('invalid-token');
Expand All @@ -123,7 +122,11 @@ API.v1.addRoute(
const agent = { agentId: _id, username };
const rid = Random.id();

return API.v1.success(await LivechatVoip.getNewRoom(guest, agent, rid, { projection: API.v1.defaultFieldsToExclude }));
return API.v1.success(
await LivechatVoip.getNewRoom(guest, agent, rid, direction as IVoipRoom['direction'], {
projection: API.v1.defaultFieldsToExclude,
}),
);
}

const room = await VoipRoom.findOneByIdAndVisitorToken(rid, token, { projection: API.v1.defaultFieldsToExclude });
Expand All @@ -137,20 +140,15 @@ API.v1.addRoute(

API.v1.addRoute(
'voip/rooms',
{ authRequired: true },
{ authRequired: true, validateParams: isVoipRoomsProps },
{
async get() {
const { offset, count } = this.getPaginationItems();

const { sort, fields } = this.parseJsonQuery();
const { agents, open, tags, queue, visitorId } = this.requestParams();
const { agents, open, tags, queue, visitorId, direction, roomName } = this.requestParams();
const { createdAt: createdAtParam, closedAt: closedAtParam } = this.requestParams();

check(agents, Match.Maybe([String]));
check(open, Match.Maybe(String));
check(tags, Match.Maybe([String]));
check(queue, Match.Maybe(String));
check(visitorId, Match.Maybe(String));

// Reusing same L room permissions for simplicity
const hasAdminAccess = hasPermission(this.userId, 'view-livechat-rooms');
const hasAgentAccess = hasPermission(this.userId, 'view-l-room') && agents?.includes(this.userId) && agents?.length === 1;
Expand All @@ -170,6 +168,8 @@ API.v1.addRoute(
visitorId,
createdAt,
closedAt,
direction,
roomName,
options: { sort, offset, count, fields },
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export const CallProvider: FC = ({ children }) => {
name: caller.callerName || caller.callerId,
},
});
const voipRoom = visitor && (await voipEndpoint({ token: visitor.token, agentId: user._id }));
const voipRoom = visitor && (await voipEndpoint({ token: visitor.token, agentId: user._id, direction: 'inbound' }));
openRoom(voipRoom.room._id);
voipRoom.room && setRoomInfo({ v: { token: voipRoom.room.v.token }, rid: voipRoom.room._id });
const queueAggregator = voipClient.getAggregator();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IVoipRoom } from '@rocket.chat/core-typings';
import { Table } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoute, useTranslation } from '@rocket.chat/ui-contexts';
Expand Down Expand Up @@ -54,6 +55,17 @@ const CallTable: FC = () => {
const query = useQuery(debouncedParams, debouncedSort, userIdLoggedIn);
const directoryRoute = useRoute('omnichannel-directory');

const resolveDirectionLabel = useCallback(
(direction: IVoipRoom['direction']) => {
const labels = {
inbound: 'Incoming',
outbound: 'Outgoing',
} as const;
return t(labels[direction] || 'Not_Available');
},
[t],
);

const onHeaderClick = useMutableCallback((id) => {
const [sortBy, sortDirection] = sort;

Expand Down Expand Up @@ -117,21 +129,21 @@ const CallTable: FC = () => {
{t('Talk_Time')}
</GenericTable.HeaderCell>,
<GenericTable.HeaderCell
key={'source'}
key='direction'
direction={sort[1]}
active={sort[0] === 'source'}
active={sort[0] === 'direction'}
onClick={onHeaderClick}
sort='source'
sort='direction'
w='x200'
>
{t('Source')}
{t('Direction')}
</GenericTable.HeaderCell>,
].filter(Boolean),
[sort, onHeaderClick, t],
);

const renderRow = useCallback(
({ _id, fname, callStarted, queue, callDuration, v }) => {
({ _id, fname, callStarted, queue, callDuration, v, direction }) => {
const duration = moment.duration(callDuration / 1000, 'seconds');
return (
<Table.Row key={_id} tabIndex={0} role='link' onClick={(): void => onRowClick(_id, v?.token)} action qa-user-id={_id}>
Expand All @@ -140,11 +152,11 @@ const CallTable: FC = () => {
<Table.Cell withTruncatedText>{queue}</Table.Cell>
<Table.Cell withTruncatedText>{moment(callStarted).format('L LTS')}</Table.Cell>
<Table.Cell withTruncatedText>{duration.isValid() && duration.humanize()}</Table.Cell>
<Table.Cell withTruncatedText>{t('Incoming')}</Table.Cell>
<Table.Cell withTruncatedText>{resolveDirectionLabel(direction)}</Table.Cell>
</Table.Row>
);
},
[onRowClick, t],
[onRowClick, resolveDirectionLabel],
);

return (
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,7 @@
"Details": "Details",
"Different_Style_For_User_Mentions": "Different style for user mentions",
"Direct": "Direct",
"Direction": "Direction",
"Direct_Message": "Direct Message",
"Direct_message_creation_description": "You are about to create a chat with multiple users. Add the ones you would like to talk, everyone in the same place, using direct messages.",
"Direct_message_someone": "Direct message someone",
Expand Down
11 changes: 11 additions & 0 deletions apps/meteor/server/models/raw/VoipRoom.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { escapeRegExp } from '@rocket.chat/string-helpers';
import type { IRoomClosingInfo, IVoipRoom, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IVoipRoomModel } from '@rocket.chat/model-typings';
import type { Collection, Cursor, Db, FilterQuery, FindOneOptions, WithoutProjection, WriteOpResult } from 'mongodb';
Expand Down Expand Up @@ -114,6 +115,8 @@ export class VoipRoomRaw extends BaseRaw<IVoipRoom> implements IVoipRoomModel {
tags,
queue,
visitorId,
direction,
roomName,
options = {},
}: {
agents?: string[];
Expand All @@ -123,6 +126,8 @@ export class VoipRoomRaw extends BaseRaw<IVoipRoom> implements IVoipRoomModel {
tags?: string[];
queue?: string;
visitorId?: string;
direction?: IVoipRoom['direction'];
roomName?: string;
options?: {
sort?: Record<string, unknown>;
count?: number;
Expand Down Expand Up @@ -167,6 +172,12 @@ export class VoipRoomRaw extends BaseRaw<IVoipRoom> implements IVoipRoomModel {
if (queue) {
query.queue = queue;
}
if (direction) {
query.direction = direction;
}
if (roomName) {
query.name = new RegExp(escapeRegExp(roomName), 'i');
tiagoevanp marked this conversation as resolved.
Show resolved Hide resolved
}

return this.find(query, {
sort: options.sort || { name: 1 },
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/server/sdk/types/IOmnichannelVoipService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface IOmnichannelVoipService {
guest: ILivechatVisitor,
agent: { agentId: string; username: string },
rid: string,
direction: IVoipRoom['direction'],
options: FindOneOptions<IVoipRoom>,
): Promise<IRoomCreationResponse>;
findRoom(token: string, rid: string): Promise<IVoipRoom | null>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMessage } from '@rocket.chat/core-typings';
import { IVoipRoom, IMessage } from '@rocket.chat/core-typings';

export type FindVoipRoomsParams = {
agents?: string[];
Expand All @@ -14,6 +14,8 @@ export type FindVoipRoomsParams = {
fields?: Record<string, unknown>;
offset?: number;
};
direction?: IVoipRoom['direction'];
roomName?: string;
};

export type IOmniRoomClosingMessage = Pick<IMessage, 't' | 'groupable'> & Partial<Pick<IMessage, 'msg'>>;
9 changes: 8 additions & 1 deletion apps/meteor/server/services/omnichannel-voip/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
name: string,
agent: { agentId: string; username: string },
guest: ILivechatVisitor,
direction: IVoipRoom['direction'],
): Promise<string> {
const status = 'online';
const { _id, department: departmentId } = guest;
Expand Down Expand Up @@ -161,6 +162,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
_id: agent.agentId,
username: agent.username,
},
direction,
_updatedAt: newRoomAt,
};

Expand Down Expand Up @@ -213,6 +215,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
guest: ILivechatVisitor,
agent: { agentId: string; username: string },
rid: string,
direction: IVoipRoom['direction'],
options: FindOneOptions<IVoipRoom> = {},
): Promise<IRoomCreationResponse> {
this.logger.debug(`Attempting to find or create a room for visitor ${guest._id}`);
Expand All @@ -224,7 +227,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
}
if (room == null) {
const name = guest.name || guest.username;
const roomId = await this.createVoipRoom(rid, name, agent, guest);
const roomId = await this.createVoipRoom(rid, name, agent, guest, direction);
room = await VoipRoom.findOneVoipRoomById(roomId);
newRoom = true;
this.logger.debug(`Room obtained for visitor ${guest._id} -> ${room?._id}`);
Expand Down Expand Up @@ -371,6 +374,8 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
visitorId,
tags,
queue,
direction,
roomName,
options: { offset = 0, count, fields, sort } = {},
}: FindVoipRoomsParams): Promise<PaginatedResult<{ rooms: IVoipRoom[] }>> {
const cursor = VoipRoom.findRoomsWithCriteria({
Expand All @@ -381,6 +386,8 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
tags,
queue,
visitorId,
direction,
roomName,
options: {
sort: sort || { ts: -1 },
offset,
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/server/startup/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,5 @@ import './v266';
import './v267';
import './v268';
import './v269';
import './v270';
import './xrun';
21 changes: 21 additions & 0 deletions apps/meteor/server/startup/migrations/v270.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { VoipRoom } from '@rocket.chat/models';

import { addMigration } from '../../lib/migrations';

addMigration({
version: 270,
async up() {
// mark all voip rooms as inbound which doesn't have any direction property set or has an invalid value
await VoipRoom.updateMany(
{
t: 'v',
murtaza98 marked this conversation as resolved.
Show resolved Hide resolved
$or: [{ direction: { $exists: false } }, { direction: { $nin: ['inbound', 'outbound'] } }],
},
{
$set: {
direction: 'inbound',
},
},
);
},
});
4 changes: 2 additions & 2 deletions apps/meteor/tests/data/rooms.helper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { api, credentials, request } from './api-data';

export const createRoom = ({ name, type, username, token, agentId, members, credentials: customCredentials }) => {
export const createRoom = ({ name, type, username, token, agentId, members, credentials: customCredentials, voipCallDirection = 'inbound' }) => {
if (!type) {
throw new Error('"type" is required in "createRoom.ts" test helper');
}
Expand All @@ -11,7 +11,7 @@ export const createRoom = ({ name, type, username, token, agentId, members, cred
* is handled separately here.
*/
return request
.get(api(`voip/room?token=${token}&agentId=${agentId}`))
.get(api(`voip/room?token=${token}&agentId=${agentId}&direction=${voipCallDirection}`))
.set(customCredentials || credentials)
.send();
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core-typings/src/IRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ export interface IVoipRoom extends IOmnichannelGenericRoom {
status: 'online' | 'busy' | 'away' | 'offline';
phone?: string | null;
};
// Outbound means the call was initiated from Rocket.Chat and vise versa
direction: 'inbound' | 'outbound';
}

export interface IOmnichannelRoomFromAppSource extends IOmnichannelRoom {
Expand Down
4 changes: 4 additions & 0 deletions packages/model-typings/src/models/IVoipRoomModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface IVoipRoomModel extends IBaseModel<IVoipRoom> {
tags,
queue,
visitorId,
direction,
roomName,
options,
}: {
agents?: string[];
Expand All @@ -32,6 +34,8 @@ export interface IVoipRoomModel extends IBaseModel<IVoipRoom> {
tags?: string[];
queue?: string;
visitorId?: string;
direction?: IVoipRoom['direction'];
roomName?: string;
options?: {
sort?: Record<string, unknown>;
count?: number;
Expand Down
Loading