diff --git a/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx b/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx index 6fe389bbc7..59080abccc 100644 --- a/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx +++ b/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx @@ -43,6 +43,7 @@ export type WebChatHeaderProps = { onSaveTranscript: (conversationId: string) => void; onOpenBotInEmulator: () => void; onCloseWebChat: () => void; + isRestartButtonDisabled: boolean; }; export const WebChatHeader: React.FC = ({ @@ -54,6 +55,7 @@ export const WebChatHeader: React.FC = ({ onOpenBotInEmulator: openBotInEmulator, onSetRestartOption, onCloseWebChat, + isRestartButtonDisabled, }) => { const menuProps: IContextualMenuProps = { items: [ @@ -114,6 +116,7 @@ export const WebChatHeader: React.FC = ({ split aria-roledescription="split button" ariaLabel="restart-conversation" + disabled={isRestartButtonDisabled} iconProps={{ iconName: 'Refresh' }} menuProps={menuProps} splitButtonAriaLabel="See 2 other restart conversation options" diff --git a/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx b/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx index 28eed78993..9326743121 100644 --- a/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx +++ b/Composer/packages/client/src/components/WebChat/WebChatPanel.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { useMemo, useEffect, useState, useRef } from 'react'; +import React, { useMemo, useEffect, useState, useRef, useCallback } from 'react'; import { useRecoilValue } from 'recoil'; import { ConversationActivityTraffic, @@ -11,6 +11,7 @@ import { import { AxiosResponse } from 'axios'; import formatMessage from 'format-message'; import { v4 as uuid } from 'uuid'; +import throttle from 'lodash/throttle'; import TelemetryClient from '../../telemetry/TelemetryClient'; import { BotStatus } from '../../constants'; @@ -55,9 +56,10 @@ export const WebChatPanel: React.FC = ({ const { projectId, botUrl, secrets, botName, activeLocale, botStatus } = botData; const [chats, setChatData] = useState>({}); const [currentConversation, setCurrentConversation] = useState(''); + const [currentRestartOption, onSetRestartOption] = useState(RestartOption.NewUserID); + const [isRestartButtonDisabled, setIsRestartButtonDisabled] = useState(false); const conversationService = useMemo(() => new ConversationService(directlineHostUrl), [directlineHostUrl]); const webChatPanelRef = useRef(null); - const [currentRestartOption, onSetRestartOption] = useState(RestartOption.NewUserID); const webChatTrafficChannel = useRef(); useEffect(() => { @@ -134,6 +136,10 @@ export const WebChatPanel: React.FC = ({ } }, [botUrl]); + useEffect(() => { + setIsRestartButtonDisabled(botStatus !== BotStatus.connected); + }, [botStatus]); + const sendInitialActivities = async (chatData: ChatData) => { try { await conversationService.sendInitialActivity(chatData.conversationId, [chatData.user]); @@ -176,24 +182,36 @@ export const WebChatPanel: React.FC = ({ }; }, [isWebChatPanelVisible]); - const onRestartConversationClick = async (oldConversationId: string, requireNewUserId: boolean) => { - try { - TelemetryClient.track('WebChatConversationRestarted', { - restartType: requireNewUserId ? 'NewUserId' : 'SameUserId', - }); - const chatData = await conversationService.restartConversation( - chats[oldConversationId], - requireNewUserId, - activeLocale, - secrets - ); - setConversationData(chatData); - sendInitialActivities(chatData); - clearWebChatLogs(projectId); - } catch (ex) { - // DL errors are handled through socket above. - } - }; + const handleThrottledRestart: (oldChatData: ChatData, requireNewUserId: boolean) => void = useCallback( + throttle( + async (oldChatData: ChatData, requireNewUserId: boolean) => { + try { + setIsRestartButtonDisabled(true); + const chatData = await conversationService.restartConversation( + oldChatData, + requireNewUserId, + activeLocale, + secrets + ); + + TelemetryClient.track('WebChatConversationRestarted', { + restartType: requireNewUserId ? 'NewUserId' : 'SameUserId', + }); + + setConversationData(chatData); + sendInitialActivities(chatData); + clearWebChatLogs(projectId); + } catch (ex) { + // DL errors are handled through socket above. + } finally { + setIsRestartButtonDisabled(false); + } + }, + 1000, + { leading: true } + ), + [] + ); const onSaveTranscriptClick = async (conversationId: string) => { try { @@ -233,6 +251,7 @@ export const WebChatPanel: React.FC = ({ botName={botName} conversationId={currentConversation} currentRestartOption={currentRestartOption} + isRestartButtonDisabled={isRestartButtonDisabled} onCloseWebChat={() => { setWebChatPanelVisibility(false); TelemetryClient.track('WebChatPaneClosed'); @@ -241,7 +260,9 @@ export const WebChatPanel: React.FC = ({ openBotInEmulator(projectId); TelemetryClient.track('EmulatorButtonClicked', { isRoot: true, projectId, location: 'WebChatPane' }); }} - onRestartConversation={onRestartConversationClick} + onRestartConversation={(oldConversationId: string, requireNewUserId: boolean) => + handleThrottledRestart(chats[oldConversationId], requireNewUserId) + } onSaveTranscript={onSaveTranscriptClick} onSetRestartOption={onSetRestartOption} /> diff --git a/Composer/packages/client/src/components/WebChat/__tests__/WebChatHeader.test.tsx b/Composer/packages/client/src/components/WebChat/__tests__/WebChatHeader.test.tsx index bf94828166..3ae43dd0a0 100644 --- a/Composer/packages/client/src/components/WebChat/__tests__/WebChatHeader.test.tsx +++ b/Composer/packages/client/src/components/WebChat/__tests__/WebChatHeader.test.tsx @@ -21,6 +21,7 @@ describe('', () => { onSaveTranscript: mockOnSaveTranscript, onOpenBotInEmulator: jest.fn(), onCloseWebChat: jest.fn(), + isRestartButtonDisabled: false, }; afterEach(() => { @@ -43,6 +44,7 @@ describe('', () => { onSaveTranscript: mockOnSaveTranscript, onOpenBotInEmulator: jest.fn(), onCloseWebChat: jest.fn(), + isRestartButtonDisabled: false, }; const { findByText } = render(); @@ -68,6 +70,7 @@ describe('', () => { onSaveTranscript: mockOnSaveTranscript, onOpenBotInEmulator: jest.fn(), onCloseWebChat: jest.fn(), + isRestartButtonDisabled: false, }; const { findByText } = render();