From 44fb8161a18963640330c2a8065464e2e0da3685 Mon Sep 17 00:00:00 2001 From: Emanuele Buccelli Date: Mon, 9 Dec 2024 14:42:29 +0100 Subject: [PATCH] Help Center: Get unread count when new message arrives (#97120) Co-authored-by: heavyweight Co-authored-by: Kosta --- .../help-center-recent-conversations.tsx | 20 +++---- .../src/components/help-center-smooch.tsx | 31 +++++++--- packages/help-center/src/components/utils.tsx | 18 +----- packages/odie-client/src/data/index.ts | 3 +- .../src/data/use-get-unread-conversations.ts | 32 ++++++++++ .../src/data/use-get-zendesk-conversation.ts | 60 ++++++++++--------- .../src/hooks/use-get-combined-chat.ts | 4 +- .../src/hooks/use-zendesk-message-listener.ts | 32 ++++++---- 8 files changed, 125 insertions(+), 75 deletions(-) create mode 100644 packages/odie-client/src/data/use-get-unread-conversations.ts diff --git a/packages/help-center/src/components/help-center-recent-conversations.tsx b/packages/help-center/src/components/help-center-recent-conversations.tsx index df87ae78db81e..c2e9135c11b6c 100644 --- a/packages/help-center/src/components/help-center-recent-conversations.tsx +++ b/packages/help-center/src/components/help-center-recent-conversations.tsx @@ -1,16 +1,15 @@ import { HelpCenterSelect } from '@automattic/data-stores'; -import { useGetSupportInteractions } from '@automattic/odie-client/src/data'; -import { useSelect, useDispatch as useDataStoreDispatch } from '@wordpress/data'; +import { + useGetSupportInteractions, + useGetUnreadConversations, +} from '@automattic/odie-client/src/data'; +import { useSelect } from '@wordpress/data'; import { sprintf } from '@wordpress/i18n'; import { useI18n } from '@wordpress/react-i18n'; import React, { useEffect, useState } from 'react'; import { HELP_CENTER_STORE } from '../stores'; import { HelpCenterSupportChatMessage } from './help-center-support-chat-message'; -import { - calculateUnread, - getConversationsFromSupportInteractions, - getZendeskConversations, -} from './utils'; +import { getConversationsFromSupportInteractions, getZendeskConversations } from './utils'; import type { ZendeskConversation } from '@automattic/odie-client'; import './help-center-recent-conversations.scss'; @@ -40,7 +39,7 @@ const HelpCenterRecentConversations: React.FC = () => { return { isChatLoaded: store.getIsChatLoaded() }; }, [] ); const sectionName = GetSectionName( unreadConversationsCount ); - const { setUnreadCount } = useDataStoreDispatch( HELP_CENTER_STORE ); + const getUnreadNotifications = useGetUnreadConversations(); useEffect( () => { if ( @@ -58,13 +57,12 @@ const HelpCenterRecentConversations: React.FC = () => { allConversations, supportInteractions ); - const { unreadConversations, unreadMessages } = calculateUnread( conversations ); + const { unreadConversations, unreadMessages } = getUnreadNotifications( conversations ); setUnreadConversationsCount( unreadConversations ); setUnreadMessagesCount( unreadMessages ); setConversations( conversations ); - setUnreadCount( unreadConversations ); } - }, [ isChatLoaded, setUnreadCount, supportInteractionsResolved, supportInteractionsOpen ] ); + }, [ isChatLoaded, supportInteractionsResolved, supportInteractionsOpen ] ); if ( ! conversations.length ) { return null; diff --git a/packages/help-center/src/components/help-center-smooch.tsx b/packages/help-center/src/components/help-center-smooch.tsx index 541b54af7f6b4..ffc31eef63ca9 100644 --- a/packages/help-center/src/components/help-center-smooch.tsx +++ b/packages/help-center/src/components/help-center-smooch.tsx @@ -1,6 +1,7 @@ import { recordTracksEvent } from '@automattic/calypso-analytics'; import config from '@automattic/calypso-config'; import { HelpCenterSelect } from '@automattic/data-stores'; +import { useGetUnreadConversations } from '@automattic/odie-client/src/data'; import { useLoadZendeskMessaging, useAuthenticateZendeskMessaging, @@ -10,11 +11,11 @@ import { SMOOCH_INTEGRATION_ID_STAGING, } from '@automattic/zendesk-client/src/constants'; import { useSelect, useDispatch as useDataStoreDispatch } from '@wordpress/data'; -import { useEffect, useRef } from '@wordpress/element'; +import { useCallback, useEffect, useRef } from '@wordpress/element'; import Smooch from 'smooch'; import { useChatStatus } from '../hooks'; import { HELP_CENTER_STORE } from '../stores'; -import { calculateUnread, getClientId, getZendeskConversations } from './utils'; +import { getClientId, getZendeskConversations } from './utils'; const destroy = () => { Smooch.destroy(); @@ -57,8 +58,19 @@ const HelpCenterSmooch: React.FC< { enableAuth: boolean } > = ( { enableAuth } ) isEligibleForChat && enableAuth, true ); - const { setIsChatLoaded, setUnreadCount, setZendeskClientId } = - useDataStoreDispatch( HELP_CENTER_STORE ); + const { setIsChatLoaded, setZendeskClientId } = useDataStoreDispatch( HELP_CENTER_STORE ); + const getUnreadNotifications = useGetUnreadConversations(); + + const getUnreadListener = useCallback( + ( message: unknown, data: { conversation: { id: string } } ) => { + if ( isHelpCenterShown ) { + return; + } + + Smooch.getConversationById( data?.conversation?.id ).then( () => getUnreadNotifications() ); + }, + [ isHelpCenterShown ] + ); // Initialize Smooch which communicates with Zendesk useEffect( () => { @@ -93,11 +105,16 @@ const HelpCenterSmooch: React.FC< { enableAuth: boolean } > = ( { enableAuth } ) useEffect( () => { if ( isChatLoaded && getZendeskConversations ) { const allConversations = getZendeskConversations(); - const { unreadConversations } = calculateUnread( allConversations ); - setUnreadCount( unreadConversations ); + getUnreadNotifications( allConversations ); setZendeskClientId( getClientId( allConversations ) ); + Smooch.on( 'message:received', getUnreadListener ); } - }, [ isChatLoaded, setUnreadCount, setZendeskClientId ] ); + + return () => { + // @ts-expect-error -- 'off' is not part of the def. + Smooch?.off?.( 'message:received', getUnreadListener ); + }; + }, [ getUnreadListener, isChatLoaded, getUnreadNotifications, setZendeskClientId ] ); return
; }; diff --git a/packages/help-center/src/components/utils.tsx b/packages/help-center/src/components/utils.tsx index e35ed1fc1413c..377a3a1c8b2d5 100644 --- a/packages/help-center/src/components/utils.tsx +++ b/packages/help-center/src/components/utils.tsx @@ -24,7 +24,7 @@ export const getLastMessage = ( { conversation }: { conversation: ZendeskConvers }; export const getZendeskConversations = () => { - const conversations = Smooch.getConversations(); + const conversations = Smooch?.getConversations?.() ?? []; return conversations as unknown as ZendeskConversation[]; }; @@ -90,22 +90,6 @@ export const getSortedRecentAndArchivedConversations = ( { }; }; -export const calculateUnread = ( conversations: ZendeskConversation[] ) => { - let unreadConversations = 0; - let unreadMessages = 0; - - conversations.forEach( ( conversation ) => { - const unreadCount = conversation.participants[ 0 ]?.unreadCount ?? 0; - - if ( unreadCount > 0 ) { - unreadConversations++; - unreadMessages += unreadCount; - } - } ); - - return { unreadConversations, unreadMessages }; -}; - export const getClientId = ( conversations: ZendeskConversation[] ): string => conversations .flatMap( ( conversation ) => conversation.messages ) diff --git a/packages/odie-client/src/data/index.ts b/packages/odie-client/src/data/index.ts index 4fc03de41336e..a12f58d90edd6 100644 --- a/packages/odie-client/src/data/index.ts +++ b/packages/odie-client/src/data/index.ts @@ -1,6 +1,7 @@ export { handleSupportInteractionsFetch } from './handle-support-interactions-fetch'; export { useGetSupportInteractions } from './use-get-support-interactions'; -export { getZendeskConversation } from './use-get-zendesk-conversation'; +export { useGetZendeskConversation } from './use-get-zendesk-conversation'; +export { useGetUnreadConversations } from './use-get-unread-conversations'; export { useManageSupportInteraction } from './use-manage-support-interaction'; export { broadcastOdieMessage, diff --git a/packages/odie-client/src/data/use-get-unread-conversations.ts b/packages/odie-client/src/data/use-get-unread-conversations.ts new file mode 100644 index 0000000000000..4da706bbea05a --- /dev/null +++ b/packages/odie-client/src/data/use-get-unread-conversations.ts @@ -0,0 +1,32 @@ +import { HELP_CENTER_STORE } from '@automattic/help-center/src/stores'; +import { useDispatch as useDataStoreDispatch } from '@wordpress/data'; +import Smooch from 'smooch'; +import { ZendeskConversation } from '../types'; + +const calculateUnread = ( conversations: Conversation[] | ZendeskConversation[] ) => { + let unreadConversations = 0; + let unreadMessages = 0; + + conversations.forEach( ( conversation ) => { + const unreadCount = conversation.participants[ 0 ]?.unreadCount ?? 0; + + if ( unreadCount > 0 ) { + unreadConversations++; + unreadMessages += unreadCount; + } + } ); + + return { unreadConversations, unreadMessages }; +}; + +export const useGetUnreadConversations = () => { + const { setUnreadCount } = useDataStoreDispatch( HELP_CENTER_STORE ); + + return ( conversations?: Conversation[] | ZendeskConversation[] ) => { + const conversationsToCheck = conversations ? conversations : Smooch.getConversations(); + const { unreadConversations, unreadMessages } = calculateUnread( conversationsToCheck ); + setUnreadCount( unreadConversations ); + + return { unreadConversations, unreadMessages }; + }; +}; diff --git a/packages/odie-client/src/data/use-get-zendesk-conversation.ts b/packages/odie-client/src/data/use-get-zendesk-conversation.ts index 42f2efa2f3817..5c053f848b60c 100644 --- a/packages/odie-client/src/data/use-get-zendesk-conversation.ts +++ b/packages/odie-client/src/data/use-get-zendesk-conversation.ts @@ -1,5 +1,6 @@ import Smooch from 'smooch'; import { zendeskMessageConverter } from '../utils'; +import { useGetUnreadConversations } from './use-get-unread-conversations'; import type { ZendeskMessage } from '../types'; const parseResponse = ( conversation: Conversation ) => { @@ -18,34 +19,39 @@ const parseResponse = ( conversation: Conversation ) => { /** * Get the conversation for the Zendesk conversation. */ -export const getZendeskConversation = ( { - chatId, - conversationId, -}: { - chatId: number | string | null | undefined; - conversationId?: string | null | undefined; -} ) => { - if ( ! chatId ) { - return null; - } - - const conversation = Smooch.getConversations().find( ( conversation ) => { - if ( conversationId ) { - return conversation.id === conversationId; +export const useGetZendeskConversation = () => { + const getUnreadNotifications = useGetUnreadConversations(); + + return ( { + chatId, + conversationId, + }: { + chatId: number | string | null | undefined; + conversationId?: string | null | undefined; + } ) => { + if ( ! chatId ) { + return null; } - return Number( conversation.metadata[ 'odieChatId' ] ) === Number( chatId ); - } ); + const conversation = Smooch.getConversations().find( ( conversation ) => { + if ( conversationId ) { + return conversation.id === conversationId; + } + + return Number( conversation.metadata[ 'odieChatId' ] ) === Number( chatId ); + } ); + + if ( ! conversation ) { + return null; + } - if ( ! conversation ) { - return null; - } - - // We need to ensure that more than one message is loaded - return Smooch.getConversationById( conversation.id ) - .then( ( conversation ) => { - Smooch.markAllAsRead( conversation.id ); - return parseResponse( conversation ); - } ) - .catch( () => parseResponse( conversation ) ); + // We need to ensure that more than one message is loaded + return Smooch.getConversationById( conversation.id ) + .then( ( conversation ) => { + Smooch.markAllAsRead( conversation.id ); + getUnreadNotifications(); + return parseResponse( conversation ); + } ) + .catch( () => parseResponse( conversation ) ); + }; }; diff --git a/packages/odie-client/src/hooks/use-get-combined-chat.ts b/packages/odie-client/src/hooks/use-get-combined-chat.ts index d88b4d9294bec..b8873dc41c051 100644 --- a/packages/odie-client/src/hooks/use-get-combined-chat.ts +++ b/packages/odie-client/src/hooks/use-get-combined-chat.ts @@ -4,7 +4,7 @@ import { useSelect } from '@wordpress/data'; import { useState, useEffect } from '@wordpress/element'; import { getOdieTransferMessageConstant } from '../constants'; import { emptyChat } from '../context'; -import { getZendeskConversation, useOdieChat } from '../data'; +import { useGetZendeskConversation, useOdieChat } from '../data'; import type { Chat, Message } from '../types'; /** @@ -24,7 +24,7 @@ export const useGetCombinedChat = ( }, [] ); const [ mainChatState, setMainChatState ] = useState< Chat >( emptyChat ); - + const getZendeskConversation = useGetZendeskConversation(); // Get the current odie chat const odieId = currentSupportInteraction?.events.find( ( event ) => event.event_source === 'odie' ) diff --git a/packages/odie-client/src/hooks/use-zendesk-message-listener.ts b/packages/odie-client/src/hooks/use-zendesk-message-listener.ts index ce0510bdb3eb3..acdee545584fd 100644 --- a/packages/odie-client/src/hooks/use-zendesk-message-listener.ts +++ b/packages/odie-client/src/hooks/use-zendesk-message-listener.ts @@ -1,7 +1,7 @@ import { HelpCenterSelect } from '@automattic/data-stores'; import { HELP_CENTER_STORE } from '@automattic/help-center/src/stores'; import { useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useCallback, useEffect } from '@wordpress/element'; import Smooch from 'smooch'; import { useOdieAssistantContext } from '../context'; import { zendeskMessageConverter } from '../utils'; @@ -25,12 +25,8 @@ export const useZendeskMessageListener = () => { ( event ) => event.event_source === 'zendesk' )?.event_external_id; - useEffect( () => { - if ( ! isChatLoaded ) { - return; - } - - Smooch.on( 'message:received', ( message, data ) => { + const messageListener = useCallback( + ( message: unknown, data: { conversation: { id: string } } ) => { const zendeskMessage = message as ZendeskMessage; if ( data.conversation.id === chat.conversationId ) { @@ -42,11 +38,27 @@ export const useZendeskMessageListener = () => { } ) ); Smooch.markAllAsRead( data.conversation.id ); } - } ); + }, + [ chat.conversationId, setChat ] + ); + + useEffect( () => { + if ( ! isChatLoaded ) { + return; + } + + Smooch.on( 'message:received', messageListener ); return () => { // @ts-expect-error -- 'off' is not part of the def. - Smooch?.off( 'message:received' ); + Smooch?.off?.( 'message:received', messageListener ); }; - }, [ isChatLoaded, currentZendeskConversationId, chat, setChat, currentSupportInteraction ] ); + }, [ + isChatLoaded, + currentZendeskConversationId, + chat, + setChat, + currentSupportInteraction, + messageListener, + ] ); };