Skip to content

Commit

Permalink
feat: [Form Editor]inline lu editor in trigger (#1872)
Browse files Browse the repository at this point in the history
* lu in form editor

* fix naming

* bugfix

* fix circular reference in shared and indexer

* add start and end line for each intent

* add diagnostics

* delete lu editor in dialog

* remove notice

* fix bug: composer crashed when switch page to lu page

* fix tslint error

* support navigation back for lu error in notification

* remove unused referrence

* update the lib

Co-authored-by: Zhixiang Zhan <zhixzhan@microsoft.com>
Co-authored-by: liweitian <liweitian93@outlook.com>
Co-authored-by: leileizhang <leilzh@microsoft.com>
  • Loading branch information
4 people committed Feb 4, 2020
1 parent 1d5f59e commit ce3ea42
Show file tree
Hide file tree
Showing 20 changed files with 298 additions and 153 deletions.
12 changes: 6 additions & 6 deletions Composer/packages/client/src/ShellApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,19 @@ export const ShellApi: React.FC = () => {
if (!file) throw new Error(`lu file ${id} not found`);
if (!intentName) throw new Error(`intentName is missing or empty`);

const newLuContent = luUtil.updateIntent(file.content, intentName, intent);
const content = luUtil.updateIntent(file.content, intentName, intent);

return await updateLuFile({ id, newLuContent });
return await updateLuFile({ id, content });
}

async function addLuIntentHandler({ id, intent }, event) {
if (isEventSourceValid(event) === false) return false;
const file = luFiles.find(file => file.id === id);
if (!file) throw new Error(`lu file ${id} not found`);

const newLuContent = luUtil.addIntent(file.content, intent);
const content = luUtil.addIntent(file.content, intent);

return await updateLuFile({ id, newLuContent });
return await updateLuFile({ id, content });
}

async function removeLuIntentHandler({ id, intentName }, event) {
Expand All @@ -240,9 +240,9 @@ export const ShellApi: React.FC = () => {
if (!file) throw new Error(`lu file ${id} not found`);
if (!intentName) throw new Error(`intentName is missing or empty`);

const newLuContent = luUtil.removeIntent(file.content, intentName);
const content = luUtil.removeIntent(file.content, intentName);

return await updateLuFile({ id, newLuContent });
return await updateLuFile({ id, content });
}

async function fileHandler(fileTargetType, fileChangeType, { id, content }, event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ const shellApi: ShellApi = {
});
},

addLuIntent: (id, intent) => {
return apiClient.apiCall('addLuIntent', { id, intent });
},

updateLuIntent: (id, intentName, intent) => {
return apiClient.apiCall('updateLuIntent', { id, intentName, intent });
},

removeLuIntent: (id, intentName) => {
return apiClient.apiCall('removeLuIntent', { id, intentName });
},

createDialog: () => {
return apiClient.apiCall('createDialog');
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@

/* eslint-disable react/display-name */
import React, { useState, useEffect } from 'react';
import { PropTypes } from 'prop-types';
import { LuEditor } from '@bfc/code-editor';
import get from 'lodash/get';
import debounce from 'lodash/debounce';
import isEmpty from 'lodash/isEmpty';
import { combineMessage, isValid } from '@bfc/indexers';
import { combineMessage, isValid, LuFile } from '@bfc/indexers';
import { RouteComponentProps } from '@reach/router';

export default function CodeEditor(props) {
interface CodeEditorProps extends RouteComponentProps<{}> {
file: LuFile;
onChange: (value: string) => {};
errorMsg: string;
}

const CodeEditor: React.FC<CodeEditorProps> = props => {
const { file, errorMsg: updateErrorMsg } = props;
const onChange = debounce(props.onChange, 500);
const diagnostics = get(file, 'diagnostics', []);
Expand Down Expand Up @@ -38,6 +44,9 @@ export default function CodeEditor(props) {

return (
<LuEditor
// typescript is unable to reconcile 'on' as part of a union type
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
options={{
lineNumbers: 'on',
minimap: 'on',
Expand All @@ -49,10 +58,6 @@ export default function CodeEditor(props) {
onChange={_onChange}
/>
);
}

CodeEditor.propTypes = {
file: PropTypes.object,
onChange: PropTypes.func,
errorMsg: PropTypes.string,
};

export default CodeEditor;
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ const LUPage: React.FC<DefineConversationProps> = props => {
</div>
<div css={contentEditor}>
{editMode ? (
<Suspense fallback={<LoadingSpinner />}>
<CodeEditor file={luFile} onChange={onChange} errorMsg={errorMsg} />
</Suspense>
luFile ? (
<Suspense fallback={<LoadingSpinner />}>
<CodeEditor file={luFile} onChange={onChange} errorMsg={errorMsg} />
</Suspense>
) : null
) : (
<TableView activeDialog={activeDialog} onClickEdit={onTableViewClickEdit} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const TableView: React.FC<TableViewProps> = props => {
name,
phrases,
fileId: luFile.id,
used: luDialog ? luDialog.luIntents.includes(name) : false, // used by it's dialog or not
used: luDialog ? !!luDialog.referredLuIntents.find(lu => lu.name === name) : false, // used by it's dialog or not
state,
});
});
Expand Down
18 changes: 11 additions & 7 deletions Composer/packages/client/src/pages/notifications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ import useNotifications from './useNotifications';
import { NotificationList } from './NotificationList';
import { NotificationHeader } from './NotificationHeader';
import { root } from './styles';
import { INotification } from './types';
import { INotification, NotificationType } from './types';
import { navigateTo } from './../../utils';
import { convertDialogDiagnosticToUrl } from './../../utils/navigation';
import { convertPathToUrl } from './../../utils/navigation';

const navigations = {
lg: (item: INotification) => {
[NotificationType.LG]: (item: INotification) => {
navigateTo(`/language-generation/${item.id}/edit#L=${item.diagnostic.range?.start.line || 0}`);
},
lu: (item: INotification) => {
navigateTo(`/dialogs/${item.id}`);
[NotificationType.LU]: (item: INotification) => {
let uri = `/language-understanding/${item.id}`;
if (item.dialogPath) {
uri = convertPathToUrl(item.id, item.dialogPath);
}
navigateTo(uri);
},
dialog: (item: INotification) => {
[NotificationType.DIALOG]: (item: INotification) => {
//path is like main.trigers[0].actions[0]
//uri = id?selected=triggers[0]&focused=triggers[0].actions[0]
const uri = convertDialogDiagnosticToUrl(item.diagnostic);
const uri = convertPathToUrl(item.id, item.dialogPath);
navigateTo(uri);
},
};
Expand Down
80 changes: 78 additions & 2 deletions Composer/packages/client/src/pages/notifications/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,89 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { Diagnostic, createSingleMessage, DialogInfo, LuFile } from '@bfc/indexers';

import { replaceDialogDiagnosticLabel } from '../../utils';

export const DiagnosticSeverity = ['Error', 'Warning']; //'Information', 'Hint'

export enum NotificationType {
DIALOG,
LG,
LU,
}

export interface INotification {
id: string;
severity: string;
type: string;
type: NotificationType;
location: string;
message: string;
diagnostic: any;
dialogPath?: string; //the data path in dialog
}

export const DiagnosticSeverity = ['Error', 'Warning']; //'Information', 'Hint'
export class DialogNotification implements INotification {
id: string;
severity: string;
type: NotificationType;
location: string;
message: string;
diagnostic: Diagnostic;
dialogPath?: string;
constructor(id: string, location: string, diagnostic: Diagnostic) {
this.id = id;
this.severity = DiagnosticSeverity[diagnostic.severity] || '';
this.message = `In ${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`;
this.diagnostic = diagnostic;
this.location = location;
this.type = NotificationType.DIALOG;
this.dialogPath = diagnostic.path;
}
}

export class LgNotification implements INotification {
id: string;
severity: string;
type: NotificationType;
location: string;
message: string;
diagnostic: Diagnostic;
dialogPath?: string;
constructor(id: string, location: string, diagnostic: Diagnostic) {
this.id = id;
this.severity = DiagnosticSeverity[diagnostic.severity] || '';
this.message = createSingleMessage(diagnostic);
this.diagnostic = diagnostic;
this.location = location;
this.type = NotificationType.LG;
}
}

export class LuNotification implements INotification {
id: string;
severity: string;
type: NotificationType;
location: string;
message: string;
diagnostic: Diagnostic;
dialogPath?: string;
constructor(id: string, location: string, diagnostic: Diagnostic, luFile: LuFile, dialogs: DialogInfo[]) {
this.id = id;
this.severity = DiagnosticSeverity[diagnostic.severity] || '';
this.message = createSingleMessage(diagnostic);
this.diagnostic = diagnostic;
this.location = location;
this.type = NotificationType.LU;
this.dialogPath = this.findDialogPath(luFile, dialogs, diagnostic);
}

private findDialogPath(luFile: LuFile, dialogs: DialogInfo[], d: Diagnostic) {
const intentName = luFile.intents.find(intent => {
const { range } = intent;
if (!range) return false;
return d.range && d.range.start.line >= range.startLineNumber && d.range.end.line <= range.endLineNumber;
})?.Name;

return dialogs.find(dialog => dialog.id === luFile.id)?.referredLuIntents.find(lu => lu.name === intentName)?.path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
// Licensed under the MIT License.

import { useContext, useMemo } from 'react';
import { createSingleMessage } from '@bfc/indexers';

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

import { INotification, DiagnosticSeverity } from './types';
import { INotification, DialogNotification, LuNotification, LgNotification } from './types';
import { getReferredFiles } from './../../utils/luUtil';

export default function useNotifications(filter?: string) {
Expand All @@ -19,40 +17,19 @@ export default function useNotifications(filter?: string) {
dialogs.forEach(dialog => {
dialog.diagnostics.map(diagnostic => {
const location = `${dialog.id}.dialog`;
notifactions.push({
type: 'dialog',
location,
message: `In ${replaceDialogDiagnosticLabel(diagnostic.path)} ${diagnostic.message}`,
severity: DiagnosticSeverity[diagnostic.severity] || '',
diagnostic,
id: dialog.id,
});
notifactions.push(new DialogNotification(dialog.id, location, diagnostic));
});
});
getReferredFiles(luFiles, dialogs).forEach(lufile => {
lufile.diagnostics.map(diagnostic => {
const location = `${lufile.id}.lu`;
notifactions.push({
type: 'lu',
location,
message: createSingleMessage(diagnostic),
severity: 'Error',
diagnostic,
id: lufile.id,
});
notifactions.push(new LuNotification(lufile.id, location, diagnostic, lufile, dialogs));
});
});
lgFiles.forEach(lgFile => {
lgFile.diagnostics.map(diagnostic => {
const location = `${lgFile.id}.lg`;
notifactions.push({
type: 'lg',
severity: DiagnosticSeverity[diagnostic.severity] || '',
location,
message: createSingleMessage(diagnostic),
diagnostic,
id: lgFile.id,
});
notifactions.push(new LgNotification(lgFile.id, location, diagnostic));
});
});
return notifactions;
Expand Down
7 changes: 2 additions & 5 deletions Composer/packages/client/src/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import cloneDeep from 'lodash/cloneDeep';
import { navigate, NavigateOptions } from '@reach/router';
import { Diagnostic } from '@bfc/indexers';

import { BreadcrumbItem, DesignPageLocation } from '../store/types';

Expand Down Expand Up @@ -78,13 +77,11 @@ interface NavigationState {
breadcrumb: BreadcrumbItem[];
}

export function convertDialogDiagnosticToUrl(diagnostic: Diagnostic): string {
export function convertPathToUrl(id: string, path?: string): string {
//path is like main.trigers[0].actions[0]
//uri = id?selected=triggers[0]&focused=triggers[0].actions[0]
const { path, source } = diagnostic;
if (!source) return '';

let uri = `/dialogs/${source}`;
let uri = `/dialogs/${id}`;
if (!path) return uri;

const items = path.split('#');
Expand Down
3 changes: 3 additions & 0 deletions Composer/packages/extensions/obiformeditor/demo/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ const mockShellApi = [
'getLgTemplates',
'createLgTemplate',
'updateLgTemplate',
'addLuIntent',
'updateLuIntent',
'removeLuIntent',
'validateExpression',
'onFocusSteps',
'onFocusEvent',
Expand Down
Loading

0 comments on commit ce3ea42

Please sign in to comment.