Skip to content

Commit

Permalink
Merge 43ab511 into cf9f0e8
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Wilaby committed Apr 25, 2019
2 parents cf9f0e8 + 43ab511 commit ff82255
Show file tree
Hide file tree
Showing 30 changed files with 423 additions and 78 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ install:
- lerna bootstrap --hoist

script:
- npm run lint
- npm run build
- npm run test:coveralls

Expand Down
70 changes: 70 additions & 0 deletions content/CHANNELS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# EAP Testing Instructions (Node Only)
## Prerequisites
EAP Testing assumes you have a bot deployed to Azure configured to use the Teams channel.
## TL;DR
1. install [ngrok](https://ngrok.com/) locally then cd to it's location in a terminal
2. run ngrok `./ngrok http 3979 --host-header=localhost`
3. Open your bot in the azure portal, select the `settings` blade and paste the ngrok url provided in the terminal then save.
4. Clone the [BotBuilder-JS](https://github.com/Microsoft/botbuilder-js) repo
5. `git checkout stevenic/4.4-planning`
6. install lerna: `npm i -g lerna` then run `lerna bootstrap --hoist && npm run build`
7. `cd samples/10. sidecarDebugging` and build using `npm run build`
8. set your environment variables (prefix with EXPORT for mac, SET for windows):
```bash
PORT=3979;
NODE_ENV=development;
MICROSOFT_APP_ID=<your app id>;
MICROSOFT_APP_PASSWORD=<your bot's password>;
EMULATOR_URL=http://localhost:9000;
```
9. Run your bot: `npm start`
10. Open the Teams chat link provided in the azure portal in the channels blade.
11. checkout the [Emulator](https://github.com/Microsoft/Botframework-emulator) branch`jwilaby/auto-connect-emulator`, build and run the Emulator as usual.
12. In the Emulator, go to the view menu and select "Sidecar debug mode" the begin chatting with your bot in teams.
# Run Your Bot in a Channel While Debugging Locally
If you have configured your bot to [run in 1 or more channels](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-manage-channels?view=azure-bot-service-4.0) and would like to test a locally running bot using the Emulator while interacting with it in a communication app (Teams, Skype, Slack, WebChat, etc.), these instructions will show you how.
Since chat messages, adaptive cards, and other features have notable differences across the many available channels, the Emulator cannot reasonably account for all of them. This guide shows you how to have a locally running bot where changes can be made quickly, breakpoints can be set and the project can be rebuilt and restarted while interacting with it via an installed app like Teams or Slack.
# How it works
The tunneling software [ngrok](https://ngrok.com/) is used to create a tunnel to your locally running bot. The tunnel's URL is provided to your web app bot in Azure. You build your bot with an Emulator aware Adapter, run it locally then chat with it in Teams or any other available channel. The Emulator still receives the conversation's message exchange as usual.
# Let's get Started
If you haven't already, get the [latest Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases), [ngrok](https://ngrok.com/) and update your bot's dependencies to use BotBuilder v4.4+

## 1. Update Your Bot's Code
Your bot will need to become "Emulator Aware" in order to connect to and send the conversation exchange to the Emulator. Include the BotDebugger class somewhere in your bot's code:
<img width="354" alt="image" src="https://user-images.githubusercontent.com/2652885/55196481-b5355580-516c-11e9-84dd-facae6c26528.png">
Then set the `EMULATOR_URL` environment variable to `http://localhost:9000` prior to launching your bot. On a mac, this is done using this command:
```bash
export EMULATOR_URL=http://localhost:9000
```
Alternatively, you can refer to documentation in [VS Code](https://code.visualstudio.com/docs/editor/variables-reference#_environment-variables) for setting up environment variables in the launch configs for your bot.
## 2. Run ngrok
Open a terminal and run ngrok with the following command to create a new tunnel:
```bash
./ngrok http 3979 --host-header=localhost
```
The output in the terminal should look something like this:
<img width="639" alt="image" src="https://user-images.githubusercontent.com/2652885/55196448-a2bb1c00-516c-11e9-87ce-98bdc1ebd7f8.png">
## 3. Update Azure to Point to the Tunnel
In the azure portal, [navigate to your bot's settings](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-manage-settings?view=azure-bot-service-4.0) and paste the
url provided by the ngrok terminal in the *Messaging endpoint* field and save the changes. Do not forget to use `/api/messages`. It's best to create a Bot in Azure that is used specifically for this purpose. Do not overwrite
the messaging endpoint of a deployed production bot.
## 4. Connect to a Channel
If you haven't already done so, [connect your bot to a channel](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-manage-channels?view=azure-bot-service-4.0). You may also need to download and install the app
associated with the channel if you have't already.
## 5. Debug!
Run your bot locally, then open the Emulator. In the Emulator choose file > Sidecar Debug Mode.
<img width="357" alt="image" src="https://user-images.githubusercontent.com/2652885/55196498-c8482580-516c-11e9-8276-e660593197c4.png">
This will place the Emulator in a read only mode an enable inbound
connections from your locally running bot. Begin chatting with your bot in Teams, Skype, Slack or WebChat. Once the first message is received, your bot will connect
to the Emulator and send it the conversation exchange.
3 changes: 2 additions & 1 deletion packages/app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build:vendors:dev": "webpack --mode development --progress --colors",
"build:shared:dev": "webpack --mode development --progress --colors",
"build:app:dev": "webpack --mode development --progress --colors",
"build": "run-s lint build:vendors build:shared build:app",
"build": "run-s build:vendors build:shared build:app",
"lint": "eslint --color --quiet --ext .js,.jsx,.ts,.tsx ./src",
"lint:fix": "npm run lint -- --fix",
"start": "run-s build:vendors:dev build:shared:dev webpackdevServer:dev",
Expand Down Expand Up @@ -111,6 +111,7 @@
"botframework-webchat-core": "^4.3.0",
"deep-diff": "^1.0.2",
"eslint-plugin-react": "^7.12.3",
"markdown-it": "^8.4.2",
"react": "~16.3.2",
"react-dom": "~16.3.2",
"react-redux": "^5.0.7",
Expand Down
30 changes: 28 additions & 2 deletions packages/app/client/src/commands/uiCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ import * as Constants from '../constants';
import { azureArmTokenDataChanged, beginAzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';
import { switchDebugMode } from '../data/action/debugModeAction';
import * as EditorActions from '../data/action/editorActions';
import { close } from '../data/action/editorActions';
import * as NavBarActions from '../data/action/navBarActions';
import { ProgressIndicatorPayload, updateProgressIndicator } from '../data/action/progressIndicatorActions';
import { switchTheme } from '../data/action/themeActions';
import { getTabGroupForDocument, showWelcomePage } from '../data/editorHelpers';
import { getTabGroupForDocument, showMarkdownPage, showWelcomePage } from '../data/editorHelpers';
import { AzureAuthState } from '../data/reducer/azureAuthReducer';
import { store } from '../data/store';
import { CommandServiceImpl } from '../platform/commands/commandServiceImpl';
Expand All @@ -61,7 +62,6 @@ import {
} from '../ui/dialogs';
import * as ExplorerActions from '../data/action/explorerActions';
import { closeConversation } from '../data/action/chatActions';
import { close } from '../data/action/editorActions';
import { ActiveBotHelper } from '../ui/helpers/activeBotHelper';

/** Register UI commands (toggling UI) */
Expand All @@ -74,6 +74,32 @@ export function registerCommands(commandRegistry: CommandRegistry) {
return showWelcomePage();
});

// ---------------------------------------------------------------------------
// Shows the markdown page after retrieving the remote source
commandRegistry.registerCommand(UI.ShowMarkdownPage, async (urlOrMarkdown: string, label: string) => {
let markdown = '';
let { onLine } = navigator;
if (!onLine) {
return showMarkdownPage(markdown, label, onLine);
}
try {
new URL(urlOrMarkdown); // Is this a valid URL?
const bytes: ArrayBuffer = await CommandServiceImpl.remoteCall(
SharedConstants.Commands.Electron.FetchRemote,
urlOrMarkdown
);
markdown = new TextDecoder().decode(bytes);
} catch (e) {
if (typeof e === 'string' && ('' + e).includes('ENOTFOUND')) {
onLine = false;
} else {
// assume this is markdown text
markdown = urlOrMarkdown;
}
}
return showMarkdownPage(markdown, label, onLine);
});

// ---------------------------------------------------------------------------
// Shows a bot creation dialog
commandRegistry.registerCommand(UI.ShowBotCreationDialog, async () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/app/client/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import { SharedConstants } from '@bfemulator/app-shared';

export const CONTENT_TYPE_MARKDOWN = 'application/vnd.microsoft.bfemulator.document.markdown';
export const CONTENT_TYPE_APP_SETTINGS = 'application/vnd.microsoft.bfemulator.document.appsettings';
export const CONTENT_TYPE_WELCOME_PAGE = 'application/vnd.microsoft.bfemulator.document.welcome';
export const CONTENT_TYPE_TRANSCRIPT = 'application/vnd.microsoft.bfemulator.document.transcript';
Expand All @@ -51,3 +52,4 @@ export const EditorKeys = [EDITOR_KEY_PRIMARY, EDITOR_KEY_SECONDARY];
export const DOCUMENT_ID_APP_SETTINGS = 'app:settings';
export const DOCUMENT_ID_BOT_SETTINGS = 'bot:settings';
export const DOCUMENT_ID_WELCOME_PAGE = 'welcome-page';
export const DOCUMENT_ID_MARKDOWN_PAGE = 'markdown-page';
11 changes: 11 additions & 0 deletions packages/app/client/src/data/editorHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ export function showWelcomePage(): void {
);
}

export function showMarkdownPage(markdown: string, label: string, onLine: boolean): void {
store.dispatch(
EditorActions.open({
contentType: Constants.CONTENT_TYPE_MARKDOWN,
documentId: Constants.DOCUMENT_ID_MARKDOWN_PAGE,
isGlobal: true,
meta: { markdown, label, onLine },
})
);
}

export function tabGroupHasDocuments(tabGroup: Editor): boolean {
return !!Object.keys(tabGroup.documents).length;
}
1 change: 0 additions & 1 deletion packages/app/client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import './ui/styles/globals.scss';
interceptError();
interceptHyperlink();

CommandServiceImpl.init();
LogService.init();

registerAllCommands(CommandRegistry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ import { ElectronIPC } from '../../ipc';
class CServiceImpl extends DisposableImpl implements CommandService {
private readonly _service: InternalSharedService;

public init() {
return null;
}

public get registry() {
return this._service.registry;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/app/client/src/ui/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import * as React from 'react';
import * as Constants from '../../constants';
import { Document } from '../../data/reducer/editor';

import { MarkdownPage } from './markdownPage/markdownPage';

import { AppSettingsEditorContainer, EmulatorContainer, WelcomePageContainer } from './index';

interface EditorFactoryProps {
Expand All @@ -62,6 +64,9 @@ export class EditorFactory extends React.Component<EditorFactoryProps> {
case Constants.CONTENT_TYPE_WELCOME_PAGE:
return <WelcomePageContainer documentId={document.documentId} />;

case Constants.CONTENT_TYPE_MARKDOWN:
return <MarkdownPage markdown={document.meta.markdown} onLine={document.meta.onLine} />;

default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Framework Emulator Github:
// https://github.com/Microsoft/BotFramwork-Emulator
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { mount, ReactWrapper } from 'enzyme';
import * as React from 'react';

import { WelcomePage } from '../welcomePage/welcomePage';

import { MarkdownPage, MarkdownPageProps } from './markdownPage';

describe('The Markdown page', () => {
let parent: ReactWrapper;
let instance: WelcomePage;
const render = (props: MarkdownPageProps) => {
parent = mount<MarkdownPage>(<MarkdownPage {...props} />);
instance = parent.instance() as WelcomePage;
};

it('should render markdown when the user is online', () => {
render({ onLine: true, markdown: '# markdown!' });
const divHtml = parent.html();
expect(divHtml).toBe('<div class="undefined "><div><div><h1>markdown!</h1>\n</div></div></div>');
});

it('should render offline content when the user is offline', () => {
render({ onLine: false, markdown: '' });
const divHtml = parent.html();
expect(divHtml).toBe(
'<div class="undefined "><div><div><h1>No Internet Connection</h1>try:<ul><li>Checking the network cables, model or router</li><li>Reconnecting to Wi-Fi</li></ul></div></div></div>'
);
});

it('should not update unless the props have changed and have different values', () => {
render({ onLine: true, markdown: '# markdown!' });
const props = { ...instance.props };
expect(instance.shouldComponentUpdate(props, {}, {})).toBe(false);
});

it('should render the "invalid markdown" message when invalid markdown is provided', () => {
render({ onLine: true, markdown: [] as any });
const divHtml = parent.html();
expect(divHtml).toBe('<div class="undefined "><div><div># Error - Invalid markdown document</div></div></div>');
});
});
16 changes: 16 additions & 0 deletions packages/app/client/src/ui/editor/markdownPage/markdownPage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.offline {
padding: 175px 20px 20px;
position: relative;
min-height: 450px;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 175px;
height: 175px;
-webkit-mask: url('../../media/ic_bot_framework.svg');
-webkit-mask-size: 175px;
background-color: #007ACC;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This is a generated file. Changes are likely to result in being overwritten
export const offline: string;
83 changes: 83 additions & 0 deletions packages/app/client/src/ui/editor/markdownPage/markdownPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Framework Emulator Github:
// https://github.com/Microsoft/BotFramwork-Emulator
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import * as React from 'react';
import { Component } from 'react';
import MarkdownIt from 'markdown-it';

import { GenericDocument } from '../../layout';

import * as styles from './markdownPage.scss';

export interface MarkdownPageProps {
markdown: string;
onLine: boolean;
}

export class MarkdownPage extends Component<MarkdownPageProps> {
private static markdownRenderer = new MarkdownIt();

private static renderMarkdown(markdown: string) {
try {
return this.markdownRenderer.render(markdown);
} catch (e) {
return '# Error - Invalid markdown document';
}
}

private static get offlineElement(): JSX.Element {
return (
<div className={styles.offline}>
<h1>No Internet Connection</h1>
try:
<ul>
<li>Checking the network cables, model or router</li>
<li>Reconnecting to Wi-Fi</li>
</ul>
</div>
);
}

public shouldComponentUpdate(nextProps: Readonly<MarkdownPageProps> = {} as MarkdownPageProps): boolean {
const props = this.props || ({} as MarkdownPageProps);
return props.markdown !== nextProps.markdown || props.onLine !== nextProps.onLine;
}

public render() {
const children = !this.props.onLine ? (
MarkdownPage.offlineElement
) : (
<div dangerouslySetInnerHTML={{ __html: MarkdownPage.renderMarkdown(this.props.markdown) }} />
);
return <GenericDocument>{children}</GenericDocument>;
}
}
Loading

0 comments on commit ff82255

Please sign in to comment.