fix: sound notification for queued chats with manual routing (#28411)

Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com>
pull/28390/head^2
Filipe Marins 3 years ago committed by GitHub
parent 2228a1b351
commit 6a3ed7d4ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      apps/meteor/app/livechat/client/lib/stream/inquiry.ts
  2. 67
      apps/meteor/app/livechat/client/lib/stream/queueManager.ts
  3. 19
      apps/meteor/client/providers/OmnichannelProvider.tsx
  4. 6
      apps/meteor/client/sidebar/hooks/useRoomList.ts
  5. 3
      apps/meteor/ee/server/models/LivechatInquiry.ts
  6. 4
      packages/core-typings/src/OmichannelRoutingConfig.ts

@ -1,73 +1,61 @@
import { Meteor } from 'meteor/meteor';
import type { ILivechatDepartment, ILivechatInquiryRecord, IOmnichannelAgent } from '@rocket.chat/core-typings';
import { APIClient, getUserPreference } from '../../../../utils/client';
import { APIClient } from '../../../../utils/client';
import { LivechatInquiry } from '../../collections/LivechatInquiry';
import { inquiryDataStream } from './inquiry';
import { callWithErrorHandling } from '../../../../../client/lib/utils/callWithErrorHandling';
import { CustomSounds } from '../../../../custom-sounds/client/lib/CustomSounds';
const departments = new Set();
const newInquirySound = () => {
const userId = Meteor.userId();
const audioVolume = getUserPreference(userId, 'notificationsSoundVolume');
const newRoomNotification = getUserPreference(userId, 'newRoomNotification');
if (newRoomNotification !== 'none') {
CustomSounds.play(newRoomNotification, {
volume: Number((audioVolume / 100).toPrecision(2)),
});
}
};
type ILivechatInquiryWithType = ILivechatInquiryRecord & { type?: 'added' | 'removed' | 'changed' };
const events = {
added: (inquiry) => {
added: (inquiry: ILivechatInquiryWithType) => {
delete inquiry.type;
departments.has(inquiry.department) && LivechatInquiry.insert({ ...inquiry, alert: true, _updatedAt: new Date(inquiry._updatedAt) });
newInquirySound();
},
changed: (inquiry) => {
changed: (inquiry: ILivechatInquiryWithType) => {
if (inquiry.status !== 'queued' || (inquiry.department && !departments.has(inquiry.department))) {
return LivechatInquiry.remove(inquiry._id);
}
delete inquiry.type;
const saveResult = LivechatInquiry.upsert({ _id: inquiry._id }, { ...inquiry, alert: true, _updatedAt: new Date(inquiry._updatedAt) });
if (saveResult?.insertedId) {
newInquirySound();
}
LivechatInquiry.upsert({ _id: inquiry._id }, { ...inquiry, alert: true, _updatedAt: new Date(inquiry._updatedAt) });
},
removed: (inquiry) => LivechatInquiry.remove(inquiry._id),
removed: (inquiry: ILivechatInquiryWithType) => LivechatInquiry.remove(inquiry._id),
};
const updateCollection = (inquiry) => {
const updateCollection = (inquiry: ILivechatInquiryWithType) => {
if (!inquiry.type) {
return;
}
events[inquiry.type](inquiry);
};
const getInquiriesFromAPI = async () => {
const { inquiries } = await APIClient.get('/v1/livechat/inquiries.queuedForUser');
const { inquiries } = await APIClient.get('/v1/livechat/inquiries.queuedForUser', {});
return inquiries;
};
const removeListenerOfDepartment = (departmentId) => {
const removeListenerOfDepartment = (departmentId: ILivechatDepartment['_id']) => {
inquiryDataStream.removeListener(`department/${departmentId}`, updateCollection);
departments.delete(departmentId);
};
const appendListenerToDepartment = (departmentId) => {
const appendListenerToDepartment = (departmentId: ILivechatDepartment['_id']) => {
departments.add(departmentId);
inquiryDataStream.on(`department/${departmentId}`, updateCollection);
return () => removeListenerOfDepartment(departmentId);
};
const addListenerForeachDepartment = (departments = []) => {
const addListenerForeachDepartment = (departments: ILivechatDepartment['_id'][] = []) => {
const cleanupFunctions = departments.map((department) => appendListenerToDepartment(department));
return () => cleanupFunctions.forEach((cleanup) => cleanup());
};
const updateInquiries = async (inquiries = []) =>
const updateInquiries = async (inquiries: ILivechatInquiryRecord[] = []) =>
inquiries.forEach((inquiry) => LivechatInquiry.upsert({ _id: inquiry._id }, { ...inquiry, _updatedAt: new Date(inquiry._updatedAt) }));
const getAgentsDepartments = async (userId) => {
const { departments } = await APIClient.get(`/v1/livechat/agents/${userId}/departments`, { enabledDepartmentsOnly: true });
const getAgentsDepartments = async (userId: IOmnichannelAgent['_id']) => {
const { departments } = await APIClient.get(`/v1/livechat/agents/${userId}/departments`, { enabledDepartmentsOnly: 'true' });
return departments;
};
@ -78,9 +66,9 @@ const addGlobalListener = () => {
return removeGlobalListener;
};
const subscribe = async (userId) => {
const subscribe = async (userId: IOmnichannelAgent['_id']) => {
const config = await callWithErrorHandling('livechat:getRoutingConfig');
if (config && config.autoAssignAgent) {
if (config?.autoAssignAgent) {
return;
}
@ -89,23 +77,24 @@ const subscribe = async (userId) => {
// Register to all depts + public queue always to match the inquiry list returned by backend
const cleanDepartmentListeners = addListenerForeachDepartment(agentDepartments);
const globalCleanup = addGlobalListener();
const inquiriesFromAPI = (await getInquiriesFromAPI()) as unknown as ILivechatInquiryRecord[];
updateInquiries(await getInquiriesFromAPI());
await updateInquiries(inquiriesFromAPI);
return () => {
LivechatInquiry.remove({});
removeGlobalListener();
cleanDepartmentListeners && cleanDepartmentListeners();
globalCleanup && globalCleanup();
cleanDepartmentListeners?.();
globalCleanup?.();
departments.clear();
};
};
export const initializeLivechatInquiryStream = (() => {
let cleanUp;
let cleanUp: (() => void) | undefined;
return async (...args) => {
cleanUp && cleanUp();
cleanUp = await subscribe(...args);
return async (...args: any[]) => {
cleanUp?.();
cleanUp = await subscribe(...(args as [IOmnichannelAgent['_id']]));
};
})();

@ -1,4 +1,9 @@
import type { IOmnichannelAgent, IRoom, OmichannelRoutingConfig, OmnichannelSortingMechanismSettingType } from '@rocket.chat/core-typings';
import type {
IOmnichannelAgent,
OmichannelRoutingConfig,
OmnichannelSortingMechanismSettingType,
ILivechatInquiryRecord,
} from '@rocket.chat/core-typings';
import { useSafely } from '@rocket.chat/fuselage-hooks';
import { useUser, useSetting, usePermission, useMethod, useEndpoint, useStream } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
@ -9,6 +14,7 @@ import { LivechatInquiry } from '../../app/livechat/client/collections/LivechatI
import { initializeLivechatInquiryStream } from '../../app/livechat/client/lib/stream/queueManager';
import { getOmniChatSortQuery } from '../../app/livechat/lib/inquiries';
import { Notifications } from '../../app/notifications/client';
import { KonchatNotification } from '../../app/ui/client';
import { useHasLicenseModule } from '../../ee/client/hooks/useHasLicenseModule';
import { ClientLogger } from '../../lib/ClientLogger';
import type { OmnichannelContextValue } from '../contexts/OmnichannelContext';
@ -47,6 +53,7 @@ const OmnichannelProvider: FC = ({ children }) => {
const getRoutingConfig = useMethod('livechat:getRoutingConfig');
const [routeConfig, setRouteConfig] = useSafely(useState<OmichannelRoutingConfig | undefined>(undefined));
const [queueNotification, setQueueNotification] = useState(new Set());
const accessible = hasAccess && omniChannelEnabled;
const iceServersSetting: any = useSetting('WebRTC_Servers');
@ -116,7 +123,7 @@ const OmnichannelProvider: FC = ({ children }) => {
};
}, [manuallySelected, user?._id]);
const queue = useReactiveValue<IRoom[] | undefined>(
const queue = useReactiveValue<ILivechatInquiryRecord[] | undefined>(
useCallback(() => {
if (!manuallySelected) {
return undefined;
@ -135,6 +142,14 @@ const OmnichannelProvider: FC = ({ children }) => {
}, [manuallySelected, omnichannelPoolMaxIncoming, omnichannelSortingMechanism, user?._id]),
);
queue?.map(({ rid }) => {
if (queueNotification.has(rid)) {
return;
}
setQueueNotification((prev) => new Set([...prev, rid]));
return KonchatNotification.newRoom(rid);
});
const contextValue = useMemo<OmnichannelContextValue>(() => {
if (!enabled) {
return emptyContextValue;

@ -1,4 +1,4 @@
import type { IRoom, ISubscription } from '@rocket.chat/core-typings';
import type { ILivechatInquiryRecord, IRoom, ISubscription } from '@rocket.chat/core-typings';
import { useDebouncedState } from '@rocket.chat/fuselage-hooks';
import { useUserPreference, useUserSubscriptions, useSetting } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
@ -10,7 +10,7 @@ import { useQueryOptions } from './useQueryOptions';
const query = { open: { $ne: false } };
const emptyQueue: IRoom[] = [];
const emptyQueue: ILivechatInquiryRecord[] = [];
export const useRoomList = (): Array<ISubscription & IRoom> => {
const [roomList, setRoomList] = useDebouncedState<(ISubscription & IRoom)[]>([], 150);
@ -29,7 +29,7 @@ export const useRoomList = (): Array<ISubscription & IRoom> => {
const incomingCalls = useVideoConfIncomingCalls();
let queue: IRoom[] = emptyQueue;
let queue = emptyQueue;
if (inquiries.enabled) {
queue = inquiries.queue;
}

@ -1,6 +1,7 @@
import { registerModel } from '@rocket.chat/models';
import { trashCollection } from '../../../server/database/trash';
import { db } from '../../../server/database/utils';
import { LivechatInquiryRawEE } from './raw/LivechatInquiry';
registerModel('ILivechatInquiryModel', new LivechatInquiryRawEE(db));
registerModel('ILivechatInquiryModel', new LivechatInquiryRawEE(db, trashCollection));

@ -1,4 +1,4 @@
import type { IRoom } from './IRoom';
import type { ILivechatInquiryRecord } from './IInquiry';
export type OmichannelRoutingConfig = {
previewRoom: boolean;
@ -13,7 +13,7 @@ export type OmichannelRoutingConfig = {
export type Inquiries =
| {
enabled: true;
queue: Array<IRoom>;
queue: Array<ILivechatInquiryRecord>;
}
| {
enabled: false;

Loading…
Cancel
Save