Skip to content

Commit

Permalink
feat: Add runtime settings page and eject (microsoft#2572)
Browse files Browse the repository at this point in the history
* add new runtime settings page

* add popup modal for pickign runtime

* add ability for plugins to specify a runtime template

* add sample runtime to localPublish
add actual file copy mechanism

* handle success and failure of runtime injection

* respect enableCustomRuntime

* pull real runtime code

* eject real code
update the readme file included in the runtime

* change the path of the declarative assets relative to the runtime

* update code copying to reflect new asset locations

* cleanup

* allow parameters to be included in start command

* update readme with new info about additional plugin APIs

* change schema of settings

* little bit more error correction

* fix issue with field binding

* fix form behaviors

* address comments from andy

Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
  • Loading branch information
benbrown and cwhitten authored Apr 23, 2020
1 parent aa518b7 commit ea0e3cd
Show file tree
Hide file tree
Showing 26 changed files with 721 additions and 100 deletions.
36 changes: 29 additions & 7 deletions BotProject/Templates/CSharp/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
## Bot Project
## Bot Runtime
Bot project is the launcher project for the bots written in declarative form (JSON), using the Composer, for the Bot Framework SDK.
This same code is used by Composer to start the bot locally for testing.

## Instructions for setting up the Bot Project runtime
The Bot Project is a regular Bot Framework SDK V4 project. Before you can launch it from the emulator, you need to make sure you can run the bot.
## Instructions for using and customizing the bot runtime

Composer can be configured to use a customized copy of this runtime.
A copy of it can be added to your project automatically by using the "runtime settings" page in Composer.

The Bot Project is a regular Bot Framework SDK V4 project. You can modify the code of this project
and continue to use it with Composer.

* Add additional middleware
* Customize the state storage system
* Add custom dialog classes

### Prerequisite:
* Install .Netcore 3.1

### Commands:
### Build:

* from root folder
* cd BotProject
* cd Templates/CSharp
* cd [my bot folder]/runtime
* dotnet user-secrets init // init the user secret id
* dotnet build // build


### Run from Command line:
* cd [my bot folder]/runtime
* dotnet run // start the bot
* It will start a web server and listening at http://localhost:3979.

### Run with Composer

Open your bot project in Composer. Navigate to the runtime settings tab.

Set the path to runtime to the full path to your runtime code. Customize the start command as necessary.

The "Start Bot" button will now use your customized runtime.

Note: the application code must be built and ready to run before Composer can manage it.

### Test bot
* You can set you emulator to connect to http://localhost:3979/api/messages.

2 changes: 1 addition & 1 deletion BotProject/Templates/CSharp/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"microsoftAppId": "",
"bot": "ComposerDialogs",
"bot": "../",
"cosmosDb": {
"authKey": "",
"collectionId": "botstate-collection",
Expand Down
2 changes: 2 additions & 0 deletions Composer/packages/client/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,11 @@ export enum ActionTypes {
GET_PUBLISH_STATUS_FAILED = 'GET_PUBLISH_STATUS_FAILED',
GET_PUBLISH_HISTORY = 'GET_PUBLISH_HISTORY',
UPDATE_BOTSTATUS = 'UPDATE_BOTSTATUS',
SET_RUNTIME_TEMPLATES = 'SET_RUNTIME_TEMPLATES',
SET_USER_SETTINGS = 'SET_USER_SETTINGS',
ADD_SKILL_DIALOG_BEGIN = 'ADD_SKILL_DIALOG_BEGIN',
ADD_SKILL_DIALOG_END = 'ADD_SKILL_DIALOG_END',
EJECT_SUCCESS = 'EJECT_SUCCESS',
SET_MESSAGE = 'SET_MESSAGE',
SET_APP_UPDATE_ERROR = 'SET_APP_UPDATE_ERROR',
SET_APP_UPDATE_PROGRESS = 'SET_APP_UPDATE_PROGRESS',
Expand Down
3 changes: 3 additions & 0 deletions Composer/packages/client/src/pages/setting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const SettingPage: React.FC<RouteComponentProps<{ '*': string }>> = props => {
publish: formatMessage('Publish'),
settings: formatMessage('Settings'),
preferences: formatMessage('User Preferences'),
runtime: formatMessage('Runtime Config'),
};

const links: INavLink[] = [
Expand All @@ -44,6 +45,8 @@ const SettingPage: React.FC<RouteComponentProps<{ '*': string }>> = props => {
url: '',
},
{ key: 'preferences', name: settingLabels.preferences, url: '' },
{ key: 'runtime', name: settingLabels.runtime, url: '' },

// { key: '/settings/publish', name: settingLabels.publish, url: '' },

// { key: 'services', name: formatMessage('Services') },
Expand Down
2 changes: 2 additions & 0 deletions Composer/packages/client/src/pages/setting/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DialogSettings } from './dialog-settings';
import { RemotePublish } from './remote-publish';
import { Deployment } from './deployment';
import { UserSettings } from './user-settings';
import { RuntimeSettings } from './runtime-settings';

const Routes = () => {
return (
Expand All @@ -19,6 +20,7 @@ const Routes = () => {
<Deployment path="deployment" />
<RemotePublish path="remote-publish" />
<UserSettings path="preferences" />
<RuntimeSettings path="runtime" />
</Router>
</ErrorBoundary>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { useEffect, useMemo, useState, useContext } from 'react';
import { Dialog, DialogType } from 'office-ui-fabric-react/lib/Dialog';
import formatMessage from 'format-message';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';

import { StoreContext } from '../../../store';

import { modalControlGroup } from './style';

export interface EjectModalProps {
ejectRuntime: (templateKey: string) => void;
hidden: boolean;
closeModal: () => void;
}

export const EjectModal: React.FC<EjectModalProps> = props => {
const [selectedTemplate, setSelectedTemplate] = useState<string | undefined>();
const { state, actions } = useContext(StoreContext);
const { runtimeTemplates } = state;

useEffect(() => {
actions.getRuntimeTemplates();
}, []);

const availableRuntimeTemplates = useMemo(() => {
return runtimeTemplates.map(t => {
return {
text: t.name,
key: t.key,
};
});
}, [runtimeTemplates]);

const selectTemplate = (ev, item?: IChoiceGroupOption) => {
if (item) {
setSelectedTemplate(item.key);
}
};

const doEject = () => {
if (selectedTemplate) {
props.ejectRuntime(selectedTemplate);
}
};

return (
<Dialog
hidden={props.hidden}
onDismiss={props.closeModal}
dialogContentProps={{
type: DialogType.normal,
title: formatMessage('Add custom runtime'),
subText: formatMessage('Select runtime version to add'),
}}
modalProps={{
isBlocking: false,
}}
>
<div css={modalControlGroup}>
<ChoiceGroup options={availableRuntimeTemplates} onChange={selectTemplate} required={true} />
</div>
<DialogFooter>
<DefaultButton onClick={props.closeModal}>Cancel</DefaultButton>
<PrimaryButton onClick={doEject} disabled={!selectedTemplate}>
{formatMessage('Okay')}
</PrimaryButton>
</DialogFooter>
</Dialog>
);
};
123 changes: 123 additions & 0 deletions Composer/packages/client/src/pages/setting/runtime-settings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import { useState, useContext } from 'react';
import formatMessage from 'format-message';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Link } from 'office-ui-fabric-react/lib/Link';
import { RouteComponentProps } from '@reach/router';

import { LoadingSpinner } from '../../../components/LoadingSpinner';
import { StoreContext } from '../../../store';

import { EjectModal } from './ejectModal';
import {
breathingSpace,
runtimeSettingsStyle,
runtimeControls,
runtimeControlsTitle,
runtimeToggle,
controlGroup,
} from './style';

export const RuntimeSettings: React.FC<RouteComponentProps> = () => {
const { state, actions } = useContext(StoreContext);
const { botName, settings, projectId } = state;
const [formDataErrors, setFormDataErrors] = useState({ command: '', path: '' });
const [ejectModalVisible, setEjectModalVisible] = useState(false);

const changeEnabled = (_, on) => {
actions.setSettings(projectId, botName, { ...settings, runtime: { ...settings.runtime, customRuntime: on } });
};

const updateSetting = field => (e, newValue) => {
let valid = true;
let error = 'There was an error';
if (newValue === '') {
valid = false;
error = 'This is a required field.';
}

actions.setSettings(projectId, botName, { ...settings, runtime: { ...settings.runtime, [field]: newValue } });

if (valid) {
setFormDataErrors({ ...formDataErrors, [field]: '' });
} else {
setFormDataErrors({ ...formDataErrors, [field]: error });
}
};

const header = () => (
<div css={runtimeControls}>
<h1 css={runtimeControlsTitle}>{formatMessage('Bot runtime settings')}</h1>
<p>{formatMessage('Configure Composer to start your bot using runtime code you can customize and control.')}</p>
</div>
);

const toggle = () => (
<div css={runtimeToggle}>
<Toggle
label={formatMessage('Use custom runtime')}
inlineLabel
onChange={changeEnabled}
checked={settings.runtime && settings.runtime.customRuntime === true}
/>
</div>
);

const showEjectModal = () => {
setEjectModalVisible(true);
};
const closeEjectModal = () => {
setEjectModalVisible(false);
};

const ejectRuntime = async (templateKey: string) => {
await actions.ejectRuntime(projectId, templateKey);
closeEjectModal();
};

return botName ? (
<div css={runtimeSettingsStyle}>
{header()}
{toggle()}
<div css={controlGroup}>
<TextField
label={formatMessage('Runtime code location')}
value={settings.runtime ? settings.runtime.path : ''}
styles={name}
required
onChange={updateSetting('path')}
errorMessage={formDataErrors.path}
data-testid="runtimeCodeLocation"
disabled={!settings.runtime || !settings.runtime.customRuntime}
/>
{formatMessage('Or: ')}
<Link
onClick={showEjectModal}
disabled={!settings.runtime || !settings.runtime.customRuntime}
css={breathingSpace}
>
{formatMessage('Get a new copy of the runtime code')}
</Link>

<TextField
label={formatMessage('Start command')}
value={settings.runtime ? settings.runtime.command : ''}
styles={name}
required
onChange={updateSetting('command')}
errorMessage={formDataErrors.command}
data-testid="runtimeCommand"
disabled={!settings.runtime || !settings.runtime.customRuntime}
/>
</div>
<EjectModal hidden={!ejectModalVisible} closeModal={closeEjectModal} ejectRuntime={ejectRuntime} />
</div>
) : (
<LoadingSpinner />
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { css } from '@emotion/core';
import { FontWeights, FontSizes } from 'office-ui-fabric-react/lib/Styling';
export const runtimeSettingsStyle = css`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 1rem;
display: flex;
flex-direction: column;
box-sizing: border-box;
`;

export const runtimeControls = css`
margin-bottom: 18px;
& > h1 {
margin-top: 0;
}
`;

export const runtimeToggle = css`
display: flex;
& > * {
margin-right: 2rem;
}
`;

export const controlGroup = css`
border: 1px solid rgb(237, 235, 233);
padding: 0.5rem 1rem 1rem 1rem;
`;

export const modalControlGroup = css`
border: 1px solid rgb(237, 235, 233);
padding: 0.5rem 1rem 1rem 1rem;
`;

export const runtimeControlsTitle = css`
font-size: ${FontSizes.xLarge};
font-weight: ${FontWeights.semibold};
`;

export const breathingSpace = css`
margin-bottom: 1rem;
`;
Loading

0 comments on commit ea0e3cd

Please sign in to comment.