Skip to content

Commit

Permalink
feat: update new trigger modal according to design (#1786)
Browse files Browse the repository at this point in the history
* update new trigger modal according to design

* handle comments

* auto-saved vscode lint setting

* handle comments

* css update

* use luUtil api

* feat: [Form Editor]inline lu editor in trigger (#1872)

* 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>

* switch tsx back to js

* switch js to tsx

* add editor in trigger wizard

* fix unit test

* when lu is not valid, don't do update

* handle comments

* rebase code

* resolve rebase conflict

* enable inline mode

* refactor luEditorWidget

* replace third-part npm registry

* update LgNotification class

* handle comments

* handle comments

* fix lu editor widget

* Configure Inline LU Editor to match Inline LG Editor

Co-authored-by: Long Alan <julong@microsoft.com>
Co-authored-by: Zhixiang Zhan <zhixzhan@microsoft.com>
Co-authored-by: leileizhang <leilzh@microsoft.com>
Co-authored-by: zeye <2295905420@qq.com>
Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
  • Loading branch information
6 people authored Mar 1, 2020
1 parent 8b0510b commit 7198c7f
Show file tree
Hide file tree
Showing 26 changed files with 483 additions and 223 deletions.
7 changes: 7 additions & 0 deletions Composer/packages/client/__tests__/components/design.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { dialogs } from '../constants.json';
import { TriggerCreationModal } from './../../src/components/ProjectTree/TriggerCreationModal';
import { ProjectTree } from './../../src/components/ProjectTree';
import { CreateDialogModal } from './../../src/pages/design/createDialogModal';

jest.mock('@bfc/code-editor', () => {
return {
LuEditor: () => <div></div>,
};
});

describe('<ProjectTree/>', () => {
it('should render the ProjectTree', async () => {
const dialogId = 'Main';
Expand Down
12 changes: 6 additions & 6 deletions Composer/packages/client/src/ShellApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,19 +217,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 @@ -238,9 +238,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 @@ -7,11 +7,14 @@ import React, { useState, useContext } from 'react';
import formatMessage from 'format-message';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Stack } from 'office-ui-fabric-react/lib/Stack';
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { DialogInfo, luIndexer, combineMessage } from '@bfc/indexers';
import get from 'lodash/get';
import { DialogInfo } from '@bfc/indexers';
import { LuEditor } from '@bfc/code-editor';

import {
addNewTrigger,
Expand All @@ -25,15 +28,16 @@ import {
getEventTypes,
getActivityTypes,
getMessageTypes,
regexRecognizerKey,
} from '../../utils/dialogUtil';
import { addIntent } from '../../utils/luUtil';
import { StoreContext } from '../../store';

import { styles, dropdownStyles, dialogWindow } from './styles';
import { styles, dropdownStyles, dialogWindow, intent } from './styles';

const nameRegex = /^[a-zA-Z0-9-_.]+$/;
const validateForm = (data: TriggerFormData): TriggerFormDataErrors => {
const errors: TriggerFormDataErrors = {};
const { $type, specifiedType } = data;
const { $type, specifiedType, intent, triggerPhrases } = data;

if ($type === eventTypeKey && !specifiedType) {
errors.specifiedType = formatMessage('Please select a event type');
Expand All @@ -46,21 +50,40 @@ const validateForm = (data: TriggerFormData): TriggerFormDataErrors => {
if (!$type) {
errors.$type = formatMessage('Please select a trigger type');
}

if (!intent || !nameRegex.test(intent)) {
errors.intent = formatMessage(
'Spaces and special characters are not allowed. Use letters, numbers, -, or _., numbers, -, and _'
);
}

if (!triggerPhrases) {
errors.triggerPhrases = formatMessage('Please input trigger phrases');
}
if (data.errors.triggerPhrases) {
errors.triggerPhrases = data.errors.triggerPhrases;
}
return errors;
};

interface LuFilePayload {
id: string;
content: string;
}

interface TriggerCreationModalProps {
dialogId: string;
isOpen: boolean;
onDismiss: () => void;
onSubmit: (dialog: DialogInfo) => void;
onSubmit: (dialog: DialogInfo, luFilePayload: LuFilePayload) => void;
}

const initialFormData: TriggerFormData = {
errors: {},
$type: intentTypeKey,
intent: '',
specifiedType: '',
intent: '',
triggerPhrases: '',
};

const triggerTypeOptions: IDropdownOption[] = getTriggerTypes();
Expand All @@ -71,7 +94,7 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
const { state } = useContext(StoreContext);
const { dialogs, luFiles } = state;
const luFile = luFiles.find(lu => lu.id === dialogId);
const dialogFile = dialogs.find(dialog => dialog.id === dialogId);

const onClickSubmitButton = e => {
e.preventDefault();
const errors = validateForm(formData);
Expand All @@ -83,38 +106,43 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
});
return;
}

const content = get(luFile, 'content', '');
const newContent = addIntent(content, { Name: formData.intent, Body: formData.triggerPhrases });
const updateLuFile = {
id: dialogId,
content: newContent,
};
const newDialog = addNewTrigger(dialogs, dialogId, formData);
onSubmit(newDialog);
onSubmit(newDialog, updateLuFile);
onDismiss();
};

const onSelectTriggerType = (e, option) => {
setFormData({ ...initialFormData, $type: option.key });
};

const onSelectIntent = (e, option) => {
setFormData({ ...formData, intent: option.key });
};

const onSelectSpecifiedTypeType = (e, option) => {
setFormData({ ...formData, specifiedType: option.key });
};

const onNameChange = (e, name) => {
setFormData({ ...formData, intent: name });
};

const onTriggerPhrasesChange = (body: string) => {
const errors = formData.errors;
const content = '#' + formData.intent + '\n' + body;
const { diagnostics } = luIndexer.parse(content);
errors.triggerPhrases = combineMessage(diagnostics);
setFormData({ ...formData, triggerPhrases: body, errors });
};

const eventTypes: IDropdownOption[] = getEventTypes();
const activityTypes: IDropdownOption[] = getActivityTypes();
const messageTypes: IDropdownOption[] = getMessageTypes();

const isRegEx = get(dialogFile, 'content.recognizer.$type', '') === regexRecognizerKey;

const regexIntents = get(dialogFile, 'content.recognizer.intents', []);
const luisIntents = get(luFile, 'intents', []);
const intents = isRegEx ? regexIntents : luisIntents;

const intentOptions = intents.map(t => {
return { key: t.name || t.Name || t.intent, text: t.name || t.Name || t.intent };
});

const showIntentDropDown = formData.$type === intentTypeKey;
const showIntentFields = formData.$type === intentTypeKey;
const showEventDropDown = formData.$type === eventTypeKey;
const showActivityDropDown = formData.$type === activityTypeKey;
const showMessageDropDown = formData.$type === messageTypeKey;
Expand Down Expand Up @@ -178,15 +206,38 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
data-testid={'messageTypeDropDown'}
/>
)}
{showIntentDropDown && (
<Dropdown
label={formatMessage('Which intent do you want to handle? (Optional)')}
options={intentOptions}
styles={dropdownStyles}
onChange={onSelectIntent}
disabled={intentOptions.length === 0}
placeholder={intentOptions.length === 0 ? formatMessage('No intents configured for this dialog') : ''}
{showIntentFields && (
<TextField
label={formatMessage('What is the name of this trigger')}
styles={intent}
onChange={onNameChange}
errorMessage={formData.errors.intent}
data-testid="TriggerName"
/>
)}
{showIntentFields && <Label>{formatMessage('Trigger Phrases')}</Label>}
{showIntentFields && (
<LuEditor
onChange={onTriggerPhrasesChange}
value={formData.triggerPhrases}
errorMsg={formData.errors.triggerPhrases}
hidePlaceholder={true}
luOption={{
fileId: dialogId,
sectionId: formData.intent || 'newSection',
}}
options={{
lineNumbers: 'off',
minimap: {
enabled: false,
},
lineDecorationsWidth: 10,
lineNumbersMinChars: 0,
glyphMargin: false,
folding: false,
renderLineHighlight: 'none',
}}
height={150}
/>
)}
</Stack>
Expand Down
18 changes: 12 additions & 6 deletions Composer/packages/client/src/components/ProjectTree/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const dropdownStyles = {
fontWeight: FontWeights.semibold,
},
dropdown: {
width: '300px',
width: '400px',
},
root: {
paddingBottom: '20px',
Expand All @@ -148,7 +148,7 @@ export const dialogWindow = css`
display: flex;
flex-direction: column;
width: 400px;
height: 250px;
min-height: 300px;
`;

export const textFieldlabel = {
Expand All @@ -162,11 +162,17 @@ export const textFieldlabel = {
};

export const intent = {
fieldGroup: {
width: 200,
root: {
width: '400px',
paddingBottom: '20px',
},
};

export const triggerPhrases = {
root: {
height: '90px',
width: '400px',
},
fieldGroup: {
height: 80,
},
subComponentStyles: textFieldlabel,
};
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
11 changes: 8 additions & 3 deletions Composer/packages/client/src/pages/design/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,19 @@ function DesignPage(props) {
setTriggerModalVisibility(true);
};

const onTriggerCreationSubmit = dialog => {
const payload = {
const onTriggerCreationSubmit = (dialog, luFile) => {
const dialogPayload = {
id: dialog.id,
content: dialog.content,
};
const luFilePayload = {
id: luFile.id,
content: luFile.content,
};
const index = get(dialog, 'content.triggers', []).length - 1;
actions.selectTo(`triggers[${index}]`);
actions.updateDialog(payload);
actions.updateLuFile(luFilePayload);
actions.updateDialog(dialogPayload);
};

function handleSelect(id, selected = '') {
Expand Down
4 changes: 2 additions & 2 deletions Composer/packages/client/src/pages/design/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ export const middleTriggerContainer = css`
display: flex;
justify-content: center;
align-items: center;
background: #e5e5e5;
background: #f6f6f6;
width: 100%;
margin-top: 48px;
margin-top: 55px;
height: calc(100% - 48px);
position: absolute;
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,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.some(lu => lu.name === name), // used by it's dialog or not
state,
});
});
Expand Down
Loading

0 comments on commit 7198c7f

Please sign in to comment.