Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixed various onboarding issues and updated content #2900

Merged
merged 5 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Composer/cypress/integration/Onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

context('Onboarding', () => {
beforeEach(() => {
window.localStorage.setItem('composer:OnboardingState', JSON.stringify({ complete: false }));
cy.visit(Cypress.env('COMPOSER_URL'));
cy.createBot('TodoSample', 'Onboarding');

Expand Down
2 changes: 1 addition & 1 deletion Composer/cypress/integration/ToDoBot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

context('ToDo Bot', () => {
before(() => {
beforeEach(() => {
cy.visit(Cypress.env('COMPOSER_URL'));
cy.createBot('TodoSample');
});
Expand Down
10 changes: 8 additions & 2 deletions Composer/packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React, { forwardRef, useContext, useState, Fragment, Suspense } from 'react';
import React, { forwardRef, useContext, useEffect, useState, Fragment, Suspense } from 'react';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { FocusZone } from 'office-ui-fabric-react/lib/FocusZone';
Expand All @@ -19,6 +19,7 @@ import { resolveToBasePath } from './utils/fileUtil';
import { ErrorBoundary } from './components/ErrorBoundary';
import { RequireAuth } from './components/RequireAuth';
import { AppUpdater } from './components/AppUpdater';
import onboardingState from './utils/onboardingStorage';

initializeIcons(undefined, { disableWarnings: true });

Expand Down Expand Up @@ -127,11 +128,16 @@ const bottomLinks = [
];

export const App: React.FC = () => {
const { state } = useContext(StoreContext);
const { actions, state } = useContext(StoreContext);
const [sideBarExpand, setSideBarExpand] = useState(false);

const { onboardingSetComplete } = actions;
const { botName, projectId, dialogs, locale, designPageLocation, announcement } = state;

useEffect(() => {
onboardingSetComplete(onboardingState.getComplete());
}, []);

const mapNavItemTo = x => resolveToBasePath(BASEPATH, x);

const openedDialogId = designPageLocation.dialogId || dialogs.find(({ isRoot }) => isRoot === true)?.id || 'Main';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ const TeachingBubbles = () => {
state: { currentSet, currentStep, teachingBubble },
} = useContext(OnboardingContext);

// Since some of the teaching bubbles are positioned with (x, y) coordinates relative
// to the extension they exist in and we get the (left, top) position of the extension
// to position the bubble relative to the app, we need to re-render the teaching bubble
// when the screen is resized.
const [, forceRender] = useState();
const rerender = useRef(debounce(() => forceRender({}), 200)).current;

Expand All @@ -46,29 +42,13 @@ const TeachingBubbles = () => {
return () => window.removeEventListener('resize', rerender);
}, [forceRender]);

const { id, location, setLength = 0, targetId = '' } = teachingBubble || {};
const { id, setLength = 0, targetId = '' } = teachingBubble || {};
const target = coachMarkRefs[targetId];

if (!target) {
return null;
}

// The teaching bubbles attach themselves to elements in Composer with component refs.
// However, the `actions` teaching bubble attaches itself to an element in the Visual
// Editor, and we can't access the component ref in the iFrame from the app. As a
// workaround, the extension adds an (x, y) coordinate to the `coachMarkRefs` map where
// the teaching bubble should be positioned relative to the extension. We can then
// add the `top` and `left` position of the extension to position the teaching bubble
// relative to the app.
let position;
const { x, y } = target;

if (typeof x !== 'undefined' && typeof y !== 'undefined' && location) {
const extension = coachMarkRefs[location];
const { left = 0, top = 0 } = (extension && extension.getBoundingClientRect()) || {};
position = { x: left + x, y: top + y };
}

const teachingBubbleProps = getTeachingBubble(id);

teachingBubbleProps.primaryButtonProps = {
Expand All @@ -93,14 +73,14 @@ const TeachingBubbles = () => {
teachingBubbleProps.onDismiss = nextStep;
}

return target ? (
return (
<TeachingBubble
styles={teachingBubbleStyles}
target={position || target}
target={target}
theme={teachingBubbleTheme}
{...teachingBubbleProps}
/>
) : null;
);
};

export default TeachingBubbles;
46 changes: 19 additions & 27 deletions Composer/packages/client/src/Onboarding/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { navigate } from '@reach/router';
import formatMessage from 'format-message';

Expand Down Expand Up @@ -30,7 +30,22 @@ const Onboarding: React.FC = () => {

const rootDialogId = dialogs.find(({ isRoot }) => isRoot === true)?.id || 'Main';

const [stepSets, setStepSets] = useState<IStepSet[]>(defaultStepSets(projectId, rootDialogId));
const stepSets = useMemo<IStepSet[]>(() => {
return defaultStepSets(projectId, rootDialogId)
.map(stepSet => ({
...stepSet,
steps: stepSet.steps.filter(({ targetId }) => {
if (!dialogs.length) {
return !(targetId === 'mainDialog' || targetId === 'newTrigger' || targetId === 'action');
} else if (!dialogs[0].triggers.length) {
return targetId !== 'action';
}
return true;
}),
}))
.filter(({ steps }) => steps.length);
}, [projectId, rootDialogId]);

const [currentSet, setCurrentSet] = useState<number>(getCurrentSet(stepSets));
const [currentStep, setCurrentStep] = useState<number>(0);
const [hideModal, setHideModal] = useState(true);
Expand All @@ -41,10 +56,6 @@ const Onboarding: React.FC = () => {
location: { pathname },
} = useLocation();

useEffect(() => {
onboardingSetComplete(onboardingState.getComplete());
}, []);

useEffect(() => {
if (didMount.current && complete) {
setCurrentSet(0);
Expand All @@ -58,34 +69,15 @@ const Onboarding: React.FC = () => {
const { steps } = stepSets[currentSet] || { steps: [] };
const coachMark = steps[currentStep] || {};
const { id, location, navigateTo, targetId } = coachMark;
!complete && navigateTo && navigate(navigateTo);

!complete && projectId && navigateTo && navigate(navigateTo);
setTeachingBubble({ currentStep, id, location, setLength: steps.length, targetId });

setMinimized(!!~currentStep);

if (currentSet > -1 && currentSet < stepSets.length) {
onboardingState.setCurrentSet(stepSets[currentSet].id);
}
}, [currentSet, currentStep, setTeachingBubble]);

useEffect(() => {
const sets = defaultStepSets(projectId, rootDialogId)
.map(stepSet => ({
...stepSet,
steps: stepSet.steps.filter(({ targetId }) => {
if (!dialogs.length) {
return !(targetId === 'mainDialog' || targetId === 'newTrigger' || targetId === 'action');
} else if (!dialogs[0].triggers.length) {
return targetId !== 'action';
}
return true;
}),
}))
.filter(({ steps }) => steps.length);

setStepSets(sets);
}, [dialogs, rootDialogId]);
}, [currentSet, currentStep, setTeachingBubble, projectId]);

useEffect(() => {
setHideModal(pathname !== `/bot/${projectId}/dialogs/${rootDialogId}`);
Expand Down
125 changes: 64 additions & 61 deletions Composer/packages/client/src/Onboarding/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React from 'react';
import formatMessage from 'format-message';
import { ITeachingBubbleProps } from 'office-ui-fabric-react/lib/TeachingBubble';
import { generateUniqueId } from '@bfc/shared';

export interface IComposerTeachingBubble extends ITeachingBubbleProps {
children?: any;
Expand Down Expand Up @@ -99,75 +100,59 @@ export const stepSets = (projectId: string, rootDialogId: string): IStepSet[] =>
},
];

const Bold = ({ children }) => <b key={generateUniqueId()}>{children}</b>;
const Italics = ({ children }) => <i key={generateUniqueId()}>{children}</i>;

export const getTeachingBubble = (id: string | undefined): IComposerTeachingBubble => {
switch (id) {
case 'setUpYourBot':
return {
children: (
<div>
{formatMessage(
'We have created a sample bot to help you get started with Composer. Click here to open the bot.'
)}
</div>
children: formatMessage(
'We have created a sample bot to help you get started with Composer. Click here to open the bot.'
),
headline: formatMessage('Get started!'),
};

case 'mainDialog':
return {
children: (
<div>{formatMessage('Main dialog is named after your bot. It is the root and entry point of a bot.')}</div>
),
children: formatMessage('The main dialog is named after your bot. It is the root and entry point of a bot.'),
headline: formatMessage('Main dialog'),
};

case 'trigger':
return {
children: (
<div>
{formatMessage(
'Triggers connect intents with bot responses. Think of a trigger as one capability of your bot. So your bot is a collection of triggers.'
)}
</div>
children: formatMessage.rich(
'Triggers connect intents with bot responses. Think of a trigger as one capability of your bot. So your bot is a collection of triggers. To add a new trigger, click the <b>Add</b> button in the toolbar, and then select the <b>Add a new trigger</b> option from the dropdown menu.',
{
b: Bold,
}
),
headline: formatMessage('Trigger'),
headline: formatMessage('Add a new trigger'),
};

case 'userInput':
return {
children: (
<div>
{formatMessage('You can define and manage ')}
<b>{formatMessage('intents ')}</b>
{formatMessage(
'here. Each intent describes a particular user intention through utterances (i.e. user says). '
)}
<b>{formatMessage('Intents are often triggers of your bot.')}</b>
</div>
children: formatMessage.rich(
'You can define and manage <b>intents</b> here. Each intent describes a particular user intention through utterances (i.e. user says). <b>Intents are often triggers of your bot.</b>',
{
b: Bold,
}
),
headline: formatMessage('User input'),
};

case 'actions':
return {
children: (
<div>
{formatMessage('Actions define ')}
<b>{formatMessage('how the bot responds ')}</b>
{formatMessage('to a certain trigger.')}
</div>
),
children: formatMessage.rich('Actions define <b>how the bot responds</b> to a certain trigger.', {
b: Bold,
}),
headline: formatMessage('Actions'),
};

case 'botResponses':
return {
children: (
<div>
{formatMessage(
'You can manage all bot responses here. Make good use of the templates to create sophisticated response logic based on your own needs.'
)}
</div>
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.'
),
headline: formatMessage('Bot responses'),
};
Expand All @@ -176,45 +161,63 @@ export const getTeachingBubble = (id: string | undefined): IComposerTeachingBubb
return {
children: (
<div>
<div>
{formatMessage('The welcome message is triggered by the ')}
<i>{formatMessage('ConversationUpdate ')}</i>
{formatMessage('event. You may customize or add a new one. To do this:')}
</div>
{formatMessage.rich(
'The welcome message is triggered by the <i>ConversationUpdate</i> event. To add a new <i>ConversationUpdate</i> trigger:',
{
i: Italics,
}
)}
<ol>
<li>
{formatMessage('Create a new or go to the trigger: ')}
<i>{formatMessage('ConversationUpdate')}</i>
{formatMessage.rich(
'Click the <b>Add</b> button in the toolbar, and select <b>Add a new trigger</b> from the dropdown menu.',
{
b: Bold,
}
)}
</li>
<li>
{formatMessage.rich(
'In the <b>Create a trigger</b> wizard, set the trigger type to <i>Activities</i> in the dropdown. Then set the <b>Activity Type</b> to <i>Greeting (ConversationUpdate activity)</i>, and click the <b>Submit</b> button.',
{ b: Bold, i: Italics }
)}
</li>
<li>
{formatMessage.rich(
"To customize the welcome message, select the <i>Send a response</i> action in the Visual Editor. Then in the Form Editor on the right, you can edit the bot's welcome message in the <b>Language Generation</b> field.",
{ b: Bold, i: Italics }
)}
</li>
<li>{formatMessage('Select/add action: send an Activity')}</li>
<li>{formatMessage('Edit the message in the right pane')}</li>
</ol>
</div>
),
headline: formatMessage('Add welcome message'),
headline: formatMessage('Add a welcome message'),
};

case 'intentTrigger':
return {
children: (
<div>
{formatMessage(
'Click on the + and select intent as the trigger type. Follow the wizard to define the intent and other trigger settings. Then add actions in the visual editor.'
)}
</div>
children: formatMessage.rich(
'Click on the <b>Add</b> button in the toolbar, and select <b>Add a new trigger</b>. In the <b>Create a trigger</b> wizard, set the <b>Trigger Type</b> to <i>Intent recognized</i> and configure the <b>Trigger Name</b> and <b>Trigger Phrases</b>. Then add actions in the Visual Editor.',
{ b: Bold, i: Italics }
),
headline: formatMessage('Add an intent trigger'),
};

case 'startBot':
return {
children: (
<div>
{formatMessage(
"This will open your Emulator application. If you don't yet have the Bot Framework Emulator installed, you can download it "
)}
<a href="https://github.com/microsoft/BotFramework-Emulator/releases/latest">{formatMessage('here.')}</a>
</div>
children: formatMessage.rich(
"This will open your Emulator application. If you don't yet have the Bot Framework Emulator installed, you can download it <a>here</a>.",
{
a: ({ children }) => (
<a
href="https://github.com/microsoft/BotFramework-Emulator/releases/latest"
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
),
}
),
headline: formatMessage('Test your bot'),
};
Expand Down
Loading