diff --git a/Composer/packages/client/src/pages/language-generation/code-editor.tsx b/Composer/packages/client/src/pages/language-generation/code-editor.tsx index b4e2af0302..03cda89121 100644 --- a/Composer/packages/client/src/pages/language-generation/code-editor.tsx +++ b/Composer/packages/client/src/pages/language-generation/code-editor.tsx @@ -8,7 +8,7 @@ import get from 'lodash/get'; import debounce from 'lodash/debounce'; import isEmpty from 'lodash/isEmpty'; import { editor } from '@bfcomposer/monaco-editor/esm/vs/editor/editor.api'; -import { lgIndexer, combineMessage, isValid, filterTemplateDiagnostics } from '@bfc/indexers'; +import { lgIndexer, filterTemplateDiagnostics } from '@bfc/indexers'; import { RouteComponentProps } from '@reach/router'; import querystring from 'query-string'; @@ -56,13 +56,7 @@ const CodeEditor: React.FC = props => { setContent(value); }, [fileId, templateId, projectId]); - useEffect(() => { - const currentDiagnostics = inlineMode && template ? filterTemplateDiagnostics(diagnostics, template) : diagnostics; - - const isInvalid = !isValid(currentDiagnostics); - const text = isInvalid ? combineMessage(currentDiagnostics) : ''; - setErrorMsg(text); - }, [diagnostics]); + const currentDiagnostics = inlineMode && template ? filterTemplateDiagnostics(diagnostics, template) : diagnostics; const editorDidMount = (lgEditor: editor.IStandaloneCodeEditor) => { setLgEditor(lgEditor); @@ -163,6 +157,7 @@ const CodeEditor: React.FC = props => { editorDidMount={editorDidMount} value={content} errorMsg={errorMsg} + diagnostics={currentDiagnostics} lgOption={lgOption} languageServer={{ path: lspServerPath, diff --git a/Composer/packages/client/src/pages/language-understanding/code-editor.tsx b/Composer/packages/client/src/pages/language-understanding/code-editor.tsx index 908677e353..1c5d05c5ec 100644 --- a/Composer/packages/client/src/pages/language-understanding/code-editor.tsx +++ b/Composer/packages/client/src/pages/language-understanding/code-editor.tsx @@ -8,7 +8,7 @@ import get from 'lodash/get'; import debounce from 'lodash/debounce'; import isEmpty from 'lodash/isEmpty'; import { editor } from '@bfcomposer/monaco-editor/esm/vs/editor/editor.api'; -import { luIndexer, combineMessage, isValid, filterTemplateDiagnostics } from '@bfc/indexers'; +import { luIndexer, filterTemplateDiagnostics } from '@bfc/indexers'; import { RouteComponentProps } from '@reach/router'; import querystring from 'query-string'; @@ -55,11 +55,7 @@ const CodeEditor: React.FC = props => { setContent(value); }, [file, sectionId, projectId]); - const errorMsg = useMemo(() => { - const currentDiagnostics = inlineMode && intent ? filterTemplateDiagnostics(diagnostics, intent) : diagnostics; - const isInvalid = !isValid(currentDiagnostics); - return isInvalid ? combineMessage(diagnostics) : httpErrorMsg; - }, [diagnostics, httpErrorMsg]); + const currentDiagnostics = inlineMode && intent ? filterTemplateDiagnostics(diagnostics, intent) : diagnostics; const editorDidMount = (luEditor: editor.IStandaloneCodeEditor) => { setLuEditor(luEditor); @@ -161,7 +157,8 @@ const CodeEditor: React.FC = props => { hidePlaceholder={inlineMode} editorDidMount={editorDidMount} value={content} - errorMsg={errorMsg} + errorMsg={httpErrorMsg} + diagnostics={currentDiagnostics} luOption={luOption} languageServer={{ path: lspServerPath, diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx index b72692d6d5..46143e5242 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx @@ -74,11 +74,8 @@ export const LgEditorWidget: React.FC = props => { }, }; - const diagnostic = lgFile && filterTemplateDiagnostics(lgFile.diagnostics, template)[0]; + const diagnostics = lgFile ? filterTemplateDiagnostics(lgFile.diagnostics, template) : []; - const errorMsg = diagnostic - ? diagnostic.message.split('error message: ')[diagnostic.message.split('error message: ').length - 1] - : ''; const [localValue, setLocalValue] = useState(template.body); const sync = useRef( debounce((shellData: any, localData: any) => { @@ -120,7 +117,7 @@ export const LgEditorWidget: React.FC = props => { onChange={onChange} value={localValue} lgOption={lgOption} - errorMsg={errorMsg} + diagnostics={diagnostics} hidePlaceholder={true} helpURL={LG_HELP} languageServer={{ diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx index 31be9364dc..884de19a20 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx @@ -78,11 +78,7 @@ export class LuEditorWidget extends React.Component { render() { const { height = 250 } = this.props; const { luFile, luFileId, luIntent, name, formContext } = this; - const diagnostic = luFile && filterSectionDiagnostics(luFile.diagnostics, luIntent)[0]; - - const errorMsg = diagnostic - ? diagnostic.message.split('error message: ')[diagnostic.message.split('error message: ').length - 1] - : ''; + const diagnostics = luFile ? filterSectionDiagnostics(luFile.diagnostics, luIntent) : []; const label = prompt ? formatMessage('Expected responses (intent: {name})', { name }) @@ -94,7 +90,7 @@ export class LuEditorWidget extends React.Component { (content); const [showError, setShowError] = useState(true); + const [showWarning, setShowWarning] = useState(false); const placeholder = `> To learn more about the LU file format, read the documentation at > ${LU_HELP}`; const errorMsg = showError ? 'example error' : undefined; + const warningMsg = showWarning ? 'example warning' : undefined; return (
+
setValue(newVal)} value={value} placeholder={placeholder} errorMsg={errorMsg} + warningMsg={warningMsg} helpURL="https://dev.botframework.com" height={500} /> diff --git a/Composer/packages/lib/code-editor/src/RichEditor.tsx b/Composer/packages/lib/code-editor/src/RichEditor.tsx index 389b7b8d22..ea23bbe8c5 100644 --- a/Composer/packages/lib/code-editor/src/RichEditor.tsx +++ b/Composer/packages/lib/code-editor/src/RichEditor.tsx @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useMemo } from 'react'; import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar'; import { SharedColors, NeutralColors } from '@uifabric/fluent-theme'; import formatMessage from 'format-message'; import { EditorDidMount } from '@bfcomposer/react-monaco-editor'; import * as monacoEditor from '@bfcomposer/monaco-editor/esm/vs/editor/editor.api'; +import { Diagnostic, findErrors, findWarnings, combineSimpleMessage } from '@bfc/indexers'; import { BaseEditor, BaseEditorProps } from './BaseEditor'; import { processSize } from './utils/common'; @@ -15,17 +16,42 @@ export interface RichEditorProps extends BaseEditorProps { hidePlaceholder?: boolean; // default false placeholder?: string; // empty placeholder errorMsg?: string; // error text show below editor + warningMsg?: string; // warning text show below editor + diagnostics?: Diagnostic[]; // indexer generic diagnostic helpURL?: string; // help link show below editor height?: number | string; } export function RichEditor(props: RichEditorProps) { - const { errorMsg, helpURL, placeholder, hidePlaceholder = false, height, editorDidMount, ...rest } = props; - const isInvalid = !!errorMsg; + const { + errorMsg, + warningMsg, + diagnostics = [], + helpURL, + placeholder, + hidePlaceholder = false, + height, + editorDidMount, + ...rest + } = props; + const [editor, setEditor] = useState(null); const [hovered, setHovered] = useState(false); const [focused, setFocused] = useState(false); + const errorMsgFromDiagnostics = useMemo(() => { + const errors = findErrors(diagnostics); + return errors.length ? combineSimpleMessage(errors) : ''; + }, [diagnostics]); + + const warningMsgFromDiagnostics = useMemo(() => { + const warnings = findWarnings(diagnostics); + return warnings.length ? combineSimpleMessage(warnings) : ''; + }, [diagnostics]); + + const hasError = !!errorMsg || !!errorMsgFromDiagnostics; + const hasWarning = !!warningMsg || !!warningMsgFromDiagnostics; + const onEditorMount: EditorDidMount = (editor, monaco) => { setEditor(editor); @@ -51,8 +77,8 @@ export function RichEditor(props: RichEditorProps) { } }, [editor]); - const errorHelp = formatMessage.rich('{errorMsg}. Refer to the syntax documentationhere.', { - errorMsg, + const messageHelp = formatMessage.rich('{msg}. Refer to the syntax documentationhere.', { + msg: errorMsg || errorMsgFromDiagnostics || warningMsg || warningMsgFromDiagnostics, a: ({ children }) => ( {children} @@ -78,7 +104,11 @@ export function RichEditor(props: RichEditorProps) { borderColor = SharedColors.cyanBlue10; } - if (isInvalid) { + if (hasWarning) { + borderColor = SharedColors.yellow10; + } + + if (hasError) { borderColor = SharedColors.red20; } @@ -99,15 +129,15 @@ export function RichEditor(props: RichEditorProps) { >
- {isInvalid && ( + {(hasError || hasWarning) && ( - {errorHelp} + {messageHelp} )} diff --git a/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts b/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts index 92c16061f3..b430ff972c 100644 --- a/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts +++ b/Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts @@ -23,6 +23,21 @@ export function combineMessage(diagnostics: Diagnostic[]): string { }, ''); } +export function combineSimpleMessage(diagnostics: Diagnostic[]): string { + const diagnostic = diagnostics[0]; + if (diagnostic) { + let msg = ''; + if (diagnostic.range) { + const { start, end } = diagnostic.range; + const position = `L${start.line}:${start.character} - L${end.line}:${end.character} `; + msg += position; + } + const [, errorInfo] = diagnostic.message.split('error message: '); + return msg + errorInfo; + } + return ''; +} + export function offsetRange(range: Range, offset: number): Range { return new Range( new Position(range.start.line - offset, range.start.character), @@ -73,6 +88,10 @@ export function findErrors(diagnostics: Diagnostic[]): Diagnostic[] { return diagnostics.filter(d => d.severity === DiagnosticSeverity.Error); } +export function findWarnings(diagnostics: Diagnostic[]): Diagnostic[] { + return diagnostics.filter(d => d.severity === DiagnosticSeverity.Warning); +} + export function isValid(diagnostics: Diagnostic[]): boolean { return diagnostics.every(d => d.severity !== DiagnosticSeverity.Error); }