From 72549d0e19ac276c6038b99056e1edebf44083e3 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Mon, 11 Sep 2023 16:50:47 -0300 Subject: [PATCH 01/60] feat: sidebar mac limit indicator --- .../RoomList/SideBarItemTemplateWithData.tsx | 2 ++ .../components/MacActivityIcon/index.tsx | 21 +++++++++++++++++++ .../rocketchat-i18n/i18n/en.i18n.json | 1 + 3 files changed, 24 insertions(+) create mode 100644 apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx index f275ff2800d8..4ea43d03f702 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx @@ -6,6 +6,7 @@ import { useLayout } from '@rocket.chat/ui-contexts'; import type { AllHTMLAttributes, ComponentType, ReactElement, ReactNode } from 'react'; import React, { memo, useMemo } from 'react'; +import { MacActivityIcon } from '../../../ee/client/omnichannel/components/MacActivityIcon'; import { useOmnichannelPriorities } from '../../../ee/client/omnichannel/hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../../../ee/client/omnichannel/priorities/PriorityIcon'; import { RoomIcon } from '../../components/RoomIcon'; @@ -171,6 +172,7 @@ function SideBarItemTemplateWithData({ )} {isOmnichannelRoom(room) && isPriorityEnabled && } + {isOmnichannelRoom(room) && } ); diff --git a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx new file mode 100644 index 000000000000..ca2697b6febb --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx @@ -0,0 +1,21 @@ +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { Icon } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +type MacActivityIconProps = { + room: IOmnichannelRoom; +}; + +const isRoomActive = (_: IOmnichannelRoom): boolean => { + return false; +}; + +export const MacActivityIcon = ({ room }: MacActivityIconProps) => { + const t = useTranslation(); + const isActive = isRoomActive(room); + + return !isActive ? ( + + ) : null; +}; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index d86de4957f3f..2fb7866b9e06 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2112,6 +2112,7 @@ "error-duplicated-sla": "An SLA with the same name or due time already exists", "error-contact-sent-last-message-so-cannot-place-on-hold": "You cannot place chat on-hold, when the Contact has sent the last message", "error-unserved-rooms-cannot-be-placed-onhold": "Room cannot be placed on hold before being served", + "Workspace_exceeded_MAC_limit_disclaimer": "The workspace has exceeded the monthly limit of active contacts. Talk to your workspace admin to address this issue.", "You_do_not_have_permission_to_do_this": "You do not have permission to do this", "You_do_not_have_permission_to_execute_this_command": "You do not have enough permissions to execute command: `/{{command}}`", "Errors_and_Warnings": "Errors and Warnings", From c7593e1505e2ec7ea5ca2eaddc589bcea86f4cc2 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Wed, 13 Sep 2023 11:50:34 -0300 Subject: [PATCH 02/60] feat: workspace over mac limit sidebar section --- apps/meteor/client/sidebar/Sidebar.tsx | 3 +++ .../sidebar/sections/OverMacLimitSection.tsx | 21 +++++++++++++++++++ .../rocketchat-i18n/i18n/en.i18n.json | 1 + 3 files changed, 25 insertions(+) create mode 100644 apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx diff --git a/apps/meteor/client/sidebar/Sidebar.tsx b/apps/meteor/client/sidebar/Sidebar.tsx index 84c63eac01be..a356cb46f0b9 100644 --- a/apps/meteor/client/sidebar/Sidebar.tsx +++ b/apps/meteor/client/sidebar/Sidebar.tsx @@ -9,6 +9,7 @@ import SidebarRoomList from './RoomList'; import SidebarFooter from './footer'; import SidebarHeader from './header'; import OmnichannelSection from './sections/OmnichannelSection'; +import { OverMacLimitSection } from './sections/OverMacLimitSection'; import StatusDisabledSection from './sections/StatusDisabledSection'; const Sidebar = () => { @@ -19,6 +20,7 @@ const Sidebar = () => { const { sidebar } = useLayout(); const [bannerDismissed, setBannerDismissed] = useSessionStorage('presence_cap_notifier', false); const presenceDisabled = useSetting('Presence_broadcast_disabled'); + const isOverMacLimit = true; // TODO: Implement MAC limit logic const sideBarBackground = css` background-color: ${Palette.surface['surface-tint']}; @@ -46,6 +48,7 @@ const Sidebar = () => { {presenceDisabled && !bannerDismissed && setBannerDismissed(true)} />} {showOmnichannel && } + {isOverMacLimit && } diff --git a/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx b/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx new file mode 100644 index 000000000000..53cbd0340339 --- /dev/null +++ b/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx @@ -0,0 +1,21 @@ +import { Icon, SidebarBanner } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React from 'react'; + +export const OverMacLimitSection = (): ReactElement => { + const t = useTranslation(); + + const handleClick = () => { + window.open('https://rocket.chat/pricing', '_blank'); + }; + + return ( + } + onClick={handleClick} + /> + ); +}; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 2fb7866b9e06..db1905f6a630 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2115,6 +2115,7 @@ "Workspace_exceeded_MAC_limit_disclaimer": "The workspace has exceeded the monthly limit of active contacts. Talk to your workspace admin to address this issue.", "You_do_not_have_permission_to_do_this": "You do not have permission to do this", "You_do_not_have_permission_to_execute_this_command": "You do not have enough permissions to execute command: `/{{command}}`", + "You_have_reached_the_limit_active_costumers_this_month": "You have reached the limit of active customers this month", "Errors_and_Warnings": "Errors and Warnings", "Esc_to": "Esc to", "Estimated_wait_time": "Estimated wait time", From a0e9aa37059bca3ef3ba3db9e0e0bc591d96ba67 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 14 Sep 2023 10:03:16 -0300 Subject: [PATCH 03/60] feat: added over the mac limit banner to current chats --- .../omnichannel/currentChats/CurrentChatsPage.tsx | 14 +++++++++++++- .../packages/rocketchat-i18n/i18n/en.i18n.json | 2 ++ .../packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index d82498ff1e50..e09fa18ab668 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -1,4 +1,4 @@ -import { Pagination } from '@rocket.chat/fuselage'; +import { Banner, Icon, Pagination } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { GETLivechatRoomsParams } from '@rocket.chat/rest-typings'; import { usePermission, useTranslation } from '@rocket.chat/ui-contexts'; @@ -118,6 +118,7 @@ const currentChatQuery: useQueryType = ( }; const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: string) => void }): ReactElement => { + const isWorkspaceOverMacLimit = true; // TODO: Implement MAC limit logic const { sortBy, sortDirection, setSort } = useSort<'fname' | 'departmentId' | 'servedBy' | 'priorityWeight' | 'ts' | 'lm' | 'open'>( 'ts', 'desc', @@ -301,6 +302,17 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s hasCustomFields={hasCustomFields} /> )} + {isWorkspaceOverMacLimit && ( + } + title={t('The_workspace_has_exceeded_the_monthly_limit_of_active_contacts')} + style={{ marginBlock: '2rem' }} + > + {t('Talk_to_your_workspace_admin_to_address_this_issue')} + + )} {isSuccess && data?.rooms.length === 0 && queryHasChanged && } {isSuccess && data?.rooms.length === 0 && !queryHasChanged && ( Date: Thu, 14 Sep 2023 13:59:07 -0300 Subject: [PATCH 04/60] fix: MacActivityIcon now expects a visitor instead of a room --- .../sidebar/RoomList/SideBarItemTemplateWithData.tsx | 2 +- .../views/omnichannel/currentChats/CurrentChatsPage.tsx | 5 +++-- .../omnichannel/components/MacActivityIcon/index.tsx | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx index 4ea43d03f702..e27311687a1b 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx @@ -172,7 +172,7 @@ function SideBarItemTemplateWithData({ )} {isOmnichannelRoom(room) && isPriorityEnabled && } - {isOmnichannelRoom(room) && } + {isOmnichannelRoom(room) && } ); diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index e09fa18ab668..fd729c9d5900 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -7,6 +7,7 @@ import moment from 'moment'; import type { ComponentProps, ReactElement } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react'; +import { MacActivityIcon } from '../../../../ee/client/omnichannel/components/MacActivityIcon'; import { useOmnichannelPriorities } from '../../../../ee/client/omnichannel/hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../../../../ee/client/omnichannel/priorities/PriorityIcon'; import GenericNoResults from '../../../components/GenericNoResults'; @@ -166,7 +167,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s }); const renderRow = useCallback( - ({ _id, fname, servedBy, ts, lm, department, open, onHold, priorityWeight }) => { + ({ _id, fname, servedBy, ts, lm, department, open, onHold, priorityWeight, v }) => { const getStatusText = (open: boolean, onHold: boolean): string => { if (!open) return t('Closed'); return onHold ? t('On_Hold_Chats') : t('Open'); @@ -195,7 +196,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s {moment(lm).format('L LTS')} - {getStatusText(open, onHold)} + {getStatusText(open, onHold)} {canRemoveClosedChats && !open && } diff --git a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx index ca2697b6febb..6a6ffa95e069 100644 --- a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx +++ b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx @@ -1,19 +1,20 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Icon } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; import React from 'react'; type MacActivityIconProps = { - room: IOmnichannelRoom; + visitor: IOmnichannelRoom['v']; }; -const isRoomActive = (_: IOmnichannelRoom): boolean => { +const isRoomActive = (_: IOmnichannelRoom['v']): boolean => { return false; }; -export const MacActivityIcon = ({ room }: MacActivityIconProps) => { +export const MacActivityIcon = ({ visitor }: MacActivityIconProps): ReactElement | null => { const t = useTranslation(); - const isActive = isRoomActive(room); + const isActive = isRoomActive(visitor); return !isActive ? ( From 0119206d3a314cdf9f033645b06f88934d9a9823 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 14 Sep 2023 15:01:42 -0300 Subject: [PATCH 05/60] chore: renamed MacActivityIcon file --- .../client/sidebar/RoomList/SideBarItemTemplateWithData.tsx | 2 +- .../client/views/omnichannel/currentChats/CurrentChatsPage.tsx | 2 +- .../MacActivityIcon/{index.tsx => MacActivityIcon.tsx} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename apps/meteor/ee/client/omnichannel/components/MacActivityIcon/{index.tsx => MacActivityIcon.tsx} (100%) diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx index e27311687a1b..2599a5bf4ac0 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx @@ -6,7 +6,7 @@ import { useLayout } from '@rocket.chat/ui-contexts'; import type { AllHTMLAttributes, ComponentType, ReactElement, ReactNode } from 'react'; import React, { memo, useMemo } from 'react'; -import { MacActivityIcon } from '../../../ee/client/omnichannel/components/MacActivityIcon'; +import { MacActivityIcon } from '../../../ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon'; import { useOmnichannelPriorities } from '../../../ee/client/omnichannel/hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../../../ee/client/omnichannel/priorities/PriorityIcon'; import { RoomIcon } from '../../components/RoomIcon'; diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index fd729c9d5900..3865cb49d678 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -7,7 +7,7 @@ import moment from 'moment'; import type { ComponentProps, ReactElement } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react'; -import { MacActivityIcon } from '../../../../ee/client/omnichannel/components/MacActivityIcon'; +import { MacActivityIcon } from '../../../../ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon'; import { useOmnichannelPriorities } from '../../../../ee/client/omnichannel/hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../../../../ee/client/omnichannel/priorities/PriorityIcon'; import GenericNoResults from '../../../components/GenericNoResults'; diff --git a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx similarity index 100% rename from apps/meteor/ee/client/omnichannel/components/MacActivityIcon/index.tsx rename to apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx From cdd41ed94da8c8a62bc07590370516950a420eae Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 14 Sep 2023 15:02:05 -0300 Subject: [PATCH 06/60] feat: over the mac limit read-only composer --- .../composer/ComposerOmnichannel/ComposerOmnichannel.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx b/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx index 649f9a9a4264..9d02c5300cf3 100644 --- a/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx +++ b/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx @@ -22,8 +22,14 @@ const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement => { const isSameAgent = servedBy?._id === userId; + const isRoomActive = false; // TODO: Infer from room visitor + if (!open) { - return {t('This_conversation_is_already_closed')}; + return {t('This_conversation_is_already_closed')}; + } + + if (!isRoomActive) { + return {t('Workspace_exceeded_MAC_limit_disclaimer')}; } if (onHold) { From 6d1c168c51ea679314cc9e9a5846117f9dfb2587 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 14 Sep 2023 15:33:51 -0300 Subject: [PATCH 07/60] feat: hidding room actions when over mac limit --- .../QuickActions/hooks/useQuickActions.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx index 54ce71bd80ec..5e26e8275b57 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx @@ -311,19 +311,20 @@ export const useQuickActions = (): { const canCloseRoom = usePermission('close-livechat-room'); const canCloseOthersRoom = usePermission('close-others-livechat-room'); const canPlaceChatOnHold = Boolean(!room.onHold && room.u && !(room as any).lastMessage?.token && manualOnHoldAllowed); + const isRoomActive = false; const hasPermissionButtons = (id: string): boolean => { switch (id) { case QuickActionsEnum.MoveQueue: - return !!roomOpen && canMoveQueue; + return isRoomActive && !!roomOpen && canMoveQueue; case QuickActionsEnum.ChatForward: - return !!roomOpen && canForwardGuest; + return isRoomActive && !!roomOpen && canForwardGuest; case QuickActionsEnum.Transcript: - return canSendTranscriptEmail || (hasLicense && canSendTranscriptPDF); + return isRoomActive && (canSendTranscriptEmail || (hasLicense && canSendTranscriptPDF)); case QuickActionsEnum.TranscriptEmail: - return canSendTranscriptEmail; + return isRoomActive && canSendTranscriptEmail; case QuickActionsEnum.TranscriptPDF: - return hasLicense && canSendTranscriptPDF; + return hasLicense && isRoomActive && canSendTranscriptPDF && isRoomActive; case QuickActionsEnum.CloseChat: return !!roomOpen && (canCloseRoom || canCloseOthersRoom); case QuickActionsEnum.OnHoldChat: From d567ce919fb98b86bf3c98a3bafee1069cb35e88 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 14 Sep 2023 16:01:09 -0300 Subject: [PATCH 08/60] feat: added mac limit hooks --- .../hooks/omnichannel/useIsOverMacLimit.tsx | 9 +++++++++ .../client/hooks/omnichannel/useIsRoomActive.tsx | 14 ++++++++++++++ .../RoomList/SideBarItemTemplateWithData.tsx | 2 +- apps/meteor/client/sidebar/Sidebar.tsx | 5 +++-- .../currentChats/CurrentChatsPage.tsx | 8 +++++--- .../QuickActions/hooks/useQuickActions.tsx | 3 ++- .../ComposerOmnichannel/ComposerOmnichannel.tsx | 6 ++++-- .../MacActivityIcon/MacActivityIcon.tsx | 16 ++++++++-------- 8 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx create mode 100644 apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx new file mode 100644 index 000000000000..9c28764d4707 --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -0,0 +1,9 @@ +// @ts-nocheck +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; + +export const useIsOverMacLimit = () => { + const getMacLimit = useEndpoint('GET', '/v1/livechat/mac'); // TODO: add the correct endpoint + const { data: isOverMacLimit } = useQuery(['omnichannel', '/v1/livechat/mac'], () => getMacLimit()); + return isOverMacLimit; +}; diff --git a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx new file mode 100644 index 000000000000..cfae1a44bf10 --- /dev/null +++ b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx @@ -0,0 +1,14 @@ +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { useMemo } from 'react'; + +export const useIsRoomActive = (room: IOmnichannelRoom) => { + // @ts-ignore + const { activity } = room.v; // TODO: add activity to IOmnichannelRoom['v'] + const isContactActive = useMemo(() => { + const date = new Date(); + const currentPeriod = `${date.getFullYear()}-${date.getMonth() + 1}`; + return activity.includes(currentPeriod); + }, [activity]); + + return isContactActive; +}; diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx index 2599a5bf4ac0..2959a6d4cf9c 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx @@ -172,7 +172,7 @@ function SideBarItemTemplateWithData({ )} {isOmnichannelRoom(room) && isPriorityEnabled && } - {isOmnichannelRoom(room) && } + {isOmnichannelRoom(room) && } ); diff --git a/apps/meteor/client/sidebar/Sidebar.tsx b/apps/meteor/client/sidebar/Sidebar.tsx index a356cb46f0b9..7d095331e353 100644 --- a/apps/meteor/client/sidebar/Sidebar.tsx +++ b/apps/meteor/client/sidebar/Sidebar.tsx @@ -4,6 +4,7 @@ import { useSessionStorage } from '@rocket.chat/fuselage-hooks'; import { useLayout, useSetting, useUserPreference } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; +import { useIsOverMacLimit } from '../hooks/omnichannel/useIsOverMacLimit'; import { useOmnichannelEnabled } from '../hooks/omnichannel/useOmnichannelEnabled'; import SidebarRoomList from './RoomList'; import SidebarFooter from './footer'; @@ -20,7 +21,7 @@ const Sidebar = () => { const { sidebar } = useLayout(); const [bannerDismissed, setBannerDismissed] = useSessionStorage('presence_cap_notifier', false); const presenceDisabled = useSetting('Presence_broadcast_disabled'); - const isOverMacLimit = true; // TODO: Implement MAC limit logic + const isWorkspaceOverMacLimit = useIsOverMacLimit(); const sideBarBackground = css` background-color: ${Palette.surface['surface-tint']}; @@ -48,7 +49,7 @@ const Sidebar = () => { {presenceDisabled && !bannerDismissed && setBannerDismissed(true)} />} {showOmnichannel && } - {isOverMacLimit && } + {isWorkspaceOverMacLimit && } diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index 3865cb49d678..54bc2f0bb8ad 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -23,6 +23,7 @@ import { import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; import Page from '../../../components/Page'; +import { useIsOverMacLimit } from '../../../hooks/omnichannel/useIsOverMacLimit'; import CustomFieldsList from './CustomFieldsList'; import FilterByText from './FilterByText'; import RemoveChatButton from './RemoveChatButton'; @@ -119,7 +120,7 @@ const currentChatQuery: useQueryType = ( }; const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: string) => void }): ReactElement => { - const isWorkspaceOverMacLimit = true; // TODO: Implement MAC limit logic + const isWorkspaceOverMacLimit = useIsOverMacLimit(); const { sortBy, sortDirection, setSort } = useSort<'fname' | 'departmentId' | 'servedBy' | 'priorityWeight' | 'ts' | 'lm' | 'open'>( 'ts', 'desc', @@ -167,7 +168,8 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s }); const renderRow = useCallback( - ({ _id, fname, servedBy, ts, lm, department, open, onHold, priorityWeight, v }) => { + (room) => { + const { _id, fname, servedBy, ts, lm, department, open, onHold, priorityWeight } = room; const getStatusText = (open: boolean, onHold: boolean): string => { if (!open) return t('Closed'); return onHold ? t('On_Hold_Chats') : t('Open'); @@ -196,7 +198,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s {moment(lm).format('L LTS')} - {getStatusText(open, onHold)} + {getStatusText(open, onHold)} {canRemoveClosedChats && !open && } diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx index 5e26e8275b57..af260d8f5526 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx @@ -22,6 +22,7 @@ import CloseChatModalData from '../../../../../../components/Omnichannel/modals/ import ForwardChatModal from '../../../../../../components/Omnichannel/modals/ForwardChatModal'; import ReturnChatQueueModal from '../../../../../../components/Omnichannel/modals/ReturnChatQueueModal'; import TranscriptModal from '../../../../../../components/Omnichannel/modals/TranscriptModal'; +import { useIsRoomActive } from '../../../../../../hooks/omnichannel/useIsRoomActive'; import { useOmnichannelRouteConfig } from '../../../../../../hooks/omnichannel/useOmnichannelRouteConfig'; import { quickActionHooks } from '../../../../../../ui'; import { useOmnichannelRoom } from '../../../../contexts/RoomContext'; @@ -311,7 +312,7 @@ export const useQuickActions = (): { const canCloseRoom = usePermission('close-livechat-room'); const canCloseOthersRoom = usePermission('close-others-livechat-room'); const canPlaceChatOnHold = Boolean(!room.onHold && room.u && !(room as any).lastMessage?.token && manualOnHoldAllowed); - const isRoomActive = false; + const isRoomActive = useIsRoomActive(room); const hasPermissionButtons = (id: string): boolean => { switch (id) { diff --git a/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx b/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx index 9d02c5300cf3..efb17d3335bc 100644 --- a/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx +++ b/apps/meteor/client/views/room/composer/ComposerOmnichannel/ComposerOmnichannel.tsx @@ -3,6 +3,7 @@ import { useTranslation, useUserId } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; +import { useIsRoomActive } from '../../../../hooks/omnichannel/useIsRoomActive'; import { useOmnichannelRoom, useUserIsSubscribed } from '../../contexts/RoomContext'; import type { ComposerMessageProps } from '../ComposerMessage'; import ComposerMessage from '../ComposerMessage'; @@ -11,7 +12,8 @@ import { ComposerOmnichannelJoin } from './ComposerOmnichannelJoin'; import { ComposerOmnichannelOnHold } from './ComposerOmnichannelOnHold'; const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement => { - const { servedBy, queuedAt, open, onHold } = useOmnichannelRoom(); + const room = useOmnichannelRoom(); + const { servedBy, queuedAt, open, onHold } = room; const userId = useUserId(); const isSubscribed = useUserIsSubscribed(); @@ -22,7 +24,7 @@ const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement => { const isSameAgent = servedBy?._id === userId; - const isRoomActive = false; // TODO: Infer from room visitor + const isRoomActive = useIsRoomActive(room); if (!open) { return {t('This_conversation_is_already_closed')}; diff --git a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx index 6a6ffa95e069..87be8eaca1c1 100644 --- a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx +++ b/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx @@ -4,19 +4,19 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; -type MacActivityIconProps = { - visitor: IOmnichannelRoom['v']; -}; +import { useIsRoomActive } from '../../../../../client/hooks/omnichannel/useIsRoomActive'; +import { useOmnichannel } from '../../../../../client/hooks/omnichannel/useOmnichannel'; -const isRoomActive = (_: IOmnichannelRoom['v']): boolean => { - return false; +type MacActivityIconProps = { + room: IOmnichannelRoom; }; -export const MacActivityIcon = ({ visitor }: MacActivityIconProps): ReactElement | null => { +export const MacActivityIcon = ({ room }: MacActivityIconProps): ReactElement | null => { const t = useTranslation(); - const isActive = isRoomActive(visitor); + const { isEnterprise } = useOmnichannel(); + const isRoomActive = useIsRoomActive(room); - return !isActive ? ( + return isEnterprise && !isRoomActive ? ( ) : null; }; From 57fe5202ce5f19a6cb37edb0de13bc492442822b Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Fri, 15 Sep 2023 10:19:43 -0300 Subject: [PATCH 09/60] fix: added default value to activity --- apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx index cfae1a44bf10..b57a35b0ce28 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; export const useIsRoomActive = (room: IOmnichannelRoom) => { // @ts-ignore - const { activity } = room.v; // TODO: add activity to IOmnichannelRoom['v'] + const { activity = [] } = room.v; // TODO: add activity to IOmnichannelRoom['v'] const isContactActive = useMemo(() => { const date = new Date(); const currentPeriod = `${date.getFullYear()}-${date.getMonth() + 1}`; From 06280a4bd42fa4ea72178caffe1eb9ab5ac63abb Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 19 Sep 2023 09:29:11 -0300 Subject: [PATCH 10/60] chore: renamed MacActivityIcon to RoomActivityIcon --- .../client/sidebar/RoomList/SideBarItemTemplateWithData.tsx | 4 ++-- .../views/omnichannel/currentChats/CurrentChatsPage.tsx | 4 ++-- .../MacActivityIcon.tsx => RoomActivityIcon/index.tsx} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename apps/meteor/ee/client/omnichannel/components/{MacActivityIcon/MacActivityIcon.tsx => RoomActivityIcon/index.tsx} (86%) diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx index 2959a6d4cf9c..ea8b6fbf00bc 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx @@ -6,7 +6,7 @@ import { useLayout } from '@rocket.chat/ui-contexts'; import type { AllHTMLAttributes, ComponentType, ReactElement, ReactNode } from 'react'; import React, { memo, useMemo } from 'react'; -import { MacActivityIcon } from '../../../ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon'; +import { RoomActivityIcon } from '../../../ee/client/omnichannel/components/RoomActivityIcon'; import { useOmnichannelPriorities } from '../../../ee/client/omnichannel/hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../../../ee/client/omnichannel/priorities/PriorityIcon'; import { RoomIcon } from '../../components/RoomIcon'; @@ -172,7 +172,7 @@ function SideBarItemTemplateWithData({ )} {isOmnichannelRoom(room) && isPriorityEnabled && } - {isOmnichannelRoom(room) && } + {isOmnichannelRoom(room) && } ); diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index 54bc2f0bb8ad..0194290432cd 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -7,7 +7,7 @@ import moment from 'moment'; import type { ComponentProps, ReactElement } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react'; -import { MacActivityIcon } from '../../../../ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon'; +import { RoomActivityIcon } from '../../../../ee/client/omnichannel/components/RoomActivityIcon'; import { useOmnichannelPriorities } from '../../../../ee/client/omnichannel/hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../../../../ee/client/omnichannel/priorities/PriorityIcon'; import GenericNoResults from '../../../components/GenericNoResults'; @@ -198,7 +198,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s {moment(lm).format('L LTS')} - {getStatusText(open, onHold)} + {getStatusText(open, onHold)} {canRemoveClosedChats && !open && } diff --git a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx b/apps/meteor/ee/client/omnichannel/components/RoomActivityIcon/index.tsx similarity index 86% rename from apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx rename to apps/meteor/ee/client/omnichannel/components/RoomActivityIcon/index.tsx index 87be8eaca1c1..c5c1fbdacc0d 100644 --- a/apps/meteor/ee/client/omnichannel/components/MacActivityIcon/MacActivityIcon.tsx +++ b/apps/meteor/ee/client/omnichannel/components/RoomActivityIcon/index.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { useIsRoomActive } from '../../../../../client/hooks/omnichannel/useIsRoomActive'; import { useOmnichannel } from '../../../../../client/hooks/omnichannel/useOmnichannel'; -type MacActivityIconProps = { +type RoomActivityIconProps = { room: IOmnichannelRoom; }; -export const MacActivityIcon = ({ room }: MacActivityIconProps): ReactElement | null => { +export const RoomActivityIcon = ({ room }: RoomActivityIconProps): ReactElement | null => { const t = useTranslation(); const { isEnterprise } = useOmnichannel(); const isRoomActive = useIsRoomActive(room); From 4ea725bd5b154c061800644db2b6302d0c1f4d5f Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 19 Sep 2023 09:31:44 -0300 Subject: [PATCH 11/60] chore: return always true for over mac limit (test only) --- apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index 9c28764d4707..21e534c962da 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -1,9 +1,9 @@ // @ts-nocheck -import { useEndpoint } from '@rocket.chat/ui-contexts'; +// import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -export const useIsOverMacLimit = () => { - const getMacLimit = useEndpoint('GET', '/v1/livechat/mac'); // TODO: add the correct endpoint +export const useIsOverMacLimit = (): boolean => { + const getMacLimit = () => Promise.resolve(true); // useEndpoint('GET', '/v1/livechat/mac'); // TODO: add the correct endpoint const { data: isOverMacLimit } = useQuery(['omnichannel', '/v1/livechat/mac'], () => getMacLimit()); return isOverMacLimit; }; From 1515984b3bd50a18d691857f4fe354f6c50169dd Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 19 Sep 2023 09:32:22 -0300 Subject: [PATCH 12/60] feat: added highlight red dot --- .../sidebar/header/actions/Administration.tsx | 14 ++++++-- .../hooks/useAdministrationHighlight.tsx | 32 +++++++++++++++++++ .../actions/hooks/useAdministrationItems.tsx | 2 ++ .../{sidebarItems.ts => sidebarItems.tsx} | 4 +++ .../components/OmnichannelHighlight/index.tsx | 14 ++++++++ .../hooks/useCurrentChatsHighlight.ts | 29 +++++++++++++++++ .../hooks/useOmnichannelHighlight.tsx | 7 ++++ 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useAdministrationHighlight.tsx rename apps/meteor/client/views/omnichannel/{sidebarItems.ts => sidebarItems.tsx} (93%) create mode 100644 apps/meteor/ee/client/omnichannel/components/OmnichannelHighlight/index.tsx create mode 100644 apps/meteor/ee/client/omnichannel/hooks/useCurrentChatsHighlight.ts create mode 100644 apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx diff --git a/apps/meteor/client/sidebar/header/actions/Administration.tsx b/apps/meteor/client/sidebar/header/actions/Administration.tsx index d2e51f191370..4cb713ff2557 100644 --- a/apps/meteor/client/sidebar/header/actions/Administration.tsx +++ b/apps/meteor/client/sidebar/header/actions/Administration.tsx @@ -4,14 +4,24 @@ import type { HTMLAttributes, VFC } from 'react'; import React from 'react'; import GenericMenu from '../../../components/GenericMenu/GenericMenu'; +import { useAdministrationHighlight } from './hooks/useAdministrationHighlight'; import { useAdministrationMenu } from './hooks/useAdministrationMenu'; -const Administration: VFC, 'is'>> = (props) => { +const Administration: VFC, 'is'>> = ({ className, ...props }) => { const t = useTranslation(); const sections = useAdministrationMenu(); + const { className: highlightDot } = useAdministrationHighlight(); - return ; + return ( + + ); }; export default Administration; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationHighlight.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationHighlight.tsx new file mode 100644 index 000000000000..465f0031e0af --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationHighlight.tsx @@ -0,0 +1,32 @@ +import { css } from '@rocket.chat/css-in-js'; +import { Palette } from '@rocket.chat/fuselage'; + +import { useOmnichannelHighlight } from '../../../../../ee/client/omnichannel/hooks/useOmnichannelHighlight'; + +const highlightDot = css` + position: relative; + + &::after { + content: ''; + position: absolute; + top: 0; + right: 0; + display: block; + flex: none; + width: 8px; + height: 8px; + background-color: ${Palette.badge['badge-background-level-4'].toString()}; + border-radius: 50%; + } +`; + +export const useAdministrationHighlight = () => { + const { isHighlit: isOmnichannelHighlit } = useOmnichannelHighlight(); + + return { + isHighlit: isOmnichannelHighlit, + get className() { + return this.isHighlit ? highlightDot : undefined; + }, + }; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx index 531545b20a43..bbc94426db26 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx @@ -9,6 +9,7 @@ import { } from '@rocket.chat/ui-contexts'; import React from 'react'; +import { OmnichannelHighlight } from '../../../../../ee/client/omnichannel/components/OmnichannelHighlight'; import type { UpgradeTabVariant } from '../../../../../lib/upgradeTab'; import { getUpgradeTabLabel, isFullyFeature } from '../../../../../lib/upgradeTab'; import Emoji from '../../../../components/Emoji'; @@ -86,6 +87,7 @@ export const useAdministrationItems = (): GenericMenuItemProps[] => { content: t('Omnichannel'), icon: 'headset', onClick: () => router.navigate('/omnichannel/current'), + addon: , }; const upgradeItem: GenericMenuItemProps = { diff --git a/apps/meteor/client/views/omnichannel/sidebarItems.ts b/apps/meteor/client/views/omnichannel/sidebarItems.tsx similarity index 93% rename from apps/meteor/client/views/omnichannel/sidebarItems.ts rename to apps/meteor/client/views/omnichannel/sidebarItems.tsx index 7942764a8b89..900d76618e5d 100644 --- a/apps/meteor/client/views/omnichannel/sidebarItems.ts +++ b/apps/meteor/client/views/omnichannel/sidebarItems.tsx @@ -1,4 +1,7 @@ +import React from 'react'; + import { hasPermission } from '../../../app/authorization/client'; +import { OmnichannelHighlight } from '../../../ee/client/omnichannel/components/OmnichannelHighlight'; import { createSidebarItems } from '../../lib/createSidebarItems'; export const { @@ -11,6 +14,7 @@ export const { href: '/omnichannel/current', icon: 'message', i18nLabel: 'Current_Chats', + badge: () => , permissionGranted: (): boolean => hasPermission('view-livechat-current-chats'), }, { diff --git a/apps/meteor/ee/client/omnichannel/components/OmnichannelHighlight/index.tsx b/apps/meteor/ee/client/omnichannel/components/OmnichannelHighlight/index.tsx new file mode 100644 index 000000000000..561112b97e56 --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/components/OmnichannelHighlight/index.tsx @@ -0,0 +1,14 @@ +import { Box, Palette } from '@rocket.chat/fuselage'; +import React from 'react'; + +import { useOmnichannelHighlight } from '../../hooks/useOmnichannelHighlight'; + +export const OmnichannelHighlight = () => { + const { isHighlit } = useOmnichannelHighlight(); + + if (!isHighlit) { + return null; + } + + return ; +}; diff --git a/apps/meteor/ee/client/omnichannel/hooks/useCurrentChatsHighlight.ts b/apps/meteor/ee/client/omnichannel/hooks/useCurrentChatsHighlight.ts new file mode 100644 index 000000000000..f6fc55317349 --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/hooks/useCurrentChatsHighlight.ts @@ -0,0 +1,29 @@ +import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { useRouter } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; + +import { useIsOverMacLimit } from '../../../../client/hooks/omnichannel/useIsOverMacLimit'; + +export const useCurrentChatsHighlight = () => { + const isOverMacLimit = useIsOverMacLimit(); + const [isHighlit, setHighlight] = useLocalStorage('omnichannel-current-chats-highlight', isOverMacLimit); + const router = useRouter(); + + useEffect(() => { + if (!isHighlit) { + return; + } + + return router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'omnichannel-current-chats') { + return; + } + + setHighlight(false); + }); + }, [isHighlit, router, setHighlight]); + + return { + isHighlit, + }; +}; diff --git a/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx b/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx new file mode 100644 index 000000000000..43601125e85c --- /dev/null +++ b/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx @@ -0,0 +1,7 @@ +import { useCurrentChatsHighlight } from './useCurrentChatsHighlight'; + +export const useOmnichannelHighlight = () => { + const { isHighlit } = useCurrentChatsHighlight(); + + return { isHighlit }; +}; From 4b769e54b4068303160b2d341d0aac2b13b9bea2 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Thu, 5 Oct 2023 16:18:21 -0300 Subject: [PATCH 13/60] chore: added mac endpoints and stream --- .../hooks/omnichannel/useIsOverMacLimit.tsx | 21 +++++++++++++------ .../hooks/omnichannel/useIsRoomActive.tsx | 13 ++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index 21e534c962da..f09cb858bcfd 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -1,9 +1,18 @@ -// @ts-nocheck -// import { useEndpoint } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useEndpoint, useStream } from '@rocket.chat/ui-contexts'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useEffect } from 'react'; export const useIsOverMacLimit = (): boolean => { - const getMacLimit = () => Promise.resolve(true); // useEndpoint('GET', '/v1/livechat/mac'); // TODO: add the correct endpoint - const { data: isOverMacLimit } = useQuery(['omnichannel', '/v1/livechat/mac'], () => getMacLimit()); - return isOverMacLimit; + const queryClient = useQueryClient(); + const notifyLogged = useStream('notify-logged'); + const getMacLimit = useEndpoint('GET', '/v1/omnichannel/mac/check'); + const { data: isOverMacLimit } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); + + useEffect(() => { + return notifyLogged(`mac.limit`, ({ limitReached }) => { + limitReached && queryClient.invalidateQueries(['/v1/omnichannel/mac/check']); + }); + }, [notifyLogged, queryClient]); + + return !!isOverMacLimit; }; diff --git a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx index b57a35b0ce28..51c6f8d20f3f 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx @@ -1,12 +1,13 @@ -import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { IOmnichannelGenericRoom } from '@rocket.chat/core-typings'; import { useMemo } from 'react'; -export const useIsRoomActive = (room: IOmnichannelRoom) => { - // @ts-ignore - const { activity = [] } = room.v; // TODO: add activity to IOmnichannelRoom['v'] +const getPeriod = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + +export const useIsRoomActive = (room: IOmnichannelGenericRoom) => { + const { activity = [] } = room.v; + const isContactActive = useMemo(() => { - const date = new Date(); - const currentPeriod = `${date.getFullYear()}-${date.getMonth() + 1}`; + const currentPeriod = getPeriod(new Date()); return activity.includes(currentPeriod); }, [activity]); From c6e3b3118cd96d23670523abd08c71189f27cc41 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Fri, 6 Oct 2023 17:44:23 -0300 Subject: [PATCH 14/60] fix: reading the endpoint incorrectly --- apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index f09cb858bcfd..fe9c14b93019 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -6,7 +6,7 @@ export const useIsOverMacLimit = (): boolean => { const queryClient = useQueryClient(); const notifyLogged = useStream('notify-logged'); const getMacLimit = useEndpoint('GET', '/v1/omnichannel/mac/check'); - const { data: isOverMacLimit } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); + const { data: { onLimit: isOverMacLimit = false } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); useEffect(() => { return notifyLogged(`mac.limit`, ({ limitReached }) => { From db6f60ff1d0ac2a66a9f495e473a5146162cc673 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Fri, 6 Oct 2023 18:06:55 -0300 Subject: [PATCH 15/60] fix: incomplete validation on useIsRoomActive --- .../client/hooks/omnichannel/useIsOverMacLimit.tsx | 13 ++----------- .../client/hooks/omnichannel/useIsRoomActive.tsx | 7 +++++-- .../meteor/client/providers/OmnichannelProvider.tsx | 6 ++++++ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index fe9c14b93019..bf558c8d9241 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -1,18 +1,9 @@ -import { useEndpoint, useStream } from '@rocket.chat/ui-contexts'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useEffect } from 'react'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; export const useIsOverMacLimit = (): boolean => { - const queryClient = useQueryClient(); - const notifyLogged = useStream('notify-logged'); const getMacLimit = useEndpoint('GET', '/v1/omnichannel/mac/check'); const { data: { onLimit: isOverMacLimit = false } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); - useEffect(() => { - return notifyLogged(`mac.limit`, ({ limitReached }) => { - limitReached && queryClient.invalidateQueries(['/v1/omnichannel/mac/check']); - }); - }, [notifyLogged, queryClient]); - return !!isOverMacLimit; }; diff --git a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx index 51c6f8d20f3f..7d87472f0753 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsRoomActive.tsx @@ -1,15 +1,18 @@ import type { IOmnichannelGenericRoom } from '@rocket.chat/core-typings'; import { useMemo } from 'react'; +import { useIsOverMacLimit } from './useIsOverMacLimit'; + const getPeriod = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; export const useIsRoomActive = (room: IOmnichannelGenericRoom) => { + const isOverMacLimit = useIsOverMacLimit(); const { activity = [] } = room.v; const isContactActive = useMemo(() => { const currentPeriod = getPeriod(new Date()); - return activity.includes(currentPeriod); - }, [activity]); + return !isOverMacLimit || activity.includes(currentPeriod); + }, [activity, isOverMacLimit]); return isContactActive; }; diff --git a/apps/meteor/client/providers/OmnichannelProvider.tsx b/apps/meteor/client/providers/OmnichannelProvider.tsx index d9eee0ac4b00..2d395186f739 100644 --- a/apps/meteor/client/providers/OmnichannelProvider.tsx +++ b/apps/meteor/client/providers/OmnichannelProvider.tsx @@ -83,6 +83,12 @@ const OmnichannelProvider: FC = ({ children }) => { }); }, [isPrioritiesEnabled, queryClient, subscribe]); + useEffect(() => { + return subscribe(`mac.limit`, ({ limitReached }) => { + limitReached && queryClient.invalidateQueries(['/v1/omnichannel/mac/check']); + }); + }, [subscribe, queryClient]); + useEffect(() => { if (!accessible) { return; From ef315971e76dfee3dbd0e426612cf387d3f7fe26 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Fri, 6 Oct 2023 18:17:10 -0300 Subject: [PATCH 16/60] fix: incorrect validation on useIsOverMacLimit --- apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index bf558c8d9241..e8e9ed17288f 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'; export const useIsOverMacLimit = (): boolean => { const getMacLimit = useEndpoint('GET', '/v1/omnichannel/mac/check'); - const { data: { onLimit: isOverMacLimit = false } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); + const { data: { onLimit = false } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); - return !!isOverMacLimit; + return !onLimit; }; From 7ff57520023cc9c8bd28cbce46b52b3139b5e2d1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 20 Sep 2023 12:51:54 -0600 Subject: [PATCH 17/60] Limit endpoints on MAC limit reached --- apps/meteor/app/livechat/server/api/rest.ts | 1 + apps/meteor/app/livechat/server/api/v1/mac.ts | 15 ++++++++++++ .../app/livechat/server/api/v1/message.ts | 5 ++++ .../meteor/app/livechat/server/api/v1/room.ts | 17 +++++++++++++ .../app/livechat/server/api/v1/transcript.ts | 9 +++++-- .../app/livechat/server/api/v1/videoCall.ts | 11 ++++++++- .../app/livechat/server/lib/Livechat.js | 6 ++++- .../app/livechat/server/lib/RoutingManager.ts | 22 +++++++++++++++++ .../meteor/server/services/omnichannel/mac.ts | 0 .../server/services/omnichannel/service.ts | 24 ++++++++++++++++++- .../src/types/IOmnichannelService.ts | 4 +++- 11 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 apps/meteor/app/livechat/server/api/v1/mac.ts create mode 100644 apps/meteor/server/services/omnichannel/mac.ts diff --git a/apps/meteor/app/livechat/server/api/rest.ts b/apps/meteor/app/livechat/server/api/rest.ts index f9da6690185e..2d48d6806c8c 100644 --- a/apps/meteor/app/livechat/server/api/rest.ts +++ b/apps/meteor/app/livechat/server/api/rest.ts @@ -13,3 +13,4 @@ import './v1/contact'; import './v1/webhooks'; import './v1/integration'; import './v1/statistics'; +import './v1/mac'; diff --git a/apps/meteor/app/livechat/server/api/v1/mac.ts b/apps/meteor/app/livechat/server/api/v1/mac.ts new file mode 100644 index 000000000000..48e4c143673e --- /dev/null +++ b/apps/meteor/app/livechat/server/api/v1/mac.ts @@ -0,0 +1,15 @@ +import { Omnichannel } from '@rocket.chat/core-services'; + +import { API } from '../../../../api/server'; + +API.v1.addRoute( + 'omnichannel/mac/check', + { authRequired: true }, + { + async get() { + return API.v1.success({ + onLimit: await Omnichannel.checkMACLimit(), + }); + }, + }, +); diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index 1dcf54e403a6..7ffce6c42534 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -1,3 +1,4 @@ +import { Omnichannel } from '@rocket.chat/core-services'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors, LivechatRooms, Messages } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; @@ -213,6 +214,10 @@ API.v1.addRoute( throw new Error('invalid-room'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + let ls = undefined; if (this.queryParams.ls) { ls = new Date(this.queryParams.ls); diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index 4f3b4eb6234d..14fd0062f15c 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -1,3 +1,4 @@ +import { Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatAgent, IOmnichannelRoom, IUser, SelectedAgent, TransferByData } from '@rocket.chat/core-typings'; import { isOmnichannelRoom, OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors, Users, LivechatRooms, Subscriptions, Messages } from '@rocket.chat/models'; @@ -74,6 +75,10 @@ API.v1.addRoute('livechat/room', { }, }; + if (!(await Omnichannel.checkMACLimit())) { + throw new Error('error-mac-limit-reached'); + } + const newRoom = await getRoom({ guest, rid, agent, roomInfo, extraParams }); return API.v1.success(newRoom); } @@ -326,6 +331,10 @@ API.v1.addRoute( throw new Error('This_conversation_is_already_closed'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + const guest = await LivechatVisitors.findOneEnabledById(room.v?._id); if (!guest) { throw new Error('error-invalid-visitor'); @@ -412,6 +421,10 @@ API.v1.addRoute( throw new Error('error-invalid-room'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + if (!(await canAccessRoomAsync(room, user))) { throw new Error('error-not-allowed'); } @@ -434,6 +447,10 @@ API.v1.addRoute( throw new Error('error-invalid-room'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + if ( (!room.servedBy || room.servedBy._id !== this.userId) && !(await hasPermissionAsync(this.userId, 'save-others-livechat-room-info')) diff --git a/apps/meteor/app/livechat/server/api/v1/transcript.ts b/apps/meteor/app/livechat/server/api/v1/transcript.ts index 3eaa91c37c7c..ca9ddd38046e 100644 --- a/apps/meteor/app/livechat/server/api/v1/transcript.ts +++ b/apps/meteor/app/livechat/server/api/v1/transcript.ts @@ -1,3 +1,4 @@ +import { Omnichannel } from '@rocket.chat/core-services'; import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatRooms, Users } from '@rocket.chat/models'; import { isPOSTLivechatTranscriptParams, isPOSTLivechatTranscriptRequestParams } from '@rocket.chat/rest-typings'; @@ -34,8 +35,8 @@ API.v1.addRoute( { async delete() { const { rid } = this.urlParams; - const room = await LivechatRooms.findOneById>(rid, { - projection: { open: 1, transcriptRequest: 1 }, + const room = await LivechatRooms.findOneById>(rid, { + projection: { open: 1, transcriptRequest: 1, v: 1 }, }); if (!room?.open) { @@ -45,6 +46,10 @@ API.v1.addRoute( throw new Error('error-transcript-not-requested'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + await LivechatRooms.unsetEmailTranscriptRequestedByRoomId(rid); return API.v1.success(); diff --git a/apps/meteor/app/livechat/server/api/v1/videoCall.ts b/apps/meteor/app/livechat/server/api/v1/videoCall.ts index 52cd8738bec9..7238307f2213 100644 --- a/apps/meteor/app/livechat/server/api/v1/videoCall.ts +++ b/apps/meteor/app/livechat/server/api/v1/videoCall.ts @@ -1,4 +1,5 @@ -import { Message } from '@rocket.chat/core-services'; +import { Message, Omnichannel } from '@rocket.chat/core-services'; +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Messages, Settings, Rooms } from '@rocket.chat/models'; import { isGETWebRTCCall, isPUTWebRTCCallId } from '@rocket.chat/rest-typings'; @@ -27,6 +28,10 @@ API.v1.addRoute( throw new Error('invalid-room'); } + if (!(await Omnichannel.isRoomEnabled(room as IOmnichannelRoom))) { + throw new Error('error-mac-limit-reached'); + } + const webrtcCallingAllowed = rcSettings.get('WebRTC_Enabled') === true && rcSettings.get('Omnichannel_call_provider') === 'WebRTC'; if (!webrtcCallingAllowed) { throw new Error('webRTC calling not enabled'); @@ -79,6 +84,10 @@ API.v1.addRoute( throw new Error('invalid-room'); } + if (!(await Omnichannel.isRoomEnabled(room as IOmnichannelRoom))) { + throw new Error('error-mac-limit-reached'); + } + const call = await Messages.findOneById(callId); if (!call || call.t !== 'livechat_webrtc_video_call') { throw new Error('invalid-callId'); diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index b208c9fb5e85..007683e7e90b 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -1,6 +1,6 @@ // Note: Please don't add any new methods to this file, since its still in js and we are migrating to ts // Please add new methods to LivechatTyped.ts -import { Message } from '@rocket.chat/core-services'; +import { Message, Omnichannel } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import { LivechatVisitors, @@ -411,6 +411,10 @@ export const Livechat = { throw new Meteor.Error('error-transcript-already-requested', 'Transcript already requested'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + const { _id, username, name, utcOffset } = user; const transcriptRequest = { requestedAt: new Date(), diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index f2fd7010eb12..7408f7b942c2 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -156,6 +156,15 @@ export const RoutingManager: Routing = { await Promise.all([Message.saveSystemMessage('command', rid, 'connected', user), Message.saveSystemMessage('uj', rid, '', user)]); } + if (!room) { + logger.debug(`Cannot assign agent to inquiry ${inquiry._id}: Room not found`); + throw new Meteor.Error('error-room-not-found', 'Room not found'); + } + + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + await dispatchAgentDelegated(rid, agent.agentId); logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`); @@ -173,6 +182,10 @@ export const RoutingManager: Routing = { return false; } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + if (departmentId && departmentId !== department) { logger.debug(`Switching department for inquiry ${inquiry._id} [Current: ${department} | Next: ${departmentId}]`); await updateChatDepartment({ @@ -223,6 +236,10 @@ export const RoutingManager: Routing = { return room; } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + if (room.servedBy && room.servedBy._id === agent.agentId) { logger.debug(`Cannot take Inquiry ${inquiry._id}: Already taken by agent ${room.servedBy._id}`); return room; @@ -260,6 +277,11 @@ export const RoutingManager: Routing = { }, async transferRoom(room, guest, transferData) { + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + + logger.debug(`Transfering room ${room._id} by ${transferData.transferredBy._id}`); if (transferData.departmentId) { logger.debug(`Transfering room ${room._id} to department ${transferData.departmentId}`); return forwardRoomToDepartment(room, guest, transferData); diff --git a/apps/meteor/server/services/omnichannel/mac.ts b/apps/meteor/server/services/omnichannel/mac.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 61c22505ca98..12f0a8ecc978 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -1,6 +1,6 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IOmnichannelService } from '@rocket.chat/core-services'; -import type { IOmnichannelQueue } from '@rocket.chat/core-typings'; +import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Livechat } from '../../../app/livechat/server/lib/LivechatTyped'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; @@ -28,6 +28,17 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha await Livechat.notifyAgentStatusChanged(user._id, user.status); } }); + + // TODO: Waiting for license definitions + /* this.onEvent('mac.limitreached', async (): Promise => { + // void Livechat.notifyMacLimitReached(); + await this.queueWorker.stop(); + }); + + this.onEvent('license.validated', async (): Promise => { + // void Livechat.notifyLicenseChanged(); + await this.queueWorker.shouldStart(); + }); */ } async started() { @@ -39,4 +50,15 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha getQueueWorker(): IOmnichannelQueue { return this.queueWorker; } + + async isRoomEnabled(_room: AtLeast): Promise { + // const currentMonth = moment.utc().format('YYYY-MM'); + // return license.isMacOnLimit() || room.v.activity.includes(currentMonth) + return true; + } + + async checkMACLimit(): Promise { + // return license.isMacOnLimit(); + return true; + } } diff --git a/packages/core-services/src/types/IOmnichannelService.ts b/packages/core-services/src/types/IOmnichannelService.ts index fb3cc60d9243..a1b2d1a9d961 100644 --- a/packages/core-services/src/types/IOmnichannelService.ts +++ b/packages/core-services/src/types/IOmnichannelService.ts @@ -1,7 +1,9 @@ -import type { IOmnichannelQueue } from '@rocket.chat/core-typings'; +import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; import type { IServiceClass } from './ServiceClass'; export interface IOmnichannelService extends IServiceClass { getQueueWorker(): IOmnichannelQueue; + isRoomEnabled(_room: AtLeast): Promise; + checkMACLimit(): Promise; } From 703bcc6f7c5516a99e30ea1a494deb46aad3ecf1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Thu, 21 Sep 2023 12:23:44 -0600 Subject: [PATCH 18/60] types of endpoint --- .../app/livechat/server/hooks/checkMAC.ts | 25 +++++++++++++++++++ apps/meteor/app/livechat/server/index.ts | 1 + packages/rest-typings/src/v1/omnichannel.ts | 3 +++ 3 files changed, 29 insertions(+) create mode 100644 apps/meteor/app/livechat/server/hooks/checkMAC.ts diff --git a/apps/meteor/app/livechat/server/hooks/checkMAC.ts b/apps/meteor/app/livechat/server/hooks/checkMAC.ts new file mode 100644 index 000000000000..56d5b26a1598 --- /dev/null +++ b/apps/meteor/app/livechat/server/hooks/checkMAC.ts @@ -0,0 +1,25 @@ +import { Omnichannel } from '@rocket.chat/core-services'; +import { isEditedMessage } from '@rocket.chat/core-typings'; + +import { callbacks } from '../../../../lib/callbacks'; + +callbacks.add('beforeSaveMessage', async (message, room) => { + if (!room || room.t !== 'l') { + return message; + } + + if (isEditedMessage(message)) { + return message; + } + + if (message.token) { + return message; + } + + const canSendMessage = await Omnichannel.isRoomEnabled(room); + if (!canSendMessage) { + throw new Error('error-mac-limit-reached'); + } + + return message; +}); diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index 7d9167966953..b6f4e98af6db 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -16,6 +16,7 @@ import './hooks/saveContactLastChat'; import './hooks/saveLastMessageToInquiry'; import './hooks/afterUserActions'; import './hooks/afterAgentRemoved'; +import './hooks/checkMAC'; import './methods/addAgent'; import './methods/addManager'; import './methods/changeLivechatStatus'; diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 3baeae111202..8572baf94d11 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -3719,6 +3719,9 @@ export type OmnichannelEndpoints = { value: string | number; }[]; }; + '/v1/omnichannel/mac/check': { + GET: () => { onLimit: boolean }; + }; } & { // EE '/v1/livechat/analytics/agents/average-service-time': { From f31ab68bea1fde08964309b21666378128b2843e Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Thu, 21 Sep 2023 14:10:14 -0600 Subject: [PATCH 19/60] block meteor methods --- apps/meteor/app/lib/server/methods/sendMessage.ts | 6 +++--- apps/meteor/app/livechat/server/hooks/checkMAC.ts | 3 ++- apps/meteor/app/livechat/server/lib/LivechatTyped.ts | 6 +++++- .../meteor/app/livechat/server/methods/returnAsInquiry.ts | 5 +++++ apps/meteor/app/livechat/server/methods/transfer.ts | 5 +++++ apps/meteor/server/services/omnichannel/service.ts | 8 +++++--- 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/meteor/app/lib/server/methods/sendMessage.ts b/apps/meteor/app/lib/server/methods/sendMessage.ts index ebdcdfd43d9b..e12ebc2d47e9 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.ts +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -82,7 +82,7 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast({ - sendMessage(message, previewUrls) { + async sendMessage(message, previewUrls) { check(message, Object); const uid = Meteor.userId(); @@ -118,7 +118,7 @@ Meteor.methods({ } try { - return executeSendMessage(uid, message, previewUrls); + return await executeSendMessage(uid, message, previewUrls); } catch (error: any) { if ((error.error || error.message) === 'error-not-allowed') { throw new Meteor.Error(error.error || error.message, error.reason, { diff --git a/apps/meteor/app/livechat/server/hooks/checkMAC.ts b/apps/meteor/app/livechat/server/hooks/checkMAC.ts index 56d5b26a1598..82878861d6d4 100644 --- a/apps/meteor/app/livechat/server/hooks/checkMAC.ts +++ b/apps/meteor/app/livechat/server/hooks/checkMAC.ts @@ -1,4 +1,5 @@ import { Omnichannel } from '@rocket.chat/core-services'; +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; import { callbacks } from '../../../../lib/callbacks'; @@ -16,7 +17,7 @@ callbacks.add('beforeSaveMessage', async (message, room) => { return message; } - const canSendMessage = await Omnichannel.isRoomEnabled(room); + const canSendMessage = await Omnichannel.isRoomEnabled(room as IOmnichannelRoom); if (!canSendMessage) { throw new Error('error-mac-limit-reached'); } diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 0d25616bda60..14cf3e23af8a 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -1,7 +1,7 @@ import dns from 'dns'; import * as util from 'util'; -import { Message, VideoConf, api } from '@rocket.chat/core-services'; +import { Message, VideoConf, api, Omnichannel } from '@rocket.chat/core-services'; import type { IOmnichannelRoom, IOmnichannelRoomClosingInfo, @@ -521,6 +521,10 @@ class LivechatClass { throw new Error('error-invalid-room'); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + const showAgentInfo = settings.get('Livechat_show_agent_info'); const closingMessage = await Messages.findLivechatClosingMessage(rid, { projection: { ts: 1 } }); const ignoredMessageTypes: MessageTypesValues[] = [ diff --git a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts index 0c12d0df5275..6c4f96e74459 100644 --- a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts @@ -1,3 +1,4 @@ +import { Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatDepartment, IRoom } from '@rocket.chat/core-typings'; import { LivechatRooms } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -29,6 +30,10 @@ Meteor.methods({ }); } + if (!(await Omnichannel.isRoomEnabled(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' }); } diff --git a/apps/meteor/app/livechat/server/methods/transfer.ts b/apps/meteor/app/livechat/server/methods/transfer.ts index 16ee1abc6191..1568dd411a28 100644 --- a/apps/meteor/app/livechat/server/methods/transfer.ts +++ b/apps/meteor/app/livechat/server/methods/transfer.ts @@ -1,3 +1,4 @@ +import { Omnichannel } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; import { LivechatVisitors, LivechatRooms, Subscriptions, Users } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -49,6 +50,10 @@ Meteor.methods({ throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:transfer' }); } + if (!(await Omnichannel.isRoomEnabled(room))) { + throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:transfer' }); + } + const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, { projection: { _id: 1 }, }); diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 12f0a8ecc978..d24711c05fde 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -1,6 +1,7 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IOmnichannelService } from '@rocket.chat/core-services'; import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import moment from 'moment'; import { Livechat } from '../../../app/livechat/server/lib/LivechatTyped'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; @@ -51,10 +52,11 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha return this.queueWorker; } - async isRoomEnabled(_room: AtLeast): Promise { - // const currentMonth = moment.utc().format('YYYY-MM'); + async isRoomEnabled(room: AtLeast): Promise { + const currentMonth = moment.utc().format('YYYY-MM'); // return license.isMacOnLimit() || room.v.activity.includes(currentMonth) - return true; + // @ts-expect-error - v.activity + return false || room.v?.activity?.includes(currentMonth); } async checkMACLimit(): Promise { From cfaa542b404648f2df251bf720dfe32cc3749fe9 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 29 Sep 2023 11:57:53 -0600 Subject: [PATCH 20/60] use license --- apps/meteor/app/livechat/server/api/v1/room.ts | 4 ---- apps/meteor/ee/app/license/server/startup.ts | 9 +++++++-- .../server/services/omnichannel/service.ts | 17 ++++++++--------- packages/core-services/src/Events.ts | 2 ++ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index 14fd0062f15c..80ee727ea66d 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -75,10 +75,6 @@ API.v1.addRoute('livechat/room', { }, }; - if (!(await Omnichannel.checkMACLimit())) { - throw new Error('error-mac-limit-reached'); - } - const newRoom = await getRoom({ guest, rid, agent, roomInfo, extraParams }); return API.v1.success(newRoom); } diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index fc6b693e0441..6116c18007e7 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -1,8 +1,9 @@ import { api } from '@rocket.chat/core-services'; import type { LicenseLimitKind } from '@rocket.chat/license'; import { License } from '@rocket.chat/license'; -import { Subscriptions, Users, Settings } from '@rocket.chat/models'; +import { Subscriptions, Users, Settings, LivechatVisitors } from '@rocket.chat/models'; import { wrapExceptions } from '@rocket.chat/tools'; +import moment from 'moment'; import { syncWorkspace } from '../../../../app/cloud/server/functions/syncWorkspace'; import { settings } from '../../../../app/settings/server'; @@ -117,10 +118,14 @@ settings.onReady(async () => { }); }); +const getCurrentPeriod = () => { + moment.utc().format('YYYY-MM'); +}; + License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount()); License.setLicenseLimitCounter('guestUsers', () => Users.getActiveLocalGuestCount()); License.setLicenseLimitCounter('roomsPerGuest', async (context) => (context?.userId ? Subscriptions.countByUserId(context.userId) : 0)); License.setLicenseLimitCounter('privateApps', () => getAppCount('private')); License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace')); // #TODO: Get real value -License.setLicenseLimitCounter('monthlyActiveContacts', async () => 0); +License.setLicenseLimitCounter('monthlyActiveContacts', async () => LivechatVisitors.countVisitorsOnPeriod(getCurrentPeriod())); diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index d24711c05fde..b4d13b11d2b8 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -1,6 +1,7 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IOmnichannelService } from '@rocket.chat/core-services'; import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { License } from '@rocket.chat/license'; import moment from 'moment'; import { Livechat } from '../../../app/livechat/server/lib/LivechatTyped'; @@ -30,16 +31,15 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha } }); - // TODO: Waiting for license definitions - /* this.onEvent('mac.limitreached', async (): Promise => { - // void Livechat.notifyMacLimitReached(); + License.onLimitReached('monthlyActiveContacts', async (): Promise => { + void this.api?.broadcast('mac.LimitReached', {}); await this.queueWorker.stop(); }); - this.onEvent('license.validated', async (): Promise => { - // void Livechat.notifyLicenseChanged(); + License.onValidateLicense(async (): Promise => { + void this.api?.broadcast('mac.limitRestored', {}); await this.queueWorker.shouldStart(); - }); */ + }); } async started() { @@ -54,13 +54,12 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha async isRoomEnabled(room: AtLeast): Promise { const currentMonth = moment.utc().format('YYYY-MM'); - // return license.isMacOnLimit() || room.v.activity.includes(currentMonth) // @ts-expect-error - v.activity - return false || room.v?.activity?.includes(currentMonth); + return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts')); } async checkMACLimit(): Promise { // return license.isMacOnLimit(); - return true; + return !(await License.shouldPreventAction('monthlyActiveContacts')); } } diff --git a/packages/core-services/src/Events.ts b/packages/core-services/src/Events.ts index c5f36d921655..4e7bd77651c3 100644 --- a/packages/core-services/src/Events.ts +++ b/packages/core-services/src/Events.ts @@ -273,4 +273,6 @@ export type EventSignatures = { 'command.updated'(command: string): void; 'command.removed'(command: string): void; 'actions.changed'(): void; + 'mac.limitReached'(): void; + 'mac.limitRestored'(): void; }; From fdc746601f43c30fdff0080ef1c46b614b35e454 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 3 Oct 2023 13:30:05 -0600 Subject: [PATCH 21/60] stream --- apps/meteor/server/modules/listeners/listeners.module.ts | 8 ++++++++ apps/meteor/server/services/omnichannel/mac.ts | 0 apps/meteor/server/services/omnichannel/service.ts | 4 ++-- ee/packages/ddp-client/src/types/streams.ts | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) delete mode 100644 apps/meteor/server/services/omnichannel/mac.ts diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index c580b47d7c6e..5d6dcbbad30f 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -474,5 +474,13 @@ export class ListenersModule { notifications.streamApps.emitWithoutBroadcast('actions/changed'); notifications.streamApps.emitWithoutBroadcast('apps', ['actions/changed', []]); }); + + service.onEvent('mac.limitReached', () => { + notifications.notifyLoggedInThisInstance('mac.limit', { limitReached: true }); + }); + + service.onEvent('mac.limitRestored', () => { + notifications.notifyLoggedInThisInstance('mac.limit', { limitReached: false }); + }); } } diff --git a/apps/meteor/server/services/omnichannel/mac.ts b/apps/meteor/server/services/omnichannel/mac.ts deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index b4d13b11d2b8..e51805a01d90 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -32,12 +32,12 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha }); License.onLimitReached('monthlyActiveContacts', async (): Promise => { - void this.api?.broadcast('mac.LimitReached', {}); + void this.api?.broadcast('mac.limitReached'); await this.queueWorker.stop(); }); License.onValidateLicense(async (): Promise => { - void this.api?.broadcast('mac.limitRestored', {}); + void this.api?.broadcast('mac.limitRestored'); await this.queueWorker.shouldStart(); }); } diff --git a/ee/packages/ddp-client/src/types/streams.ts b/ee/packages/ddp-client/src/types/streams.ts index abadd53c1851..ecbefa3f7d8f 100644 --- a/ee/packages/ddp-client/src/types/streams.ts +++ b/ee/packages/ddp-client/src/types/streams.ts @@ -237,6 +237,7 @@ export interface StreamerEvents { }, { key: 'voip.statuschanged'; args: [boolean] }, + { key: 'mac.limit'; args: [{ limitReached: boolean }] }, { key: 'omnichannel.priority-changed'; args: [{ id: string; clientAction: ClientAction; name?: string }] }, ]; From 771b67b571bf44f193fbf83a1c9c745904c517f0 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 6 Oct 2023 15:08:40 -0600 Subject: [PATCH 22/60] Service events --- apps/meteor/ee/app/license/server/startup.ts | 3 +-- .../server/services/omnichannel/service.ts | 16 +++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index 6116c18007e7..f1cafb02a20a 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -119,7 +119,7 @@ settings.onReady(async () => { }); const getCurrentPeriod = () => { - moment.utc().format('YYYY-MM'); + return moment.utc().format('YYYY-MM'); }; License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount()); @@ -127,5 +127,4 @@ License.setLicenseLimitCounter('guestUsers', () => Users.getActiveLocalGuestCoun License.setLicenseLimitCounter('roomsPerGuest', async (context) => (context?.userId ? Subscriptions.countByUserId(context.userId) : 0)); License.setLicenseLimitCounter('privateApps', () => getAppCount('private')); License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace')); -// #TODO: Get real value License.setLicenseLimitCounter('monthlyActiveContacts', async () => LivechatVisitors.countVisitorsOnPeriod(getCurrentPeriod())); diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index e51805a01d90..77bab940cd9b 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -30,6 +30,12 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha await Livechat.notifyAgentStatusChanged(user._id, user.status); } }); + } + + async started() { + settings.watch('Livechat_enabled', (enabled) => { + void (enabled && RoutingManager.isMethodSet() ? this.queueWorker.shouldStart() : this.queueWorker.stop()); + }); License.onLimitReached('monthlyActiveContacts', async (): Promise => { void this.api?.broadcast('mac.limitReached'); @@ -38,13 +44,7 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha License.onValidateLicense(async (): Promise => { void this.api?.broadcast('mac.limitRestored'); - await this.queueWorker.shouldStart(); - }); - } - - async started() { - settings.watch('Livechat_enabled', (enabled) => { - void (enabled && RoutingManager.isMethodSet() ? this.queueWorker.shouldStart() : this.queueWorker.stop()); + RoutingManager.isMethodSet() && (await this.queueWorker.shouldStart()); }); } @@ -54,12 +54,10 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha async isRoomEnabled(room: AtLeast): Promise { const currentMonth = moment.utc().format('YYYY-MM'); - // @ts-expect-error - v.activity return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts')); } async checkMACLimit(): Promise { - // return license.isMacOnLimit(); return !(await License.shouldPreventAction('monthlyActiveContacts')); } } From 11763fbb03a4d9371b0a3860b10ff16a729a73e7 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 9 Oct 2023 07:29:08 -0600 Subject: [PATCH 23/60] ts --- apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx b/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx index 53cbd0340339..a972b9010120 100644 --- a/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx +++ b/apps/meteor/client/sidebar/sections/OverMacLimitSection.tsx @@ -14,6 +14,7 @@ export const OverMacLimitSection = (): ReactElement => { } onClick={handleClick} /> From c8a0ff6577fa73140cd377089bb8d81a59feebd6 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 9 Oct 2023 09:51:44 -0600 Subject: [PATCH 24/60] Dont limit saveinfo/messages endpoints --- apps/meteor/app/livechat/server/api/v1/room.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index 80ee727ea66d..33aaa5c77dad 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -443,10 +443,6 @@ API.v1.addRoute( throw new Error('error-invalid-room'); } - if (!(await Omnichannel.isRoomEnabled(room))) { - throw new Error('error-mac-limit-reached'); - } - if ( (!room.servedBy || room.servedBy._id !== this.userId) && !(await hasPermissionAsync(this.userId, 'save-others-livechat-room-info')) From 9d6c8eadb051a45abbaea84e6f572f558c7ebe9b Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 9 Oct 2023 11:49:18 -0600 Subject: [PATCH 25/60] tests --- .../tests/end-to-end/api/livechat/22-mac.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 apps/meteor/tests/end-to-end/api/livechat/22-mac.ts diff --git a/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts b/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts new file mode 100644 index 000000000000..55a4fd0e5c58 --- /dev/null +++ b/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts @@ -0,0 +1,41 @@ +import { expect } from 'chai'; +import { before, describe, it } from 'mocha'; + +import { getCredentials } from '../../../data/api-data'; +import { + createVisitor, + createLivechatRoom, + createAgent, + makeAgentAvailable, + sendAgentMessage, + getLivechatRoomInfo, +} from '../../../data/livechat/rooms'; +import { IS_EE } from '../../../e2e/config/constants'; + +(IS_EE ? describe : describe.skip)('MAC', () => { + before((done) => getCredentials(done)); + + before(async () => { + await createAgent(); + await makeAgentAvailable(); + }); + + it('Should create an innactive room by default', async () => { + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); + + expect(room).to.be.an('object'); + expect(room.v.activity).to.be.undefined; + }); + + it('should mark room as active when agent sends a message', async () => { + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); + + await sendAgentMessage(room._id); + + const updatedRoom = await getLivechatRoomInfo(room._id); + + expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array'); + }); +}); From 34552f8dd69e507588a6aa638aaa5ec96e35ab0d Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 9 Oct 2023 12:15:44 -0600 Subject: [PATCH 26/60] tests --- .../tests/end-to-end/api/livechat/22-mac.ts | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts b/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts index 55a4fd0e5c58..4d1d658d40be 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { getCredentials } from '../../../data/api-data'; +import { api, getCredentials, request, credentials } from '../../../data/api-data'; import { createVisitor, createLivechatRoom, @@ -20,22 +20,32 @@ import { IS_EE } from '../../../e2e/config/constants'; await makeAgentAvailable(); }); - it('Should create an innactive room by default', async () => { - const visitor = await createVisitor(); - const room = await createLivechatRoom(visitor.token); + describe('MAC rooms', () => { + it('Should create an innactive room by default', async () => { + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); - expect(room).to.be.an('object'); - expect(room.v.activity).to.be.undefined; - }); + expect(room).to.be.an('object'); + expect(room.v.activity).to.be.undefined; + }); + + it('should mark room as active when agent sends a message', async () => { + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); - it('should mark room as active when agent sends a message', async () => { - const visitor = await createVisitor(); - const room = await createLivechatRoom(visitor.token); + await sendAgentMessage(room._id); - await sendAgentMessage(room._id); + const updatedRoom = await getLivechatRoomInfo(room._id); + + expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array'); + }); + }); - const updatedRoom = await getLivechatRoomInfo(room._id); + describe('MAC check', () => { + it('should return `onLimit: true` when MAC limit has not been reached', async () => { + const { body } = await request.get(api('omnichannel/mac/check')).set(credentials).expect(200); - expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array'); + expect(body).to.have.property('onLimit', true); + }); }); }); From 8233f4762ed97a818ef5dbca53beb7a21ff7bae1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 9 Oct 2023 12:28:03 -0600 Subject: [PATCH 27/60] tests --- .../tests/end-to-end/api/livechat/22-mac.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts b/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts index 4d1d658d40be..db31c30ac1d3 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/22-mac.ts @@ -1,5 +1,7 @@ +import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; +import moment from 'moment'; import { api, getCredentials, request, credentials } from '../../../data/api-data'; import { @@ -21,6 +23,7 @@ import { IS_EE } from '../../../e2e/config/constants'; }); describe('MAC rooms', () => { + let visitor: ILivechatVisitor; it('Should create an innactive room by default', async () => { const visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); @@ -30,7 +33,7 @@ import { IS_EE } from '../../../e2e/config/constants'; }); it('should mark room as active when agent sends a message', async () => { - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await sendAgentMessage(room._id); @@ -39,6 +42,29 @@ import { IS_EE } from '../../../e2e/config/constants'; expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array'); }); + + it('should mark multiple rooms as active when they come from same visitor', async () => { + const room = await createLivechatRoom(visitor.token); + + await sendAgentMessage(room._id); + + const updatedRoom = await getLivechatRoomInfo(room._id); + + expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array'); + }); + + it('visitor should be marked as active for period', async () => { + const { body } = await request + .get(api(`livechat/visitors.info?visitorId=${visitor._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.nested.property('visitor').and.to.be.an('object'); + expect(body.visitor).to.have.nested.property('activity').and.to.be.an('array'); + expect(body.visitor.activity).to.have.lengthOf(1); + expect(body.visitor.activity[0]).to.equal(moment.utc().format('YYYY-MM')); + }); }); describe('MAC check', () => { From 9be0b37514a61bc2dcc795239d8db814ec3cb009 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 08:05:17 -0600 Subject: [PATCH 28/60] dedupe events received from license --- apps/meteor/server/services/omnichannel/queue.ts | 11 ++++++++++- apps/meteor/server/services/omnichannel/service.ts | 12 +++++++++++- .../tests/end-to-end/api/livechat/04-dashboards.ts | 2 -- packages/core-typings/src/omnichannel/queue.ts | 1 + 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/meteor/server/services/omnichannel/queue.ts b/apps/meteor/server/services/omnichannel/queue.ts index cbedf1cdcdec..8bb2e86f2313 100644 --- a/apps/meteor/server/services/omnichannel/queue.ts +++ b/apps/meteor/server/services/omnichannel/queue.ts @@ -1,4 +1,5 @@ import type { InquiryWithAgentInfo, IOmnichannelQueue } from '@rocket.chat/core-typings'; +import { License } from '@rocket.chat/license'; import { LivechatInquiry } from '@rocket.chat/models'; import { dispatchAgentDelegated } from '../../../app/livechat/server/lib/Helper'; @@ -19,6 +20,10 @@ export class OmnichannelQueue implements IOmnichannelQueue { return timeout < 1 ? DEFAULT_RACE_TIMEOUT : timeout * 1000; } + public isRunning() { + return this.running; + } + async start() { if (this.running) { return; @@ -94,12 +99,16 @@ export class OmnichannelQueue implements IOmnichannelQueue { } } - shouldStart() { + async shouldStart() { if (!settings.get('Livechat_enabled')) { void this.stop(); return; } + if (await License.shouldPreventAction('monthlyActiveContacts')) { + return; + } + const routingSupportsAutoAssign = RoutingManager.getConfig()?.autoAssignAgent; queueLogger.debug({ msg: 'Routing method supports auto assignment', diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 77bab940cd9b..425126ecec96 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -14,6 +14,8 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha private queueWorker: IOmnichannelQueue; + private macLimitReached = false; + constructor() { super(); this.queueWorker = new OmnichannelQueue(); @@ -38,11 +40,19 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha }); License.onLimitReached('monthlyActiveContacts', async (): Promise => { + if (this.macLimitReached) { + // Dupe events + return; + } + + this.macLimitReached = true; void this.api?.broadcast('mac.limitReached'); - await this.queueWorker.stop(); + // @ts-expect-error - isRunning + this.queueWorker.isRunning() && (await this.queueWorker.stop()); }); License.onValidateLicense(async (): Promise => { + this.macLimitReached = false; void this.api?.broadcast('mac.limitRestored'); RoutingManager.isMethodSet() && (await this.queueWorker.shouldStart()); }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index c12b875783ab..e17484d25d83 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -196,13 +196,11 @@ describe('LIVECHAT - dashboards', function () { }); const minMessages = TOTAL_MESSAGES.min * TOTAL_ROOMS; - const maxMessages = TOTAL_MESSAGES.max * TOTAL_ROOMS; const totalMessages = result.body.totalizers.find((item: any) => item.title === 'Total_messages'); expect(totalMessages).to.not.be.undefined; const totalMessagesValue = parseInt(totalMessages.value); expect(totalMessagesValue).to.be.greaterThanOrEqual(minMessages); - expect(totalMessagesValue).to.be.lessThanOrEqual(maxMessages); }); }); diff --git a/packages/core-typings/src/omnichannel/queue.ts b/packages/core-typings/src/omnichannel/queue.ts index 46036622713f..14f90bfb52e6 100644 --- a/packages/core-typings/src/omnichannel/queue.ts +++ b/packages/core-typings/src/omnichannel/queue.ts @@ -2,4 +2,5 @@ export interface IOmnichannelQueue { start(): Promise; shouldStart(): void; stop(): Promise; + isRunning(): Promise; } From f562911ca9d6ea14e177f2ccad29cd74c3d27b40 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 08:34:02 -0600 Subject: [PATCH 29/60] ts --- packages/core-typings/src/omnichannel/queue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-typings/src/omnichannel/queue.ts b/packages/core-typings/src/omnichannel/queue.ts index 14f90bfb52e6..1ae697c88a23 100644 --- a/packages/core-typings/src/omnichannel/queue.ts +++ b/packages/core-typings/src/omnichannel/queue.ts @@ -2,5 +2,5 @@ export interface IOmnichannelQueue { start(): Promise; shouldStart(): void; stop(): Promise; - isRunning(): Promise; + isRunning(): boolean; } From 5774dde473e0650418de17781595efd69b979766 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 08:50:41 -0600 Subject: [PATCH 30/60] Create seven-emus-pay.md --- .changeset/seven-emus-pay.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/seven-emus-pay.md diff --git a/.changeset/seven-emus-pay.md b/.changeset/seven-emus-pay.md new file mode 100644 index 000000000000..169c42d5ab54 --- /dev/null +++ b/.changeset/seven-emus-pay.md @@ -0,0 +1,10 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/core-services": patch +"@rocket.chat/core-typings": patch +"@rocket.chat/rest-typings": patch +"@rocket.chat/ddp-client": patch +--- + +feat: Improve UI when MAC limits are reached +feat: Limit endpoints on MAC limit reached From 87336063925bf26858d44830e616ef3e38e176fe Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 09:50:16 -0600 Subject: [PATCH 31/60] unused ts-expect-error --- apps/meteor/server/services/omnichannel/service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 425126ecec96..edc720e135ab 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -47,7 +47,6 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha this.macLimitReached = true; void this.api?.broadcast('mac.limitReached'); - // @ts-expect-error - isRunning this.queueWorker.isRunning() && (await this.queueWorker.stop()); }); From 1f09f395deb9ca58d51c3541f89898037b0a3ba0 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 10 Oct 2023 13:41:45 -0300 Subject: [PATCH 32/60] fix: show red dot highlight only to managers --- apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index e8e9ed17288f..5b455e9ac6e2 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -1,9 +1,10 @@ -import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useEndpoint, usePermission } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; export const useIsOverMacLimit = (): boolean => { const getMacLimit = useEndpoint('GET', '/v1/omnichannel/mac/check'); - const { data: { onLimit = false } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); + const isLivechatManager = usePermission('view-livechat-manager'); + const { data: { onLimit = true } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit(), { enabled: isLivechatManager }); return !onLimit; }; From bd50c885e8486bf47f51f78b6f8411899e616861 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 10 Oct 2023 15:00:32 -0300 Subject: [PATCH 33/60] chore: changed position of the mac banner on the sidebar --- apps/meteor/client/sidebar/Sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/sidebar/Sidebar.tsx b/apps/meteor/client/sidebar/Sidebar.tsx index 7d095331e353..087474505ebb 100644 --- a/apps/meteor/client/sidebar/Sidebar.tsx +++ b/apps/meteor/client/sidebar/Sidebar.tsx @@ -48,8 +48,8 @@ const Sidebar = () => { > {presenceDisabled && !bannerDismissed && setBannerDismissed(true)} />} - {showOmnichannel && } {isWorkspaceOverMacLimit && } + {showOmnichannel && } From edff4a68820a11525530b724ba6c2666efea19e2 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 12:15:52 -0600 Subject: [PATCH 34/60] translations --- apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json | 1 + apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index f09c958d988e..38c0d3147ca7 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2045,6 +2045,7 @@ "error-max-guests-number-reached": "You reached the maximum number of guest users allowed by your license. Contact sale@rocket.chat for a new license.", "error-max-number-simultaneous-chats-reached": "The maximum number of simultaneous chats per agent has been reached.", "error-max-rooms-per-guest-reached": "The maximum number of rooms per guest has been reached.", + "error-mac-limit-reached": "The maximum number of monthly active contacts for this workspace has been reached.", "error-message-deleting-blocked": "Message deleting is blocked", "error-message-editing-blocked": "Message editing is blocked", "error-message-size-exceeded": "Message size exceeds Message_MaxAllowedSize", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json index aac9f8bfa4db..e44f564b381b 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json @@ -1719,6 +1719,7 @@ "error-logged-user-not-in-room": "No estás en la sala \"%s\"", "error-max-guests-number-reached": "Has alcanzado la cantidad máxima de usuarios invitados que permite tu licencia. Escribe a sale@rocket.chat para obtener una nueva licencia.", "error-max-number-simultaneous-chats-reached": "Se ha alcanzado el máximo de chats simultáneos por agente.", + "error-mac-limit-reached": "Se ha alcanzado el máximo de contactos activos por mes para este espacio de trabajo.", "error-message-deleting-blocked": "La eliminación de mensajes está bloqueada", "error-message-editing-blocked": "La edición de mensajes está bloqueada", "error-message-size-exceeded": "El tamaño de mensaje excede el máximo: Message_MaxAllowedSize", From b8a6ac4eebc797dbfb3eaf53bf296f2be5ae541d Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 10 Oct 2023 16:16:47 -0300 Subject: [PATCH 35/60] fix: incorrect over mac limit validation --- apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx | 5 ++--- .../ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx index 5b455e9ac6e2..d1bb70f8107a 100644 --- a/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx +++ b/apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx @@ -1,10 +1,9 @@ -import { useEndpoint, usePermission } from '@rocket.chat/ui-contexts'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; export const useIsOverMacLimit = (): boolean => { const getMacLimit = useEndpoint('GET', '/v1/omnichannel/mac/check'); - const isLivechatManager = usePermission('view-livechat-manager'); - const { data: { onLimit = true } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit(), { enabled: isLivechatManager }); + const { data: { onLimit = true } = {} } = useQuery(['/v1/omnichannel/mac/check'], () => getMacLimit()); return !onLimit; }; diff --git a/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx b/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx index 43601125e85c..d7bfb500d886 100644 --- a/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx +++ b/apps/meteor/ee/client/omnichannel/hooks/useOmnichannelHighlight.tsx @@ -1,7 +1,10 @@ +import { usePermission } from '@rocket.chat/ui-contexts'; + import { useCurrentChatsHighlight } from './useCurrentChatsHighlight'; export const useOmnichannelHighlight = () => { + const isLivechatManager = usePermission('view-livechat-manager'); const { isHighlit } = useCurrentChatsHighlight(); - return { isHighlit }; + return { isHighlit: isLivechatManager && isHighlit }; }; From 65cca47c94289762596f4fba0ef04896c3c83c07 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 13:25:06 -0600 Subject: [PATCH 36/60] limits --- apps/meteor/app/livechat/server/api/v1/message.ts | 5 ----- apps/meteor/app/livechat/server/hooks/checkMAC.ts | 4 ++++ apps/meteor/app/livechat/server/lib/QueueManager.ts | 6 ++++++ apps/meteor/app/livechat/server/lib/RoutingManager.ts | 8 -------- apps/meteor/app/livechat/server/methods/takeInquiry.ts | 8 +++++++- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index 7ffce6c42534..1dcf54e403a6 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -1,4 +1,3 @@ -import { Omnichannel } from '@rocket.chat/core-services'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors, LivechatRooms, Messages } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; @@ -214,10 +213,6 @@ API.v1.addRoute( throw new Error('invalid-room'); } - if (!(await Omnichannel.isRoomEnabled(room))) { - throw new Error('error-mac-limit-reached'); - } - let ls = undefined; if (this.queryParams.ls) { ls = new Date(this.queryParams.ls); diff --git a/apps/meteor/app/livechat/server/hooks/checkMAC.ts b/apps/meteor/app/livechat/server/hooks/checkMAC.ts index 82878861d6d4..cb9bf5a1c705 100644 --- a/apps/meteor/app/livechat/server/hooks/checkMAC.ts +++ b/apps/meteor/app/livechat/server/hooks/checkMAC.ts @@ -17,6 +17,10 @@ callbacks.add('beforeSaveMessage', async (message, room) => { return message; } + if (message.t) { + return message; + } + const canSendMessage = await Omnichannel.isRoomEnabled(room as IOmnichannelRoom); if (!canSendMessage) { throw new Error('error-mac-limit-reached'); diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index aed0061e808e..c1434c73bfbe 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -1,3 +1,4 @@ +import { Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatInquiryRecord, ILivechatVisitor, IMessage, IOmnichannelRoom, SelectedAgent } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; import { LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; @@ -20,6 +21,11 @@ export const queueInquiry = async (inquiry: ILivechatInquiryRecord, defaultAgent logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`); await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent); + const room = await LivechatRooms.findOneById(inquiry.rid, { projection: { v: 1 } }); + if (!room || !(await Omnichannel.isRoomEnabled(room))) { + logger.error({ msg: 'MAC limit reached, not routing inquiry', inquiry }); + return; + } const dbInquiry = await LivechatInquiry.findOneById(inquiry._id); if (!dbInquiry) { diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 7408f7b942c2..ca0d46d53d37 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -161,10 +161,6 @@ export const RoutingManager: Routing = { throw new Meteor.Error('error-room-not-found', 'Room not found'); } - if (!(await Omnichannel.isRoomEnabled(room))) { - throw new Error('error-mac-limit-reached'); - } - await dispatchAgentDelegated(rid, agent.agentId); logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`); @@ -236,10 +232,6 @@ export const RoutingManager: Routing = { return room; } - if (!(await Omnichannel.isRoomEnabled(room))) { - throw new Error('error-mac-limit-reached'); - } - if (room.servedBy && room.servedBy._id === agent.agentId) { logger.debug(`Cannot take Inquiry ${inquiry._id}: Already taken by agent ${room.servedBy._id}`); return room; diff --git a/apps/meteor/app/livechat/server/methods/takeInquiry.ts b/apps/meteor/app/livechat/server/methods/takeInquiry.ts index 17007d7da8c2..e61f6038ea83 100644 --- a/apps/meteor/app/livechat/server/methods/takeInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/takeInquiry.ts @@ -1,4 +1,5 @@ -import { LivechatInquiry, Users } from '@rocket.chat/models'; +import { Omnichannel } from '@rocket.chat/core-services'; +import { LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; @@ -48,6 +49,11 @@ export const takeInquiry = async ( }); } + const room = await LivechatRooms.findOneById(inquiry.rid); + if (!room || !(await Omnichannel.isRoomEnabled(room))) { + throw new Error('error-mac-limit-reached'); + } + const agent = { agentId: user._id, username: user.username, From c724d4a7ac15ca50a65e4db73ac4534b7014058e Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Tue, 10 Oct 2023 16:28:41 -0300 Subject: [PATCH 37/60] feat: disabled canned responses when over mac limit --- .../CannedResponse/CannedResponse.stories.tsx | 2 +- .../contextualBar/CannedResponse/CannedResponse.tsx | 9 +++++---- .../contextualBar/CannedResponse/CannedResponseList.tsx | 4 ++++ .../components/contextualBar/CannedResponse/Item.tsx | 5 +++-- .../contextualBar/CannedResponse/WrapCannedResponse.tsx | 8 +++++--- .../components/contextualBar/CannedResponse/index.tsx | 5 +++++ 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.stories.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.stories.tsx index 1f5bd2e7c539..0c9f40f7c955 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.stories.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.stories.tsx @@ -17,7 +17,7 @@ export default { export const Default: ComponentStory = (args) => ; Default.storyName = 'CannedResponse'; Default.args = { - canEdit: true, + allowEdit: true, data: { shortcut: 'test3 long long long long long long long long long', text: 'simple canned response test3 long long long long long long long long long long long long long long long longlong long long long long long longlong long long long long long longlong long long long long long longlong long long long long long longlong long long long long long longlong long long long long long long', diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.tsx index 05d6895d5dfc..90d6a4523cb7 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponse.tsx @@ -15,7 +15,8 @@ import { import { useScopeDict } from '../../../hooks/useScopeDict'; const CannedResponse: FC<{ - canEdit: boolean; + allowEdit: boolean; + allowUse: boolean; data: { departmentName: ILivechatDepartment['name']; shortcut: IOmnichannelCannedResponse['shortcut']; @@ -26,7 +27,7 @@ const CannedResponse: FC<{ onClickBack: MouseEventHandler; onClickEdit: MouseEventHandler; onClickUse: MouseEventHandler; -}> = ({ canEdit, data: { departmentName, shortcut, text, scope: dataScope, tags }, onClickBack, onClickEdit, onClickUse }) => { +}> = ({ allowEdit, allowUse, data: { departmentName, shortcut, text, scope: dataScope, tags }, onClickBack, onClickEdit, onClickUse }) => { const t = useTranslation(); const scope = useScopeDict(dataScope, departmentName); @@ -84,8 +85,8 @@ const CannedResponse: FC<{ - {canEdit && } - } + diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx index 1a41402368d5..1218bb10b042 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx @@ -30,6 +30,7 @@ const CannedResponseList: FC<{ setText: FormEventHandler; type: string; setType: Dispatch>; + isRoomActive: boolean; onClickItem: (data: any) => void; onClickCreate: (e: MouseEvent) => void; onClickUse: (e: MouseEvent, text: string) => void; @@ -45,6 +46,7 @@ const CannedResponseList: FC<{ setText, type, setType, + isRoomActive, onClickItem, onClickCreate, onClickUse, @@ -98,6 +100,7 @@ const CannedResponseList: FC<{ itemContent={(_index, data): ReactElement => ( { onClickItem(data); }} @@ -112,6 +115,7 @@ const CannedResponseList: FC<{ {cannedId && ( canned._id === (cannedId as unknown))} onClickBack={onClickItem} onClickUse={onClickUse} diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx index 3c1dfa304f79..bcb6a7d9949f 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx @@ -9,9 +9,10 @@ import { useScopeDict } from '../../../hooks/useScopeDict'; const Item: FC<{ data: IOmnichannelCannedResponse & { departmentName: ILivechatDepartment['name'] }; + allowUse?: boolean; onClickItem: (e: MouseEvent) => void; onClickUse: (e: MouseEvent, text: string) => void; -}> = ({ data, onClickItem, onClickUse }) => { +}> = ({ data, allowUse, onClickItem, onClickUse }) => { const t = useTranslation(); const scope = useScopeDict(data.scope, data.departmentName); @@ -47,7 +48,7 @@ const Item: FC<{