* Added monaco editor with custom schema validation

* Added styling for custom activity editor.

* Dumbed down component

* Changelog

* Fixed linting and tests

* Added monaco editor module path to root test config

* Addressed PR comments

* Changed monaco module path

* Added a monaco editor mock
This commit is contained in:
Tony Anziano 2020-12-30 14:14:20 -08:00 коммит произвёл GitHub
Родитель 402ac60c4b
Коммит 7cb5a86dc8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 389 добавлений и 3 удалений

Просмотреть файл

@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
- [client/main] Added UI to open bot dialog that allows the user to set transcript testing properties `randomSeed` and `randomValue` in PR [2209](https://github.com/microsoft/BotFramework-Emulator/pull/2209)
- [client] Added a dialog to the "Conversation -> Send System Activity" menu that allows the user to send custom activity payloads to their bot in PR [2213](https://github.com/microsoft/BotFramework-Emulator/pull/2213)
## v4.11.0 - 2020 - 11 - 05
- [client] Moved from master to main as the default branch. [2194](https://github.com/microsoft/BotFramework-Emulator/pull/2194)

Просмотреть файл

@ -0,0 +1,45 @@
//
// 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.
//
module.exports = {
editor: {
createModel: () => null,
},
languages: {
json: {
jsonDefaults: {
setDiagnosticsOptions: () => null,
},
},
},
};

60
package-lock.json сгенерированный
Просмотреть файл

@ -3423,6 +3423,25 @@
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.37.tgz",
"integrity": "sha512-3/9rNkHO+f72RztnAQIgtZwUMVqxSz3JL+NIMMIlPHrDVQW7fJ69Im27WippydDzUZpUL1YiGSDIfqKa1amOdA=="
},
"@monaco-editor/react": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-3.7.1.tgz",
"integrity": "sha512-8R6+Q8QgMzV24QEfmX2gL/L4HjZGUdi9H34VwPpvTyk8ZMuZ8mxSwjOYBU69g9uLUE/z4uMmi4EVW4JdrK84uw==",
"requires": {
"@babel/runtime": "^7.11.0",
"state-local": "^1.0.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -20159,6 +20178,39 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"monaco-editor": {
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.21.2.tgz",
"integrity": "sha512-jS51RLuzMaoJpYbu7F6TPuWpnWTLD4kjRW0+AZzcryvbxrTwhNy1KC9yboyKpgMTahpUbDUsuQULoo0GV1EPqg=="
},
"monaco-editor-webpack-plugin": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-2.0.0.tgz",
"integrity": "sha512-z3nUGhnNis8eHcPepqrxyt3XwrnHD76E4KwV2ozWlBwYM6B3R5YYqzy40ECfJWqRFcwT4DhaLYaXOk5ym4MZhA==",
"requires": {
"loader-utils": "^2.0.0"
},
"dependencies": {
"json5": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
}
}
},
"moo": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz",
@ -22957,7 +23009,8 @@
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
"optional": true
},
"pidtree": {
"version": "0.3.0",
@ -26747,6 +26800,11 @@
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz",
"integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI="
},
"state-local": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.4.tgz",
"integrity": "sha512-ea0qFgZAlqjuwyUnIEST/sDxKhbW3CEcZJVR6ay3hCCXFPDfONsXjslPjHzhhtM/PFlIv0FTpteg3H5wFTNDyw=="
},
"state-toggle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz",

Просмотреть файл

@ -59,6 +59,7 @@
"node"
],
"moduleNameMapper": {
"monaco-editor": "<rootDir>/jestMocks/monacoEditorMock.js",
".\\.css$": "<rootDir>/jestMocks/styleMock.js",
".\\.scss$": "<rootDir>/jestMocks/styleMock.js",
".\\.svg$": "<rootDir>/jestMocks/svgMock.js"

Просмотреть файл

@ -29,6 +29,7 @@
"**/?(*.)(spec|test).(ts)?(x)"
],
"moduleNameMapper": {
"monaco-editor": "<rootDir>/../../../../jestMocks/monacoEditorMock.js",
".\\.scss$": "<rootDir>/../../../../jestMocks/styleMock.js"
},
"moduleFileExtensions": [
@ -101,6 +102,7 @@
"@bfemulator/sdk-client": "^1.0.0",
"@bfemulator/sdk-shared": "^1.0.0",
"@bfemulator/ui-react": "^1.0.0",
"@monaco-editor/react": "^3.7.1",
"@uifabric/icons": "^5.6.0",
"@uifabric/merge-styles": "^6.2.0",
"@uifabric/styling": "^5.20.0",
@ -112,6 +114,8 @@
"core-js": "^3.6.5",
"eslint-plugin-react": "^7.12.3",
"markdown-it": "^8.4.2",
"monaco-editor": "^0.21.2",
"monaco-editor-webpack-plugin": "^2.0.0",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-redux": "^5.0.7",

Просмотреть файл

@ -67,6 +67,7 @@ import {
UpdateUnavailableDialogContainer,
DataCollectionDialogContainer,
} from '../ui/dialogs';
import { CustomActivityEditorContainer } from '../ui/dialogs/customActivityEditor/customActivityEditorContainer';
import { OpenBotDialogProps } from '../ui/dialogs/openBotDialog/openBotDialog';
const { UI, Telemetry } = SharedConstants.Commands;
@ -264,4 +265,9 @@ export class UiCommands {
protected showDataCollectionDialog() {
return DialogService.showDialog(DataCollectionDialogContainer);
}
@Command(UI.ShowCustomActivityEditor)
protected showCustomActivityEditor() {
return DialogService.showDialog(CustomActivityEditorContainer);
}
}

Просмотреть файл

@ -0,0 +1,29 @@
.container {
background-color: var(--custom-activity-editor-bg);
box-sizing: border-box;
display: flex;
flex-flow: column nowrap;
height: 600px;
padding-top: 16px;
width: 648px;
}
.monaco-container {
border: 1px solid var(--custom-activity-editor-border);
border-radius: 2px;
box-sizing: border-box;
height: 100%;
margin-bottom: 20px;
width: 100%;
}
.button-container {
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
margin-top: auto;
}
.send-button {
margin-left: 8px;
}

Просмотреть файл

@ -0,0 +1,5 @@
// This is a generated file. Changes are likely to result in being overwritten
export const container: string;
export const monacoContainer: string;
export const buttonContainer: string;
export const sendButton: string;

Просмотреть файл

@ -0,0 +1,108 @@
//
// 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 { DefaultButton, Dialog, PrimaryButton } from '@bfemulator/ui-react';
import React, { useCallback, useEffect, useState } from 'react';
import * as monaco from 'monaco-editor';
import { Activity } from 'botframework-schema';
import customActivitySchema from './customActivitySchema.json';
import styles from './customActivityEditor.scss';
export interface CustomActivityEditorProps {
conversationId: string;
onDismiss: () => void;
onSendActivity: (activity: Activity, conversationId: string, serverUrl: string) => void;
serverUrl: string;
}
const editorDefaultContent = {
text: 'Hello world!',
type: 'message',
};
// create a model that validates against our custom activity schema
// TODO: get custom schema validation errors to show as errors instead of warnings
const model = monaco.editor.createModel(JSON.stringify(editorDefaultContent, null, 2), 'json');
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'bfemulator://schemas/customActivity.json',
fileMatch: ['*'],
schema: customActivitySchema,
},
],
});
export const CustomActivityEditor: React.FC<CustomActivityEditorProps> = (props: CustomActivityEditorProps) => {
const [json, setJson] = useState(JSON.stringify(editorDefaultContent));
const [isValid, setIsValid] = useState(false);
const { conversationId, onDismiss, onSendActivity, serverUrl } = props;
useEffect(() => {
const editor = monaco.editor.create(document.getElementById('monaco-container'), {
model,
});
// store the updated editor's content in state
editor.onDidChangeModelContent(e => {
const val = editor.getValue();
setJson(val);
});
// disable "send" button depending on JSON validation
editor.onDidChangeModelDecorations(e => {
const markers = monaco.editor.getModelMarkers({ owner: 'json' });
// warnings are 4 and errors are 8
setIsValid(!markers.some(m => m.severity >= monaco.MarkerSeverity.Warning));
});
}, []);
const onSendActivityClick = useCallback(() => {
const activity = JSON.parse(json);
onSendActivity(activity, conversationId, serverUrl);
}, [conversationId, json, serverUrl]);
return (
<Dialog cancel={onDismiss}>
<div className={styles.container}>
<div id="monaco-container" className={styles.monacoContainer}></div>
<div className={styles.buttonContainer}>
<DefaultButton onClick={onDismiss}>Cancel</DefaultButton>
<PrimaryButton className={styles.sendButton} disabled={!isValid} onClick={onSendActivityClick}>
Send activity
</PrimaryButton>
</div>
</div>
</Dialog>
);
};

Просмотреть файл

@ -0,0 +1,63 @@
//
// 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 { connect } from 'react-redux';
import { ConversationService } from '@bfemulator/sdk-shared';
import { Activity } from 'botframework-schema';
import { RootState } from '../../../state';
import { DialogService } from '../service';
import { CustomActivityEditor, CustomActivityEditorProps } from './customActivityEditor';
const mapStateToProps = (state: RootState) => {
const activeDocumentId = state.editor.editors[state.editor.activeEditor].activeDocumentId;
return {
conversationId: state.chat.chats[activeDocumentId].conversationId,
serverUrl: state.clientAwareSettings.serverUrl,
};
};
const mapDispatchToProps = (): Partial<CustomActivityEditorProps> => {
return {
onDismiss: () => {
DialogService.hideDialog();
},
onSendActivity: (activity: Activity, conversationId: string, serverUrl: string) => {
ConversationService.sendActivityToBot(serverUrl, conversationId, activity);
DialogService.hideDialog();
},
};
};
export const CustomActivityEditorContainer = connect(mapStateToProps, mapDispatchToProps)(CustomActivityEditor);

Просмотреть файл

@ -0,0 +1,34 @@
{
"$id": "bfemulator://schemas/customActivity.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "JSON Schema definition of a custom activity that can be sent to a bot via the Emulator's custom activity editor.",
"type": "object",
"properties": {
"attachments": {
"type": "array"
},
"channelData": {
"type": "object"
},
"channelId": false,
"conversation": false,
"from": false,
"id": false,
"locale": false,
"localTimestamp": false,
"name": {
"type": "string"
},
"recipient": false,
"text": {
"type": "string"
},
"timestamp": false,
"type": {
"type": "string"
}
},
"required": [
"type"
]
}

Просмотреть файл

@ -53,7 +53,7 @@ const {
SendDeleteUserData,
},
Ngrok: { OpenStatusViewer },
UI: { ShowBotCreationDialog, ShowMarkdownPage, ShowOpenBotDialog, ShowWelcomePage },
UI: { ShowBotCreationDialog, ShowCustomActivityEditor, ShowMarkdownPage, ShowOpenBotDialog, ShowWelcomePage },
},
} = SharedConstants;
@ -200,6 +200,10 @@ export class AppMenuTemplate {
label: 'Send System Activity',
type: 'submenu',
items: [
{
label: 'Custom activity (New)',
onClick: () => AppMenuTemplate.commandService.call(ShowCustomActivityEditor),
},
{
label: 'conversationUpdate ( user added )',
onClick: () => AppMenuTemplate.commandService.remoteCall(SendConversationUpdateUserAdded),

Просмотреть файл

@ -303,6 +303,10 @@ html {
/* Webchat style overrides */
--bubble-text-color: #fff;
/* Custom Activity Editor */
--custom-activity-editor-bg: transparent;
--custom-activity-editor-border: var(--neutral-10);
}
.dialog {

Просмотреть файл

@ -300,6 +300,10 @@ html {
/* Webchat style overrides */
--bubble-text-color: #000000;
/* Custom Activity Editor */
--custom-activity-editor-bg: transparent;
--custom-activity-editor-border: var(--neutral-10);
}
.dialog .ms-Button-label {

Просмотреть файл

@ -303,4 +303,8 @@ html {
/* Webchat style overrides */
--bubble-text-color: #fff;
/* Custom Activity Editor */
--custom-activity-editor-bg: transparent;
--custom-activity-editor-border: var(--neutral-10);
}

Просмотреть файл

@ -5,6 +5,7 @@
"lib": ["dom", "es2015.proxy", "es5", "esnext", "es7"],
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"target": "esnext"
},
"include": ["./src"]

Просмотреть файл

@ -37,6 +37,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const { DllPlugin, DllReferencePlugin, NamedModulesPlugin, DefinePlugin, WatchIgnorePlugin } = webpack;
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const { npm_lifecycle_event = '' } = process.env;
const manifestLocation = path.resolve('./generated');
@ -103,6 +104,10 @@ const defaultConfig = {
'sass-loader',
],
},
{
test: /\.ttf$/,
use: ['file-loader'],
},
],
},
@ -144,6 +149,9 @@ const defaultConfig = {
DEV: JSON.stringify(npm_lifecycle_event.includes('dev')),
}),
new WatchIgnorePlugin(['./src/**/*.d.ts']),
new MonacoWebpackPlugin({
languages: ['json'],
}),
],
};

Просмотреть файл

@ -338,6 +338,12 @@ export class AppMenuBuilder {
id: 'send-activity',
label: 'Send System Activity',
submenu: [
{
label: 'Custom activity (New)',
click: () => {
AppMenuBuilder.commandService.remoteCall(SharedConstants.Commands.UI.ShowCustomActivityEditor);
},
},
{
label: 'conversationUpdate ( user added )',
click: createClickHandler(ConversationService.addUser, () =>

Просмотреть файл

@ -188,6 +188,7 @@ export const SharedConstants = {
ShowProgressIndicator: 'progress-indicator:show',
ShowOpenUrlDialog: 'chat:open-url',
ShowDataCollectionDialog: 'data-collection:show',
ShowCustomActivityEditor: 'custom-activity-editor:show',
},
},
ContentTypes: {

Просмотреть файл

@ -5,7 +5,7 @@
"experimentalDecorators": true,
"lib": ["dom", "es2015.proxy", "es5", "esnext", "es7"],
"outDir": "build",
"target": "esnext",
"target": "esnext"
},
"include": [
"./src"