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}