From 944ec034df098181074a1aeae78638097f932643 Mon Sep 17 00:00:00 2001 From: TJ Durnford Date: Mon, 5 Apr 2021 11:36:06 -0600 Subject: [PATCH] feat: Update onboarding content and help links (#6572) * feat: Update onboarding content and help links * Add product tour to get started panel * Render action bubble on first node * lint Co-authored-by: Chris Whitten --- .../renderers/NodeWrapper.tsx | 9 ++- .../client/src/Onboarding/TeachingBubbles.tsx | 61 +++++++++++++++---- .../WelcomeModal/Expanded/styles.ts | 5 +- .../client/src/Onboarding/content.tsx | 52 ++++++++++------ .../src/components/GetStarted/GetStarted.tsx | 5 +- .../components/GetStarted/GetStartedLearn.tsx | 22 ++++++- .../packages/client/src/components/Header.tsx | 1 + .../packages/server/src/locales/en-US.json | 31 +++++----- 8 files changed, 136 insertions(+), 50 deletions(-) diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx index e57555236e..149f6e06bb 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx @@ -55,9 +55,12 @@ export const ActionNodeWrapper: FC = ({ id, tab, data, onEvent const { shellApi: { addCoachMarkRef }, } = useShellApi(); - const actionRef = useCallback((action) => { - addCoachMarkRef({ action }); - }, []); + const actionRef = useCallback( + (action) => { + nodeFocused && addCoachMarkRef({ action }); + }, + [nodeFocused] + ); useEffect(() => { if (nodeSelected || nodeDoubleSelected) { diff --git a/Composer/packages/client/src/Onboarding/TeachingBubbles.tsx b/Composer/packages/client/src/Onboarding/TeachingBubbles.tsx index 51dd5b693f..cff5ba3ac9 100644 --- a/Composer/packages/client/src/Onboarding/TeachingBubbles.tsx +++ b/Composer/packages/client/src/Onboarding/TeachingBubbles.tsx @@ -8,13 +8,15 @@ import { TeachingBubble } from 'office-ui-fabric-react/lib/TeachingBubble'; import { useRecoilValue } from 'recoil'; import { FluentTheme } from '@uifabric/fluent-theme'; import { ITeachingBubbleStyles } from 'office-ui-fabric-react/lib/TeachingBubble'; +import { ILinkStyles, Link } from 'office-ui-fabric-react/lib/components/Link'; +import TelemetryClient from '../telemetry/TelemetryClient'; import { onboardingState } from '../recoilModel'; import { useOnboardingContext } from './OnboardingContext'; import { getTeachingBubble } from './content'; -export const teachingBubbleStyles: Partial = { +const teachingBubbleStyles: Partial = { bodyContent: { selectors: { a: { @@ -24,6 +26,17 @@ export const teachingBubbleStyles: Partial = { }, }; +const linkStyles: Partial = { + root: { + textDecoration: 'underline', + selectors: { + ':hover': { + color: FluentTheme.palette.white, + }, + }, + }, +}; + function getPrimaryButtonText(currentStep, setLength) { if (setLength > 1) { if (currentStep === setLength - 1) { @@ -58,45 +71,71 @@ const TeachingBubbles = () => { return null; } - const teachingBubbleProps = getTeachingBubble(id); + const { content, headline, helpLink, calloutProps } = getTeachingBubble(id); - teachingBubbleProps.primaryButtonProps = { + const primaryButtonProps = { children: getPrimaryButtonText(currentStep, setLength), onClick: nextStep, 'data-testid': 'onboardingNext', }; + let secondaryButtonProps; if (currentStep > 0) { - teachingBubbleProps.secondaryButtonProps = { + secondaryButtonProps = { children: formatMessage('Previous'), onClick: previousStep, 'data-testid': 'onboardingPrevious', }; } + let footerContent; if (setLength > 1) { - teachingBubbleProps.footerContent = `${formatMessage('{step} of {setLength}', { + footerContent = `${formatMessage('{step} of {setLength}', { step: currentStep + 1, setLength, })}`; } + let onDismiss; if (currentSet === 0) { - teachingBubbleProps.onDismiss = nextStep; + onDismiss = nextStep; } return ( + footerContent={footerContent} + headline={headline} + primaryButtonProps={primaryButtonProps} + secondaryButtonProps={secondaryButtonProps} + styles={teachingBubbleStyles} + target={target} + onDismiss={onDismiss} + > + {content} + {helpLink && headline && ( + <> +   + { + TelemetryClient.track('HelpLinkClicked', { url: helpLink }); + }} + > + {formatMessage('Learn more')} + + + )} + ); }; diff --git a/Composer/packages/client/src/Onboarding/WelcomeModal/Expanded/styles.ts b/Composer/packages/client/src/Onboarding/WelcomeModal/Expanded/styles.ts index a6531a0329..b469b5d2d0 100644 --- a/Composer/packages/client/src/Onboarding/WelcomeModal/Expanded/styles.ts +++ b/Composer/packages/client/src/Onboarding/WelcomeModal/Expanded/styles.ts @@ -6,8 +6,8 @@ import { NeutralColors, SharedColors } from '@uifabric/fluent-theme'; export const buttonStyle = css` position: absolute; - right: -24px; - top: -24px; + right: -16px; + top: -16px; `; export const contentStyle = css` @@ -54,6 +54,7 @@ export const subtitleStyle = css` `; export const titleStyle = css` + padding-top: 8px; font-size: 24px; `; diff --git a/Composer/packages/client/src/Onboarding/content.tsx b/Composer/packages/client/src/Onboarding/content.tsx index ce82913e6a..5780ca4649 100644 --- a/Composer/packages/client/src/Onboarding/content.tsx +++ b/Composer/packages/client/src/Onboarding/content.tsx @@ -4,7 +4,7 @@ import React from 'react'; import formatMessage from 'format-message'; import { ITeachingBubbleProps } from 'office-ui-fabric-react/lib/TeachingBubble'; -import { DirectionalHint } from 'office-ui-fabric-react/lib/Callout'; +import { DirectionalHint, ICalloutProps } from 'office-ui-fabric-react/lib/Callout'; export interface IComposerTeachingBubble extends ITeachingBubbleProps { children?: any; @@ -44,7 +44,7 @@ export const stepSets = (projectId: string, rootDialogId: string): IStepSet[] => { id: 'actions', location: 'visualEditor', - navigateTo: `/bot/${projectId}/dialogs/${rootDialogId}?selected=triggers[0]`, + navigateTo: `/bot/${projectId}/dialogs/${rootDialogId}?selected=triggers[0]&focused=triggers[0].actions[0]`, targetId: 'action', }, ], @@ -90,49 +90,63 @@ export const stepSets = (projectId: string, rootDialogId: string): IStepSet[] => }, ]; -export const getTeachingBubble = (id: string | undefined): IComposerTeachingBubble => { +type TeachingBubble = { + calloutProps?: ICalloutProps; + content?: string | any[]; + headline?: string; + helpLink?: string; +}; + +export const getTeachingBubble = (id: string | undefined): TeachingBubble => { switch (id) { case 'mainDialog': return { - children: formatMessage('The main dialog is named after your bot. It is the root and entry point of a bot.'), + content: formatMessage( + 'The main dialog is the foundation of every bot created in Composer. There is only one main dialog and all other dialogs are children of it. It gets initialized every time your bot runs and is the entry point into the bot.' + ), headline: formatMessage('Main dialog'), + helpLink: 'https://docs.microsoft.com/en-us/composer/concept-dialog', }; case 'trigger': return { - children: formatMessage( - 'Trigger connects user intent with bot responses. Think of a trigger as one capability of your bot. So a dialog contains a collection of triggers. To add a new trigger from the dialog menu.' + content: formatMessage( + 'Triggers are the main component of a dialog, they are how you catch and respond to events. Each trigger has a condition and a collection of actions to execute when the condition is met.' ), headline: formatMessage('Add a new trigger'), + helpLink: 'https://docs.microsoft.com/en-us/composer/concept-events-and-triggers', }; - case 'userInput': + case 'actions': return { - children: formatMessage( - 'Manage intents here. Each intent describes a particular user intention through utterances (i.e. user says). ' + content: formatMessage( + 'Actions are the main component of a trigger, they are what enable your bot to take action whether in response to user input or any other event that may occur.' ), - headline: formatMessage('User input'), + headline: formatMessage('Actions'), + helpLink: 'https://docs.microsoft.com/en-us/composer/concept-dialog#action', }; - case 'actions': + case 'userInput': return { - children: formatMessage( - 'Actions define how the bot responds to a certain trigger, for example, the bot logics and responses are defined here. Click the + button to add an action.' + content: formatMessage( + 'The User Input page is where the Language Understanding editor locates. From here users can view all the Language Understanding templates and edit them.' ), - headline: formatMessage('Actions'), + headline: formatMessage('User input'), + helpLink: 'https://docs.microsoft.com/en-us/composer/concept-language-understanding', }; case 'botResponses': return { - children: formatMessage( - 'You can manage all bot responses here. Make good use of the templates to create sophisticated response logic based on your own needs.' + content: formatMessage( + 'The Bot Responses page is where the Language Generation (LG) editor locates. From here users can view all the LG templates and edit them.' ), headline: formatMessage('Bot responses'), + helpLink: 'https://docs.microsoft.com/en-us/composer/concept-language-generation', }; case 'startBot': return { - children: formatMessage.rich( + content: formatMessage.rich( "Click the start button to test your bot using Web Chat or Emulator. If you don't yet have the Bot Framework Emulator installed, you can download it here.", { a: ({ children }) => ( @@ -149,13 +163,15 @@ export const getTeachingBubble = (id: string | undefined): IComposerTeachingBubb headline: formatMessage('Test with Web Chat or Emulator'), calloutProps: { directionalHint: DirectionalHint.bottomCenter, + gapSpace: 8, }, }; case 'projectSettings': return { - children: formatMessage('Publish your bot to Azure and manage published bots here.'), + content: formatMessage('Publish your bot to Azure and manage published bots here.'), headline: formatMessage('Configure and publish'), + helpLink: 'https://docs.microsoft.com/en-us/composer/how-to-publish-bot', }; default: diff --git a/Composer/packages/client/src/components/GetStarted/GetStarted.tsx b/Composer/packages/client/src/components/GetStarted/GetStarted.tsx index fa776410c6..08a1309e52 100644 --- a/Composer/packages/client/src/components/GetStarted/GetStarted.tsx +++ b/Composer/packages/client/src/components/GetStarted/GetStarted.tsx @@ -16,6 +16,7 @@ type GetStartedProps = { showTeachingBubble: boolean; requiresLUIS: boolean; requiresQNA: boolean; + projectId: string; onDismiss: () => void; onBotReady: () => void; }; @@ -33,6 +34,8 @@ const panelStyles = { const pivotStyles = { root: { paddingLeft: 20, paddingTop: 10, width: '100%' } } as IPivotStyles; export const GetStarted: React.FC = (props) => { + const { projectId, onDismiss } = props; + const renderTabs = () => { return ( @@ -40,7 +43,7 @@ export const GetStarted: React.FC = (props) => { - + ); diff --git a/Composer/packages/client/src/components/GetStarted/GetStartedLearn.tsx b/Composer/packages/client/src/components/GetStarted/GetStartedLearn.tsx index 04ad0a4bd0..0ffa849006 100644 --- a/Composer/packages/client/src/components/GetStarted/GetStartedLearn.tsx +++ b/Composer/packages/client/src/components/GetStarted/GetStartedLearn.tsx @@ -7,8 +7,10 @@ import React from 'react'; import formatMessage from 'format-message'; import { Link } from 'office-ui-fabric-react/lib/Link'; import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'; +import { useRecoilValue } from 'recoil'; import TelemetryClient from '../../telemetry/TelemetryClient'; +import { dispatcherState } from '../../recoilModel'; import { h3Style, ulStyle, liStyle } from './styles'; @@ -32,12 +34,30 @@ const linkClick = (event) => { TelemetryClient.track('GettingStartedLinkClicked', { method: 'link', url: event.target.href }); }; -export const GetStartedLearn: React.FC = () => { +type Props = { + projectId: string; + onDismiss: () => void; +}; + +export const GetStartedLearn: React.FC = ({ projectId, onDismiss }) => { + const { navTo, onboardingSetComplete } = useRecoilValue(dispatcherState); + + const onStartProductTourClicked = React.useCallback(() => { + onboardingSetComplete(false); + navTo(projectId, null); + onDismiss(); + }, [onboardingSetComplete, onDismiss]); + return (

{formatMessage('Get started')}

    +
  • + + {formatMessage('Take a product tour')} + +
  • {formatMessage('Get started with Bot Framework Composer')} diff --git a/Composer/packages/client/src/components/Header.tsx b/Composer/packages/client/src/components/Header.tsx index b3cd6ab09d..e5e7c36593 100644 --- a/Composer/packages/client/src/components/Header.tsx +++ b/Composer/packages/client/src/components/Header.tsx @@ -413,6 +413,7 @@ export const Header = () => { ) : null}