Skip to content

Commit

Permalink
feat: Update onboarding content and help links (#6572)
Browse files Browse the repository at this point in the history
* 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 <christopher.whitten@microsoft.com>
  • Loading branch information
tdurnford and cwhitten committed Apr 5, 2021
1 parent fa4af0e commit 944ec03
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ export const ActionNodeWrapper: FC<NodeWrapperProps> = ({ 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) {
Expand Down
61 changes: 50 additions & 11 deletions Composer/packages/client/src/Onboarding/TeachingBubbles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ITeachingBubbleStyles> = {
const teachingBubbleStyles: Partial<ITeachingBubbleStyles> = {
bodyContent: {
selectors: {
a: {
Expand All @@ -24,6 +26,17 @@ export const teachingBubbleStyles: Partial<ITeachingBubbleStyles> = {
},
};

const linkStyles: Partial<ILinkStyles> = {
root: {
textDecoration: 'underline',
selectors: {
':hover': {
color: FluentTheme.palette.white,
},
},
},
};

function getPrimaryButtonText(currentStep, setLength) {
if (setLength > 1) {
if (currentStep === setLength - 1) {
Expand Down Expand Up @@ -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 (
<TeachingBubble
styles={teachingBubbleStyles}
target={target}
{...teachingBubbleProps}
calloutProps={{
preventDismissOnLostFocus: true,
preventDismissOnResize: true,
preventDismissOnScroll: true,
...teachingBubbleProps.calloutProps,
...calloutProps,
}}
/>
footerContent={footerContent}
headline={headline}
primaryButtonProps={primaryButtonProps}
secondaryButtonProps={secondaryButtonProps}
styles={teachingBubbleStyles}
target={target}
onDismiss={onDismiss}
>
{content}
{helpLink && headline && (
<>
&nbsp;
<Link
aria-label={formatMessage('Learn more about {title}', { title: headline.toLowerCase() })}
href={helpLink}
rel="noopener noreferrer"
styles={linkStyles}
target="_blank"
onClick={() => {
TelemetryClient.track('HelpLinkClicked', { url: helpLink });
}}
>
{formatMessage('Learn more')}
</Link>
</>
)}
</TeachingBubble>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -54,6 +54,7 @@ export const subtitleStyle = css`
`;

export const titleStyle = css`
padding-top: 8px;
font-size: 24px;
`;

Expand Down
52 changes: 34 additions & 18 deletions Composer/packages/client/src/Onboarding/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
},
],
Expand Down Expand Up @@ -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 <a>here</a>.",
{
a: ({ children }) => (
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type GetStartedProps = {
showTeachingBubble: boolean;
requiresLUIS: boolean;
requiresQNA: boolean;
projectId: string;
onDismiss: () => void;
onBotReady: () => void;
};
Expand All @@ -33,14 +34,16 @@ const panelStyles = {
const pivotStyles = { root: { paddingLeft: 20, paddingTop: 10, width: '100%' } } as IPivotStyles;

export const GetStarted: React.FC<GetStartedProps> = (props) => {
const { projectId, onDismiss } = props;

const renderTabs = () => {
return (
<Pivot styles={pivotStyles}>
<PivotItem headerText={formatMessage('Next steps')}>
<GetStartedNextSteps {...props} />
</PivotItem>
<PivotItem headerText={formatMessage('Learning')}>
<GetStartedLearn />
<GetStartedLearn projectId={projectId} onDismiss={onDismiss} />
</PivotItem>
</Pivot>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<Props> = ({ projectId, onDismiss }) => {
const { navTo, onboardingSetComplete } = useRecoilValue(dispatcherState);

const onStartProductTourClicked = React.useCallback(() => {
onboardingSetComplete(false);
navTo(projectId, null);
onDismiss();
}, [onboardingSetComplete, onDismiss]);

return (
<ScrollablePane styles={{ root: { marginTop: 60 } }}>
<div css={{ paddingTop: 20, paddingLeft: 27, paddingRight: 20 }}>
<h3 style={h3Style}>{formatMessage('Get started')}</h3>
<ul style={ulStyle}>
<li style={liStyle}>
<Link target="_blank" onClick={onStartProductTourClicked}>
{formatMessage('Take a product tour')}
</Link>
</li>
<li style={liStyle}>
<Link href={linkToGetStarted} target="_blank" onClick={linkClick}>
{formatMessage('Get started with Bot Framework Composer')}
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/client/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ export const Header = () => {
) : null}
<GetStarted
isOpen={showGetStarted}
projectId={projectId}
requiresLUIS={requiresLUIS}
requiresQNA={requiresQNA}
showTeachingBubble={showGetStartedTeachingBubble}
Expand Down
Loading

0 comments on commit 944ec03

Please sign in to comment.