diff --git a/Composer/cypress/integration/NotificationPage.spec.ts b/Composer/cypress/integration/NotificationPage.spec.ts index c5b0b0a815..d71fcbab05 100644 --- a/Composer/cypress/integration/NotificationPage.spec.ts +++ b/Composer/cypress/integration/NotificationPage.spec.ts @@ -19,11 +19,9 @@ context('Notification Page', () => { cy.findByTestId('showcode').click(); cy.get('textarea').type('#', { delay: 200 }); - cy.findByTestId('LeftNav-CommandBarButtonDiagnostics').click(); + cy.findByTestId('DebugPanelDrawer').click(); - cy.findByTestId('diagnostics-table-view').within(() => { - cy.findAllByText('TestBot_TestSample.en-us.lg').should('exist').first().click(); - }); + cy.findAllByText('TestBot_TestSample.en-us.lg').should('exist').first().click(); cy.findAllByText('Bot responses').should('exist'); }); @@ -39,11 +37,9 @@ context('Notification Page', () => { cy.findByTestId('showcode').click(); cy.get('textarea').type('t*', { delay: 200 }); - cy.findByTestId('LeftNav-CommandBarButtonDiagnostics').click(); + cy.findByTestId('DebugPanelDrawer').click(); - cy.findByTestId('diagnostics-table-view').within(() => { - cy.findAllByText('TestBot_TestSample.en-us.lu').should('exist').first().dblclick(); - }); + cy.findAllByText('TestBot_TestSample.en-us.lu').should('exist').first().dblclick(); cy.findAllByText('TestBot_TestSample').should('exist'); }); diff --git a/Composer/packages/client/src/components/Header.tsx b/Composer/packages/client/src/components/Header.tsx index 39a15d084e..2fce7ed60b 100644 --- a/Composer/packages/client/src/components/Header.tsx +++ b/Composer/packages/client/src/components/Header.tsx @@ -12,11 +12,9 @@ import { useCallback, useState, Fragment, useMemo, useEffect } from 'react'; import { NeutralColors, SharedColors, FontSizes, CommunicationColors } from '@uifabric/fluent-theme'; import { useRecoilValue } from 'recoil'; import { FontWeights } from 'office-ui-fabric-react/lib/Styling'; -import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; import { TeachingBubble } from 'office-ui-fabric-react/lib/TeachingBubble'; import { useLocation } from '../utils/hooks'; -import { BASEPATH } from '../constants'; import { dispatcherState, appUpdateState, @@ -35,11 +33,10 @@ import { AppUpdaterStatus } from '../constants'; import TelemetryClient from '../telemetry/TelemetryClient'; import { useBotControllerBar } from '../hooks/useControllerBar'; -import { WebChatPanel } from './WebChat/WebChatPanel'; import { languageListTemplates, languageFullName } from './MultiLanguage'; import { NotificationButton } from './Notifications/NotificationButton'; -import { GetStarted } from './GetStarted/GetStarted'; import { BotController } from './BotRuntimeController/BotController'; +import { GetStarted } from './GetStarted/GetStarted'; export const actionButton = css` font-size: ${FontSizes.size18}; margin-top: 2px; @@ -158,6 +155,7 @@ export const Header = () => { const locale = useRecoilValue(localeState(projectId)); const appUpdate = useRecoilValue(appUpdateState); const [teachingBubbleVisibility, setTeachingBubbleVisibility] = useState(); + const [showGetStartedTeachingBubble, setShowGetStartedTeachingBubble] = useState(false); const settings = useRecoilValue(settingsState(projectId)); const isWebChatPanelVisible = useRecoilValue(isWebChatPanelVisibleState); @@ -383,43 +381,6 @@ export const Header = () => { )} - { - setWebChatPanelVisibility(false); - TelemetryClient.track('WebChatPaneClosed'); - }} - > - {webchatEssentials?.projectId ? ( - - ) : null} - - css` flex-grow: 1; border-top: 1px solid #dddddd; position: relative; + overflow: auto; nav { ul { margin-top: 0px; @@ -136,7 +144,6 @@ const Page: React.FC = (props) => { contentStyle = defaultContentStyle, shouldShowEditorError = false, useNewTree, - useDebugPane, pageMode, showCommonLinks = false, projectId, @@ -168,64 +175,60 @@ const Page: React.FC = (props) => { const displayedToolbarItems = toolbarItems.concat(debugItems); return ( -
-
- -
-

{title}

- {onRenderHeaderContent &&
{onRenderHeaderContent()}
} -
-
- - {useNewTree ? ( - { - navigateTo(buildURL(pageMode, link)); - }} - /> - ) : ( - - )} -
-
{children}
+
+ +
+

{title}

+ {onRenderHeaderContent &&
{onRenderHeaderContent()}
} +
+ +
+
+
+ {useNewTree ? ( + { + navigateTo(buildURL(pageMode, link)); + }} + /> + ) : ( + + )}
- +
- {useDebugPane ? : null} -
+
+
{children}
+
+
); }; diff --git a/Composer/packages/client/src/components/WebChat/WebChatComposer.tsx b/Composer/packages/client/src/components/WebChat/WebChatComposer.tsx new file mode 100644 index 0000000000..a91826a6f2 --- /dev/null +++ b/Composer/packages/client/src/components/WebChat/WebChatComposer.tsx @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import React from 'react'; +import { createStyleSet, Components } from 'botframework-webchat'; +import { CommunicationColors, NeutralColors } from '@uifabric/fluent-theme'; + +import { ConversationService } from './utils/conversationService'; +import webChatStyleOptions from './utils/webChatTheme'; +import { ChatData, ActivityType } from './types'; +const { BasicWebChat, Composer } = Components; + +export type WebChatComposerProps = { + currentConversation: string; + activeLocale: string; + conversationService: ConversationService; + botUrl: string; + chatData: ChatData; + isDisabled: boolean; +}; + +const createCardActionMiddleware = () => (next) => async ({ cardAction, getSignInUrl }) => { + const { type, value } = cardAction; + + switch (type) { + case 'signin': { + // eslint-disable-next-line security/detect-non-literal-fs-filename + const popup = window.open(); + const url = await getSignInUrl(); + if (popup) { + popup.location.href = url; + } + break; + } + + case 'downloadFile': + //Fall through + + case 'playAudio': + //Fall through + + case 'playVideo': + //Fall through + + case 'showImage': + //Fall through + + case 'openUrl': + // eslint-disable-next-line security/detect-non-literal-fs-filename + window.open(value, '_blank'); + break; + + default: + return next({ cardAction, getSignInUrl }); + } +}; + +const createActivityMiddleware = () => (next: unknown) => (...setupArgs) => (...renderArgs) => { + const card = setupArgs[0]; + switch (card.activity.type) { + case ActivityType.Trace: + return false; + + case ActivityType.EndOfConversation: + return false; + + default: + if (typeof next === 'function') { + const middlewareResult = next(...setupArgs); + if (middlewareResult) { + return middlewareResult(...renderArgs); + } + return false; + } + return false; + } +}; + +const areEqual = (prevProps: WebChatComposerProps, nextProps: WebChatComposerProps) => { + const result = + prevProps.currentConversation === nextProps.currentConversation && prevProps.isDisabled === nextProps.isDisabled; + return result; +}; + +export const WebChatComposer = React.memo((props: WebChatComposerProps) => { + const { activeLocale, chatData, isDisabled, currentConversation } = props; + + if (!currentConversation || !chatData) { + return null; + } + + const styleSet = createStyleSet({ ...webChatStyleOptions }); + styleSet.fileContent = { + ...styleSet.fileContent, + background: `${NeutralColors.white}`, + '& .webchat__fileContent__fileName': { + color: `${CommunicationColors.primary}`, + }, + '& .webchat__fileContent__size': { + color: `${NeutralColors.white}`, + }, + '& .webchat__fileContent__downloadIcon': { + fill: `${NeutralColors.white}`, + }, + '& .webchat__fileContent__badge': { + padding: '4px', + }, + }; + + return ( + + + + ); +}, areEqual); diff --git a/Composer/packages/client/src/components/WebChat/WebChatContainer.tsx b/Composer/packages/client/src/components/WebChat/WebChatContainer.tsx index b30b96b9c5..1dbfe0b4bf 100644 --- a/Composer/packages/client/src/components/WebChat/WebChatContainer.tsx +++ b/Composer/packages/client/src/components/WebChat/WebChatContainer.tsx @@ -1,117 +1,40 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React from 'react'; -import ReactWebChat, { createStyleSet } from 'botframework-webchat'; -import { CommunicationColors, NeutralColors } from '@uifabric/fluent-theme'; +/** @jsx jsx */ +import { jsx } from '@emotion/core'; +import { NeutralColors } from '@uifabric/fluent-theme'; +import { useRecoilValue } from 'recoil'; -import { ConversationService, ActivityType, ChatData } from './utils/conversationService'; -import webChatStyleOptions from './utils/webChatTheme'; +import { zIndices } from '../../utils/zIndices'; +import { isWebChatPanelVisibleState, webChatEssentialsSelector, rootBotProjectIdSelector } from '../../recoilModel'; +import { BASEPATH } from '../../constants'; +import { WebChatPanel } from '../../components/WebChat/WebChatPanel'; -export type WebChatContainerProps = { - currentConversation: string; - activeLocale: string; - conversationService: ConversationService; - botUrl: string; - chatData: ChatData; - isDisabled: boolean; -}; - -const createCardActionMiddleware = () => (next) => async ({ cardAction, getSignInUrl }) => { - const { type, value } = cardAction; - - switch (type) { - case 'signin': { - // eslint-disable-next-line security/detect-non-literal-fs-filename - const popup = window.open(); - const url = await getSignInUrl(); - if (popup) { - popup.location.href = url; - } - break; - } - - case 'downloadFile': - //Fall through - - case 'playAudio': - //Fall through - - case 'playVideo': - //Fall through - - case 'showImage': - //Fall through - - case 'openUrl': - // eslint-disable-next-line security/detect-non-literal-fs-filename - window.open(value, '_blank'); - break; - - default: - return next({ cardAction, getSignInUrl }); - } -}; - -const createActivityMiddleware = () => (next: unknown) => (...setupArgs) => (...renderArgs) => { - const card = setupArgs[0]; - switch (card.activity.type) { - case ActivityType.Trace: - return false; - - case ActivityType.EndOfConversation: - return false; - - default: - if (typeof next === 'function') { - const middlewareResult = next(...setupArgs); - if (middlewareResult) { - return middlewareResult(...renderArgs); - } - return false; - } - return false; - } -}; - -const areEqual = (prevProps: WebChatContainerProps, nextProps: WebChatContainerProps) => - prevProps.currentConversation === nextProps.currentConversation && prevProps.isDisabled === nextProps.isDisabled; - -export const WebChatContainer = React.memo((props: WebChatContainerProps) => { - const { activeLocale, chatData, isDisabled, currentConversation } = props; - - if (!currentConversation || !chatData) { - return null; - } - - const styleSet = createStyleSet({ ...webChatStyleOptions }); - styleSet.fileContent = { - ...styleSet.fileContent, - background: `${NeutralColors.white}`, - '& .webchat__fileContent__fileName': { - color: `${CommunicationColors.primary}`, - }, - '& .webchat__fileContent__size': { - color: `${NeutralColors.white}`, - }, - '& .webchat__fileContent__downloadIcon': { - fill: `${NeutralColors.white}`, - }, - '& .webchat__fileContent__badge': { - padding: '4px', - }, - }; +export const WebChatContainer = () => { + const rootBotId = useRecoilValue(rootBotProjectIdSelector) ?? ''; + const webchatEssentials = useRecoilValue(webChatEssentialsSelector(rootBotId)); + const isWebChatPanelVisible = useRecoilValue(isWebChatPanelVisibleState); return ( - +
+ {webchatEssentials?.projectId ? ( + + ) : null} +
); -}, areEqual); +}; diff --git a/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx b/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx index d61442e65b..6fe389bbc7 100644 --- a/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx +++ b/Composer/packages/client/src/components/WebChat/WebChatHeader.tsx @@ -1,13 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +/** @jsx jsx */ +import { jsx } from '@emotion/core'; import React from 'react'; -import { ActionButton, DefaultButton, IButtonStyles } from 'office-ui-fabric-react/lib/Button'; +import { ActionButton, DefaultButton, IButtonStyles, IconButton } from 'office-ui-fabric-react/lib/Button'; import { IContextualMenuProps } from 'office-ui-fabric-react/lib/ContextualMenu'; import formatMessage from 'format-message'; import { CommunicationColors, NeutralColors } from '@uifabric/fluent-theme'; -import { RestartOption } from './type'; +import { RestartOption } from './types'; const customButtonStyles: IButtonStyles = { root: { @@ -33,21 +35,25 @@ const customButtonStyles: IButtonStyles = { }; export type WebChatHeaderProps = { + botName: string; currentRestartOption: RestartOption; onSetRestartOption: (restartOption: RestartOption) => void; conversationId: string; - onRestartConversation: (conversationId: string, requireNewUserId: boolean) => Promise; - onSaveTranscript: (conversationId: string) => Promise; - openBotInEmulator: () => void; + onRestartConversation: (conversationId: string, requireNewUserId: boolean) => void; + onSaveTranscript: (conversationId: string) => void; + onOpenBotInEmulator: () => void; + onCloseWebChat: () => void; }; export const WebChatHeader: React.FC = ({ + botName, conversationId, currentRestartOption, onRestartConversation, onSaveTranscript, - openBotInEmulator, + onOpenBotInEmulator: openBotInEmulator, onSetRestartOption, + onCloseWebChat, }) => { const menuProps: IContextualMenuProps = { items: [ @@ -73,7 +79,37 @@ export const WebChatHeader: React.FC = ({ }; return ( -
+
+

+ {botName} + +

= ({ clearWebChatLogs, setDebugPanelExpansion, setActiveTabInDebugPanel, + setWebChatPanelVisibility, } = useRecoilValue(dispatcherState); const { projectId, botUrl, secrets, botName, activeLocale, botStatus } = botData; const [chats, setChatData] = useState>({}); @@ -225,11 +228,16 @@ export const WebChatPanel: React.FC = ({ }; return ( -
+
{ + onCloseWebChat={() => { + setWebChatPanelVisibility(false); + TelemetryClient.track('WebChatPaneClosed'); + }} + onOpenBotInEmulator={() => { openBotInEmulator(projectId); TelemetryClient.track('EmulatorButtonClicked', { isRoot: true, projectId, location: 'WebChatPane' }); }} @@ -237,7 +245,7 @@ export const WebChatPanel: React.FC = ({ onSaveTranscript={onSaveTranscriptClick} onSetRestartOption={onSetRestartOption} /> - ', () => { - fit('should not render webchat if no conversation or chat data', async () => { - const props: WebChatContainerProps = { +describe('', () => { + it('should not render webchat if no conversation or chat data', async () => { + const props: WebChatComposerProps = { currentConversation: '', activeLocale: 'en-us', conversationService: new ConversationService('http://localhost:4000'), @@ -20,7 +21,7 @@ describe('', () => { isDisabled: false, }; - const { container } = render(); + const { container } = render(); expect(container.innerHTML).toBe(''); }); }); 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 f5aeafca74..bf94828166 100644 --- a/Composer/packages/client/src/components/WebChat/__tests__/WebChatHeader.test.tsx +++ b/Composer/packages/client/src/components/WebChat/__tests__/WebChatHeader.test.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { act, fireEvent, render } from '@botframework-composer/test-utils'; import { WebChatHeader, WebChatHeaderProps } from '../WebChatHeader'; -import { RestartOption } from '../type'; +import { RestartOption } from '../types'; describe('', () => { const mockOnSetRestartOption = jest.fn(); @@ -13,12 +13,14 @@ describe('', () => { const mockOnSaveTranscript = jest.fn(); const props: WebChatHeaderProps = { + botName: 'Test-Bot', currentRestartOption: RestartOption.NewUserID, onSetRestartOption: mockOnSetRestartOption, conversationId: '123-abc-conv', onRestartConversation: mockOnRestartConversation, onSaveTranscript: mockOnSaveTranscript, - openBotInEmulator: jest.fn(), + onOpenBotInEmulator: jest.fn(), + onCloseWebChat: jest.fn(), }; afterEach(() => { @@ -33,12 +35,14 @@ describe('', () => { const mockOnSaveTranscript = jest.fn(); const props: WebChatHeaderProps = { + botName: 'Test-Bot', currentRestartOption: RestartOption.SameUserID, onSetRestartOption: mockOnSetRestartOption, conversationId: '123-abc-conv', onRestartConversation: mockOnRestartConversation, onSaveTranscript: mockOnSaveTranscript, - openBotInEmulator: jest.fn(), + onOpenBotInEmulator: jest.fn(), + onCloseWebChat: jest.fn(), }; const { findByText } = render(); @@ -56,12 +60,14 @@ describe('', () => { const mockOnSaveTranscript = jest.fn(); const props: WebChatHeaderProps = { + botName: 'Test-Bot', currentRestartOption: RestartOption.NewUserID, onSetRestartOption: mockOnSetRestartOption, conversationId: '123-abc-conv', onRestartConversation: mockOnRestartConversation, onSaveTranscript: mockOnSaveTranscript, - openBotInEmulator: jest.fn(), + onOpenBotInEmulator: jest.fn(), + onCloseWebChat: jest.fn(), }; const { findByText } = render(); diff --git a/Composer/packages/client/src/components/WebChat/type.ts b/Composer/packages/client/src/components/WebChat/type.ts deleted file mode 100644 index a3aee00ca3..0000000000 --- a/Composer/packages/client/src/components/WebChat/type.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -export enum RestartOption { - SameUserID, - NewUserID, -} diff --git a/Composer/packages/client/src/components/WebChat/types.ts b/Composer/packages/client/src/components/WebChat/types.ts new file mode 100644 index 0000000000..8fca26e73e --- /dev/null +++ b/Composer/packages/client/src/components/WebChat/types.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type User = { + id: string; + name: string; + role: string; +}; + +export type BotSecrets = { msAppId: string; msPassword: string }; +export type ChannelService = 'public' | 'usgov'; + +export type WebChatMode = 'livechat' | 'transcript'; +export type ConversationMember = { + id: string; + name: string; + role?: string; +}; + +export type StartConversationPayload = { + bot?: ConversationMember; + botUrl: string; + channelServiceType: ChannelService; + members: ConversationMember[]; + mode: WebChatMode; + locale: string; + msaAppId?: string; + msaPassword?: string; +}; + +// All activity types. Just parsing for Trace currently. +export enum ActivityType { + Message = 'message', + ContactRelationUpdate = 'contactRelationUpdate', + ConversationUpdate = 'conversationUpdate', + Typing = 'typing', + EndOfConversation = 'endOfConversation', + Event = 'event', + Invoke = 'invoke', + InvokeResponse = 'invokeResponse', + DeleteUserData = 'deleteUserData', + MessageUpdate = 'messageUpdate', + MessageDelete = 'messageDelete', + InstallationUpdate = 'installationUpdate', + MessageReaction = 'messageReaction', + Suggestion = 'suggestion', + Trace = 'trace', + Handoff = 'handoff', +} + +export type ChatData = { + webChatMode: WebChatMode; + directline: { + end: () => void; + }; + projectId: string; + user: User; + conversationId: string; + webChatStore: unknown; +}; + +export enum RestartOption { + SameUserID, + NewUserID, +} diff --git a/Composer/packages/client/src/components/WebChat/utils/conversationService.ts b/Composer/packages/client/src/components/WebChat/utils/conversationService.ts index c410666565..1cf48822ea 100644 --- a/Composer/packages/client/src/components/WebChat/utils/conversationService.ts +++ b/Composer/packages/client/src/components/WebChat/utils/conversationService.ts @@ -8,63 +8,7 @@ import { createDirectLine } from 'botframework-webchat'; import moment from 'moment'; import formatMessage from 'format-message'; -export type User = { - id: string; - name: string; - role: string; -}; - -export type BotSecrets = { msAppId: string; msPassword: string }; -export type ChannelService = 'public' | 'usgov'; - -type WebChatMode = 'livechat' | 'transcript'; -type ConversationMember = { - id: string; - name: string; - role?: string; -}; - -type StartConversationPayload = { - bot?: ConversationMember; - botUrl: string; - channelServiceType: ChannelService; - members: ConversationMember[]; - mode: WebChatMode; - locale: string; - msaAppId?: string; - msaPassword?: string; -}; - -// All activity types. Just parsing for Trace currently. -export enum ActivityType { - Message = 'message', - ContactRelationUpdate = 'contactRelationUpdate', - ConversationUpdate = 'conversationUpdate', - Typing = 'typing', - EndOfConversation = 'endOfConversation', - Event = 'event', - Invoke = 'invoke', - InvokeResponse = 'invokeResponse', - DeleteUserData = 'deleteUserData', - MessageUpdate = 'messageUpdate', - MessageDelete = 'messageDelete', - InstallationUpdate = 'installationUpdate', - MessageReaction = 'messageReaction', - Suggestion = 'suggestion', - Trace = 'trace', - Handoff = 'handoff', -} - -export type ChatData = { - webChatMode: WebChatMode; - directline: { - end: () => void; - }; - projectId: string; - user: User; - conversationId: string; - webChatStore: unknown; -}; +import { BotSecrets, WebChatMode, User, ChatData, StartConversationPayload } from '../types'; export const getDateTimeFormatted = (): string => { return moment().local().format('YYYY-MM-DD HH:mm:ss'); @@ -186,6 +130,7 @@ export class ConversationService { endpointId: endpointId, userId: user.id, }); + const webChatStore: unknown = createWebChatStore({}); return { directline, diff --git a/Composer/packages/client/src/components/__tests__/useDebugPane.test.ts b/Composer/packages/client/src/components/__tests__/useDebugPane.test.ts new file mode 100644 index 0000000000..5a4cd89833 --- /dev/null +++ b/Composer/packages/client/src/components/__tests__/useDebugPane.test.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { renderHook } from '@botframework-composer/test-utils/lib/hooks'; + +import { useDebugPane } from '../useDebugPane'; + +const mockLocation = jest.fn(() => { + return { + location: { + pathname: 'home', + }, + }; +}); + +jest.mock('../../utils/hooks.ts', () => ({ + useLocation: () => mockLocation(), +})); + +// TODO: An integration test needs to be added to test this component better. +describe('useDebug pane', () => { + it('should render debug pane in dialogs page', async () => { + mockLocation.mockReturnValue({ + location: { + pathname: '/bot/43925.21156467697/dialogs/conversational_core_1', + }, + }); + const { result } = renderHook(() => useDebugPane()); + expect(result.current).toBeTruthy(); + }); + + it('should not render debug pane in package manager page', async () => { + mockLocation.mockReturnValue({ + location: { + pathname: '/bot/43925.21156467697/plugin/package-manager', + }, + }); + const { result } = renderHook(() => useDebugPane()); + expect(result.current).toBeFalsy(); + }); + + it('should render in skills LG Page', async () => { + mockLocation.mockReturnValue({ + location: { + pathname: '/bot/43925.21156467697/skill/88276.36979482269/language-generation/People', + }, + }); + const { result } = renderHook(() => useDebugPane()); + expect(result.current).toBeTruthy(); + }); + + it('should render in skills LU Page', async () => { + mockLocation.mockReturnValue({ + location: { + pathname: '/bot/43925.21156467697/skill/88276.36979482269/language-understanding/People', + }, + }); + const { result } = renderHook(() => useDebugPane()); + expect(result.current).toBeTruthy(); + }); +}); diff --git a/Composer/packages/client/src/components/useDebugPane.ts b/Composer/packages/client/src/components/useDebugPane.ts new file mode 100644 index 0000000000..bc01dab744 --- /dev/null +++ b/Composer/packages/client/src/components/useDebugPane.ts @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { useEffect, useState } from 'react'; + +import { useLocation } from '../utils/hooks'; + +const pagesToShow = [ + 'dialogs', + 'language-generation', + 'language-understanding', + 'knowledge-base', + 'botProjectsSettings', +]; + +export const useDebugPane = () => { + const { location: currentLocation } = useLocation(); + const [showDebugPanel, setShowDebugPanel] = useState(false); + + useEffect(() => { + if (currentLocation?.pathname) { + let isShow = false; + // URL Patterns we parse are /bot/1234.123/dialogs or /bot/1234.123/skill/3434.34/dialogs. Removing the leading slash in pathname to avoid an empty item at the start of the list. + const result = currentLocation.pathname.replace(/^\/|\/$/g, '').split('/'); + let pageName = result[2]; + + if (result[2] === 'skill') { + pageName = result[4]; + } + + if (pageName) { + isShow = pagesToShow.some((pattern: string) => { + return pageName.includes(pattern); + }); + } + + setShowDebugPanel(isShow); + } + }, [currentLocation]); + + return showDebugPanel; +}; diff --git a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx index 5f257ca99e..258c53abe9 100644 --- a/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx +++ b/Composer/packages/client/src/pages/design/DebugPanel/DebugPanel.tsx @@ -134,7 +134,7 @@ export const DebugPanel: React.FC = () => { css={css` ${debugPaneContainerStyle} `} - data-testid="debug-panel" + data-testid="DebugPanelDrawer" defaultSize={{ width: '100%', height: isPanelExpanded ? debugPanelDefaultHeight : computedPivotHeight, diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 56c8412188..c110399fad 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -2,7 +2,7 @@ // Licensed under the MIT License. /** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +import { jsx } from '@emotion/core'; import { RouteComponentProps } from '@reach/router'; import { useRecoilValue } from 'recoil'; import { Split, SplitMeasuredSizes } from '@geoffcox/react-splitter'; @@ -16,25 +16,8 @@ import CommandBar from './CommandBar'; import VisualPanel from './VisualPanel'; import PropertyPanel from './PropertyPanel'; import useEmptyPropsHandler from './useEmptyPropsHandler'; -import { contentWrapper, editorContainer, editorWrapper } from './styles'; +import { contentWrapper, splitPaneContainer, splitPaneWrapper } from './styles'; import Modals from './Modals'; -import { DebugPanel } from './DebugPanel/DebugPanel'; - -export const root = css` - height: calc(100vh - 50px); - display: flex; - flex-direction: row; - - label: Page; -`; - -export const pageWrapper = css` - display: flex; - flex-direction: column; - flex-grow: 1; - - label: PageWrapper; -`; const DesignPage: React.FC> = ( props @@ -51,39 +34,43 @@ const DesignPage: React.FC -
- - -
- - -
- - - - -
-
+
+ +
+
+
+ +
- - - -
+
+ +
+ + +
+ + + + +
+
+
+ +
); }; diff --git a/Composer/packages/client/src/pages/design/styles.ts b/Composer/packages/client/src/pages/design/styles.ts index 94d4b9551d..9d0e9a70be 100644 --- a/Composer/packages/client/src/pages/design/styles.ts +++ b/Composer/packages/client/src/pages/design/styles.ts @@ -18,6 +18,7 @@ export const contentWrapper = css` flex-direction: column; flex-grow: 1; height: 100%; + position: relative; label: DesignPageContent; `; @@ -61,14 +62,14 @@ export const assetTree = css` `; /*******/ -export const editorContainer = css` +export const splitPaneContainer = css` display: flex; flex-direction: column; height: 0; flex-grow: 1; `; -export const editorWrapper = css` +export const splitPaneWrapper = css` display: flex; flex-direction: row; flex-grow: 1; diff --git a/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx b/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx index 8f255ae817..cdab993c4e 100644 --- a/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx +++ b/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx @@ -130,7 +130,6 @@ const QnAPage: React.FC import('./pages/design/DesignPage')); const LUPage = React.lazy(() => import('./pages/language-understanding/LUPage')); @@ -41,79 +43,118 @@ const Publish = React.lazy(() => import('./pages/publish/Publish')); const BotCreationFlowRouterV2 = React.lazy(() => import('./components/CreationFlow/v2/CreationFlow')); const FormDialogPage = React.lazy(() => import('./pages/form-dialog/FormDialogPage')); +export const root = css` + height: calc(100vh - 50px); + display: flex; + flex-direction: row; + + label: Page; +`; + +export const pageWrapper = css` + display: flex; + flex-direction: column; + flex-grow: 1; + + label: PageWrapper; +`; + +export const contentWrapper = css` + display: flex; + flex-direction: column; + flex-grow: 1; + height: 100%; + position: relative; + label: PageContent; +`; + const Routes = (props) => { const botOpening = useRecoilValue(botOpeningState); const pluginPages = useRecoilValue(pluginPagesSelector); const spinnerText = useRecoilValue(botOpeningMessage); + const showDebugPanel = useDebugPane(); return ( -
- }> - - - - - - - - - - - - - - - - - - {pluginPages.map((page) => ( - +
+
+ + }> + + + - ))} - - - - - - - - - - - - - - - - - - - - - - - - - - {botOpening && ( -
- + + + + + + + + + + + + + + + {pluginPages.map((page) => ( + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + {botOpening && ( +
+ +
+ )}
- )} + {showDebugPanel && } +
); }; diff --git a/Composer/packages/client/src/utils/pageLinks.ts b/Composer/packages/client/src/utils/pageLinks.ts index c3b2c5d99c..3a6d79a9e3 100644 --- a/Composer/packages/client/src/utils/pageLinks.ts +++ b/Composer/packages/client/src/utils/pageLinks.ts @@ -71,14 +71,6 @@ export const topLinks = ( match: /knowledge-base\/[a-zA-Z0-9_-]+$/, isDisabledForPVA: isPVASchema, }, - { - to: `/bot/${rootProjectId || projectId}/diagnostics`, - iconName: 'Warning', - labelName: formatMessage('Diagnostics'), - disabled: !botLoaded, - match: /diagnostics/, - isDisabledForPVA: false, - }, { to: `/bot/${rootProjectId || projectId}/publish`, iconName: 'CloudUpload', diff --git a/Composer/packages/client/src/utils/zIndices.ts b/Composer/packages/client/src/utils/zIndices.ts index 3e9bb6e7fa..d68193391b 100644 --- a/Composer/packages/client/src/utils/zIndices.ts +++ b/Composer/packages/client/src/utils/zIndices.ts @@ -10,4 +10,5 @@ import { ZIndexes } from 'office-ui-fabric-react/lib/Styling'; */ export const zIndices = { notificationContainer: ZIndexes.Layer + 1, + webChatContainer: ZIndexes.Layer, }; diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index 6b96acd29a..020eb26567 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -635,6 +635,9 @@ "close_d634289d": { "message": "Close" }, + "close_webchat_b26d03e1": { + "message": "Close WebChat" + }, "cognitive_service_region_87c668be": { "message": "Cognitive Service Region" }, @@ -1733,9 +1736,6 @@ "get_started_with_bot_framework_composer_57a6d38b": { "message": "Get started with Bot Framework Composer" }, - "getting_help_ab6811b0": { - "message": "Getting Help" - }, "getting_template_910a4116": { "message": "Getting template" }, @@ -3074,6 +3074,9 @@ "replace_this_dialog_e304015e": { "message": "Replace this dialog" }, + "report_a_bug_or_request_a_feature_36eb52c7": { + "message": "Report a bug or request a feature" + }, "reprompt_dialog_event_c42d2c33": { "message": "Reprompt dialog event" },