refactor: convert routing manager to ts (#29406)

pull/29625/head^2
Kevin Aleman 3 years ago committed by GitHub
parent e22dfc15ef
commit bc33bf5ab7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/meteor/app/livechat/server/api/lib/livechat.ts
  2. 2
      apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts
  3. 81
      apps/meteor/app/livechat/server/lib/RoutingManager.ts
  4. 2
      apps/meteor/app/livechat/server/methods/getRoutingConfig.ts
  5. 7
      apps/meteor/app/livechat/server/methods/takeInquiry.ts
  6. 3
      apps/meteor/app/livechat/server/roomAccessValidator.compatibility.ts
  7. 4
      apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts
  8. 2
      apps/meteor/ee/app/livechat-enterprise/server/lib/AutoTransferChatScheduler.ts
  9. 1
      apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  10. 2
      apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts
  11. 17
      apps/meteor/lib/callbacks.ts
  12. 7
      packages/core-typings/src/IInquiry.ts
  13. 1
      packages/core-typings/src/omnichannel/routing.ts

@ -126,7 +126,7 @@ export function getRoom({
return LivechatTyped.getRoom(guest, message, roomInfo, agent, extraParams);
}
export async function findAgent(agentId: string): Promise<void | { hiddenInfo: true } | ILivechatAgent> {
export async function findAgent(agentId?: string): Promise<void | { hiddenInfo: true } | ILivechatAgent> {
return normalizeAgent(agentId);
}

@ -12,7 +12,7 @@ callbacks.add(
return message;
}
if (!RoutingManager.getConfig().showQueue) {
if (!RoutingManager.getConfig()?.showQueue) {
// since last message is only getting used on UI as preview message when queue is enabled
return message;
}

@ -2,6 +2,16 @@ import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users } from '@rocket.chat/models';
import { Message } from '@rocket.chat/core-services';
import type {
ILivechatInquiryRecord,
ILivechatVisitor,
IOmnichannelRoom,
IRoutingMethod,
IRoutingMethodConstructor,
RoutingMethodConfig,
SelectedAgent,
InquiryWithAgentInfo,
} from '@rocket.chat/core-typings';
import {
createLivechatSubscription,
@ -19,7 +29,45 @@ import { Apps, AppEvents } from '../../../../ee/server/apps';
const logger = new Logger('RoutingManager');
export const RoutingManager = {
type Routing = {
methodName: string | null;
methods: Record<string, IRoutingMethod>;
startQueue(): void;
isMethodSet(): boolean;
setMethodNameAndStartQueue(name: string): void;
registerMethod(name: string, Method: IRoutingMethodConstructor): void;
getMethod(): IRoutingMethod;
getConfig(): RoutingMethodConfig | undefined;
getNextAgent(department?: string, ignoreAgentId?: string): Promise<SelectedAgent | null | undefined>;
delegateInquiry(
inquiry: InquiryWithAgentInfo,
agent?: SelectedAgent | null,
options?: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId: string; transferData: any } },
): Promise<IOmnichannelRoom | null | void>;
assignAgent(inquiry: InquiryWithAgentInfo, agent: SelectedAgent): Promise<InquiryWithAgentInfo>;
unassignAgent(inquiry: ILivechatInquiryRecord, departmentId?: string): Promise<boolean>;
takeInquiry(
inquiry: Omit<
ILivechatInquiryRecord,
'estimatedInactivityCloseTimeAt' | 'message' | 't' | 'source' | 'estimatedWaitingTimeQueue' | 'priorityWeight' | '_updatedAt'
>,
agent: SelectedAgent | null,
options?: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId: string; transferData: any } },
): Promise<IOmnichannelRoom | null | void>;
transferRoom(
room: IOmnichannelRoom,
guest: ILivechatVisitor,
transferData: {
departmentId?: string;
userId?: string;
transferredBy: { _id: string };
},
): Promise<boolean>;
delegateAgent(agent: SelectedAgent, inquiry: ILivechatInquiryRecord): Promise<SelectedAgent | null | undefined>;
removeAllRoomSubscriptions(room: Pick<IOmnichannelRoom, '_id'>, ignoreUser?: { _id: string }): Promise<void>;
};
export const RoutingManager: Routing = {
methodName: null,
methods: {},
@ -44,12 +92,16 @@ export const RoutingManager = {
this.startQueue();
},
// eslint-disable-next-line @typescript-eslint/naming-convention
registerMethod(name, Method) {
logger.debug(`Registering new routing method with name ${name}`);
this.methods[name] = new Method();
},
getMethod() {
if (!this.methodName) {
throw new Meteor.Error('error-routing-method-not-set');
}
if (!this.methods[this.methodName]) {
throw new Meteor.Error('error-routing-method-not-available');
}
@ -57,7 +109,7 @@ export const RoutingManager = {
},
getConfig() {
return this.getMethod().config || {};
return this.getMethod().config;
},
async getNextAgent(department, ignoreAgentId) {
@ -71,7 +123,7 @@ export const RoutingManager = {
if (!agent || (agent.username && !(await Users.findOneOnlineAgentByUserList(agent.username)) && !(await allowAgentSkipQueue(agent)))) {
logger.debug(`Agent offline or invalid. Using routing method to get next agent for inquiry ${inquiry._id}`);
agent = await this.getNextAgent(department);
logger.debug(`Routing method returned agent ${agent && agent.agentId} for inquiry ${inquiry._id}`);
logger.debug(`Routing method returned agent ${agent?.agentId} for inquiry ${inquiry._id}`);
}
if (!agent) {
@ -101,17 +153,19 @@ export const RoutingManager = {
}
await LivechatRooms.changeAgentByRoomId(rid, agent);
await Rooms.incUsersCountById(rid);
await Rooms.incUsersCountById(rid, 1);
const user = await Users.findOneById(agent.agentId);
const room = await LivechatRooms.findOneById(rid);
await Promise.all([Message.saveSystemMessage('command', rid, 'connected', user), Message.saveSystemMessage('uj', rid, '', user)]);
if (user) {
await Promise.all([Message.saveSystemMessage('command', rid, 'connected', user), Message.saveSystemMessage('uj', rid, '', user)]);
}
await dispatchAgentDelegated(rid, agent.agentId);
logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`);
Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user });
void Apps.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user });
return inquiry;
},
@ -120,7 +174,7 @@ export const RoutingManager = {
const room = await LivechatRooms.findOneById(rid);
logger.debug(`Removing assignations of inquiry ${inquiry._id}`);
if (!room || !room.open) {
if (!room?.open) {
logger.debug(`Cannot unassign agent from inquiry ${inquiry._id}: Room already closed`);
return false;
}
@ -171,7 +225,7 @@ export const RoutingManager = {
const { _id, rid } = inquiry;
const room = await LivechatRooms.findOneById(rid);
if (!room || !room.open) {
if (!room?.open) {
logger.debug(`Cannot take Inquiry ${inquiry._id}: Room is closed`);
return room;
}
@ -196,12 +250,16 @@ export const RoutingManager = {
if (!agent) {
logger.debug(`Cannot take Inquiry ${inquiry._id}: Precondition failed for agent`);
const cbRoom = await callbacks.run('livechat.onAgentAssignmentFailed', { inquiry, room, options });
const cbRoom = await callbacks.run<'livechat.onAgentAssignmentFailed'>('livechat.onAgentAssignmentFailed', {
inquiry,
room,
options,
});
return cbRoom;
}
await LivechatInquiry.takeInquiry(_id);
const inq = await this.assignAgent(inquiry, agent);
const inq = await this.assignAgent(inquiry as InquiryWithAgentInfo, agent);
logger.debug(`Inquiry ${inquiry._id} taken by agent ${agent.agentId}`);
callbacks.runAsync('livechat.afterTakeInquiry', inq, agent);
@ -249,7 +307,8 @@ export const RoutingManager = {
if (ignoreUser && ignoreUser._id === u._id) {
return;
}
removeAgentFromSubscription(roomId, u);
// @ts-expect-error - File still in JS, expecting error for now on `u` types
void removeAgentFromSubscription(roomId, u);
});
},
};

@ -7,7 +7,7 @@ import { RoutingManager } from '../lib/RoutingManager';
declare module '@rocket.chat/ui-contexts' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
'livechat:getRoutingConfig'(): OmichannelRoutingConfig;
'livechat:getRoutingConfig'(): OmichannelRoutingConfig | undefined;
}
}

@ -9,14 +9,17 @@ import { settings } from '../../../settings/server';
declare module '@rocket.chat/ui-contexts' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
'livechat:takeInquiry'(inquiryId: string, options?: { clientAction: boolean; forwardingToDepartment?: boolean }): unknown;
'livechat:takeInquiry'(
inquiryId: string,
options?: { clientAction: boolean; forwardingToDepartment?: { oldDepartmentId: string; transferData: any } },
): unknown;
}
}
export const takeInquiry = async (
userId: string,
inquiryId: string,
options?: { clientAction: boolean; forwardingToDepartment?: boolean },
options?: { clientAction: boolean; forwardingToDepartment?: { oldDepartmentId: string; transferData: any } },
): Promise<void> => {
if (!userId || !(await hasPermissionAsync(userId, 'view-l-room'))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {

@ -41,8 +41,7 @@ export const validators: OmnichannelRoomAccessValidator[] = [
if (!user?._id) {
return false;
}
const { previewRoom } = RoutingManager.getConfig();
if (!previewRoom) {
if (!RoutingManager.getConfig()?.previewRoom) {
return;
}

@ -40,7 +40,7 @@ settings.watch<boolean>('Livechat_last_chatted_agent_routing', function (value)
return inquiry;
}
if (!RoutingManager.getConfig().autoAssignAgent) {
if (!RoutingManager.getConfig()?.autoAssignAgent) {
return inquiry;
}
@ -64,7 +64,7 @@ settings.watch<boolean>('Livechat_last_chatted_agent_routing', function (value)
return inquiry;
}
if (!RoutingManager.getConfig().autoAssignAgent) {
if (!RoutingManager.getConfig()?.autoAssignAgent) {
return inquiry;
}

@ -89,7 +89,7 @@ class AutoTransferChatSchedulerClass {
const timeoutDuration = settings.get<number>('Livechat_auto_transfer_chat_timeout').toString();
if (!RoutingManager.getConfig().autoAssignAgent) {
if (!RoutingManager.getConfig()?.autoAssignAgent) {
this.logger.debug(`Auto-assign agent is disabled, returning room ${roomId} as inquiry`);
await Livechat.returnRoomAsInquiry(room._id, departmentId, {

@ -32,6 +32,7 @@ type QueueInfo = {
statistics: Document;
numberMostRecentChats: number;
};
export const getMaxNumberSimultaneousChat = async ({ agentId, departmentId }: { agentId?: string; departmentId?: string }) => {
if (departmentId) {
const department = await LivechatDepartmentRaw.findOneById(departmentId);

@ -378,7 +378,7 @@ function shouldQueueStart() {
return;
}
const routingSupportsAutoAssign = RoutingManager.getConfig().autoAssignAgent;
const routingSupportsAutoAssign = RoutingManager.getConfig()?.autoAssignAgent;
queueLogger.debug(
`Routing method ${RoutingManager.methodName} supports auto assignment: ${routingSupportsAutoAssign}. ${
routingSupportsAutoAssign ? 'Starting' : 'Stopping'

@ -18,6 +18,7 @@ import type {
IOmnichannelRoom,
ILivechatTag,
SelectedAgent,
InquiryWithAgentInfo,
} from '@rocket.chat/core-typings';
import { Random } from '@rocket.chat/random';
@ -60,7 +61,7 @@ interface EventLikeCallbackSignatures {
'livechat:afterReturnRoomAsInquiry': (params: { room: IRoom }) => void;
'livechat.setUserStatusLivechat': (params: { userId: IUser['_id']; status: OmnichannelAgentStatus }) => void;
'livechat.agentStatusChanged': (params: { userId: IUser['_id']; status: OmnichannelAgentStatus }) => void;
'livechat.afterTakeInquiry': (inq: ILivechatInquiryRecord, agent: { agentId: string; username: string }) => void;
'livechat.afterTakeInquiry': (inq: InquiryWithAgentInfo, agent: { agentId: string; username: string }) => void;
'afterAddedToRoom': (params: { user: IUser; inviter?: IUser }, room: IRoom) => void;
'beforeAddedToRoom': (params: { user: IUser; inviter: IUser }) => void;
'afterCreateDirectRoom': (params: IRoom, second: { members: IUser[]; creatorId: IUser['_id'] }) => void;
@ -156,19 +157,7 @@ type ChainedCallbackSignatures = {
agentsId: ILivechatAgent['_id'][];
};
'livechat.applySimultaneousChatRestrictions': (_: undefined, params: { departmentId?: ILivechatDepartmentRecord['_id'] }) => undefined;
'livechat.beforeDelegateAgent': (
agent: {
agentId: string;
username: string;
},
params?: { department?: string },
) =>
| {
agentId: string;
username: string;
}
| null
| undefined;
'livechat.beforeDelegateAgent': (agent: SelectedAgent | undefined, params?: { department?: string }) => SelectedAgent | null | undefined;
'livechat.applyDepartmentRestrictions': (
query: FilterOperators<ILivechatDepartmentRecord>,
params: { userId: IUser['_id'] },

@ -1,7 +1,7 @@
import type { ILivechatPriority } from './ILivechatPriority';
import type { IOmnichannelRoom, OmnichannelSourceType } from './IRoom';
import type { IOmnichannelServiceLevelAgreements } from './IOmnichannelServiceLevelAgreements';
import type { IUser } from './IUser';
import type { SelectedAgent } from './omnichannel/routing';
import type { IMessage } from './IMessage';
import type { IRocketChatRecord } from './IRocketChatRecord';
@ -41,10 +41,7 @@ export interface ILivechatInquiryRecord extends IRocketChatRecord {
locked?: boolean;
lockedAt?: Date;
lastMessage?: IMessage & { token?: string };
defaultAgent?: {
agentId: IUser['_id'];
username?: IUser['username'];
};
defaultAgent?: SelectedAgent;
source: {
type: OmnichannelSourceType;
};

@ -18,4 +18,5 @@ export type SelectedAgent = {
};
export interface IRoutingMethod {
getNextAgent(departmentId?: string, ignoreAgentId?: string): Promise<SelectedAgent | null | undefined>;
config?: RoutingMethodConfig;
}

Loading…
Cancel
Save