Skip to content

Commit

Permalink
fix: trigger creation bug (#2151)
Browse files Browse the repository at this point in the history
* fix bug

* save tmp code

* add regex intent back

* update

* update label text

* rename onMessageActivity to onMessageReceivedMessage

* update trigger type validation function

* remove commented code

* rename onMessageActivity to onMessageReceivedMessage & update trigger type validation function

* update regex field

* remove commented code

* remove commented code

* revert auto-saved file

* update label text

* fix bug

* save tmp code

* add regex intent back

* update

* update label text

* rename onMessageActivity to onMessageReceivedMessage

* update trigger type validation function

* remove commented code

* update regex field

* remove commented code

* remove commented code

* revert auto-saved file

* update label text

* update label text

* update regEx api

* add shellapi updateRegExIntentHandler

* shell api

* inline regex in form editor

* create new inline regex intent when no intent

Co-authored-by: Long Alan <[email protected]>
Co-authored-by: Chris Whitten <[email protected]>
  • Loading branch information
3 people authored Mar 9, 2020
1 parent 27a4fdb commit 66e5fb4
Show file tree
Hide file tree
Showing 17 changed files with 230 additions and 78 deletions.
10 changes: 10 additions & 0 deletions Composer/packages/client/src/ShellApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import get from 'lodash/get';
import { isExpression } from './utils';
import * as lgUtil from './utils/lgUtil';
import * as luUtil from './utils/luUtil';
import { updateRegExIntent } from './utils/dialogUtil';
import { StoreContext } from './store';
import ApiClient from './messenger/ApiClient';
import { getDialogData, setDialogData, sanitizeDialogData } from './utils';
Expand Down Expand Up @@ -243,6 +244,14 @@ export const ShellApi: React.FC = () => {
return await updateLuFile({ id, content });
}

async function updateRegExIntentHandler({ id, intentName, pattern }, event) {
if (isEventSourceValid(event) === false) return false;
const dialog = dialogs.find(dialog => dialog.id === id);
if (!dialog) throw new Error(`dialog ${dialogId} not found`);
const newDialog = updateRegExIntent(dialog, intentName, pattern);
return await updateDialog({ id, content: newDialog.content });
}

async function fileHandler(fileTargetType, fileChangeType, { id, content }, event) {
if (isEventSourceValid(event) === false) return false;

Expand Down Expand Up @@ -345,6 +354,7 @@ export const ShellApi: React.FC = () => {
apiClient.registerApi('addLuIntent', addLuIntentHandler);
apiClient.registerApi('updateLuIntent', updateLuIntentHandler);
apiClient.registerApi('removeLuIntent', removeLuIntentHandler);
apiClient.registerApi('updateRegExIntent', updateRegExIntentHandler);
apiClient.registerApi('navTo', navTo);
apiClient.registerApi('onFocusEvent', focusEvent);
apiClient.registerApi('onFocusSteps', focusSteps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import get from 'lodash/get';
import { LuEditor } from '@bfc/code-editor';

import {
addNewTrigger,
generateNewDialog,
getTriggerTypes,
TriggerFormData,
TriggerFormDataErrors,
Expand All @@ -28,16 +28,21 @@ import {
getEventTypes,
getActivityTypes,
getMessageTypes,
regexRecognizerKey,
} from '../../utils/dialogUtil';
import { addIntent } from '../../utils/luUtil';
import { StoreContext } from '../../store';

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

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

if ($type === eventTypeKey && !specifiedType) {
errors.specifiedType = formatMessage('Please select a event type');
Expand All @@ -47,17 +52,29 @@ const validateForm = (data: TriggerFormData): TriggerFormDataErrors => {
errors.specifiedType = formatMessage('Please select an activity type');
}

if ($type === messageTypeKey && !specifiedType) {
errors.specifiedType = formatMessage('Please select a message type');
}

if (!$type) {
errors.$type = formatMessage('Please select a trigger type');
}

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

if (!triggerPhrases) {
if ($type === intentTypeKey && isRegEx && regExIntents.find(ri => ri.intent === intent)) {
errors.intent = `regEx ${intent} is already defined`;
}

if ($type === intentTypeKey && isRegEx && !regexEx) {
errors.regexEx = formatMessage('Please input regEx pattern');
}

if ($type === intentTypeKey && !isRegEx && !triggerPhrases) {
errors.triggerPhrases = formatMessage('Please input trigger phrases');
}
if (data.errors.triggerPhrases) {
Expand All @@ -66,7 +83,7 @@ const validateForm = (data: TriggerFormData): TriggerFormDataErrors => {
return errors;
};

interface LuFilePayload {
export interface LuFilePayload {
id: string;
content: string;
}
Expand All @@ -75,7 +92,7 @@ interface TriggerCreationModalProps {
dialogId: string;
isOpen: boolean;
onDismiss: () => void;
onSubmit: (dialog: DialogInfo, luFilePayload: LuFilePayload) => void;
onSubmit: (dialog: DialogInfo, luFilePayload?: LuFilePayload) => void;
}

const initialFormData: TriggerFormData = {
Expand All @@ -84,6 +101,7 @@ const initialFormData: TriggerFormData = {
specifiedType: '',
intent: '',
triggerPhrases: '',
regexEx: '',
};

const triggerTypeOptions: IDropdownOption[] = getTriggerTypes();
Expand All @@ -94,10 +112,12 @@ 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 isRegEx = get(dialogFile, 'content.recognizer.$type', '') === regexRecognizerKey;
const regexIntents = get(dialogFile, 'content.recognizer.intents', []);
const onClickSubmitButton = e => {
e.preventDefault();
const errors = validateForm(formData);
const errors = validateForm(formData, isRegEx, regexIntents);

if (Object.keys(errors).length) {
setFormData({
Expand All @@ -108,13 +128,17 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
}

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, updateLuFile);
const newDialog = generateNewDialog(dialogs, dialogId, formData);
if (formData.$type === intentTypeKey && !isRegEx) {
const newContent = addIntent(content, { Name: formData.intent, Body: formData.triggerPhrases });
const updateLuFile = {
id: dialogId,
content: newContent,
};
onSubmit(newDialog, updateLuFile);
} else {
onSubmit(newDialog);
}
onDismiss();
};

Expand All @@ -130,6 +154,10 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
setFormData({ ...formData, intent: name });
};

const onChangeRegEx = (e, pattern) => {
setFormData({ ...formData, regexEx: pattern });
};

const onTriggerPhrasesChange = (body: string) => {
const errors = formData.errors;
const content = '#' + formData.intent + '\n' + body;
Expand All @@ -142,7 +170,9 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
const activityTypes: IDropdownOption[] = getActivityTypes();
const messageTypes: IDropdownOption[] = getMessageTypes();

const showIntentFields = formData.$type === intentTypeKey;
const showIntentName = formData.$type === intentTypeKey;
const showRegExDropDown = formData.$type === intentTypeKey && isRegEx;
const showTriggerPhrase = formData.$type === intentTypeKey && !isRegEx;
const showEventDropDown = formData.$type === eventTypeKey;
const showActivityDropDown = formData.$type === activityTypeKey;
const showMessageDropDown = formData.$type === messageTypeKey;
Expand Down Expand Up @@ -172,7 +202,6 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
data-testid={'triggerTypeDropDown'}
defaultSelectedKey={intentTypeKey}
/>

{showEventDropDown && (
<Dropdown
placeholder={formatMessage('Select a event type')}
Expand Down Expand Up @@ -206,17 +235,30 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = props =
data-testid={'messageTypeDropDown'}
/>
)}
{showIntentFields && (
{showIntentName && (
<TextField
label={formatMessage('What is the name of this trigger')}
label={
isRegEx
? formatMessage('What is the name of this trigger (RegEx)')
: formatMessage('What is the name of this trigger (Luis)')
}
styles={intent}
onChange={onNameChange}
errorMessage={formData.errors.intent}
data-testid="TriggerName"
/>
)}
{showIntentFields && <Label>{formatMessage('Trigger Phrases')}</Label>}
{showIntentFields && (

{showRegExDropDown && (
<TextField
label={formatMessage('Please input regex pattern')}
onChange={onChangeRegEx}
errorMessage={formData.errors.regexEx}
data-testid={'RegExDropDown'}
/>
)}
{showTriggerPhrase && <Label>{formatMessage('Trigger phrases')}</Label>}
{showTriggerPhrase && (
<LuEditor
onChange={onTriggerPhrasesChange}
value={formData.triggerPhrases}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ const shellApi: ShellApi = {
return apiClient.apiCall('removeLuIntent', { id, intentName });
},

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

createDialog: () => {
return apiClient.apiCall('createDialog');
},
Expand Down
18 changes: 11 additions & 7 deletions Composer/packages/client/src/pages/design/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { globalHistory } from '@reach/router';
import get from 'lodash/get';
import { PromptTab } from '@bfc/shared';
import { getNewDesigner, seedNewDialog } from '@bfc/shared';
import { DialogInfo } from '@bfc/indexers';

import { VisualEditorAPI } from '../../messenger/FrameAPI';
import { TestController } from '../../TestController';
import { BASEPATH, DialogDeleting } from '../../constants';
import { createSelectedPath, deleteTrigger, getbreadcrumbLabel } from '../../utils';
import { TriggerCreationModal } from '../../components/ProjectTree/TriggerCreationModal';
import { TriggerCreationModal, LuFilePayload } from '../../components/ProjectTree/TriggerCreationModal';
import { Conversation } from '../../components/Conversation';
import { DialogStyle } from '../../components/Modal/styles';
import { OpenConfirmModal } from '../../components/Modal/Confirm';
Expand Down Expand Up @@ -171,18 +172,21 @@ function DesignPage(props) {
setTriggerModalVisibility(true);
};

const onTriggerCreationSubmit = (dialog, luFile) => {
const onTriggerCreationSubmit = (dialog: DialogInfo, luFile?: LuFilePayload) => {
const dialogPayload = {
id: dialog.id,
content: dialog.content,
};
const luFilePayload = {
id: luFile.id,
content: luFile.content,
};
if (luFile) {
const luFilePayload = {
id: luFile.id,
content: luFile.content,
};
actions.updateLuFile(luFilePayload);
}

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

Expand Down
58 changes: 48 additions & 10 deletions Composer/packages/client/src/utils/dialogUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ export interface TriggerFormData {
specifiedType: string;
intent: string;
triggerPhrases: string;
regexEx: string;
}

export interface TriggerFormDataErrors {
$type?: string;
intent?: string;
specifiedType?: string;
triggerPhrases?: string;
regexEx?: string;
}

export function getDialog(dialogs: DialogInfo[], dialogId: string) {
Expand Down Expand Up @@ -60,8 +62,15 @@ export function getFriendlyName(data) {
return data.$type;
}

export function insert(content, path: string, position: number | undefined, data: TriggerFormData) {
export function insert(content, path: string, position: number | undefined, data: any) {
const current = get(content, path, []);
const insertAt = typeof position === 'undefined' ? current.length : position;
current.splice(insertAt, 0, data);
set(content, path, current);
return content;
}

export function generateNewTrigger(data: TriggerFormData) {
const optionalAttributes: { intent?: string; event?: string } = {};
if (data.specifiedType) {
data.$type = data.specifiedType;
Expand All @@ -73,23 +82,52 @@ export function insert(content, path: string, position: number | undefined, data
$type: data.$type,
...seedNewDialog(data.$type, {}, optionalAttributes),
};
return newStep;
}

const insertAt = typeof position === 'undefined' ? current.length : position;

current.splice(insertAt, 0, newStep);
export function generateRegexExpression(intent: string, pattern: string) {
return { intent, pattern };
}

set(content, path, current);
export function createNewTrigger(dialog: DialogInfo, data: TriggerFormData): DialogInfo {
const dialogCopy = cloneDeep(dialog);
const trigger = generateNewTrigger(data);
insert(dialogCopy.content, 'triggers', undefined, trigger);
return dialogCopy;
}

return content;
export function createRegExIntent(dialog: DialogInfo, intent: string, pattern: string): DialogInfo {
const regex = generateRegexExpression(intent, pattern);
const dialogCopy = cloneDeep(dialog);
insert(dialogCopy.content, 'recognizer.intents', undefined, regex);
return dialogCopy;
}

export function addNewTrigger(dialogs: DialogInfo[], dialogId: string, data: TriggerFormData): DialogInfo {
const dialogCopy = getDialog(dialogs, dialogId);
if (!dialogCopy) throw new Error(`dialog ${dialogId} does not exist`);
insert(dialogCopy.content, 'triggers', undefined, data);
export function updateRegExIntent(dialog: DialogInfo, intent: string, pattern: string): DialogInfo {
let dialogCopy = cloneDeep(dialog);
const regexIntents = get(dialogCopy, 'content.recognizer.intents', []);
const targetIntent = regexIntents.find(ri => ri.intent === intent);
if (!targetIntent) {
dialogCopy = createRegExIntent(dialog, intent, pattern);
} else {
targetIntent.pattern = pattern;
}
return dialogCopy;
}

export function generateNewDialog(dialogs: DialogInfo[], dialogId: string, data: TriggerFormData): DialogInfo {
//add new trigger
const dialog = dialogs.find(dialog => dialog.id === dialogId);
if (!dialog) throw new Error(`dialog ${dialogId} does not exist`);
let updatedDialog = createNewTrigger(dialog, data);

//add regex expression
if (data.regexEx) {
updatedDialog = createRegExIntent(updatedDialog, data.intent, data.regexEx);
}
return updatedDialog;
}

export function createSelectedPath(selected: number) {
return `triggers[${selected}]`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ const mockShellApi = [
'addLuIntent',
'updateLuIntent',
'removeLuIntent',
'updateRegExIntent',
'validateExpression',
'onFocusSteps',
'onFocusEvent',
Expand Down
Loading

0 comments on commit 66e5fb4

Please sign in to comment.