diff --git a/Composer/babel.config.js b/Composer/babel.config.js index abbd567431..8d6062d167 100644 --- a/Composer/babel.config.js +++ b/Composer/babel.config.js @@ -13,6 +13,7 @@ module.exports = { ], '@babel/preset-react', '@babel/preset-typescript', + '@emotion/babel-preset-css-prop', ], plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-transform-runtime'], }, diff --git a/Composer/cypress/integration/Breadcrumb.spec.js b/Composer/cypress/integration/Breadcrumb.spec.js index c2955b8b7c..7ac2eeca9b 100644 --- a/Composer/cypress/integration/Breadcrumb.spec.js +++ b/Composer/cypress/integration/Breadcrumb.spec.js @@ -57,7 +57,7 @@ context('breadcrumb', () => { it('can show action name in breadcrumb', () => { cy.wait(100); cy.get('[data-testid="ProjectTree"]').within(() => { - cy.getByText('Handle ConversationUpdate').click(); + cy.getByText('Handle an Event: BeginDialog').click(); cy.wait(500); }); diff --git a/Composer/package.json b/Composer/package.json index 1ab46e8930..fd49d3bd4e 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -58,6 +58,7 @@ "@babel/preset-env": "7.3.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", + "@emotion/babel-preset-css-prop": "^10.0.17", "@typescript-eslint/eslint-plugin": "2.4.0", "@typescript-eslint/parser": "2.4.0", "babel-jest": "24.0.0", diff --git a/Composer/packages/client/babel.config.js b/Composer/packages/client/babel.config.js index ddb23fa70a..24a670dc81 100644 --- a/Composer/packages/client/babel.config.js +++ b/Composer/packages/client/babel.config.js @@ -23,6 +23,7 @@ module.exports = { }, ], '@babel/preset-react', + '@emotion/babel-preset-css-prop', ], plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-transform-runtime'], }, diff --git a/Composer/packages/client/src/ShellApi.ts b/Composer/packages/client/src/ShellApi.ts index f315fa6ad0..e29575b3aa 100644 --- a/Composer/packages/client/src/ShellApi.ts +++ b/Composer/packages/client/src/ShellApi.ts @@ -10,6 +10,7 @@ import * as lgUtil from './utils/lgUtil'; import { StoreContext } from './store'; import ApiClient from './messenger/ApiClient'; import { getDialogData, setDialogData, sanitizeDialogData } from './utils'; +import { isAbsHosted } from './utils/envUtil'; import { OpenAlertModal, DialogStyle } from './components/Modal'; import { getFocusPath, navigateTo } from './utils/navigation'; @@ -171,6 +172,7 @@ export const ShellApi: React.FC = () => { focusedEvent: selected, focusedSteps: focused ? [focused] : selected ? [selected] : [], focusedTab: promptTab, + hosted: !!isAbsHosted(), }; } diff --git a/Composer/packages/client/src/TestController.tsx b/Composer/packages/client/src/TestController.tsx index 16c5cafe02..bcfefff2b7 100644 --- a/Composer/packages/client/src/TestController.tsx +++ b/Composer/packages/client/src/TestController.tsx @@ -17,6 +17,7 @@ import { bot, botButton, calloutLabel, calloutDescription, calloutContainer } fr import { BotStatus, LuisConfig, Text } from './constants'; import { PublishLuisDialog } from './publishDialog'; import { OpenAlertModal, DialogStyle } from './components/Modal'; +import { isAbsHosted } from './utils/envUtil'; import { getReferredFiles } from './utils/luUtil'; const openInEmulator = (url, authSettings: { MicrosoftAppId: string; MicrosoftAppPassword: string }) => { @@ -37,8 +38,6 @@ const STATE = { SUCCESS: 2, }; -const isAbsHosted = () => process.env.COMPOSER_AUTH_PROVIDER === 'abs-h'; - export const TestController: React.FC = () => { const { state, actions } = useContext(StoreContext); const [modalOpen, setModalOpen] = useState(false); diff --git a/Composer/packages/client/src/components/RequireAuth/index.tsx b/Composer/packages/client/src/components/RequireAuth/index.tsx index 2f8ddc1e46..766a9afc25 100644 --- a/Composer/packages/client/src/components/RequireAuth/index.tsx +++ b/Composer/packages/client/src/components/RequireAuth/index.tsx @@ -1,3 +1,5 @@ +/** @jsx jsx */ +import { jsx } from '@emotion/core'; import React, { useEffect, useState, useContext } from 'react'; import { Spinner, SpinnerSize, Dialog, DialogType, DialogFooter, PrimaryButton } from 'office-ui-fabric-react'; import formatMessage from 'format-message'; @@ -60,9 +62,9 @@ export const RequireAuth: React.FC = props => { } return ( - <> + {sessionExpiredDialog} {props.children} - + ); }; diff --git a/Composer/packages/client/src/pages/setting/dialog-settings/index.js b/Composer/packages/client/src/pages/setting/dialog-settings/index.js deleted file mode 100644 index fafa38e6ea..0000000000 --- a/Composer/packages/client/src/pages/setting/dialog-settings/index.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useState, useContext, useEffect } from 'react'; -import { Controlled as CodeMirror } from 'react-codemirror2'; -import jsonlint from 'jsonlint-webpack'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/addon/lint/lint.css'; -import 'codemirror/theme/neat.css'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/addon/lint/lint'; -import 'codemirror/addon/lint/json-lint'; - -import './style.css'; - -import { StoreContext } from './../../../store'; - -window.jsonlint = jsonlint; - -const cmOptions = { - theme: 'neat', - mode: { - name: 'javascript', - json: true, - statementIndent: 2, - }, - lineWrapping: true, - indentWithTabs: false, - lint: true, - tabSize: 2, - smartIndent: true, -}; - -export const DialogSettings = () => { - const { state, actions } = useContext(StoreContext); - const { botName, settings } = state; - const [value, setValue] = useState(JSON.stringify(settings, null, 2)); - - useEffect(() => { - setValue(JSON.stringify(settings, null, 2)); - }, [settings]); - - const updateFormData = (editor, data, newValue) => { - try { - setValue(newValue); - const result = JSON.parse(newValue); - try { - actions.setSettings(botName, result); - } catch (err) { - console.error(err.message); - } - } catch (err) { - //Do Nothing - } - }; - - return botName ? ( - - ) : ( -
Data Loading...
- ); -}; diff --git a/Composer/packages/client/src/pages/setting/dialog-settings/index.tsx b/Composer/packages/client/src/pages/setting/dialog-settings/index.tsx new file mode 100644 index 0000000000..86a4ce99f0 --- /dev/null +++ b/Composer/packages/client/src/pages/setting/dialog-settings/index.tsx @@ -0,0 +1,125 @@ +/** @jsx jsx */ +import { jsx } from '@emotion/core'; +import { useState, useContext, useEffect } from 'react'; +import { RichEditor } from 'code-editor'; +import formatMessage from 'format-message'; +import { DefaultButton, ChoiceGroup, Link, Toggle } from 'office-ui-fabric-react'; + +import { StoreContext } from '../../../store'; +import { isAbsHosted } from '../../../utils/envUtil'; +import { obfuscate } from '../../../utils/objUtil'; + +import { hostedSettings, hostedControls, hostedToggle, slotChoice, settingsEditor } from './style'; + +const hostControlLabels = { + showKeys: formatMessage('Show keys'), + productionSlot: formatMessage('In production'), + integrationSlot: formatMessage('In test'), + botSettings: formatMessage('Settings'), + botSettingDescription: formatMessage( + 'Settings contains detailed information about your bot. For security reasons, they are hidden by default. To test your bot or publish to Azure, you may need to provide these settings.' + ), + learnMore: formatMessage('Learn more.'), +}; + +export const DialogSettings = () => { + const { state, actions } = useContext(StoreContext); + const { botName, settings: origSettings, botEnvironment } = state; + const absHosted = isAbsHosted(); + const { luis, MicrosoftAppPassword, MicrosoftAppId, ...settings } = origSettings; + const managedSettings = { luis, MicrosoftAppPassword, MicrosoftAppId }; + const visibleSettings = absHosted ? settings : origSettings; + const [value, setValue] = useState(JSON.stringify(visibleSettings, null, 2)); + const [editing, setEditing] = useState(false); + const [slot, setSlot] = useState(botEnvironment === 'editing' ? 'integration' : botEnvironment); + const [parseError, setParseError] = useState(''); + + useEffect(() => { + setValue(JSON.stringify(editing ? visibleSettings : obfuscate(visibleSettings), null, 2)); + }, [origSettings, editing]); + + const changeEditing = (_, on) => { + setEditing(on); + actions.setEditDialogSettings(on, absHosted ? slot : undefined); + }; + + const slots = [ + { key: 'production', text: hostControlLabels.productionSlot, checked: slot === 'production' }, + { key: 'integration', text: hostControlLabels.integrationSlot, checked: slot === 'integration' }, + ]; + + const changeSlot = (_, option) => { + setSlot(option.key); + actions.setDialogSettingsSlot(editing, option.key); + }; + + const hostedControl = () => ( +
+

{hostControlLabels.botSettings}

+

+ {hostControlLabels.botSettingDescription} +   + + {hostControlLabels.learnMore} + +

+ {absHosted ? : null} +
+ ); + + const toggle = () => ( +
+ + {absHosted && ( + handleChange(value, true)} /> + )} +
+ ); + + const saveChangeResult = result => { + try { + const mergedResult = absHosted ? { ...managedSettings, ...result } : result; + actions.setSettings(botName, mergedResult, absHosted ? slot : undefined); + } catch (err) { + console.error(err.message); + } + }; + + const handleChange = (value, commit) => { + setValue(value); + try { + const result = JSON.parse(value); + if (commit || !absHosted) { + saveChangeResult(result); + } + } catch (err) { + setParseError('invalid json'); + } + }; + + const handleMount = monaco => { + monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ + validate: true, + }); + }; + + return botName ? ( +
+ {hostedControl()} + {toggle()} +
+ handleChange(x, false)} + errorMsg={parseError} + editorWillMount={handleMount} + options={{ folding: true, readOnly: !editing }} + value={value} + helpURL="https://www.json.org" + /> +
+
+ ) : ( +
{formatMessage('Data loading...')}
+ ); +}; diff --git a/Composer/packages/client/src/pages/setting/dialog-settings/style.css b/Composer/packages/client/src/pages/setting/dialog-settings/style.css deleted file mode 100644 index fcb59481ce..0000000000 --- a/Composer/packages/client/src/pages/setting/dialog-settings/style.css +++ /dev/null @@ -1,9 +0,0 @@ -.CodeMirror { - position: absolute; - top: 10px; - bottom: 0; - left: 0; - right: 0; - height: auto; - overflow: hidden; -} diff --git a/Composer/packages/client/src/pages/setting/dialog-settings/style.ts b/Composer/packages/client/src/pages/setting/dialog-settings/style.ts new file mode 100644 index 0000000000..da7366bc57 --- /dev/null +++ b/Composer/packages/client/src/pages/setting/dialog-settings/style.ts @@ -0,0 +1,38 @@ +import { css } from '@emotion/core'; + +export const hostedSettings = css` + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 1rem; + display: flex; + flex-direction: column; + box-sizing: border-box; +`; + +export const hostedControls = css` + margin-bottom: 18px; + + & > h1 { + margin-top: 0; + } +`; + +export const hostedToggle = css` + display: flex; + + & > * { + margin-right: 2rem; + } +`; + +export const slotChoice = css` + max-width: 40ch; +`; + +export const settingsEditor = css` + flex: 1; + max-height: 70%; +`; diff --git a/Composer/packages/client/src/pages/setting/index.js b/Composer/packages/client/src/pages/setting/index.js index b1501a4fa5..b26cf36422 100644 --- a/Composer/packages/client/src/pages/setting/index.js +++ b/Composer/packages/client/src/pages/setting/index.js @@ -5,6 +5,7 @@ import { Link } from '@reach/router'; import { ToolBar } from '../../components/ToolBar'; import { navigateTo } from '../../utils'; +import { isAbsHosted } from '../../utils/envUtil'; import Routes from './router'; import { Tree } from './../../components/Tree/index'; @@ -13,19 +14,22 @@ import { title, fileList, contentEditor, linkItem } from './styles'; import { MainContent } from './../../components/MainContent/index'; import { TestController } from './../../TestController'; +const settingLabels = { + title: formatMessage('Configuration'), + publish: formatMessage('Publish'), + settings: formatMessage('Settings'), +}; + +const absHosted = isAbsHosted(); + const links = [ - { key: '/setting/dialog-settings', name: formatMessage('Dialog settings') }, + { key: '/setting/dialog-settings', name: settingLabels.settings }, + { key: `/setting/${absHosted ? 'remote-publish' : 'deployment'}`, name: settingLabels.publish }, // { key: 'services', name: formatMessage('Services') }, // { key: 'composer-configuration', name: formatMessage('Composer configuration'), disabled: true }, // { key: 'publishing-staging', name: formatMessage('Publishing and staging'), disabled: true }, ]; -if (process.env.COMPOSER_AUTH_PROVIDER === 'abs-h' || process.env.MOCKHOSTED) { - links.push({ key: '/setting/remote-publish', name: formatMessage('Publish') }); -} else { - links.push({ key: '/setting/deployment', name: formatMessage('Deployment') }); -} - export const SettingPage = () => { const [active, setActive] = useState(); @@ -63,7 +67,7 @@ export const SettingPage = () => {
-
{formatMessage('Settings')}
+
{settingLabels.title}