From c1df63a898d92f24184b7f9b24d1782be73d8e87 Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 17:39:37 +0800 Subject: [PATCH 01/45] chore: move TriggerCreationModal to folder --- .../packages/client/__tests__/components/design.test.tsx | 2 +- .../__tests__/components/triggerCreationModal.test.tsx | 2 +- .../TriggerCreationModal.tsx | 0 .../client/src/components/TriggerCreationModal/index.tsx | 9 +++++++++ Composer/packages/client/src/pages/design/DesignPage.tsx | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) rename Composer/packages/client/src/components/{ProjectTree => TriggerCreationModal}/TriggerCreationModal.tsx (100%) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/index.tsx diff --git a/Composer/packages/client/__tests__/components/design.test.tsx b/Composer/packages/client/__tests__/components/design.test.tsx index b702e4b4b6..0c0439e034 100644 --- a/Composer/packages/client/__tests__/components/design.test.tsx +++ b/Composer/packages/client/__tests__/components/design.test.tsx @@ -8,7 +8,7 @@ import { DialogInfo } from '@bfc/shared'; import { renderWithRecoil } from '../testUtils'; import { dialogs } from '../constants.json'; import { ProjectTree } from '../../src/components/ProjectTree/ProjectTree'; -import { TriggerCreationModal } from '../../src/components/ProjectTree/TriggerCreationModal'; +import { TriggerCreationModal } from '../../src/components/TriggerCreationModal'; import { CreateDialogModal } from '../../src/pages/design/createDialogModal'; jest.mock('@bfc/code-editor', () => { diff --git a/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx b/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx index aa6a82e8c1..0e7aec9df6 100644 --- a/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx +++ b/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { fireEvent, waitFor } from '@bfc/test-utils'; -import { TriggerCreationModal } from '../../src/components/ProjectTree/TriggerCreationModal'; +import { TriggerCreationModal } from '../../src/components/TriggerCreationModal'; import { renderWithRecoil } from '../testUtils'; describe('', () => { diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx similarity index 100% rename from Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx rename to Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx diff --git a/Composer/packages/client/src/components/TriggerCreationModal/index.tsx b/Composer/packages/client/src/components/TriggerCreationModal/index.tsx new file mode 100644 index 0000000000..54b2120951 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/index.tsx @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { TriggerCreationModal } from './TriggerCreationModal'; + +// default export required by React.lazy() +export default TriggerCreationModal; + +export { TriggerCreationModal } from './TriggerCreationModal'; diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 7d784a6141..43d953331c 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -74,7 +74,7 @@ const CreateSkillModal = React.lazy(() => import('../../components/CreateSkillMo const CreateDialogModal = React.lazy(() => import('./createDialogModal')); const DisplayManifestModal = React.lazy(() => import('../../components/Modal/DisplayManifestModal')); const ExportSkillModal = React.lazy(() => import('./exportSkillModal')); -const TriggerCreationModal = React.lazy(() => import('../../components/ProjectTree/TriggerCreationModal')); +const TriggerCreationModal = React.lazy(() => import('../../components/TriggerCreationModal')); function onRenderContent(subTitle, style) { return ( From e7775253e558d942b4fbaf3ee203de79a757393f Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 17:46:21 +0800 Subject: [PATCH 02/45] chore: move styles into styles.ts --- .../TriggerCreationModal.tsx | 84 ++++--------------- .../components/TriggerCreationModal/styles.ts | 61 ++++++++++++++ 2 files changed, 78 insertions(+), 67 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/styles.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index b42bf98421..3a7bc75d65 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -2,7 +2,7 @@ // Licensed under the MIT License. /** @jsx jsx */ -import { jsx, css } from '@emotion/core'; +import { jsx } from '@emotion/core'; import React, { useState } from 'react'; import formatMessage from 'format-message'; import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog'; @@ -19,8 +19,6 @@ import { SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; import { IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; import { useRecoilValue } from 'recoil'; -import { FontWeights } from '@uifabric/styling'; -import { FontSizes } from '@uifabric/fluent-theme'; import { getTriggerTypes, @@ -40,64 +38,16 @@ import { userSettingsState } from '../../recoilModel'; import { nameRegex } from '../../constants'; import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; -// -------------------- Styles -------------------- // - -const styles = { - dialog: { - title: { - fontWeight: FontWeights.bold, - fontSize: FontSizes.size20, - paddingTop: '14px', - paddingBottom: '11px', - }, - subText: { - fontSize: FontSizes.size14, - }, - }, - modal: { - main: { - maxWidth: '600px !important', - }, - }, -}; - -const dropdownStyles = { - label: { - fontWeight: FontWeights.semibold, - }, - dropdown: { - width: '400px', - }, - root: { - marginBottom: '20px', - }, -}; - -const dialogWindow = css` - display: flex; - flex-direction: column; - width: 400px; - min-height: 300px; -`; - -const intent = { - root: { - width: '400px', - paddingBottom: '20px', - }, -}; -const optionRow = { - display: 'flex', - height: 15, - fontSize: 15, -}; - -export const warningIcon = { - marginLeft: 5, - color: '#BE880A', - fontSize: 5, -}; +import { + optionStyles, + dialogContentStyles, + modalStyles, + dialogWindowStyles, + dropdownStyles, + intentStyles, + warningIconStyles, +} from './styles'; // -------------------- Validation Helpers -------------------- // @@ -250,9 +200,9 @@ export const TriggerCreationModal: React.FC = (props) const onRenderOption = (option: IDropdownOption) => { return ( -
+
{option.text} - {option.data && option.data.icon && } + {option.data && option.data.icon && }
); }; @@ -344,16 +294,16 @@ export const TriggerCreationModal: React.FC = (props) dialogContentProps={{ type: DialogType.normal, title: formatMessage('Create a trigger'), - styles: styles.dialog, + styles: dialogContentStyles, }} hidden={!isOpen} modalProps={{ isBlocking: false, - styles: styles.modal, + styles: modalStyles, }} onDismiss={onDismiss} > -
+
= (props) data-testid="CustomEventName" errorMessage={formData.errors.event} label={formatMessage('What is the name of the custom event?')} - styles={intent} + styles={intentStyles} onChange={handleEventNameChange} /> )} @@ -409,7 +359,7 @@ export const TriggerCreationModal: React.FC = (props) ? formatMessage('What is the name of this trigger (LUIS)') : formatMessage('What is the name of this trigger') } - styles={intent} + styles={intentStyles} onChange={onNameChange} /> )} diff --git a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts new file mode 100644 index 0000000000..980d333700 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { css } from '@emotion/core'; +import { FontWeights } from '@uifabric/styling'; +import { FontSizes } from '@uifabric/fluent-theme'; + +export const dialogContentStyles = { + title: { + fontWeight: FontWeights.bold, + fontSize: FontSizes.size20, + paddingTop: '14px', + paddingBottom: '11px', + }, + subText: { + fontSize: FontSizes.size14, + }, +}; +export const modalStyles = { + main: { + maxWidth: '600px !important', + }, +}; + +export const dropdownStyles = { + label: { + fontWeight: FontWeights.semibold, + }, + dropdown: { + width: '400px', + }, + root: { + marginBottom: '20px', + }, +}; + +export const dialogWindowStyles = css` + display: flex; + flex-direction: column; + width: 400px; + min-height: 300px; +`; + +export const intentStyles = { + root: { + width: '400px', + paddingBottom: '20px', + }, +}; + +export const optionStyles = { + display: 'flex', + height: 15, + fontSize: 15, +}; + +export const warningIconStyles = { + marginLeft: 5, + color: '#BE880A', + fontSize: 5, +}; From 11aea2d7c56706e71185f50d388383c7c5703dbe Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 18:00:48 +0800 Subject: [PATCH 03/45] chore: move validators out --- .../TriggerCreationModal.tsx | 102 ++---------------- .../TriggerCreationModal/validators.ts | 94 ++++++++++++++++ 2 files changed, 102 insertions(+), 94 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/validators.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 3a7bc75d65..a4d4d6be83 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -13,7 +13,6 @@ import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; import { TextField } from 'office-ui-fabric-react/lib/TextField'; -import { luIndexer, combineMessage } from '@bfc/indexers'; import { PlaceHolderSectionName } from '@bfc/indexers/lib/utils/luUtil'; import { SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; @@ -35,7 +34,6 @@ import { } from '../../utils/dialogUtil'; import { projectIdState } from '../../recoilModel/atoms/botState'; import { userSettingsState } from '../../recoilModel'; -import { nameRegex } from '../../constants'; import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; @@ -48,8 +46,14 @@ import { intentStyles, warningIconStyles, } from './styles'; - -// -------------------- Validation Helpers -------------------- // +import { + validateForm, + validateEventName, + validateEventKind, + validateIntentName, + getLuDiagnostics, + validateRegExPattern, +} from './validators'; const initialFormDataErrors = { $kind: '', @@ -60,96 +64,6 @@ const initialFormDataErrors = { activity: '', }; -const getLuDiagnostics = (intent: string, triggerPhrases: string) => { - const content = `#${intent}\n${triggerPhrases}`; - const { diagnostics } = luIndexer.parse(content); - return combineMessage(diagnostics); -}; - -const validateIntentName = (selectedType: string, intent: string): string | undefined => { - if (selectedType === intentTypeKey && (!intent || !nameRegex.test(intent))) { - return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); - } - return undefined; -}; - -const validateDupRegExIntent = ( - selectedType: string, - intent: string, - isRegEx: boolean, - regExIntents: [{ intent: string; pattern: string }] -): string | undefined => { - if (selectedType === intentTypeKey && isRegEx && regExIntents.find((ri) => ri.intent === intent)) { - return formatMessage(`RegEx {intent} is already defined`, { intent }); - } - return undefined; -}; - -const validateRegExPattern = (selectedType: string, isRegEx: boolean, regEx: string): string | undefined => { - if (selectedType === intentTypeKey && isRegEx && !regEx) { - return formatMessage('Please input regEx pattern'); - } - return undefined; -}; - -const validateEventName = (selectedType: string, $kind: string, eventName: string): string | undefined => { - if (selectedType === customEventKey && $kind === eventTypeKey && !eventName) { - return formatMessage('Please enter an event name'); - } - return undefined; -}; - -const validateEventKind = (selectedType: string, $kind: string): string | undefined => { - if (selectedType === eventTypeKey && !$kind) { - return formatMessage('Please select a event type'); - } - - if (selectedType === activityTypeKey && !$kind) { - return formatMessage('Please select an activity type'); - } - return undefined; -}; - -const validateTriggerKind = (selectedType: string): string | undefined => { - if (!selectedType) { - return formatMessage('Please select a trigger type'); - } - return undefined; -}; - -const validateTriggerPhrases = ( - selectedType: string, - isRegEx: boolean, - intent: string, - triggerPhrases: string -): string | undefined => { - if (selectedType === intentTypeKey && !isRegEx && triggerPhrases) { - return getLuDiagnostics(intent, triggerPhrases); - } - return undefined; -}; - -const validateForm = ( - selectedType: string, - data: TriggerFormData, - isRegEx: boolean, - regExIntents: [{ intent: string; pattern: string }] -): TriggerFormDataErrors => { - const errors: TriggerFormDataErrors = {}; - const { $kind, event: eventName, intent, regEx, triggerPhrases } = data; - - errors.event = validateEventName(selectedType, $kind, eventName) ?? validateEventKind(selectedType, $kind); - errors.$kind = validateTriggerKind(selectedType); - errors.intent = validateIntentName(selectedType, intent); - errors.regEx = - validateDupRegExIntent(selectedType, intent, isRegEx, regExIntents) ?? - validateRegExPattern(selectedType, isRegEx, regEx); - errors.triggerPhrases = validateTriggerPhrases(selectedType, isRegEx, intent, triggerPhrases); - return errors; -}; - -// -------------------- TriggerCreationModal -------------------- // - interface TriggerCreationModalProps { dialogId: string; isOpen: boolean; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/validators.ts b/Composer/packages/client/src/components/TriggerCreationModal/validators.ts new file mode 100644 index 0000000000..873cf82486 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/validators.ts @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import formatMessage from 'format-message'; +import { luIndexer, combineMessage } from '@bfc/indexers'; + +import { + TriggerFormData, + TriggerFormDataErrors, + eventTypeKey, + customEventKey, + intentTypeKey, + activityTypeKey, +} from '../../utils/dialogUtil'; +import { nameRegex } from '../../constants'; +export const getLuDiagnostics = (intent: string, triggerPhrases: string) => { + const content = `#${intent}\n${triggerPhrases}`; + const { diagnostics } = luIndexer.parse(content); + return combineMessage(diagnostics); +}; +export const validateIntentName = (selectedType: string, intent: string): string | undefined => { + if (selectedType === intentTypeKey && (!intent || !nameRegex.test(intent))) { + return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); + } + return undefined; +}; +const validateDupRegExIntent = ( + selectedType: string, + intent: string, + isRegEx: boolean, + regExIntents: [{ intent: string; pattern: string }] +): string | undefined => { + if (selectedType === intentTypeKey && isRegEx && regExIntents.find((ri) => ri.intent === intent)) { + return formatMessage(`RegEx {intent} is already defined`, { intent }); + } + return undefined; +}; +export const validateRegExPattern = (selectedType: string, isRegEx: boolean, regEx: string): string | undefined => { + if (selectedType === intentTypeKey && isRegEx && !regEx) { + return formatMessage('Please input regEx pattern'); + } + return undefined; +}; +export const validateEventName = (selectedType: string, $kind: string, eventName: string): string | undefined => { + if (selectedType === customEventKey && $kind === eventTypeKey && !eventName) { + return formatMessage('Please enter an event name'); + } + return undefined; +}; +export const validateEventKind = (selectedType: string, $kind: string): string | undefined => { + if (selectedType === eventTypeKey && !$kind) { + return formatMessage('Please select a event type'); + } + + if (selectedType === activityTypeKey && !$kind) { + return formatMessage('Please select an activity type'); + } + return undefined; +}; +const validateTriggerKind = (selectedType: string): string | undefined => { + if (!selectedType) { + return formatMessage('Please select a trigger type'); + } + return undefined; +}; +const validateTriggerPhrases = ( + selectedType: string, + isRegEx: boolean, + intent: string, + triggerPhrases: string +): string | undefined => { + if (selectedType === intentTypeKey && !isRegEx && triggerPhrases) { + return getLuDiagnostics(intent, triggerPhrases); + } + return undefined; +}; +export const validateForm = ( + selectedType: string, + data: TriggerFormData, + isRegEx: boolean, + regExIntents: [{ intent: string; pattern: string }] +): TriggerFormDataErrors => { + const errors: TriggerFormDataErrors = {}; + const { $kind, event: eventName, intent, regEx, triggerPhrases } = data; + + errors.event = validateEventName(selectedType, $kind, eventName) ?? validateEventKind(selectedType, $kind); + errors.$kind = validateTriggerKind(selectedType); + errors.intent = validateIntentName(selectedType, intent); + errors.regEx = + validateDupRegExIntent(selectedType, intent, isRegEx, regExIntents) ?? + validateRegExPattern(selectedType, isRegEx, regEx); + errors.triggerPhrases = validateTriggerPhrases(selectedType, isRegEx, intent, triggerPhrases); + return errors; +}; From 45a29ef5f7549907707adb5df11afedced0576f3 Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 19:05:55 +0800 Subject: [PATCH 04/45] Extract dropdown options as constants --- .../TriggerCreationModal.tsx | 11 ++--- .../TriggerCreationModal/constants.ts | 41 +++++++++++++++++++ .../getDropdownOptions.ts | 19 +++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/constants.ts create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index a4d4d6be83..272b44d336 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -16,19 +16,15 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { PlaceHolderSectionName } from '@bfc/indexers/lib/utils/luUtil'; import { SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; -import { IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; import { useRecoilValue } from 'recoil'; import { - getTriggerTypes, TriggerFormData, TriggerFormDataErrors, eventTypeKey, customEventKey, intentTypeKey, activityTypeKey, - getEventTypes, - getActivityTypes, qnaMatcherKey, onChooseIntentKey, } from '../../utils/dialogUtil'; @@ -54,6 +50,7 @@ import { getLuDiagnostics, validateRegExPattern, } from './validators'; +import { getEventOptions, getActivityOptions, getTriggerOptions } from './getDropdownOptions'; const initialFormDataErrors = { $kind: '', @@ -97,9 +94,9 @@ export const TriggerCreationModal: React.FC = (props) const showEventDropDown = selectedType === eventTypeKey; const showActivityDropDown = selectedType === activityTypeKey; const showCustomEvent = selectedType === customEventKey; - const eventTypes: IComboBoxOption[] = getEventTypes(); - const activityTypes: IDropdownOption[] = getActivityTypes(); - const triggerTypeOptions: IDropdownOption[] = getTriggerTypes(); + const eventTypes: IDropdownOption[] = getEventOptions(); + const activityTypes: IDropdownOption[] = getActivityOptions(); + const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(); if (isRegEx) { const qnaMatcherOption = triggerTypeOptions.find((t) => t.key === qnaMatcherKey); diff --git a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts new file mode 100644 index 0000000000..b9223c0c27 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; +import { SDKKinds } from '@bfc/shared'; + +export const eventTypeKey = SDKKinds.OnDialogEvent; +export const intentTypeKey = SDKKinds.OnIntent; +export const activityTypeKey = SDKKinds.OnActivity; +export const customEventKey = 'CustomEvents'; + +export const TriggerOptions: IDropdownOption[] = [ + { key: 'Microsoft.OnIntent', text: 'Intent recognized' }, + { key: 'Microsoft.OnQnAMatch', text: 'QnA Intent recognized' }, + { key: 'Microsoft.OnUnknownIntent', text: 'Unknown intent' }, + { key: 'Microsoft.OnDialogEvent', text: 'Dialog events' }, + { key: 'Microsoft.OnActivity', text: 'Activities' }, + { key: 'Microsoft.OnChooseIntent', text: 'Duplicated intents recognized' }, + { key: customEventKey, text: 'Custom events' }, +]; + +export const ActivityOptions: IDropdownOption[] = [ + { key: 'Microsoft.OnActivity', text: 'Activities (Activity received)' }, + { key: 'Microsoft.OnConversationUpdateActivity', text: 'Greeting (ConversationUpdate activity)' }, + { key: 'Microsoft.OnEndOfConversationActivity', text: 'Conversation ended (EndOfConversation activity)' }, + { key: 'Microsoft.OnEventActivity', text: 'Event received (Event activity)' }, + { key: 'Microsoft.OnHandoffActivity', text: 'Handover to human (Handoff activity)' }, + { key: 'Microsoft.OnInvokeActivity', text: 'Conversation invoked (Invoke activity)' }, + { key: 'Microsoft.OnTypingActivity', text: 'User is typing (Typing activity)' }, + { key: 'Microsoft.OnMessageActivity', text: 'Message received (Message received activity)' }, + { key: 'Microsoft.OnMessageDeleteActivity', text: 'Message deleted (Message deleted activity)' }, + { key: 'Microsoft.OnMessageReactionActivity', text: 'Message reaction (Message reaction activity)' }, + { key: 'Microsoft.OnMessageUpdateActivity', text: 'Message updated (Message updated activity)' }, +]; + +export const EventOptions: IDropdownOption[] = [ + { key: 'Microsoft.OnBeginDialog', text: 'Dialog started (Begin dialog event)' }, + { key: 'Microsoft.OnCancelDialog', text: 'Dialog cancelled (Cancel dialog event)' }, + { key: 'Microsoft.OnError', text: 'Error occurred (Error event)' }, + { key: 'Microsoft.OnRepromptDialog', text: 'Re-prompt for input (Reprompt dialog event)' }, +]; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts new file mode 100644 index 0000000000..df1f3c068b --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; +import cloneDeep from 'lodash/cloneDeep'; + +import { TriggerOptions, ActivityOptions, EventOptions } from './constants'; + +export const getTriggerOptions = (): IDropdownOption[] => { + return cloneDeep(TriggerOptions); +}; + +export const getActivityOptions = (): IDropdownOption[] => { + return cloneDeep(ActivityOptions); +}; + +export const getEventOptions = (): IDropdownOption[] => { + return cloneDeep(EventOptions); +}; From 360fd69beefd2c41047314b0c8c8f12a2b87bc99 Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 19:07:27 +0800 Subject: [PATCH 05/45] remove outdated dropdown option generators --- .../client/__tests__/utils/dialogUtil.test.js | 49 ------------- .../packages/client/src/utils/dialogUtil.ts | 73 +------------------ 2 files changed, 1 insertion(+), 121 deletions(-) diff --git a/Composer/packages/client/__tests__/utils/dialogUtil.test.js b/Composer/packages/client/__tests__/utils/dialogUtil.test.js index 2520dc81aa..b55d27e31b 100644 --- a/Composer/packages/client/__tests__/utils/dialogUtil.test.js +++ b/Composer/packages/client/__tests__/utils/dialogUtil.test.js @@ -9,9 +9,6 @@ import { updateRegExIntent, createSelectedPath, deleteTrigger, - getTriggerTypes, - getEventTypes, - getActivityTypes, getFriendlyName, getbreadcrumbLabel, getSelected, @@ -181,52 +178,6 @@ describe('deleteTrigger', () => { }); }); -describe('getTriggerTypes', () => { - it('return trigger types', () => { - const triggerTypes = getTriggerTypes(); - expect(triggerTypes).toEqual([ - { key: 'Microsoft.OnIntent', text: 'Intent recognized' }, - { key: 'Microsoft.OnQnAMatch', text: 'QnA Intent recognized' }, - { key: 'Microsoft.OnUnknownIntent', text: 'Unknown intent' }, - { key: 'Microsoft.OnDialogEvent', text: 'Dialog events' }, - { key: 'Microsoft.OnActivity', text: 'Activities' }, - { key: 'Microsoft.OnChooseIntent', text: 'Duplicated intents recognized' }, - { key: 'OnCustomEvent', text: 'Custom events' }, - ]); - }); -}); - -describe('getEventTypes', () => { - it('return event types', () => { - const eventTypes = getEventTypes(); - expect(eventTypes).toEqual([ - { key: 'Microsoft.OnBeginDialog', text: 'Dialog started (Begin dialog event)' }, - { key: 'Microsoft.OnCancelDialog', text: 'Dialog cancelled (Cancel dialog event)' }, - { key: 'Microsoft.OnError', text: 'Error occurred (Error event)' }, - { key: 'Microsoft.OnRepromptDialog', text: 'Re-prompt for input (Reprompt dialog event)' }, - ]); - }); -}); - -describe('getActivityTypes', () => { - it('return activity types', () => { - const activityTypes = getActivityTypes(); - expect(activityTypes).toEqual([ - { key: 'Microsoft.OnActivity', text: 'Activities (Activity received)' }, - { key: 'Microsoft.OnConversationUpdateActivity', text: 'Greeting (ConversationUpdate activity)' }, - { key: 'Microsoft.OnEndOfConversationActivity', text: 'Conversation ended (EndOfConversation activity)' }, - { key: 'Microsoft.OnEventActivity', text: 'Event received (Event activity)' }, - { key: 'Microsoft.OnHandoffActivity', text: 'Handover to human (Handoff activity)' }, - { key: 'Microsoft.OnInvokeActivity', text: 'Conversation invoked (Invoke activity)' }, - { key: 'Microsoft.OnTypingActivity', text: 'User is typing (Typing activity)' }, - { key: 'Microsoft.OnMessageActivity', text: 'Message received (Message received activity)' }, - { key: 'Microsoft.OnMessageDeleteActivity', text: 'Message deleted (Message deleted activity)' }, - { key: 'Microsoft.OnMessageReactionActivity', text: 'Message reaction (Message reaction activity)' }, - { key: 'Microsoft.OnMessageUpdateActivity', text: 'Message updated (Message updated activity)' }, - ]); - }); -}); - describe('getFriendlyName', () => { it('return friendly name', () => { const name = getFriendlyName(dialogs[0].content); diff --git a/Composer/packages/client/src/utils/dialogUtil.ts b/Composer/packages/client/src/utils/dialogUtil.ts index 1ef458f90e..73347d8dab 100644 --- a/Composer/packages/client/src/utils/dialogUtil.ts +++ b/Composer/packages/client/src/utils/dialogUtil.ts @@ -1,21 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { - ConceptLabels, - DialogGroup, - SDKKinds, - dialogGroups, - DialogInfo, - DialogFactory, - ITriggerCondition, -} from '@bfc/shared'; +import { ConceptLabels, SDKKinds, DialogInfo, DialogFactory, ITriggerCondition } from '@bfc/shared'; import get from 'lodash/get'; import set from 'lodash/set'; import cloneDeep from 'lodash/cloneDeep'; -import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; -import { IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; -import formatMessage from 'format-message'; import { getFocusPath } from './navigation'; import { upperCaseName } from './fileUtil'; @@ -194,66 +183,6 @@ export function deleteTrigger( return dialogCopy.content; } -export function getTriggerTypes(): IDropdownOption[] { - const triggerTypes: IDropdownOption[] = [ - ...dialogGroups[DialogGroup.EVENTS].types.map((t) => { - let name = t as string; - const labelOverrides = ConceptLabels[t]; - - if (labelOverrides && labelOverrides.title) { - name = labelOverrides.title; - } - - return { key: t, text: name || t }; - }), - { - key: customEventKey, - text: formatMessage('Custom events'), - }, - ]; - return triggerTypes; -} - -export function getEventTypes(): IComboBoxOption[] { - const eventTypes: IComboBoxOption[] = [ - ...dialogGroups[DialogGroup.DIALOG_EVENT_TYPES].types.map((t) => { - let name = t as string; - const labelOverrides = ConceptLabels[t]; - - if (labelOverrides && labelOverrides.title) { - if (labelOverrides.subtitle) { - name = `${labelOverrides.title} (${labelOverrides.subtitle})`; - } else { - name = labelOverrides.title; - } - } - - return { key: t, text: name || t }; - }), - ]; - return eventTypes; -} - -export function getActivityTypes(): IDropdownOption[] { - const activityTypes: IDropdownOption[] = [ - ...dialogGroups[DialogGroup.ADVANCED_EVENTS].types.map((t) => { - let name = t as string; - const labelOverrides = ConceptLabels[t]; - - if (labelOverrides && labelOverrides.title) { - if (labelOverrides.subtitle) { - name = `${labelOverrides.title} (${labelOverrides.subtitle})`; - } else { - name = labelOverrides.title; - } - } - - return { key: t, text: name || t }; - }), - ]; - return activityTypes; -} - function getDialogsMap(dialogs: DialogInfo[]): DialogsMap { return dialogs.reduce((result, dialog) => { result[dialog.id] = dialog.content; From bde230df3d0625581704bf8326d633177c8d7d3b Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 19:34:06 +0800 Subject: [PATCH 06/45] don't re-declare SDKKinds as type key --- .../TriggerCreationModal.tsx | 17 ++++++++--------- .../TriggerCreationModal/constants.ts | 2 ++ .../TriggerCreationModal/validators.ts | 12 ++++-------- .../client/src/pages/design/DesignPage.tsx | 3 +-- .../packages/client/src/shell/triggerApi.ts | 10 +++++----- .../packages/client/src/utils/dialogUtil.ts | 12 +----------- .../client/src/utils/dialogValidator.ts | 12 +++++++----- 7 files changed, 28 insertions(+), 40 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 272b44d336..76d8927a1e 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -18,21 +18,20 @@ import { SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; import { useRecoilValue } from 'recoil'; +import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; +import { projectIdState } from '../../recoilModel/atoms/botState'; +import { userSettingsState } from '../../recoilModel'; +import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; +import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; + import { - TriggerFormData, - TriggerFormDataErrors, eventTypeKey, customEventKey, intentTypeKey, activityTypeKey, qnaMatcherKey, onChooseIntentKey, -} from '../../utils/dialogUtil'; -import { projectIdState } from '../../recoilModel/atoms/botState'; -import { userSettingsState } from '../../recoilModel'; -import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; -import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; - +} from './constants'; import { optionStyles, dialogContentStyles, @@ -87,7 +86,7 @@ export const TriggerCreationModal: React.FC = (props) regEx: '', }; const [formData, setFormData] = useState(initialFormData); - const [selectedType, setSelectedType] = useState(intentTypeKey); + const [selectedType, setSelectedType] = useState(intentTypeKey); const showIntentName = selectedType === intentTypeKey; const showRegExDropDown = selectedType === intentTypeKey && isRegEx; const showTriggerPhrase = selectedType === intentTypeKey && isLUISnQnA; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts index b9223c0c27..3828bde389 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts @@ -7,6 +7,8 @@ import { SDKKinds } from '@bfc/shared'; export const eventTypeKey = SDKKinds.OnDialogEvent; export const intentTypeKey = SDKKinds.OnIntent; export const activityTypeKey = SDKKinds.OnActivity; +export const qnaMatcherKey = SDKKinds.OnQnAMatch; +export const onChooseIntentKey = SDKKinds.OnChooseIntent; export const customEventKey = 'CustomEvents'; export const TriggerOptions: IDropdownOption[] = [ diff --git a/Composer/packages/client/src/components/TriggerCreationModal/validators.ts b/Composer/packages/client/src/components/TriggerCreationModal/validators.ts index 873cf82486..dc7c733061 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/validators.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/validators.ts @@ -4,15 +4,11 @@ import formatMessage from 'format-message'; import { luIndexer, combineMessage } from '@bfc/indexers'; -import { - TriggerFormData, - TriggerFormDataErrors, - eventTypeKey, - customEventKey, - intentTypeKey, - activityTypeKey, -} from '../../utils/dialogUtil'; +import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; import { nameRegex } from '../../constants'; + +import { eventTypeKey, customEventKey, intentTypeKey, activityTypeKey } from './constants'; + export const getLuDiagnostics = (intent: string, triggerPhrases: string) => { const content = `#${intent}\n${triggerPhrases}`; const { diagnostics } = luIndexer.parse(content); diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 43d953331c..ad315d2067 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -21,7 +21,6 @@ import { createSelectedPath, deleteTrigger, getbreadcrumbLabel, - qnaMatcherKey, TriggerFormData, getDialogData, } from '../../utils/dialogUtil'; @@ -576,7 +575,7 @@ const DesignPage: React.FC { cancelImportQnAModal(); const formData = { - $kind: qnaMatcherKey, + $kind: SDKKinds.OnQnAMatch, errors: { $kind: '', intent: '', event: '', triggerPhrases: '', regEx: '', activity: '' }, event: '', intent: '', diff --git a/Composer/packages/client/src/shell/triggerApi.ts b/Composer/packages/client/src/shell/triggerApi.ts index f3d1d9d2cc..ab3fcec328 100644 --- a/Composer/packages/client/src/shell/triggerApi.ts +++ b/Composer/packages/client/src/shell/triggerApi.ts @@ -2,14 +2,14 @@ // Licensed under the MIT License. import { useEffect, useState } from 'react'; -import { LuFile, LgFile, DialogInfo, LgTemplateSamples } from '@bfc/shared'; +import { LuFile, LgFile, DialogInfo, LgTemplateSamples, SDKKinds } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; import { LgTemplate } from '@bfc/shared'; import get from 'lodash/get'; import { useResolvers } from '../hooks/useResolver'; import { projectIdState, schemasState, dialogsState, localeState, lgFilesState } from '../recoilModel/atoms'; -import { onChooseIntentKey, generateNewDialog, intentTypeKey, qnaMatcherKey } from '../utils/dialogUtil'; +import { generateNewDialog } from '../utils/dialogUtil'; import { navigateTo } from '../utils/navigation'; import { dispatcherState } from './../recoilModel/DispatcherWrapper'; @@ -39,10 +39,10 @@ function createTriggerApi( if (!dialog) throw new Error(`dialog ${id} not found`); const newDialog = generateNewDialog(dialogs, dialog.id, formData, schemas.sdk?.content); const index = get(newDialog, 'content.triggers', []).length - 1; - if (formData.$kind === intentTypeKey && formData.triggerPhrases) { + if (formData.$kind === SDKKinds.OnIntent && formData.triggerPhrases) { const intent = { Name: formData.intent, Body: formData.triggerPhrases }; luFile && (await createLuIntent({ id: luFile.id, intent, projectId })); - } else if (formData.$kind === qnaMatcherKey) { + } else if (formData.$kind === SDKKinds.OnQnAMatch) { const designerId1 = getDesignerIdFromDialogPath( newDialog, `content.triggers[${index}].actions[0].actions[1].prompt` @@ -56,7 +56,7 @@ function createTriggerApi( LgTemplateSamples.SendActivityForQnAMatcher(designerId2) as LgTemplate, ]; await createLgTemplates({ id: lgFile.id, templates: lgTemplates }); - } else if (formData.$kind === onChooseIntentKey) { + } else if (formData.$kind === SDKKinds.OnChooseIntent) { const designerId1 = getDesignerIdFromDialogPath(newDialog, `content.triggers[${index}].actions[4].prompt`); const designerId2 = getDesignerIdFromDialogPath( newDialog, diff --git a/Composer/packages/client/src/utils/dialogUtil.ts b/Composer/packages/client/src/utils/dialogUtil.ts index 73347d8dab..55526b7027 100644 --- a/Composer/packages/client/src/utils/dialogUtil.ts +++ b/Composer/packages/client/src/utils/dialogUtil.ts @@ -36,16 +36,6 @@ export function getDialog(dialogs: DialogInfo[], dialogId: string) { return cloneDeep(dialog); } -export const eventTypeKey: string = SDKKinds.OnDialogEvent; -export const intentTypeKey: string = SDKKinds.OnIntent; -export const qnaTypeKey: string = SDKKinds.OnQnAMatch; -export const activityTypeKey: string = SDKKinds.OnActivity; -export const regexRecognizerKey: string = SDKKinds.RegexRecognizer; -export const crossTrainedRecognizerSetKey: string = SDKKinds.CrossTrainedRecognizerSet; -export const customEventKey = 'OnCustomEvent'; -export const qnaMatcherKey: string = SDKKinds.OnQnAMatch; -export const onChooseIntentKey: string = SDKKinds.OnChooseIntent; - function insert(content, path: string, position: number | undefined, data: any) { const current = get(content, path, []); const insertAt = typeof position === 'undefined' ? current.length : position; @@ -170,7 +160,7 @@ export function deleteTrigger( ) { let dialogCopy = getDialog(dialogs, dialogId); if (!dialogCopy) return null; - const isRegEx = get(dialogCopy, 'content.recognizer.$kind', '') === regexRecognizerKey; + const isRegEx = get(dialogCopy, 'content.recognizer.$kind', '') === SDKKinds.RegexRecognizer; if (isRegEx) { const regExIntent = get(dialogCopy, `content.triggers[${index}].intent`, ''); dialogCopy = deleteRegExIntent(dialogCopy, regExIntent); diff --git a/Composer/packages/client/src/utils/dialogValidator.ts b/Composer/packages/client/src/utils/dialogValidator.ts index fe7e850374..6d49323a20 100644 --- a/Composer/packages/client/src/utils/dialogValidator.ts +++ b/Composer/packages/client/src/utils/dialogValidator.ts @@ -1,14 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import get from 'lodash/get'; -import { DialogInfo, ITrigger } from '@bfc/shared'; +import { DialogInfo, ITrigger, SDKKinds } from '@bfc/shared'; -import { regexRecognizerKey, onChooseIntentKey, qnaMatcherKey } from '../utils/dialogUtil'; import { triggerNotSupportedWarning } from '../constants'; export const isRegExRecognizerType = (dialog: DialogInfo | undefined) => { if (!dialog) return false; - return get(dialog, 'content.recognizer.$kind', '') === regexRecognizerKey; + return get(dialog, 'content.recognizer.$kind', '') === SDKKinds.RegexRecognizer; }; export const isLUISnQnARecognizerType = (dialog: DialogInfo | undefined) => { @@ -22,7 +21,7 @@ export const containUnsupportedTriggers = (dialog: DialogInfo | undefined) => { if ( isRegExRecognizerType(dialog) && - dialog.triggers.some((t) => t.type === qnaMatcherKey || t.type === onChooseIntentKey) + dialog.triggers.some((t) => t.type === SDKKinds.OnQnAMatch || t.type === SDKKinds.OnChooseIntent) ) { return triggerNotSupportedWarning; } @@ -31,7 +30,10 @@ export const containUnsupportedTriggers = (dialog: DialogInfo | undefined) => { export const triggerNotSupported = (dialog: DialogInfo | undefined, trigger: ITrigger | undefined) => { if (!dialog || !trigger) return ''; - if (isRegExRecognizerType(dialog) && (trigger.type === qnaMatcherKey || trigger.type === onChooseIntentKey)) { + if ( + isRegExRecognizerType(dialog) && + (trigger.type === SDKKinds.OnQnAMatch || trigger.type === SDKKinds.OnChooseIntent) + ) { return triggerNotSupportedWarning; } return ''; From eb76dd077ebb1219cf2f4abb59df9cd2e32719d3 Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 19:58:16 +0800 Subject: [PATCH 07/45] remove initialError, fix tslint --- .../TriggerCreationModal.tsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 76d8927a1e..b2af8bbdb5 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -51,15 +51,6 @@ import { } from './validators'; import { getEventOptions, getActivityOptions, getTriggerOptions } from './getDropdownOptions'; -const initialFormDataErrors = { - $kind: '', - intent: '', - event: '', - triggerPhrases: '', - regEx: '', - activity: '', -}; - interface TriggerCreationModalProps { dialogId: string; isOpen: boolean; @@ -78,7 +69,7 @@ export const TriggerCreationModal: React.FC = (props) const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); const regexIntents = dialogFile?.content?.recognizer?.intents ?? []; const initialFormData: TriggerFormData = { - errors: initialFormDataErrors, + errors: {}, $kind: intentTypeKey, event: '', intent: '', @@ -108,7 +99,8 @@ export const TriggerCreationModal: React.FC = (props) } } - const onRenderOption = (option: IDropdownOption) => { + const onRenderOption = (option?: IDropdownOption) => { + if (!option) return null; return (
{option.text} @@ -149,7 +141,7 @@ export const TriggerCreationModal: React.FC = (props) } else { newFormData = { ...newFormData, $kind: option.key === customEventKey ? SDKKinds.OnDialogEvent : option.key }; } - setFormData({ ...newFormData, errors: initialFormDataErrors }); + setFormData({ ...newFormData, errors: {} }); }; const handleEventNameChange = (event: React.FormEvent, value?: string) => { @@ -223,7 +215,6 @@ export const TriggerCreationModal: React.FC = (props) options={triggerTypeOptions} styles={dropdownStyles} onChange={onSelectTriggerType} - //@ts-ignore: onRenderOption={onRenderOption} /> {showEventDropDown && ( From 8d80721444bd03f61da2c2be52accbd39412a86f Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 20:04:36 +0800 Subject: [PATCH 08/45] fix warning icon size --- .../client/src/components/TriggerCreationModal/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts index 980d333700..5ef9d61c92 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts @@ -57,5 +57,5 @@ export const optionStyles = { export const warningIconStyles = { marginLeft: 5, color: '#BE880A', - fontSize: 5, + fontSize: 15, }; From 7dd1340b4457444cb5fd0fe17f02d9d84cbba8cd Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 10 Sep 2020 20:26:03 +0800 Subject: [PATCH 09/45] refactor show warning logic --- .../TriggerCreationModal.tsx | 25 +++---------------- .../getDropdownOptions.ts | 19 +++++++++++--- .../client/src/utils/dialogValidator.ts | 14 +++++++++++ 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index b2af8bbdb5..18f52efe00 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -22,16 +22,9 @@ import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; import { projectIdState } from '../../recoilModel/atoms/botState'; import { userSettingsState } from '../../recoilModel'; import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; -import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; +import { isRegExRecognizerType, isLUISnQnARecognizerType, resolveRecognizer$kind } from '../../utils/dialogValidator'; -import { - eventTypeKey, - customEventKey, - intentTypeKey, - activityTypeKey, - qnaMatcherKey, - onChooseIntentKey, -} from './constants'; +import { eventTypeKey, customEventKey, intentTypeKey, activityTypeKey } from './constants'; import { optionStyles, dialogContentStyles, @@ -65,6 +58,7 @@ export const TriggerCreationModal: React.FC = (props) const projectId = useRecoilValue(projectIdState); const userSettings = useRecoilValue(userSettingsState); const dialogFile = dialogs.find((dialog) => dialog.id === dialogId); + const recognizer$kind = resolveRecognizer$kind(dialogFile); const isRegEx = isRegExRecognizerType(dialogFile); const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); const regexIntents = dialogFile?.content?.recognizer?.intents ?? []; @@ -86,18 +80,7 @@ export const TriggerCreationModal: React.FC = (props) const showCustomEvent = selectedType === customEventKey; const eventTypes: IDropdownOption[] = getEventOptions(); const activityTypes: IDropdownOption[] = getActivityOptions(); - const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(); - - if (isRegEx) { - const qnaMatcherOption = triggerTypeOptions.find((t) => t.key === qnaMatcherKey); - if (qnaMatcherOption) { - qnaMatcherOption.data = { icon: 'Warning' }; - } - const onChooseIntentOption = triggerTypeOptions.find((t) => t.key === onChooseIntentKey); - if (onChooseIntentOption) { - onChooseIntentOption.data = { icon: 'Warning' }; - } - } + const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(recognizer$kind); const onRenderOption = (option?: IDropdownOption) => { if (!option) return null; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts index df1f3c068b..6a32a6539a 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts @@ -3,11 +3,24 @@ import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import cloneDeep from 'lodash/cloneDeep'; +import { SDKKinds } from '@bfc/shared'; -import { TriggerOptions, ActivityOptions, EventOptions } from './constants'; +import { TriggerOptions, ActivityOptions, EventOptions, qnaMatcherKey, onChooseIntentKey } from './constants'; -export const getTriggerOptions = (): IDropdownOption[] => { - return cloneDeep(TriggerOptions); +export const getTriggerOptions = (recognizerType?: SDKKinds): IDropdownOption[] => { + const triggerTypeOptions = cloneDeep(TriggerOptions); + + if (recognizerType === SDKKinds.RegexRecognizer) { + const qnaMatcherOption = triggerTypeOptions.find((t) => t.key === qnaMatcherKey); + if (qnaMatcherOption) { + qnaMatcherOption.data = { icon: 'Warning' }; + } + const onChooseIntentOption = triggerTypeOptions.find((t) => t.key === onChooseIntentKey); + if (onChooseIntentOption) { + onChooseIntentOption.data = { icon: 'Warning' }; + } + } + return triggerTypeOptions; }; export const getActivityOptions = (): IDropdownOption[] => { diff --git a/Composer/packages/client/src/utils/dialogValidator.ts b/Composer/packages/client/src/utils/dialogValidator.ts index 6d49323a20..c0381a9e97 100644 --- a/Composer/packages/client/src/utils/dialogValidator.ts +++ b/Composer/packages/client/src/utils/dialogValidator.ts @@ -5,6 +5,20 @@ import { DialogInfo, ITrigger, SDKKinds } from '@bfc/shared'; import { triggerNotSupportedWarning } from '../constants'; +export const resolveRecognizer$kind = (dialog: DialogInfo | undefined): SDKKinds | undefined => { + if (!dialog) return undefined; + + const recognizer = get(dialog, 'content.recognizer'); + const $kind = get(recognizer, '$kind', undefined); + + if ($kind) return $kind; + + if (typeof recognizer === 'string') { + return recognizer.endsWith('.lu.qna') ? SDKKinds.LuisRecognizer : undefined; + } + return; +}; + export const isRegExRecognizerType = (dialog: DialogInfo | undefined) => { if (!dialog) return false; return get(dialog, 'content.recognizer.$kind', '') === SDKKinds.RegexRecognizer; From 5d4a0d9884c41b5a6104922372a8b2d5299f16ba Mon Sep 17 00:00:00 2001 From: zeye Date: Fri, 11 Sep 2020 12:55:35 +0800 Subject: [PATCH 10/45] move static func outside TriggerModal --- .../TriggerCreationModal.tsx | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 18f52efe00..ecc79fe09b 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -44,6 +44,27 @@ import { } from './validators'; import { getEventOptions, getActivityOptions, getTriggerOptions } from './getDropdownOptions'; +const renderDropdownOption = (option?: IDropdownOption) => { + if (!option) return null; + return ( +
+ {option.text} + {option.data && option.data.icon && } +
+ ); +}; + +const hasError = (errors: TriggerFormDataErrors) => Object.values(errors).some((msg) => !!msg); + +const initialFormData: TriggerFormData = { + errors: {}, + $kind: SDKKinds.OnIntent, + event: '', + intent: '', + triggerPhrases: '', + regEx: '', +}; + interface TriggerCreationModalProps { dialogId: string; isOpen: boolean; @@ -62,14 +83,7 @@ export const TriggerCreationModal: React.FC = (props) const isRegEx = isRegExRecognizerType(dialogFile); const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); const regexIntents = dialogFile?.content?.recognizer?.intents ?? []; - const initialFormData: TriggerFormData = { - errors: {}, - $kind: intentTypeKey, - event: '', - intent: '', - triggerPhrases: '', - regEx: '', - }; + const [formData, setFormData] = useState(initialFormData); const [selectedType, setSelectedType] = useState(intentTypeKey); const showIntentName = selectedType === intentTypeKey; @@ -82,31 +96,12 @@ export const TriggerCreationModal: React.FC = (props) const activityTypes: IDropdownOption[] = getActivityOptions(); const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(recognizer$kind); - const onRenderOption = (option?: IDropdownOption) => { - if (!option) return null; - return ( -
- {option.text} - {option.data && option.data.icon && } -
- ); - }; - - const shouldDisable = (errors: TriggerFormDataErrors) => { - for (const key in errors) { - if (errors[key]) { - return true; - } - } - return false; - }; - const onClickSubmitButton = (e) => { e.preventDefault(); //If still have some errors here, it is a bug. const errors = validateForm(selectedType, formData, isRegEx, regexIntents); - if (shouldDisable(errors)) { + if (hasError(errors)) { setFormData({ ...formData, errors }); return; } @@ -172,7 +167,7 @@ export const TriggerCreationModal: React.FC = (props) setFormData({ ...formData, triggerPhrases: body, errors: { ...formData.errors, ...errors } }); }; const errors = validateForm(selectedType, formData, isRegEx, regexIntents); - const disable = shouldDisable(errors); + const disable = hasError(errors); return ( = (props) options={triggerTypeOptions} styles={dropdownStyles} onChange={onSelectTriggerType} - onRenderOption={onRenderOption} + onRenderOption={renderDropdownOption} /> {showEventDropDown && ( Date: Fri, 11 Sep 2020 16:56:40 +0800 Subject: [PATCH 11/45] refactor: move trigger widget out of Modal --- .../TriggerCreationModal.tsx | 122 ++------------ .../resolveTriggerWidget.tsx | 149 ++++++++++++++++++ 2 files changed, 163 insertions(+), 108 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index ecc79fe09b..1bf149bfba 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -7,22 +7,18 @@ import React, { useState } 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 { Icon } from 'office-ui-fabric-react/lib/Icon'; import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; -import { TextField } from 'office-ui-fabric-react/lib/TextField'; -import { PlaceHolderSectionName } from '@bfc/indexers/lib/utils/luUtil'; import { SDKKinds } from '@bfc/shared'; -import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; import { useRecoilValue } from 'recoil'; import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; import { projectIdState } from '../../recoilModel/atoms/botState'; import { userSettingsState } from '../../recoilModel'; import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; -import { isRegExRecognizerType, isLUISnQnARecognizerType, resolveRecognizer$kind } from '../../utils/dialogValidator'; +import { isRegExRecognizerType, resolveRecognizer$kind } from '../../utils/dialogValidator'; import { eventTypeKey, customEventKey, intentTypeKey, activityTypeKey } from './constants'; import { @@ -31,18 +27,11 @@ import { modalStyles, dialogWindowStyles, dropdownStyles, - intentStyles, warningIconStyles, } from './styles'; -import { - validateForm, - validateEventName, - validateEventKind, - validateIntentName, - getLuDiagnostics, - validateRegExPattern, -} from './validators'; +import { validateForm, validateEventKind } from './validators'; import { getEventOptions, getActivityOptions, getTriggerOptions } from './getDropdownOptions'; +import { resolveTriggerWidget } from './resolveTriggerWidget'; const renderDropdownOption = (option?: IDropdownOption) => { if (!option) return null; @@ -81,17 +70,12 @@ export const TriggerCreationModal: React.FC = (props) const dialogFile = dialogs.find((dialog) => dialog.id === dialogId); const recognizer$kind = resolveRecognizer$kind(dialogFile); const isRegEx = isRegExRecognizerType(dialogFile); - const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); const regexIntents = dialogFile?.content?.recognizer?.intents ?? []; const [formData, setFormData] = useState(initialFormData); const [selectedType, setSelectedType] = useState(intentTypeKey); - const showIntentName = selectedType === intentTypeKey; - const showRegExDropDown = selectedType === intentTypeKey && isRegEx; - const showTriggerPhrase = selectedType === intentTypeKey && isLUISnQnA; const showEventDropDown = selectedType === eventTypeKey; const showActivityDropDown = selectedType === activityTypeKey; - const showCustomEvent = selectedType === customEventKey; const eventTypes: IDropdownOption[] = getEventOptions(); const activityTypes: IDropdownOption[] = getActivityOptions(); const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(recognizer$kind); @@ -99,7 +83,6 @@ export const TriggerCreationModal: React.FC = (props) const onClickSubmitButton = (e) => { e.preventDefault(); - //If still have some errors here, it is a bug. const errors = validateForm(selectedType, formData, isRegEx, regexIntents); if (hasError(errors)) { setFormData({ ...formData, errors }); @@ -122,17 +105,6 @@ export const TriggerCreationModal: React.FC = (props) setFormData({ ...newFormData, errors: {} }); }; - const handleEventNameChange = (event: React.FormEvent, value?: string) => { - const errors: TriggerFormDataErrors = {}; - errors.event = validateEventName(selectedType, SDKKinds.OnDialogEvent, value || ''); - setFormData({ - ...formData, - $kind: SDKKinds.OnDialogEvent, - event: value || '', - errors: { ...formData.errors, ...errors }, - }); - }; - const handleEventTypeChange = (e: React.FormEvent, option?: IDropdownOption) => { if (option) { const errors: TriggerFormDataErrors = {}; @@ -141,34 +113,19 @@ export const TriggerCreationModal: React.FC = (props) } }; - const onNameChange = (e, name) => { - const errors: TriggerFormDataErrors = {}; - errors.intent = validateIntentName(selectedType, name); - if (showTriggerPhrase && formData.triggerPhrases) { - errors.triggerPhrases = getLuDiagnostics(name, formData.triggerPhrases); - } - setFormData({ ...formData, intent: name, errors: { ...formData.errors, ...errors } }); - }; - - const onChangeRegEx = (e, pattern) => { - const errors: TriggerFormDataErrors = {}; - errors.regEx = validateRegExPattern(selectedType, isRegEx, pattern); - setFormData({ ...formData, regEx: pattern, errors: { ...formData.errors, ...errors } }); - }; - - //Trigger phrase is optional - const onTriggerPhrasesChange = (body: string) => { - const errors: TriggerFormDataErrors = {}; - if (body) { - errors.triggerPhrases = getLuDiagnostics(formData.intent, body); - } else { - errors.triggerPhrases = ''; - } - setFormData({ ...formData, triggerPhrases: body, errors: { ...formData.errors, ...errors } }); - }; const errors = validateForm(selectedType, formData, isRegEx, regexIntents); const disable = hasError(errors); + const triggerWidget = resolveTriggerWidget( + selectedType, + dialogFile, + formData, + setFormData, + userSettings, + projectId, + dialogId + ); + return ( = (props) onChange={handleEventTypeChange} /> )} - {showCustomEvent && ( - - )} {showActivityDropDown && ( = (props) onChange={handleEventTypeChange} /> )} - {showIntentName && ( - - )} - - {showRegExDropDown && ( - - )} - {showTriggerPhrase && ( - - - - - )} + {triggerWidget}
diff --git a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx new file mode 100644 index 0000000000..4f2e6d1891 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react'; +import formatMessage from 'format-message'; +import { Label } from 'office-ui-fabric-react/lib/Label'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { PlaceHolderSectionName } from '@bfc/indexers/lib/utils/luUtil'; +import { UserSettings } from '@bfc/shared'; +import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; +import { DialogInfo, SDKKinds } from '@bfc/shared/src/types'; + +import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; +import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; + +import { customEventKey, intentTypeKey } from './constants'; +import { intentStyles } from './styles'; +import { validateEventName, validateIntentName, getLuDiagnostics, validateRegExPattern } from './validators'; + +export function resolveTriggerWidget( + selectedType: string, + dialogFile: DialogInfo | undefined, + formData: TriggerFormData, + setFormData: (data: TriggerFormData) => void, + userSettings: UserSettings, + projectId: string, + dialogId: string +) { + const isRegEx = isRegExRecognizerType(dialogFile); + const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); + const showTriggerPhrase = selectedType === intentTypeKey && isLUISnQnA; + + const onNameChange = (e, name) => { + const errors: TriggerFormDataErrors = {}; + errors.intent = validateIntentName(selectedType, name); + if (showTriggerPhrase && formData.triggerPhrases) { + errors.triggerPhrases = getLuDiagnostics(name, formData.triggerPhrases); + } + setFormData({ ...formData, intent: name, errors: { ...formData.errors, ...errors } }); + }; + + const onChangeRegEx = (e, pattern) => { + const errors: TriggerFormDataErrors = {}; + errors.regEx = validateRegExPattern(selectedType, isRegEx, pattern); + setFormData({ ...formData, regEx: pattern, errors: { ...formData.errors, ...errors } }); + }; + + //Trigger phrase is optional + const onTriggerPhrasesChange = (body: string) => { + const errors: TriggerFormDataErrors = {}; + if (body) { + errors.triggerPhrases = getLuDiagnostics(formData.intent, body); + } else { + errors.triggerPhrases = ''; + } + setFormData({ ...formData, triggerPhrases: body, errors: { ...formData.errors, ...errors } }); + }; + + const handleEventNameChange = (event: React.FormEvent, value?: string) => { + const errors: TriggerFormDataErrors = {}; + errors.event = validateEventName(selectedType, SDKKinds.OnDialogEvent, value || ''); + setFormData({ + ...formData, + $kind: SDKKinds.OnDialogEvent, + event: value || '', + errors: { ...formData.errors, ...errors }, + }); + }; + + const onIntentWidgetRegex = ( + + + + + ); + + const onIntentWidgetLUISQnA = ( + + + + + + ); + + const onIntentWidgetCustom = ( + + ); + + const onIntentWidget = isRegEx ? onIntentWidgetRegex : isLUISnQnA ? onIntentWidgetLUISQnA : onIntentWidgetCustom; + + const onEventWidget = ( + + ); + + let widget; + switch (selectedType) { + case intentTypeKey: + widget = onIntentWidget; + break; + case customEventKey: + widget = onEventWidget; + break; + default: + break; + } + return widget; +} From d04c877b0a2485778d12125a1a27e5b10609a62b Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 24 Sep 2020 17:20:57 +0800 Subject: [PATCH 12/45] replay changes in #4117 'mutiple projects' --- .../TriggerCreationModal/TriggerCreationModal.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 1bf149bfba..3d7d0157ce 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -15,9 +15,8 @@ import { SDKKinds } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; -import { projectIdState } from '../../recoilModel/atoms/botState'; -import { userSettingsState } from '../../recoilModel'; -import { validatedDialogsSelector } from '../../recoilModel/selectors/validatedDialogs'; +import { userSettingsState } from '../../recoilModel/atoms'; +import { validateDialogSelectorFamily } from '../../recoilModel'; import { isRegExRecognizerType, resolveRecognizer$kind } from '../../utils/dialogValidator'; import { eventTypeKey, customEventKey, intentTypeKey, activityTypeKey } from './constants'; @@ -55,6 +54,7 @@ const initialFormData: TriggerFormData = { }; interface TriggerCreationModalProps { + projectId: string; dialogId: string; isOpen: boolean; onDismiss: () => void; @@ -62,10 +62,9 @@ interface TriggerCreationModalProps { } export const TriggerCreationModal: React.FC = (props) => { - const { isOpen, onDismiss, onSubmit, dialogId } = props; - const dialogs = useRecoilValue(validatedDialogsSelector); + const { isOpen, onDismiss, onSubmit, dialogId, projectId } = props; + const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); - const projectId = useRecoilValue(projectIdState); const userSettings = useRecoilValue(userSettingsState); const dialogFile = dialogs.find((dialog) => dialog.id === dialogId); const recognizer$kind = resolveRecognizer$kind(dialogFile); From 732ae47a49f3579fb99a40356cc422eac712d0d2 Mon Sep 17 00:00:00 2001 From: zeye Date: Thu, 24 Sep 2020 20:08:36 +0800 Subject: [PATCH 13/45] fix a wrong import path --- .../components/TriggerCreationModal/resolveTriggerWidget.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx index 4f2e6d1891..dc917907d7 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx @@ -6,9 +6,8 @@ import formatMessage from 'format-message'; import { Label } from 'office-ui-fabric-react/lib/Label'; import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { PlaceHolderSectionName } from '@bfc/indexers/lib/utils/luUtil'; -import { UserSettings } from '@bfc/shared'; +import { UserSettings, DialogInfo, SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; -import { DialogInfo, SDKKinds } from '@bfc/shared/src/types'; import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; From f6482a16fa9d1aa441a816ff13d48cb371aa1c87 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 14 Oct 2020 19:09:12 +0800 Subject: [PATCH 14/45] refactor: extract TriggerDropdownGroup --- .../TriggerCreationModal.tsx | 91 +++---------------- .../TriggerDropdownGroup.tsx | 88 ++++++++++++++++++ .../TriggerCreationModal/constants.ts | 15 +-- .../getDropdownOptions.ts | 31 ++++--- .../resolveTriggerWidget.tsx | 7 +- .../TriggerCreationModal/validators.ts | 26 ++---- .../packages/client/src/utils/dialogUtil.ts | 1 - 7 files changed, 135 insertions(+), 124 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 3d7d0157ce..4ace7a4070 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -7,10 +7,8 @@ import React, { useState } 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 { Stack } from 'office-ui-fabric-react/lib/Stack'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; -import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; import { SDKKinds } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; @@ -19,20 +17,12 @@ import { userSettingsState } from '../../recoilModel/atoms'; import { validateDialogSelectorFamily } from '../../recoilModel'; import { isRegExRecognizerType, resolveRecognizer$kind } from '../../utils/dialogValidator'; -import { eventTypeKey, customEventKey, intentTypeKey, activityTypeKey } from './constants'; -import { - optionStyles, - dialogContentStyles, - modalStyles, - dialogWindowStyles, - dropdownStyles, - warningIconStyles, -} from './styles'; -import { validateForm, validateEventKind } from './validators'; -import { getEventOptions, getActivityOptions, getTriggerOptions } from './getDropdownOptions'; +import { optionStyles, dialogContentStyles, modalStyles, dialogWindowStyles, warningIconStyles } from './styles'; +import { validateForm } from './validators'; import { resolveTriggerWidget } from './resolveTriggerWidget'; +import { TriggerDropdownGroup } from './TriggerDropdownGroup'; -const renderDropdownOption = (option?: IDropdownOption) => { +export const renderDropdownOption = (option?: IDropdownOption) => { if (!option) return null; return (
@@ -44,7 +34,7 @@ const renderDropdownOption = (option?: IDropdownOption) => { const hasError = (errors: TriggerFormDataErrors) => Object.values(errors).some((msg) => !!msg); -const initialFormData: TriggerFormData = { +export const initialFormData: TriggerFormData = { errors: {}, $kind: SDKKinds.OnIntent, event: '', @@ -72,12 +62,7 @@ export const TriggerCreationModal: React.FC = (props) const regexIntents = dialogFile?.content?.recognizer?.intents ?? []; const [formData, setFormData] = useState(initialFormData); - const [selectedType, setSelectedType] = useState(intentTypeKey); - const showEventDropDown = selectedType === eventTypeKey; - const showActivityDropDown = selectedType === activityTypeKey; - const eventTypes: IDropdownOption[] = getEventOptions(); - const activityTypes: IDropdownOption[] = getActivityOptions(); - const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(recognizer$kind); + const [selectedType, setSelectedType] = useState(SDKKinds.OnIntent); const onClickSubmitButton = (e) => { e.preventDefault(); @@ -88,28 +73,7 @@ export const TriggerCreationModal: React.FC = (props) return; } onDismiss(); - onSubmit(dialogId, formData); - }; - - const onSelectTriggerType = (e, option) => { - setSelectedType(option.key || ''); - const compoundTypes = [activityTypeKey, eventTypeKey]; - const isCompound = compoundTypes.some((t) => option.key === t); - let newFormData: TriggerFormData = initialFormData; - if (isCompound) { - newFormData = { ...newFormData, $kind: '' }; - } else { - newFormData = { ...newFormData, $kind: option.key === customEventKey ? SDKKinds.OnDialogEvent : option.key }; - } - setFormData({ ...newFormData, errors: {} }); - }; - - const handleEventTypeChange = (e: React.FormEvent, option?: IDropdownOption) => { - if (option) { - const errors: TriggerFormDataErrors = {}; - errors.event = validateEventKind(selectedType, option.key as string); - setFormData({ ...formData, $kind: option.key as string, errors: { ...formData.errors, ...errors } }); - } + onSubmit(dialogId, { ...formData, $kind: selectedType }); }; const errors = validateForm(selectedType, formData, isRegEx, regexIntents); @@ -140,41 +104,12 @@ export const TriggerCreationModal: React.FC = (props) onDismiss={onDismiss} >
- - - {showEventDropDown && ( - - )} - {showActivityDropDown && ( - - )} - {triggerWidget} - + + {triggerWidget}
diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx new file mode 100644 index 0000000000..813f86c8ca --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React, { FC, useState } from 'react'; +import formatMessage from 'format-message'; +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 { SDKKinds } from '@bfc/shared'; + +import { activityTypeKey, dialogEventKey } from './constants'; +import { dropdownStyles } from './styles'; +import { getEventOptions, getActivityOptions, checkTriggerOptions, getTriggerOptions } from './getDropdownOptions'; +import { renderDropdownOption } from './TriggerCreationModal'; + +export interface TriggerDropwdownGroupProps { + recognizerType: SDKKinds | undefined; + triggerType: string; + setTriggerType: (type: string) => void; +} + +export const TriggerDropdownGroup: FC = ({ + recognizerType, + triggerType, + setTriggerType, +}) => { + const eventTypes: IDropdownOption[] = getEventOptions(); + const activityTypes: IDropdownOption[] = getActivityOptions(); + const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(); + + // Mark out incompatible triggers of current recognizer + checkTriggerOptions(triggerTypeOptions, recognizerType); + + const [rootKey, setRootKey] = useState(triggerType); + const [childKey, setChildKey] = useState(''); + + const showEventDropDown = rootKey === dialogEventKey; + const showActivityDropDown = rootKey === activityTypeKey; + + const compoundTypes = [activityTypeKey, dialogEventKey]; + const onSelectTriggerType = (e, option) => { + const type = option.key; + setRootKey(type); + setChildKey(''); + const isCompound = compoundTypes.some((t) => type === t); + setTriggerType(isCompound ? '' : type); + }; + const onChangeChildKey = (e, option) => { + setChildKey(option.key); + setTriggerType(option.key); + }; + + return ( + + + {showEventDropDown && ( + + )} + {showActivityDropDown && ( + + )} + + ); +}; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts index 3828bde389..7cee6196fc 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts @@ -2,23 +2,18 @@ // Licensed under the MIT License. import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; -import { SDKKinds } from '@bfc/shared'; -export const eventTypeKey = SDKKinds.OnDialogEvent; -export const intentTypeKey = SDKKinds.OnIntent; -export const activityTypeKey = SDKKinds.OnActivity; -export const qnaMatcherKey = SDKKinds.OnQnAMatch; -export const onChooseIntentKey = SDKKinds.OnChooseIntent; -export const customEventKey = 'CustomEvents'; +export const dialogEventKey = 'DialogEvent'; +export const activityTypeKey = 'Activities'; export const TriggerOptions: IDropdownOption[] = [ { key: 'Microsoft.OnIntent', text: 'Intent recognized' }, { key: 'Microsoft.OnQnAMatch', text: 'QnA Intent recognized' }, { key: 'Microsoft.OnUnknownIntent', text: 'Unknown intent' }, - { key: 'Microsoft.OnDialogEvent', text: 'Dialog events' }, - { key: 'Microsoft.OnActivity', text: 'Activities' }, + { key: dialogEventKey, text: 'Dialog events' }, + { key: activityTypeKey, text: 'Activities' }, { key: 'Microsoft.OnChooseIntent', text: 'Duplicated intents recognized' }, - { key: customEventKey, text: 'Custom events' }, + { key: 'Microsoft.OnDialogEvent', text: 'Custom events' }, ]; export const ActivityOptions: IDropdownOption[] = [ diff --git a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts index 6a32a6539a..e44f3c4cd2 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts @@ -5,28 +5,33 @@ import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import cloneDeep from 'lodash/cloneDeep'; import { SDKKinds } from '@bfc/shared'; -import { TriggerOptions, ActivityOptions, EventOptions, qnaMatcherKey, onChooseIntentKey } from './constants'; +import { TriggerOptions, ActivityOptions, EventOptions } from './constants'; -export const getTriggerOptions = (recognizerType?: SDKKinds): IDropdownOption[] => { - const triggerTypeOptions = cloneDeep(TriggerOptions); +export function getTriggerOptions(): IDropdownOption[] { + return cloneDeep(TriggerOptions); +} +export const getActivityOptions = (): IDropdownOption[] => { + return cloneDeep(ActivityOptions); +}; + +export const getEventOptions = (): IDropdownOption[] => { + return cloneDeep(EventOptions); +}; + +export const checkTriggerOptions = ( + triggerTypeOptions: IDropdownOption[], + recognizerType?: SDKKinds +): IDropdownOption[] => { if (recognizerType === SDKKinds.RegexRecognizer) { - const qnaMatcherOption = triggerTypeOptions.find((t) => t.key === qnaMatcherKey); + const qnaMatcherOption = triggerTypeOptions.find((t) => t.key === SDKKinds.OnQnAMatch); if (qnaMatcherOption) { qnaMatcherOption.data = { icon: 'Warning' }; } - const onChooseIntentOption = triggerTypeOptions.find((t) => t.key === onChooseIntentKey); + const onChooseIntentOption = triggerTypeOptions.find((t) => t.key === SDKKinds.OnChooseIntent); if (onChooseIntentOption) { onChooseIntentOption.data = { icon: 'Warning' }; } } return triggerTypeOptions; }; - -export const getActivityOptions = (): IDropdownOption[] => { - return cloneDeep(ActivityOptions); -}; - -export const getEventOptions = (): IDropdownOption[] => { - return cloneDeep(EventOptions); -}; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx index bc2b736d59..9221eee487 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx @@ -12,7 +12,6 @@ import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; -import { customEventKey, intentTypeKey } from './constants'; import { intentStyles } from './styles'; import { validateEventName, validateIntentName, getLuDiagnostics, validateRegExPattern } from './validators'; @@ -27,7 +26,7 @@ export function resolveTriggerWidget( ) { const isRegEx = isRegExRecognizerType(dialogFile); const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); - const showTriggerPhrase = selectedType === intentTypeKey && isLUISnQnA; + const showTriggerPhrase = selectedType === SDKKinds.OnIntent && isLUISnQnA; const onNameChange = (e, name) => { const errors: TriggerFormDataErrors = {}; @@ -136,10 +135,10 @@ export function resolveTriggerWidget( let widget; switch (selectedType) { - case intentTypeKey: + case SDKKinds.OnIntent: widget = onIntentWidget; break; - case customEventKey: + case SDKKinds.OnDialogEvent: widget = onEventWidget; break; default: diff --git a/Composer/packages/client/src/components/TriggerCreationModal/validators.ts b/Composer/packages/client/src/components/TriggerCreationModal/validators.ts index fc625c96aa..f0d6bbb84f 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/validators.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/validators.ts @@ -3,19 +3,18 @@ import formatMessage from 'format-message'; import { luIndexer, combineMessage } from '@bfc/indexers'; +import { SDKKinds } from '@bfc/shared'; import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; import { nameRegex } from '../../constants'; -import { eventTypeKey, customEventKey, intentTypeKey, activityTypeKey } from './constants'; - export const getLuDiagnostics = (intent: string, triggerPhrases: string) => { const content = `#${intent}\n${triggerPhrases}`; const { diagnostics } = luIndexer.parse(content, '', {}); return combineMessage(diagnostics); }; export const validateIntentName = (selectedType: string, intent: string): string | undefined => { - if (selectedType === intentTypeKey && (!intent || !nameRegex.test(intent))) { + if (selectedType === SDKKinds.OnIntent && (!intent || !nameRegex.test(intent))) { return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); } return undefined; @@ -26,46 +25,37 @@ const validateDupRegExIntent = ( isRegEx: boolean, regExIntents: [{ intent: string; pattern: string }] ): string | undefined => { - if (selectedType === intentTypeKey && isRegEx && regExIntents.find((ri) => ri.intent === intent)) { + if (selectedType === SDKKinds.OnIntent && isRegEx && regExIntents.find((ri) => ri.intent === intent)) { return formatMessage(`RegEx {intent} is already defined`, { intent }); } return undefined; }; export const validateRegExPattern = (selectedType: string, isRegEx: boolean, regEx: string): string | undefined => { - if (selectedType === intentTypeKey && isRegEx && !regEx) { + if (selectedType === SDKKinds.OnIntent && isRegEx && !regEx) { return formatMessage('Please input regEx pattern'); } return undefined; }; export const validateEventName = (selectedType: string, $kind: string, eventName: string): string | undefined => { - if (selectedType === customEventKey && $kind === eventTypeKey && !eventName) { + if (selectedType === SDKKinds.OnDialogEvent && !eventName) { return formatMessage('Please enter an event name'); } return undefined; }; -export const validateEventKind = (selectedType: string, $kind: string): string | undefined => { - if (selectedType === eventTypeKey && !$kind) { - return formatMessage('Please select a event type'); - } - - if (selectedType === activityTypeKey && !$kind) { - return formatMessage('Please select an activity type'); - } - return undefined; -}; const validateTriggerKind = (selectedType: string): string | undefined => { if (!selectedType) { return formatMessage('Please select a trigger type'); } return undefined; }; + const validateTriggerPhrases = ( selectedType: string, isRegEx: boolean, intent: string, triggerPhrases: string ): string | undefined => { - if (selectedType === intentTypeKey && !isRegEx && triggerPhrases) { + if (selectedType === SDKKinds.OnIntent && !isRegEx && triggerPhrases) { return getLuDiagnostics(intent, triggerPhrases); } return undefined; @@ -79,7 +69,7 @@ export const validateForm = ( const errors: TriggerFormDataErrors = {}; const { $kind, event: eventName, intent, regEx, triggerPhrases } = data; - errors.event = validateEventName(selectedType, $kind, eventName) ?? validateEventKind(selectedType, $kind); + errors.event = validateEventName(selectedType, $kind, eventName); errors.$kind = validateTriggerKind(selectedType); errors.intent = validateIntentName(selectedType, intent); errors.regEx = diff --git a/Composer/packages/client/src/utils/dialogUtil.ts b/Composer/packages/client/src/utils/dialogUtil.ts index b9abe57d7f..3919ebda08 100644 --- a/Composer/packages/client/src/utils/dialogUtil.ts +++ b/Composer/packages/client/src/utils/dialogUtil.ts @@ -28,7 +28,6 @@ export interface TriggerFormDataErrors { event?: string; triggerPhrases?: string; regEx?: string; - activity?: string; } export function getDialog(dialogs: DialogInfo[], dialogId: string) { From 78413bc40558f59de3d9a62855f23ec76061f800 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 14 Oct 2020 19:25:55 +0800 Subject: [PATCH 15/45] define builtinSchema --- .../schema/TriggerOption.ts | 13 +++ .../schema/builtinSchema.ts | 102 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts new file mode 100644 index 0000000000..8a1359828a --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export interface TriggerUIOption { + label: string; + submenu?: TriggerSubmenuInfo | string | false; +} + +export interface TriggerSubmenuInfo { + label: string; + prompt?: string; + placeholder?: string; +} diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts new file mode 100644 index 0000000000..73db335d61 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SDKKinds } from '@bfc/shared'; + +import { TriggerUIOption } from './TriggerOption'; + +export const builtinSchema: { [key: string]: TriggerUIOption } = { + [SDKKinds.OnIntent]: { + label: 'Intent recognized', + }, + [SDKKinds.OnQnAMatch]: { + label: 'QnA Intent recognized', + }, + [SDKKinds.OnUnknownIntent]: { + label: 'Unknown intent', + }, + [SDKKinds.OnChooseIntent]: { + label: 'Duplicated intents recognized', + }, + [SDKKinds.OnDialogEvent]: { + label: 'Custom events', + }, + // Subgroup - Dialog events + [SDKKinds.OnBeginDialog]: { + label: 'Dialog started (Begin dialog event)', + submenu: { + label: 'Dialog events', + prompt: 'Which event?', + placeholder: 'Select an event type', + }, + }, + [SDKKinds.OnCancelDialog]: { + label: 'Dialog cancelled (Cancel dialog event)', + submenu: 'Dialog events', + }, + [SDKKinds.OnBeginDialog]: { + label: 'Dialog started (Begin dialog event)', + submenu: 'Dialog events', + }, + [SDKKinds.OnCancelDialog]: { + label: 'Dialog cancelled (Cancel dialog event)', + submenu: 'Dialog events', + }, + [SDKKinds.OnError]: { + label: 'Error occurred (Error event)', + submenu: 'Dialog events', + }, + [SDKKinds.OnRepromptDialog]: { + label: 'Re-prompt for input (Reprompt dialog event)', + submenu: 'Dialog events', + }, + // Subgroup - Activities + [SDKKinds.OnActivity]: { + label: 'Activities (Activity received)', + submenu: { + label: 'Activities', + prompt: 'Which activity type?', + placeholder: 'Select an activity type', + }, + }, + [SDKKinds.OnConversationUpdateActivity]: { + label: 'Greeting (ConversationUpdate activity)', + submenu: 'Activities', + }, + [SDKKinds.OnEndOfConversationActivity]: { + label: 'Conversation ended (EndOfConversation activity)', + submenu: 'Activities', + }, + [SDKKinds.OnEventActivity]: { + label: 'Event received (Event activity)', + submenu: 'Activities', + }, + [SDKKinds.OnHandoffActivity]: { + label: 'Handover to human (Handoff activity)', + submenu: 'Activities', + }, + [SDKKinds.OnInvokeActivity]: { + label: 'Conversation invoked (Invoke activity)', + submenu: 'Activities', + }, + [SDKKinds.OnTypingActivity]: { + label: 'User is typing (Typing activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageActivity]: { + label: 'Message received (Message received activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageDeleteActivity]: { + label: 'Message deleted (Message deleted activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageReactionActivity]: { + label: 'Message reaction (Message reaction activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageUpdateActivity]: { + label: 'Message updated (Message updated activity)', + submenu: 'Activities', + }, +}; From 2635cccc30aeb3b870d825727429d7deab4c8b17 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 14 Oct 2020 20:08:43 +0800 Subject: [PATCH 16/45] TriggerOptionTree --- .../TriggerDropdownGroup.tsx | 8 ++- .../TriggerCreationModal/TriggerOptionTree.ts | 71 +++++++++++++++++++ .../schema/TriggerOption.ts | 1 + .../schema/builtinSchema.ts | 4 +- 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 813f86c8ca..e7bb92b85d 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { FC, useState } from 'react'; +import React, { FC, useMemo, useState } from 'react'; import formatMessage from 'format-message'; import { Stack } from 'office-ui-fabric-react/lib/Stack'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; @@ -12,6 +12,8 @@ import { activityTypeKey, dialogEventKey } from './constants'; import { dropdownStyles } from './styles'; import { getEventOptions, getActivityOptions, checkTriggerOptions, getTriggerOptions } from './getDropdownOptions'; import { renderDropdownOption } from './TriggerCreationModal'; +import { generateTriggerOptionTree } from './TriggerOptionTree'; +import { builtinTriggerUISchema } from './schema/builtinSchema'; export interface TriggerDropwdownGroupProps { recognizerType: SDKKinds | undefined; @@ -50,6 +52,10 @@ export const TriggerDropdownGroup: FC = ({ setTriggerType(option.key); }; + const triggerOptionTree = useMemo(() => { + return generateTriggerOptionTree(builtinTriggerUISchema); + }, []); + return ( (typeof submenu === 'object' ? submenu.label : submenu || ''); + +export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): TriggerOptionTree => { + const root = new TriggerOptionTree( + '', + formatMessage('What is the type of this trigger?'), + formatMessage('Select a trigger type') + ); + + const leafNodeList = Object.entries(triggerUIOptions) + .filter(([, options]) => !options.submenu) + .map(([$kind, options]) => new TriggerOptionLeafNode(options.label, $kind)); + root.children.push(...leafNodeList); + + const groups = Object.values(triggerUIOptions) + .map((options) => options.submenu) + .filter((submenu) => !!submenu) + .reduce((result, submenu) => { + const name = getGroupKey(submenu); + if (!result[name]) result[name] = new TriggerOptionTree(name, '', ''); + if (typeof submenu === 'object') { + const tree: TriggerOptionTree = result[name]; + tree.prompt = submenu.prompt; + tree.placeholder = submenu.placeholder; + } + return result; + }, {} as { [key: string]: TriggerOptionTree }); + + Object.entries(triggerUIOptions) + .filter(([, options]) => options.submenu) + .forEach(([$kind, options]) => { + const { label, submenu } = options; + const node = new TriggerOptionLeafNode(label, $kind); + const groupName = getGroupKey(submenu); + groups[groupName].children.push(node); + }); + root.children.push(...Object.values(groups)); + return root; +}; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts index 8a1359828a..401e657ca8 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts @@ -5,6 +5,7 @@ export interface TriggerUIOption { label: string; submenu?: TriggerSubmenuInfo | string | false; } +export type TriggerUIOptionMap = { [key: string]: TriggerUIOption }; export interface TriggerSubmenuInfo { label: string; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts index 73db335d61..3f6b82f41c 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts @@ -3,9 +3,9 @@ import { SDKKinds } from '@bfc/shared'; -import { TriggerUIOption } from './TriggerOption'; +import { TriggerUIOptionMap } from './TriggerOption'; -export const builtinSchema: { [key: string]: TriggerUIOption } = { +export const builtinTriggerUISchema: TriggerUIOptionMap = { [SDKKinds.OnIntent]: { label: 'Intent recognized', }, From 7a6891c21ef29f8536dc119f3010209fb9834301 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 14 Oct 2020 20:18:30 +0800 Subject: [PATCH 17/45] remove duplicated $kinds --- .../TriggerCreationModal/schema/builtinSchema.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts index 3f6b82f41c..4932a2fb2f 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts @@ -34,14 +34,6 @@ export const builtinTriggerUISchema: TriggerUIOptionMap = { label: 'Dialog cancelled (Cancel dialog event)', submenu: 'Dialog events', }, - [SDKKinds.OnBeginDialog]: { - label: 'Dialog started (Begin dialog event)', - submenu: 'Dialog events', - }, - [SDKKinds.OnCancelDialog]: { - label: 'Dialog cancelled (Cancel dialog event)', - submenu: 'Dialog events', - }, [SDKKinds.OnError]: { label: 'Error occurred (Error event)', submenu: 'Dialog events', From ea575998a3ef2d20f003e4631267e37314750da0 Mon Sep 17 00:00:00 2001 From: zeye Date: Sat, 17 Oct 2020 14:44:34 +0800 Subject: [PATCH 18/45] link leaf ndoe to parent node --- .../TriggerCreationModal/TriggerOptionTree.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index 13eb5a4c74..6a2374f313 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -3,11 +3,12 @@ import formatMessage from 'format-message'; -import { TriggerSubmenuInfo, TriggerUIOptionMap } from './schema/TriggerOption'; +import { TriggerUIOptionMap } from './schema/TriggerOption'; -class TriggerOptionLeafNode { +export class TriggerOptionLeafNode { label: string; $kind: string; + parent: TriggerOptionGroupNode | null = null; constructor(label: string, $kind: string) { this.label = label; @@ -15,13 +16,14 @@ class TriggerOptionLeafNode { } } -export class TriggerOptionTree { +export class TriggerOptionGroupNode { label: string; /** Title of a dropdown. 'Which activity type?' */ prompt?: string; /** Placeholder of a dropdown input. 'Select an activity type' */ placeholder?: string; - children: (TriggerOptionLeafNode | TriggerOptionTree)[] = []; + children: (TriggerOptionLeafNode | TriggerOptionGroupNode)[] = []; + parent: TriggerOptionGroupNode | null = null; constructor(label: string, prompt?: string, placeholder?: string) { this.label = label; @@ -30,10 +32,14 @@ export class TriggerOptionTree { } } +export type TriggerOptionTree = TriggerOptionGroupNode; + +export type TriggerOptionTreeNode = TriggerOptionGroupNode | TriggerOptionLeafNode; + const getGroupKey = (submenu) => (typeof submenu === 'object' ? submenu.label : submenu || ''); export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): TriggerOptionTree => { - const root = new TriggerOptionTree( + const root = new TriggerOptionGroupNode( '', formatMessage('What is the type of this trigger?'), formatMessage('Select a trigger type') @@ -43,20 +49,22 @@ export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): .filter(([, options]) => !options.submenu) .map(([$kind, options]) => new TriggerOptionLeafNode(options.label, $kind)); root.children.push(...leafNodeList); + leafNodeList.forEach((leaf) => (leaf.parent = root)); const groups = Object.values(triggerUIOptions) .map((options) => options.submenu) .filter((submenu) => !!submenu) .reduce((result, submenu) => { const name = getGroupKey(submenu); - if (!result[name]) result[name] = new TriggerOptionTree(name, '', ''); + if (!result[name]) result[name] = new TriggerOptionGroupNode(name, '', ''); if (typeof submenu === 'object') { - const tree: TriggerOptionTree = result[name]; + const tree: TriggerOptionGroupNode = result[name]; tree.prompt = submenu.prompt; tree.placeholder = submenu.placeholder; + tree.parent = root; } return result; - }, {} as { [key: string]: TriggerOptionTree }); + }, {} as { [key: string]: TriggerOptionGroupNode }); Object.entries(triggerUIOptions) .filter(([, options]) => options.submenu) @@ -64,7 +72,9 @@ export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): const { label, submenu } = options; const node = new TriggerOptionLeafNode(label, $kind); const groupName = getGroupKey(submenu); - groups[groupName].children.push(node); + const groupParent = groups[groupName]; + groupParent.children.push(node); + node.parent = groupParent; }); root.children.push(...Object.values(groups)); return root; From 235b99d2e912cefdcda6440e5469e4bf9a5399d2 Mon Sep 17 00:00:00 2001 From: zeye Date: Sat, 17 Oct 2020 16:14:10 +0800 Subject: [PATCH 19/45] migrate to option tree --- .../TriggerDropdownGroup.tsx | 127 +++++++++--------- 1 file changed, 61 insertions(+), 66 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index e7bb92b85d..f5042e52a3 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -1,18 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { FC, useMemo, useState } from 'react'; -import formatMessage from 'format-message'; +import React, { FC, ReactNode, useMemo, useState } from 'react'; 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 { SDKKinds } from '@bfc/shared'; -import { activityTypeKey, dialogEventKey } from './constants'; import { dropdownStyles } from './styles'; -import { getEventOptions, getActivityOptions, checkTriggerOptions, getTriggerOptions } from './getDropdownOptions'; +import { checkTriggerOptions, getTriggerOptions } from './getDropdownOptions'; import { renderDropdownOption } from './TriggerCreationModal'; -import { generateTriggerOptionTree } from './TriggerOptionTree'; +import { + generateTriggerOptionTree, + TriggerOptionGroupNode, + TriggerOptionLeafNode, + TriggerOptionTreeNode, +} from './TriggerOptionTree'; import { builtinTriggerUISchema } from './schema/builtinSchema'; export interface TriggerDropwdownGroupProps { @@ -21,74 +24,66 @@ export interface TriggerDropwdownGroupProps { setTriggerType: (type: string) => void; } -export const TriggerDropdownGroup: FC = ({ - recognizerType, - triggerType, - setTriggerType, -}) => { - const eventTypes: IDropdownOption[] = getEventOptions(); - const activityTypes: IDropdownOption[] = getActivityOptions(); +export const TriggerDropdownGroup: FC = ({ recognizerType, setTriggerType }) => { const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(); // Mark out incompatible triggers of current recognizer checkTriggerOptions(triggerTypeOptions, recognizerType); - const [rootKey, setRootKey] = useState(triggerType); - const [childKey, setChildKey] = useState(''); - - const showEventDropDown = rootKey === dialogEventKey; - const showActivityDropDown = rootKey === activityTypeKey; - - const compoundTypes = [activityTypeKey, dialogEventKey]; - const onSelectTriggerType = (e, option) => { - const type = option.key; - setRootKey(type); - setChildKey(''); - const isCompound = compoundTypes.some((t) => type === t); - setTriggerType(isCompound ? '' : type); - }; - const onChangeChildKey = (e, option) => { - setChildKey(option.key); - setTriggerType(option.key); - }; - const triggerOptionTree = useMemo(() => { return generateTriggerOptionTree(builtinTriggerUISchema); }, []); + const [activeNode, setActiveNode] = useState(triggerOptionTree); + const onClickNode = (node: TriggerOptionTreeNode) => { + setActiveNode(node); + if (node instanceof TriggerOptionLeafNode) { + setTriggerType(node.$kind); + } else { + setTriggerType(''); + } + }; + + const getDropdownList = (activeNode: TriggerOptionTreeNode) => { + const treePath: TriggerOptionTreeNode[] = [activeNode]; + while (treePath[0].parent) { + treePath.unshift(treePath[0].parent); + } + + const dropdownList: ReactNode[] = []; + + const getKey = (x: TriggerOptionTreeNode) => (x instanceof TriggerOptionLeafNode ? x.$kind : x.label); + + for (let i = 0; i < treePath.length; i++) { + const currentNode: TriggerOptionTreeNode = treePath[i]; + if (currentNode instanceof TriggerOptionGroupNode) { + const nextNode = treePath[i + 1]; + const selectedKey = nextNode ? getKey(nextNode) : ''; + const dropdown = ( + { + return { + key: getKey(x), + text: x.label, + node: x, + }; + })} + placeholder={currentNode.placeholder} + selectedKey={selectedKey} + styles={dropdownStyles} + onChange={(e, opt: any) => { + onClickNode(opt.node); + }} + onRenderOption={renderDropdownOption} + /> + ); + dropdownList.push(dropdown); + } + } + + return dropdownList; + }; - return ( - - - {showEventDropDown && ( - - )} - {showActivityDropDown && ( - - )} - - ); + const dropdownList = getDropdownList(activeNode); + return {...dropdownList}; }; From 1f3824ebe4ff29b1821c60d22de2f32346fa917a Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 15:28:43 +0800 Subject: [PATCH 20/45] fix a React grammar --- .../components/TriggerCreationModal/TriggerDropdownGroup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index f5042e52a3..f055508550 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -85,5 +85,5 @@ export const TriggerDropdownGroup: FC = ({ recognize }; const dropdownList = getDropdownList(activeNode); - return {...dropdownList}; + return {dropdownList}; }; From f186639115c4fa9cfb16829fc11a10e0871abcfc Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 15:30:15 +0800 Subject: [PATCH 21/45] rename builtinSchema --- .../components/TriggerCreationModal/TriggerDropdownGroup.tsx | 2 +- .../schema/{builtinSchema.ts => builtinTriggerSchema.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Composer/packages/client/src/components/TriggerCreationModal/schema/{builtinSchema.ts => builtinTriggerSchema.ts} (100%) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index f055508550..18d85c67ff 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -16,7 +16,7 @@ import { TriggerOptionLeafNode, TriggerOptionTreeNode, } from './TriggerOptionTree'; -import { builtinTriggerUISchema } from './schema/builtinSchema'; +import { builtinTriggerUISchema } from './schema/builtinTriggerSchema'; export interface TriggerDropwdownGroupProps { recognizerType: SDKKinds | undefined; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinTriggerSchema.ts similarity index 100% rename from Composer/packages/client/src/components/TriggerCreationModal/schema/builtinSchema.ts rename to Composer/packages/client/src/components/TriggerCreationModal/schema/builtinTriggerSchema.ts From ba8013a5dcb6c4c5d7641e0156a6d9e97046b347 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 16:12:08 +0800 Subject: [PATCH 22/45] refactor the warning icon logic of trigger modal --- .../TriggerCreationModal.tsx | 14 +-------- .../TriggerDropdownGroup.tsx | 30 +++++++++++++------ .../checkRecognizerCompatibility.ts | 15 ++++++++++ 3 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx index 34f411d95a..34e9badbef 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerCreationModal.tsx @@ -7,8 +7,6 @@ import React, { useState } 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 { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; -import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { SDKKinds, RegexRecognizer } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; @@ -17,21 +15,11 @@ import { userSettingsState } from '../../recoilModel/atoms'; import { validateDialogSelectorFamily } from '../../recoilModel'; import { isRegExRecognizerType, resolveRecognizer$kind } from '../../utils/dialogValidator'; -import { optionStyles, dialogContentStyles, modalStyles, dialogWindowStyles, warningIconStyles } from './styles'; +import { dialogContentStyles, modalStyles, dialogWindowStyles } from './styles'; import { validateForm } from './validators'; import { resolveTriggerWidget } from './resolveTriggerWidget'; import { TriggerDropdownGroup } from './TriggerDropdownGroup'; -export const renderDropdownOption = (option?: IDropdownOption) => { - if (!option) return null; - return ( -
- {option.text} - {option.data && option.data.icon && } -
- ); -}; - const hasError = (errors: TriggerFormDataErrors) => Object.values(errors).some((msg) => !!msg); export const initialFormData: TriggerFormData = { diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 18d85c67ff..cf2198e5c3 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React, { FC, ReactNode, useMemo, useState } from 'react'; +/** @jsx jsx */ +import { jsx } from '@emotion/core'; +import { FC, ReactNode, useCallback, useMemo, useState } from 'react'; 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 { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; +import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { SDKKinds } from '@bfc/shared'; -import { dropdownStyles } from './styles'; -import { checkTriggerOptions, getTriggerOptions } from './getDropdownOptions'; -import { renderDropdownOption } from './TriggerCreationModal'; +import { dropdownStyles, optionStyles, warningIconStyles } from './styles'; import { generateTriggerOptionTree, TriggerOptionGroupNode, @@ -17,6 +18,7 @@ import { TriggerOptionTreeNode, } from './TriggerOptionTree'; import { builtinTriggerUISchema } from './schema/builtinTriggerSchema'; +import { checkRecognizerCompatibility } from './checkRecognizerCompatibility'; export interface TriggerDropwdownGroupProps { recognizerType: SDKKinds | undefined; @@ -25,14 +27,24 @@ export interface TriggerDropwdownGroupProps { } export const TriggerDropdownGroup: FC = ({ recognizerType, setTriggerType }) => { - const triggerTypeOptions: IDropdownOption[] = getTriggerOptions(); - - // Mark out incompatible triggers of current recognizer - checkTriggerOptions(triggerTypeOptions, recognizerType); + const renderDropdownOption = useCallback( + (option?: IDropdownOption) => { + if (!option) return null; + const compatible = checkRecognizerCompatibility(option.key as SDKKinds, recognizerType); + return ( +
+ {option.text} + {!compatible && } +
+ ); + }, + [recognizerType] + ); const triggerOptionTree = useMemo(() => { return generateTriggerOptionTree(builtinTriggerUISchema); }, []); + const [activeNode, setActiveNode] = useState(triggerOptionTree); const onClickNode = (node: TriggerOptionTreeNode) => { setActiveNode(node); diff --git a/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts b/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts new file mode 100644 index 0000000000..447cc3c3c3 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SDKKinds } from '@bfc/shared'; + +/** + * Returns 'false' if recognizer and trigger is not compatible. + */ +export const checkRecognizerCompatibility = (triggerType: SDKKinds, recognizerType?: SDKKinds): boolean => { + if (recognizerType === SDKKinds.RegexRecognizer) { + if (triggerType === SDKKinds.OnQnAMatch) return false; + if (triggerType === SDKKinds.OnChooseIntent) return false; + } + return true; +}; From 80cd8402839dfb1324d64f037cc876f3e7b27089 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 16:14:17 +0800 Subject: [PATCH 23/45] remove unreferenced utils --- .../TriggerCreationModal/constants.ts | 38 ------------------- .../getDropdownOptions.ts | 37 ------------------ 2 files changed, 75 deletions(-) delete mode 100644 Composer/packages/client/src/components/TriggerCreationModal/constants.ts delete mode 100644 Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts b/Composer/packages/client/src/components/TriggerCreationModal/constants.ts deleted file mode 100644 index 7cee6196fc..0000000000 --- a/Composer/packages/client/src/components/TriggerCreationModal/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; - -export const dialogEventKey = 'DialogEvent'; -export const activityTypeKey = 'Activities'; - -export const TriggerOptions: IDropdownOption[] = [ - { key: 'Microsoft.OnIntent', text: 'Intent recognized' }, - { key: 'Microsoft.OnQnAMatch', text: 'QnA Intent recognized' }, - { key: 'Microsoft.OnUnknownIntent', text: 'Unknown intent' }, - { key: dialogEventKey, text: 'Dialog events' }, - { key: activityTypeKey, text: 'Activities' }, - { key: 'Microsoft.OnChooseIntent', text: 'Duplicated intents recognized' }, - { key: 'Microsoft.OnDialogEvent', text: 'Custom events' }, -]; - -export const ActivityOptions: IDropdownOption[] = [ - { key: 'Microsoft.OnActivity', text: 'Activities (Activity received)' }, - { key: 'Microsoft.OnConversationUpdateActivity', text: 'Greeting (ConversationUpdate activity)' }, - { key: 'Microsoft.OnEndOfConversationActivity', text: 'Conversation ended (EndOfConversation activity)' }, - { key: 'Microsoft.OnEventActivity', text: 'Event received (Event activity)' }, - { key: 'Microsoft.OnHandoffActivity', text: 'Handover to human (Handoff activity)' }, - { key: 'Microsoft.OnInvokeActivity', text: 'Conversation invoked (Invoke activity)' }, - { key: 'Microsoft.OnTypingActivity', text: 'User is typing (Typing activity)' }, - { key: 'Microsoft.OnMessageActivity', text: 'Message received (Message received activity)' }, - { key: 'Microsoft.OnMessageDeleteActivity', text: 'Message deleted (Message deleted activity)' }, - { key: 'Microsoft.OnMessageReactionActivity', text: 'Message reaction (Message reaction activity)' }, - { key: 'Microsoft.OnMessageUpdateActivity', text: 'Message updated (Message updated activity)' }, -]; - -export const EventOptions: IDropdownOption[] = [ - { key: 'Microsoft.OnBeginDialog', text: 'Dialog started (Begin dialog event)' }, - { key: 'Microsoft.OnCancelDialog', text: 'Dialog cancelled (Cancel dialog event)' }, - { key: 'Microsoft.OnError', text: 'Error occurred (Error event)' }, - { key: 'Microsoft.OnRepromptDialog', text: 'Re-prompt for input (Reprompt dialog event)' }, -]; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts b/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts deleted file mode 100644 index e44f3c4cd2..0000000000 --- a/Composer/packages/client/src/components/TriggerCreationModal/getDropdownOptions.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; -import cloneDeep from 'lodash/cloneDeep'; -import { SDKKinds } from '@bfc/shared'; - -import { TriggerOptions, ActivityOptions, EventOptions } from './constants'; - -export function getTriggerOptions(): IDropdownOption[] { - return cloneDeep(TriggerOptions); -} - -export const getActivityOptions = (): IDropdownOption[] => { - return cloneDeep(ActivityOptions); -}; - -export const getEventOptions = (): IDropdownOption[] => { - return cloneDeep(EventOptions); -}; - -export const checkTriggerOptions = ( - triggerTypeOptions: IDropdownOption[], - recognizerType?: SDKKinds -): IDropdownOption[] => { - if (recognizerType === SDKKinds.RegexRecognizer) { - const qnaMatcherOption = triggerTypeOptions.find((t) => t.key === SDKKinds.OnQnAMatch); - if (qnaMatcherOption) { - qnaMatcherOption.data = { icon: 'Warning' }; - } - const onChooseIntentOption = triggerTypeOptions.find((t) => t.key === SDKKinds.OnChooseIntent); - if (onChooseIntentOption) { - onChooseIntentOption.data = { icon: 'Warning' }; - } - } - return triggerTypeOptions; -}; From a049f217e0ce26560c9f4db1e1880c44bc21f23d Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 16:18:46 +0800 Subject: [PATCH 24/45] add a todo --- .../TriggerCreationModal/checkRecognizerCompatibility.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts b/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts index 447cc3c3c3..0ebccac3e4 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/checkRecognizerCompatibility.ts @@ -3,6 +3,7 @@ import { SDKKinds } from '@bfc/shared'; +// TODO (zeye): define triggers compatibility in sdk schema /** * Returns 'false' if recognizer and trigger is not compatible. */ From b6d59789f4a453ebc80ecdd38aff6f0df819c9b6 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 16:50:46 +0800 Subject: [PATCH 25/45] fix UT by adding data-testid --- .../client/__tests__/components/triggerCreationModal.test.tsx | 2 +- .../components/TriggerCreationModal/TriggerDropdownGroup.tsx | 1 + .../src/components/TriggerCreationModal/TriggerOptionTree.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx b/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx index e733c3b3e0..f235fb77df 100644 --- a/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx +++ b/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx @@ -30,7 +30,7 @@ describe('', () => { expect(component.container).toBeDefined(); }); - it('hould create a Luis Intent recognized', async () => { + it('should create a Luis Intent recognized', async () => { const component = renderComponent(); const triggerType = component.getByTestId('triggerTypeDropDown'); fireEvent.click(triggerType); diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index cf2198e5c3..16d0c3b3e0 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -72,6 +72,7 @@ export const TriggerDropdownGroup: FC = ({ recognize const selectedKey = nextNode ? getKey(nextNode) : ''; const dropdown = ( { return { diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index 6a2374f313..88db642638 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -40,7 +40,7 @@ const getGroupKey = (submenu) => (typeof submenu === 'object' ? submenu.label : export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): TriggerOptionTree => { const root = new TriggerOptionGroupNode( - '', + 'triggerTypeDropDown', formatMessage('What is the type of this trigger?'), formatMessage('Select a trigger type') ); From 23dd5b87fa4504970fcdc584f6517e608bef5e50 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 17:33:42 +0800 Subject: [PATCH 26/45] declare TriggerUISchema in extension --- .../extension-client/src/types/extension.ts | 2 + .../extension-client/src/types/index.ts | 1 + .../src/types/triggerSchema.ts | 17 ++++ .../composer/src/defaultTriggerSchema.ts | 93 +++++++++++++++++++ .../packages/ui-plugins/composer/src/index.ts | 12 ++- 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 Composer/packages/extension-client/src/types/triggerSchema.ts create mode 100644 Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts diff --git a/Composer/packages/extension-client/src/types/extension.ts b/Composer/packages/extension-client/src/types/extension.ts index f7d7705cf5..99172af739 100644 --- a/Composer/packages/extension-client/src/types/extension.ts +++ b/Composer/packages/extension-client/src/types/extension.ts @@ -6,6 +6,7 @@ import { SDKKinds } from '@botframework-composer/types'; import { UIOptions } from './formSchema'; import { FlowEditorWidgetMap, FlowWidget } from './flowSchema'; import { MenuOptions } from './menuSchema'; +import { TriggerUIOption } from './triggerSchema'; import { RecognizerOptions } from './recognizerSchema'; import { FieldWidget } from './form'; @@ -22,6 +23,7 @@ export type UISchema = { flow?: FlowWidget; form?: UIOptions; menu?: MenuOptions | MenuOptions[]; + trigger?: TriggerUIOption; recognizer?: RecognizerOptions; }; }; diff --git a/Composer/packages/extension-client/src/types/index.ts b/Composer/packages/extension-client/src/types/index.ts index bc071a2148..e84e4bc608 100644 --- a/Composer/packages/extension-client/src/types/index.ts +++ b/Composer/packages/extension-client/src/types/index.ts @@ -6,5 +6,6 @@ export * from './form'; export * from './formSchema'; export * from './flowSchema'; export * from './menuSchema'; +export * from './triggerSchema'; export * from './recognizerSchema'; export * from './pluginType'; diff --git a/Composer/packages/extension-client/src/types/triggerSchema.ts b/Composer/packages/extension-client/src/types/triggerSchema.ts new file mode 100644 index 0000000000..1b128c37ed --- /dev/null +++ b/Composer/packages/extension-client/src/types/triggerSchema.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SDKKinds } from '@botframework-composer/types'; + +export interface TriggerUIOption { + label: string; + submenu?: TriggerSubmenuInfo | string | false; +} + +export interface TriggerSubmenuInfo { + label: string; + prompt?: string; + placeholder?: string; +} + +export type TriggerUISchema = { [key in SDKKinds]?: TriggerUIOption }; diff --git a/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts b/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts new file mode 100644 index 0000000000..bc291349bb --- /dev/null +++ b/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { TriggerUISchema } from '@bfc/extension-client'; +import { SDKKinds } from '@bfc/shared'; + +export const DefaultTriggerSchema: TriggerUISchema = { + [SDKKinds.OnIntent]: { + label: 'Intent recognized', + }, + [SDKKinds.OnQnAMatch]: { + label: 'QnA Intent recognized', + }, + [SDKKinds.OnUnknownIntent]: { + label: 'Unknown intent', + }, + [SDKKinds.OnChooseIntent]: { + label: 'Duplicated intents recognized', + }, + [SDKKinds.OnDialogEvent]: { + label: 'Custom events', + }, + // Subgroup - Dialog events + [SDKKinds.OnBeginDialog]: { + label: 'Dialog started (Begin dialog event)', + submenu: { + label: 'Dialog events', + prompt: 'Which event?', + placeholder: 'Select an event type', + }, + }, + [SDKKinds.OnCancelDialog]: { + label: 'Dialog cancelled (Cancel dialog event)', + submenu: 'Dialog events', + }, + [SDKKinds.OnError]: { + label: 'Error occurred (Error event)', + submenu: 'Dialog events', + }, + [SDKKinds.OnRepromptDialog]: { + label: 'Re-prompt for input (Reprompt dialog event)', + submenu: 'Dialog events', + }, + // Subgroup - Activities + [SDKKinds.OnActivity]: { + label: 'Activities (Activity received)', + submenu: { + label: 'Activities', + prompt: 'Which activity type?', + placeholder: 'Select an activity type', + }, + }, + [SDKKinds.OnConversationUpdateActivity]: { + label: 'Greeting (ConversationUpdate activity)', + submenu: 'Activities', + }, + [SDKKinds.OnEndOfConversationActivity]: { + label: 'Conversation ended (EndOfConversation activity)', + submenu: 'Activities', + }, + [SDKKinds.OnEventActivity]: { + label: 'Event received (Event activity)', + submenu: 'Activities', + }, + [SDKKinds.OnHandoffActivity]: { + label: 'Handover to human (Handoff activity)', + submenu: 'Activities', + }, + [SDKKinds.OnInvokeActivity]: { + label: 'Conversation invoked (Invoke activity)', + submenu: 'Activities', + }, + [SDKKinds.OnTypingActivity]: { + label: 'User is typing (Typing activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageActivity]: { + label: 'Message received (Message received activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageDeleteActivity]: { + label: 'Message deleted (Message deleted activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageReactionActivity]: { + label: 'Message reaction (Message reaction activity)', + submenu: 'Activities', + }, + [SDKKinds.OnMessageUpdateActivity]: { + label: 'Message updated (Message updated activity)', + submenu: 'Activities', + }, +}; diff --git a/Composer/packages/ui-plugins/composer/src/index.ts b/Composer/packages/ui-plugins/composer/src/index.ts index 43d5faff27..2bf304f9bb 100644 --- a/Composer/packages/ui-plugins/composer/src/index.ts +++ b/Composer/packages/ui-plugins/composer/src/index.ts @@ -9,6 +9,7 @@ import { MenuUISchema, FlowUISchema, RecognizerUISchema, + TriggerUISchema, } from '@bfc/extension-client'; import { SDKKinds } from '@bfc/shared'; import formatMessage from 'format-message'; @@ -17,6 +18,7 @@ import { IntentField, RecognizerField, QnAActionsField } from '@bfc/adaptive-for import { DefaultMenuSchema } from './defaultMenuSchema'; import { DefaultFlowSchema } from './defaultFlowSchema'; import { DefaultRecognizerSchema } from './defaultRecognizerSchema'; +import { DefaultTriggerSchema } from './defaultTriggerSchema'; const DefaultFormSchema: FormUISchema = { [SDKKinds.AdaptiveDialog]: { @@ -173,18 +175,26 @@ const synthesizeUISchema = ( formSchema: FormUISchema, menuSchema: MenuUISchema, flowSchema: FlowUISchema, + triggerSchema: TriggerUISchema, recognizerSchema: RecognizerUISchema ): UISchema => { let uischema: UISchema = {}; uischema = mergeWith(uischema, formSchema, (origin, formOption) => ({ ...origin, form: formOption })); uischema = mergeWith(uischema, menuSchema, (origin, menuOption) => ({ ...origin, menu: menuOption })); uischema = mergeWith(uischema, flowSchema, (origin, flowOption) => ({ ...origin, flow: flowOption })); + uischema = mergeWith(uischema, triggerSchema, (origin, triggerOption) => ({ ...origin, trigger: triggerOption })); uischema = mergeWith(uischema, recognizerSchema, (origin, opt) => ({ ...origin, recognizer: opt })); return uischema; }; const config: PluginConfig = { - uiSchema: synthesizeUISchema(DefaultFormSchema, DefaultMenuSchema, DefaultFlowSchema, DefaultRecognizerSchema), + uiSchema: synthesizeUISchema( + DefaultFormSchema, + DefaultMenuSchema, + DefaultFlowSchema, + DefaultTriggerSchema, + DefaultRecognizerSchema + ), }; export default config; From 4d703071702180bf53bfe78dbb041eb4ce5a8467 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 18:04:50 +0800 Subject: [PATCH 27/45] use trigger uischema from extension context --- .../TriggerDropdownGroup.tsx | 5 ++-- .../client/src/pages/design/DesignPage.tsx | 16 +++++++------ .../extension-client/src/hooks/index.ts | 1 + .../src/hooks/useTriggerConfig.ts | 23 +++++++++++++++++++ 4 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 Composer/packages/extension-client/src/hooks/useTriggerConfig.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 16d0c3b3e0..8df4c929d7 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -9,6 +9,7 @@ import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { SDKKinds } from '@bfc/shared'; +import { useTriggerConfig } from '@bfc/extension-client'; import { dropdownStyles, optionStyles, warningIconStyles } from './styles'; import { @@ -17,7 +18,6 @@ import { TriggerOptionLeafNode, TriggerOptionTreeNode, } from './TriggerOptionTree'; -import { builtinTriggerUISchema } from './schema/builtinTriggerSchema'; import { checkRecognizerCompatibility } from './checkRecognizerCompatibility'; export interface TriggerDropwdownGroupProps { @@ -41,8 +41,9 @@ export const TriggerDropdownGroup: FC = ({ recognize [recognizerType] ); + const triggerUISchema = useTriggerConfig(); const triggerOptionTree = useMemo(() => { - return generateTriggerOptionTree(builtinTriggerUISchema); + return generateTriggerOptionTree(triggerUISchema); }, []); const [activeNode, setActiveNode] = useState(triggerOptionTree); diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 161fc2cba4..2142ecb490 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -678,13 +678,15 @@ const DesignPage: React.FC )} {triggerModalVisible && ( - + + + )} ) {displaySkillManifest && ( diff --git a/Composer/packages/extension-client/src/hooks/index.ts b/Composer/packages/extension-client/src/hooks/index.ts index 423f77167f..5dfabec327 100644 --- a/Composer/packages/extension-client/src/hooks/index.ts +++ b/Composer/packages/extension-client/src/hooks/index.ts @@ -4,6 +4,7 @@ export * from './useDialogApi'; export * from './useFlowConfig'; export * from './useFormConfig'; export * from './useMenuConfig'; +export * from './useTriggerConfig'; export * from './useRecognizerConfig'; export * from './useProjectApi'; export * from './useShellApi'; diff --git a/Composer/packages/extension-client/src/hooks/useTriggerConfig.ts b/Composer/packages/extension-client/src/hooks/useTriggerConfig.ts new file mode 100644 index 0000000000..9c6e203a43 --- /dev/null +++ b/Composer/packages/extension-client/src/hooks/useTriggerConfig.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { useContext, useMemo } from 'react'; + +import { EditorExtensionContext } from '../EditorExtensionContext'; +import { TriggerUISchema } from '../types'; + +export function useTriggerConfig() { + const { plugins } = useContext(EditorExtensionContext); + + const triggerConfig: TriggerUISchema = useMemo(() => { + const implementedTriggerSchema: TriggerUISchema = {}; + Object.entries(plugins.uiSchema ?? {}).forEach(([$kind, options]) => { + if (options?.trigger) { + implementedTriggerSchema[$kind] = options.trigger; + } + }); + return implementedTriggerSchema; + }, [plugins.uiSchema]); + + return triggerConfig; +} From f3a76a41f9bfc7563e1e68308f33e3d39ab7090c Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 18:28:26 +0800 Subject: [PATCH 28/45] check trigger option existence --- .../TriggerCreationModal/TriggerOptionTree.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index 88db642638..ce0e72fb7b 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { TriggerUISchema } from '@bfc/extension-client'; import formatMessage from 'format-message'; -import { TriggerUIOptionMap } from './schema/TriggerOption'; +import { TriggerUIOption } from './schema/TriggerOption'; export class TriggerOptionLeafNode { label: string; @@ -38,7 +39,7 @@ export type TriggerOptionTreeNode = TriggerOptionGroupNode | TriggerOptionLeafNo const getGroupKey = (submenu) => (typeof submenu === 'object' ? submenu.label : submenu || ''); -export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): TriggerOptionTree => { +export const generateTriggerOptionTree = (triggerUIOptions: TriggerUISchema): TriggerOptionTree => { const root = new TriggerOptionGroupNode( 'triggerTypeDropDown', formatMessage('What is the type of this trigger?'), @@ -46,13 +47,13 @@ export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): ); const leafNodeList = Object.entries(triggerUIOptions) - .filter(([, options]) => !options.submenu) - .map(([$kind, options]) => new TriggerOptionLeafNode(options.label, $kind)); + .filter(([, options]) => options && !options.submenu) + .map(([$kind, options]) => new TriggerOptionLeafNode(options?.label ?? '', $kind)); root.children.push(...leafNodeList); leafNodeList.forEach((leaf) => (leaf.parent = root)); const groups = Object.values(triggerUIOptions) - .map((options) => options.submenu) + .map((options) => options && options.submenu) .filter((submenu) => !!submenu) .reduce((result, submenu) => { const name = getGroupKey(submenu); @@ -67,9 +68,9 @@ export const generateTriggerOptionTree = (triggerUIOptions: TriggerUIOptionMap): }, {} as { [key: string]: TriggerOptionGroupNode }); Object.entries(triggerUIOptions) - .filter(([, options]) => options.submenu) + .filter(([, options]) => options && options.submenu) .forEach(([$kind, options]) => { - const { label, submenu } = options; + const { label, submenu } = options as TriggerUIOption; const node = new TriggerOptionLeafNode(label, $kind); const groupName = getGroupKey(submenu); const groupParent = groups[groupName]; From 2e8cf9b1dda666be64e1210273b6947f5abf5122 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 19 Oct 2020 18:28:55 +0800 Subject: [PATCH 29/45] sort trigger dropdown labels --- .../TriggerDropdownGroup.tsx | 21 +++-- .../schema/builtinTriggerSchema.ts | 94 ------------------- .../schema/defaultTriggerOrder.ts | 17 ++++ 3 files changed, 31 insertions(+), 101 deletions(-) delete mode 100644 Composer/packages/client/src/components/TriggerCreationModal/schema/builtinTriggerSchema.ts create mode 100644 Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 8df4c929d7..8386233da9 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -19,6 +19,7 @@ import { TriggerOptionTreeNode, } from './TriggerOptionTree'; import { checkRecognizerCompatibility } from './checkRecognizerCompatibility'; +import { triggerOrderMap } from './schema/defaultTriggerOrder'; export interface TriggerDropwdownGroupProps { recognizerType: SDKKinds | undefined; @@ -26,6 +27,10 @@ export interface TriggerDropwdownGroupProps { setTriggerType: (type: string) => void; } +const getTriggerLabelOrder = (label: string) => { + return triggerOrderMap[label] ?? Number.MAX_VALUE; +}; + export const TriggerDropdownGroup: FC = ({ recognizerType, setTriggerType }) => { const renderDropdownOption = useCallback( (option?: IDropdownOption) => { @@ -75,13 +80,15 @@ export const TriggerDropdownGroup: FC = ({ recognize { - return { - key: getKey(x), - text: x.label, - node: x, - }; - })} + options={currentNode.children + .map((x) => { + return { + key: getKey(x), + text: x.label, + node: x, + }; + }) + .sort((opt1, opt2) => getTriggerLabelOrder(opt1.text) - getTriggerLabelOrder(opt2.text))} placeholder={currentNode.placeholder} selectedKey={selectedKey} styles={dropdownStyles} diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinTriggerSchema.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinTriggerSchema.ts deleted file mode 100644 index 4932a2fb2f..0000000000 --- a/Composer/packages/client/src/components/TriggerCreationModal/schema/builtinTriggerSchema.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { SDKKinds } from '@bfc/shared'; - -import { TriggerUIOptionMap } from './TriggerOption'; - -export const builtinTriggerUISchema: TriggerUIOptionMap = { - [SDKKinds.OnIntent]: { - label: 'Intent recognized', - }, - [SDKKinds.OnQnAMatch]: { - label: 'QnA Intent recognized', - }, - [SDKKinds.OnUnknownIntent]: { - label: 'Unknown intent', - }, - [SDKKinds.OnChooseIntent]: { - label: 'Duplicated intents recognized', - }, - [SDKKinds.OnDialogEvent]: { - label: 'Custom events', - }, - // Subgroup - Dialog events - [SDKKinds.OnBeginDialog]: { - label: 'Dialog started (Begin dialog event)', - submenu: { - label: 'Dialog events', - prompt: 'Which event?', - placeholder: 'Select an event type', - }, - }, - [SDKKinds.OnCancelDialog]: { - label: 'Dialog cancelled (Cancel dialog event)', - submenu: 'Dialog events', - }, - [SDKKinds.OnError]: { - label: 'Error occurred (Error event)', - submenu: 'Dialog events', - }, - [SDKKinds.OnRepromptDialog]: { - label: 'Re-prompt for input (Reprompt dialog event)', - submenu: 'Dialog events', - }, - // Subgroup - Activities - [SDKKinds.OnActivity]: { - label: 'Activities (Activity received)', - submenu: { - label: 'Activities', - prompt: 'Which activity type?', - placeholder: 'Select an activity type', - }, - }, - [SDKKinds.OnConversationUpdateActivity]: { - label: 'Greeting (ConversationUpdate activity)', - submenu: 'Activities', - }, - [SDKKinds.OnEndOfConversationActivity]: { - label: 'Conversation ended (EndOfConversation activity)', - submenu: 'Activities', - }, - [SDKKinds.OnEventActivity]: { - label: 'Event received (Event activity)', - submenu: 'Activities', - }, - [SDKKinds.OnHandoffActivity]: { - label: 'Handover to human (Handoff activity)', - submenu: 'Activities', - }, - [SDKKinds.OnInvokeActivity]: { - label: 'Conversation invoked (Invoke activity)', - submenu: 'Activities', - }, - [SDKKinds.OnTypingActivity]: { - label: 'User is typing (Typing activity)', - submenu: 'Activities', - }, - [SDKKinds.OnMessageActivity]: { - label: 'Message received (Message received activity)', - submenu: 'Activities', - }, - [SDKKinds.OnMessageDeleteActivity]: { - label: 'Message deleted (Message deleted activity)', - submenu: 'Activities', - }, - [SDKKinds.OnMessageReactionActivity]: { - label: 'Message reaction (Message reaction activity)', - submenu: 'Activities', - }, - [SDKKinds.OnMessageUpdateActivity]: { - label: 'Message updated (Message updated activity)', - submenu: 'Activities', - }, -}; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts new file mode 100644 index 0000000000..453921edc2 --- /dev/null +++ b/Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const defaultTriggerOrder = [ + 'Intent recognized', + 'QnA Intent recognized', + 'Unknown intent', + 'Dialog events', + 'Activities', + 'Duplicated intents recognized', + 'Custom events', +]; + +export const triggerOrderMap: { [label: string]: number } = defaultTriggerOrder.reduce((result, label, index) => { + result[label] = index; + return result; +}, {}); From 7f918b7c64d4206de6617d98ea18ea716f57cdc5 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 21 Oct 2020 13:49:37 +0800 Subject: [PATCH 30/45] move root text out of tree utils --- .../TriggerCreationModal/TriggerDropdownGroup.tsx | 7 ++++++- .../TriggerCreationModal/TriggerOptionTree.ts | 13 ++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 8386233da9..d1a1a74639 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -10,6 +10,7 @@ import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { SDKKinds } from '@bfc/shared'; import { useTriggerConfig } from '@bfc/extension-client'; +import formatMessage from 'format-message'; import { dropdownStyles, optionStyles, warningIconStyles } from './styles'; import { @@ -48,7 +49,11 @@ export const TriggerDropdownGroup: FC = ({ recognize const triggerUISchema = useTriggerConfig(); const triggerOptionTree = useMemo(() => { - return generateTriggerOptionTree(triggerUISchema); + return generateTriggerOptionTree( + triggerUISchema, + formatMessage('What is the type of this trigger?'), + formatMessage('Select a trigger type') + ); }, []); const [activeNode, setActiveNode] = useState(triggerOptionTree); diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index ce0e72fb7b..7d9d45efdd 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { TriggerUISchema } from '@bfc/extension-client'; -import formatMessage from 'format-message'; import { TriggerUIOption } from './schema/TriggerOption'; @@ -39,12 +38,12 @@ export type TriggerOptionTreeNode = TriggerOptionGroupNode | TriggerOptionLeafNo const getGroupKey = (submenu) => (typeof submenu === 'object' ? submenu.label : submenu || ''); -export const generateTriggerOptionTree = (triggerUIOptions: TriggerUISchema): TriggerOptionTree => { - const root = new TriggerOptionGroupNode( - 'triggerTypeDropDown', - formatMessage('What is the type of this trigger?'), - formatMessage('Select a trigger type') - ); +export const generateTriggerOptionTree = ( + triggerUIOptions: TriggerUISchema, + rootPrompt = '', + rootPlaceHolder = '' +): TriggerOptionTree => { + const root = new TriggerOptionGroupNode('triggerTypeDropDown', rootPrompt, rootPlaceHolder); const leafNodeList = Object.entries(triggerUIOptions) .filter(([, options]) => options && !options.submenu) From 2aceb8263a9f53fc985aebea935715e4e53e851b Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 21 Oct 2020 14:04:49 +0800 Subject: [PATCH 31/45] pass in option compare fn --- .../TriggerDropdownGroup.tsx | 19 +++++++++---------- .../TriggerCreationModal/TriggerOptionTree.ts | 12 ++++++++++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index d1a1a74639..2ea2a75189 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -52,7 +52,8 @@ export const TriggerDropdownGroup: FC = ({ recognize return generateTriggerOptionTree( triggerUISchema, formatMessage('What is the type of this trigger?'), - formatMessage('Select a trigger type') + formatMessage('Select a trigger type'), + (opt1, opt2) => getTriggerLabelOrder(opt1.label) - getTriggerLabelOrder(opt2.label) ); }, []); @@ -85,15 +86,13 @@ export const TriggerDropdownGroup: FC = ({ recognize { - return { - key: getKey(x), - text: x.label, - node: x, - }; - }) - .sort((opt1, opt2) => getTriggerLabelOrder(opt1.text) - getTriggerLabelOrder(opt2.text))} + options={currentNode.children.map((x) => { + return { + key: getKey(x), + text: x.label, + node: x, + }; + })} placeholder={currentNode.placeholder} selectedKey={selectedKey} styles={dropdownStyles} diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index 7d9d45efdd..3ff5996913 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -40,8 +40,9 @@ const getGroupKey = (submenu) => (typeof submenu === 'object' ? submenu.label : export const generateTriggerOptionTree = ( triggerUIOptions: TriggerUISchema, - rootPrompt = '', - rootPlaceHolder = '' + rootPrompt: string, + rootPlaceHolder: string, + optionCompareFn?: (a: TriggerOptionTreeNode, b: TriggerOptionTreeNode) => number ): TriggerOptionTree => { const root = new TriggerOptionGroupNode('triggerTypeDropDown', rootPrompt, rootPlaceHolder); @@ -77,5 +78,12 @@ export const generateTriggerOptionTree = ( node.parent = groupParent; }); root.children.push(...Object.values(groups)); + + // sort tree nodes + if (optionCompareFn) { + root.children.sort(optionCompareFn); + Object.values(groups).forEach((x) => x.children.sort(optionCompareFn)); + } + return root; }; From 4718f6f6792e5d0fff8f16bf7acb88bb4f2f519f Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 21 Oct 2020 14:20:02 +0800 Subject: [PATCH 32/45] add UT for triggerOptionTree --- .../triggerCreationModal.test.tsx | 4 +- .../triggerOptionTree.test.ts | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) rename Composer/packages/client/__tests__/components/{ => TriggerCreationModal}/triggerCreationModal.test.tsx (92%) create mode 100644 Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts diff --git a/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx similarity index 92% rename from Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx rename to Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx index f235fb77df..de0be459be 100644 --- a/Composer/packages/client/__tests__/components/triggerCreationModal.test.tsx +++ b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx @@ -4,8 +4,8 @@ import * as React from 'react'; import { fireEvent, waitFor } from '@botframework-composer/test-utils'; -import { TriggerCreationModal } from '../../src/components/TriggerCreationModal'; -import { renderWithRecoil } from '../testUtils'; +import { TriggerCreationModal } from '../../../src/components/TriggerCreationModal'; +import { renderWithRecoil } from '../../testUtils'; const projectId = '123a-bv3c4'; diff --git a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts new file mode 100644 index 0000000000..91fc7fa894 --- /dev/null +++ b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { TriggerUISchema } from '@bfc/extension-client'; +import { SDKKinds } from '@botframework-composer/types'; + +import { + generateTriggerOptionTree, + TriggerOptionGroupNode, +} from '../../../src/components/TriggerCreationModal/TriggerOptionTree'; + +const labelAlphabeticalCompareFn = (opt1, opt2) => (opt1.label < opt2.label ? -1 : 1); + +describe('generateTriggerOptionTree()', () => { + it('can generate one layer tree.', () => { + const simpleTriggerUIOptions: TriggerUISchema = { + [SDKKinds.OnIntent]: { + label: '1.OnIntent', + }, + [SDKKinds.OnInvokeActivity]: { + label: '2.OnInvokeActivity', + }, + }; + const tree = generateTriggerOptionTree( + simpleTriggerUIOptions, + 'Select a trigger', + 'Which trigger?', + labelAlphabeticalCompareFn + ); + + expect(tree.prompt).toEqual('Select a trigger'); + expect(tree.placeholder).toEqual('Which trigger?'); + + expect(tree.parent).toBeNull(); + expect(tree.children.length).toEqual(2); + + expect(tree.children[0].label).toEqual('1.OnIntent'); + expect(tree.children[0].parent).toEqual(tree); + + expect(tree.children[1].label).toEqual('2.OnInvokeActivity'); + expect(tree.children[1].parent).toEqual(tree); + }); + + it('can generate tree with submenu.', () => { + const advancedTriggerUIOptions: TriggerUISchema = { + [SDKKinds.OnIntent]: { + label: '1.OnIntent', + }, + [SDKKinds.OnTypingActivity]: { + label: '2.1.OnTypingActivity', + submenu: { + label: '2.Activities', + prompt: 'Select an activity trigger', + placeholder: 'Which activity?', + }, + }, + [SDKKinds.OnEventActivity]: { + label: '2.2OnEventActivity', + submenu: '2.Activities', + }, + [SDKKinds.OnInvokeActivity]: { + label: '2.3OnInvokeActivity', + submenu: '2.Activities', + }, + }; + const tree = generateTriggerOptionTree( + advancedTriggerUIOptions, + 'Select a trigger', + 'Which trigger?', + labelAlphabeticalCompareFn + ); + + expect(tree.children.length).toEqual(2); + + expect(tree.children[0].label).toEqual('1.OnIntent'); + expect(tree.children[0].parent).toEqual(tree); + + const secondChild = tree.children[1] as TriggerOptionGroupNode; + expect(secondChild.label).toEqual('2.Activities'); + expect(secondChild.prompt).toEqual('Select an activity trigger'); + expect(secondChild.children.length).toEqual(3); + + expect(secondChild.children[0].label).toEqual('2.1.OnTypingActivity'); + expect(secondChild.children[0].parent).toEqual(secondChild); + }); +}); From 66c6c37a689378fc9f8bf83c18f2dfc677dfaaa3 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 18 Nov 2020 12:13:21 +0800 Subject: [PATCH 33/45] align icon size with main --- .../client/src/components/TriggerCreationModal/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts index 5ef9d61c92..c14e6f6882 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts @@ -57,5 +57,5 @@ export const optionStyles = { export const warningIconStyles = { marginLeft: 5, color: '#BE880A', - fontSize: 15, + fontSize: 12, }; From d85f49fa3e1911bcd33cefaca971d4028734dd15 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 18 Nov 2020 15:50:27 +0800 Subject: [PATCH 34/45] migrate 1.2 PVA logic --- .../TriggerCreationModal/resolveTriggerWidget.tsx | 6 +++--- Composer/packages/client/src/utils/dialogValidator.ts | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx index 9221eee487..d48f08091b 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx @@ -10,7 +10,7 @@ import { UserSettings, DialogInfo, SDKKinds } from '@bfc/shared'; import { LuEditor, inlineModePlaceholder } from '@bfc/code-editor'; import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil'; -import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; +import { isRegExRecognizerType, isLUISnQnARecognizerType, isPVARecognizerType } from '../../utils/dialogValidator'; import { intentStyles } from './styles'; import { validateEventName, validateIntentName, getLuDiagnostics, validateRegExPattern } from './validators'; @@ -25,8 +25,8 @@ export function resolveTriggerWidget( dialogId: string ) { const isRegEx = isRegExRecognizerType(dialogFile); - const isLUISnQnA = isLUISnQnARecognizerType(dialogFile); - const showTriggerPhrase = selectedType === SDKKinds.OnIntent && isLUISnQnA; + const isLUISnQnA = isLUISnQnARecognizerType(dialogFile) || isPVARecognizerType(dialogFile); + const showTriggerPhrase = selectedType === SDKKinds.OnIntent && !isRegEx; const onNameChange = (e, name) => { const errors: TriggerFormDataErrors = {}; diff --git a/Composer/packages/client/src/utils/dialogValidator.ts b/Composer/packages/client/src/utils/dialogValidator.ts index c0381a9e97..af7b2660da 100644 --- a/Composer/packages/client/src/utils/dialogValidator.ts +++ b/Composer/packages/client/src/utils/dialogValidator.ts @@ -24,6 +24,11 @@ export const isRegExRecognizerType = (dialog: DialogInfo | undefined) => { return get(dialog, 'content.recognizer.$kind', '') === SDKKinds.RegexRecognizer; }; +export const isPVARecognizerType = (dialog: DialogInfo | undefined) => { + if (!dialog) return false; + return get(dialog, 'content.recognizer.$kind', '') === 'Microsoft.VirtualAgents.Recognizer'; +}; + export const isLUISnQnARecognizerType = (dialog: DialogInfo | undefined) => { if (!dialog) return false; const recognizer = get(dialog, 'content.recognizer', ''); From 9b92c0a3b4be18d15cb6708b22b6fca4f6e710ad Mon Sep 17 00:00:00 2001 From: zeye <2295905420@qq.com> Date: Wed, 18 Nov 2020 16:35:09 +0800 Subject: [PATCH 35/45] use 'Boolean' to filter trigger menus Co-authored-by: Andy Brown --- .../src/components/TriggerCreationModal/TriggerOptionTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index 3ff5996913..2a436996c1 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -54,7 +54,7 @@ export const generateTriggerOptionTree = ( const groups = Object.values(triggerUIOptions) .map((options) => options && options.submenu) - .filter((submenu) => !!submenu) + .filter(Boolean) .reduce((result, submenu) => { const name = getGroupKey(submenu); if (!result[name]) result[name] = new TriggerOptionGroupNode(name, '', ''); From e844fa33ddcdedae2e2de09ef0e20fe9269eacb4 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 18 Nov 2020 16:50:12 +0800 Subject: [PATCH 36/45] wrap trigger UI Schema with formatMessage --- .../composer/src/defaultTriggerSchema.ts | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts b/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts index bc291349bb..aa04ecbeeb 100644 --- a/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts +++ b/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts @@ -3,91 +3,92 @@ import { TriggerUISchema } from '@bfc/extension-client'; import { SDKKinds } from '@bfc/shared'; +import formatMessage from 'format-message'; export const DefaultTriggerSchema: TriggerUISchema = { [SDKKinds.OnIntent]: { - label: 'Intent recognized', + label: formatMessage('Intent recognized'), }, [SDKKinds.OnQnAMatch]: { - label: 'QnA Intent recognized', + label: formatMessage('QnA Intent recognized'), }, [SDKKinds.OnUnknownIntent]: { - label: 'Unknown intent', + label: formatMessage('Unknown intent'), }, [SDKKinds.OnChooseIntent]: { - label: 'Duplicated intents recognized', + label: formatMessage('Duplicated intents recognized'), }, [SDKKinds.OnDialogEvent]: { - label: 'Custom events', + label: formatMessage('Custom events'), }, // Subgroup - Dialog events [SDKKinds.OnBeginDialog]: { - label: 'Dialog started (Begin dialog event)', + label: formatMessage('Dialog started (Begin dialog event)'), submenu: { - label: 'Dialog events', - prompt: 'Which event?', - placeholder: 'Select an event type', + label: formatMessage('Dialog events'), + prompt: formatMessage('Which event?'), + placeholder: formatMessage('Select an event type'), }, }, [SDKKinds.OnCancelDialog]: { - label: 'Dialog cancelled (Cancel dialog event)', - submenu: 'Dialog events', + label: formatMessage('Dialog cancelled (Cancel dialog event)'), + submenu: formatMessage('Dialog events'), }, [SDKKinds.OnError]: { - label: 'Error occurred (Error event)', - submenu: 'Dialog events', + label: formatMessage('Error occurred (Error event)'), + submenu: formatMessage('Dialog events'), }, [SDKKinds.OnRepromptDialog]: { - label: 'Re-prompt for input (Reprompt dialog event)', - submenu: 'Dialog events', + label: formatMessage('Re-prompt for input (Reprompt dialog event)'), + submenu: formatMessage('Dialog events'), }, // Subgroup - Activities [SDKKinds.OnActivity]: { - label: 'Activities (Activity received)', + label: formatMessage('Activities (Activity received)'), submenu: { - label: 'Activities', - prompt: 'Which activity type?', - placeholder: 'Select an activity type', + label: formatMessage('Activities'), + prompt: formatMessage('Which activity type?'), + placeholder: formatMessage('Select an activity type'), }, }, [SDKKinds.OnConversationUpdateActivity]: { - label: 'Greeting (ConversationUpdate activity)', - submenu: 'Activities', + label: formatMessage('Greeting (ConversationUpdate activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnEndOfConversationActivity]: { - label: 'Conversation ended (EndOfConversation activity)', - submenu: 'Activities', + label: formatMessage('Conversation ended (EndOfConversation activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnEventActivity]: { - label: 'Event received (Event activity)', - submenu: 'Activities', + label: formatMessage('Event received (Event activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnHandoffActivity]: { - label: 'Handover to human (Handoff activity)', - submenu: 'Activities', + label: formatMessage('Handover to human (Handoff activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnInvokeActivity]: { - label: 'Conversation invoked (Invoke activity)', - submenu: 'Activities', + label: formatMessage('Conversation invoked (Invoke activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnTypingActivity]: { - label: 'User is typing (Typing activity)', - submenu: 'Activities', + label: formatMessage('User is typing (Typing activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageActivity]: { - label: 'Message received (Message received activity)', - submenu: 'Activities', + label: formatMessage('Message received (Message received activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageDeleteActivity]: { - label: 'Message deleted (Message deleted activity)', - submenu: 'Activities', + label: formatMessage('Message deleted (Message deleted activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageReactionActivity]: { - label: 'Message reaction (Message reaction activity)', - submenu: 'Activities', + label: formatMessage('Message reaction (Message reaction activity)'), + submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageUpdateActivity]: { - label: 'Message updated (Message updated activity)', - submenu: 'Activities', + label: formatMessage('Message updated (Message updated activity)'), + submenu: formatMessage('Activities'), }, }; From a569fbfa4ee96321a43dde594bbe311439658358 Mon Sep 17 00:00:00 2001 From: zeye Date: Wed, 18 Nov 2020 16:53:43 +0800 Subject: [PATCH 37/45] add 'px' unit to styles --- .../client/src/components/TriggerCreationModal/styles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts index c14e6f6882..d7e0bcba1c 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/styles.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/styles.ts @@ -50,12 +50,12 @@ export const intentStyles = { export const optionStyles = { display: 'flex', - height: 15, - fontSize: 15, + height: '15px', + fontSize: '15px', }; export const warningIconStyles = { - marginLeft: 5, + marginLeft: '5px', color: '#BE880A', - fontSize: 12, + fontSize: '12px', }; From 3b865a0d5305e694b0a14bdb4b473c86fe80a1d8 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 30 Nov 2020 15:06:51 +0800 Subject: [PATCH 38/45] add trigger menu order --- .../src/types/triggerSchema.ts | 1 + .../composer/src/defaultTriggerSchema.ts | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Composer/packages/extension-client/src/types/triggerSchema.ts b/Composer/packages/extension-client/src/types/triggerSchema.ts index 1b128c37ed..4b4a77f350 100644 --- a/Composer/packages/extension-client/src/types/triggerSchema.ts +++ b/Composer/packages/extension-client/src/types/triggerSchema.ts @@ -5,6 +5,7 @@ import { SDKKinds } from '@botframework-composer/types'; export interface TriggerUIOption { label: string; + order?: number; submenu?: TriggerSubmenuInfo | string | false; } diff --git a/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts b/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts index aa04ecbeeb..487c3cdc1f 100644 --- a/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts +++ b/Composer/packages/ui-plugins/composer/src/defaultTriggerSchema.ts @@ -8,22 +8,28 @@ import formatMessage from 'format-message'; export const DefaultTriggerSchema: TriggerUISchema = { [SDKKinds.OnIntent]: { label: formatMessage('Intent recognized'), + order: 1, }, [SDKKinds.OnQnAMatch]: { label: formatMessage('QnA Intent recognized'), + order: 2, }, [SDKKinds.OnUnknownIntent]: { label: formatMessage('Unknown intent'), + order: 3, }, [SDKKinds.OnChooseIntent]: { label: formatMessage('Duplicated intents recognized'), + order: 6, }, [SDKKinds.OnDialogEvent]: { label: formatMessage('Custom events'), + order: 7, }, // Subgroup - Dialog events [SDKKinds.OnBeginDialog]: { label: formatMessage('Dialog started (Begin dialog event)'), + order: 4.1, submenu: { label: formatMessage('Dialog events'), prompt: formatMessage('Which event?'), @@ -32,19 +38,23 @@ export const DefaultTriggerSchema: TriggerUISchema = { }, [SDKKinds.OnCancelDialog]: { label: formatMessage('Dialog cancelled (Cancel dialog event)'), + order: 4.2, submenu: formatMessage('Dialog events'), }, [SDKKinds.OnError]: { label: formatMessage('Error occurred (Error event)'), + order: 4.3, submenu: formatMessage('Dialog events'), }, [SDKKinds.OnRepromptDialog]: { label: formatMessage('Re-prompt for input (Reprompt dialog event)'), + order: 4.4, submenu: formatMessage('Dialog events'), }, // Subgroup - Activities [SDKKinds.OnActivity]: { label: formatMessage('Activities (Activity received)'), + order: 5.1, submenu: { label: formatMessage('Activities'), prompt: formatMessage('Which activity type?'), @@ -53,42 +63,52 @@ export const DefaultTriggerSchema: TriggerUISchema = { }, [SDKKinds.OnConversationUpdateActivity]: { label: formatMessage('Greeting (ConversationUpdate activity)'), + order: 5.2, submenu: formatMessage('Activities'), }, [SDKKinds.OnEndOfConversationActivity]: { label: formatMessage('Conversation ended (EndOfConversation activity)'), + order: 5.3, submenu: formatMessage('Activities'), }, [SDKKinds.OnEventActivity]: { label: formatMessage('Event received (Event activity)'), + order: 5.4, submenu: formatMessage('Activities'), }, [SDKKinds.OnHandoffActivity]: { label: formatMessage('Handover to human (Handoff activity)'), + order: 5.5, submenu: formatMessage('Activities'), }, [SDKKinds.OnInvokeActivity]: { label: formatMessage('Conversation invoked (Invoke activity)'), + order: 5.6, submenu: formatMessage('Activities'), }, [SDKKinds.OnTypingActivity]: { label: formatMessage('User is typing (Typing activity)'), + order: 5.7, submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageActivity]: { label: formatMessage('Message received (Message received activity)'), + order: 5.81, submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageDeleteActivity]: { label: formatMessage('Message deleted (Message deleted activity)'), + order: 5.82, submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageReactionActivity]: { label: formatMessage('Message reaction (Message reaction activity)'), + order: 5.83, submenu: formatMessage('Activities'), }, [SDKKinds.OnMessageUpdateActivity]: { label: formatMessage('Message updated (Message updated activity)'), + order: 5.84, submenu: formatMessage('Activities'), }, }; From fe233c91205404800c7949d745bdd4d1ce34f9e9 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 30 Nov 2020 15:52:00 +0800 Subject: [PATCH 39/45] 'order' property to manage trigger order --- .../triggerOptionTree.test.ts | 22 ++++++--------- .../TriggerDropdownGroup.tsx | 8 +----- .../TriggerCreationModal/TriggerOptionTree.ts | 28 ++++++++++--------- .../schema/TriggerOption.ts | 14 ---------- .../schema/defaultTriggerOrder.ts | 17 ----------- 5 files changed, 24 insertions(+), 65 deletions(-) delete mode 100644 Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts delete mode 100644 Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts diff --git a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts index 91fc7fa894..adb91d2738 100644 --- a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts +++ b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerOptionTree.test.ts @@ -9,24 +9,19 @@ import { TriggerOptionGroupNode, } from '../../../src/components/TriggerCreationModal/TriggerOptionTree'; -const labelAlphabeticalCompareFn = (opt1, opt2) => (opt1.label < opt2.label ? -1 : 1); - describe('generateTriggerOptionTree()', () => { it('can generate one layer tree.', () => { const simpleTriggerUIOptions: TriggerUISchema = { [SDKKinds.OnIntent]: { label: '1.OnIntent', + order: 1, }, [SDKKinds.OnInvokeActivity]: { label: '2.OnInvokeActivity', + order: 2, }, }; - const tree = generateTriggerOptionTree( - simpleTriggerUIOptions, - 'Select a trigger', - 'Which trigger?', - labelAlphabeticalCompareFn - ); + const tree = generateTriggerOptionTree(simpleTriggerUIOptions, 'Select a trigger', 'Which trigger?'); expect(tree.prompt).toEqual('Select a trigger'); expect(tree.placeholder).toEqual('Which trigger?'); @@ -45,9 +40,11 @@ describe('generateTriggerOptionTree()', () => { const advancedTriggerUIOptions: TriggerUISchema = { [SDKKinds.OnIntent]: { label: '1.OnIntent', + order: 1, }, [SDKKinds.OnTypingActivity]: { label: '2.1.OnTypingActivity', + order: 2.1, submenu: { label: '2.Activities', prompt: 'Select an activity trigger', @@ -56,19 +53,16 @@ describe('generateTriggerOptionTree()', () => { }, [SDKKinds.OnEventActivity]: { label: '2.2OnEventActivity', + order: 2.2, submenu: '2.Activities', }, [SDKKinds.OnInvokeActivity]: { label: '2.3OnInvokeActivity', + order: 2.3, submenu: '2.Activities', }, }; - const tree = generateTriggerOptionTree( - advancedTriggerUIOptions, - 'Select a trigger', - 'Which trigger?', - labelAlphabeticalCompareFn - ); + const tree = generateTriggerOptionTree(advancedTriggerUIOptions, 'Select a trigger', 'Which trigger?'); expect(tree.children.length).toEqual(2); diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 2ea2a75189..57de50649a 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -20,7 +20,6 @@ import { TriggerOptionTreeNode, } from './TriggerOptionTree'; import { checkRecognizerCompatibility } from './checkRecognizerCompatibility'; -import { triggerOrderMap } from './schema/defaultTriggerOrder'; export interface TriggerDropwdownGroupProps { recognizerType: SDKKinds | undefined; @@ -28,10 +27,6 @@ export interface TriggerDropwdownGroupProps { setTriggerType: (type: string) => void; } -const getTriggerLabelOrder = (label: string) => { - return triggerOrderMap[label] ?? Number.MAX_VALUE; -}; - export const TriggerDropdownGroup: FC = ({ recognizerType, setTriggerType }) => { const renderDropdownOption = useCallback( (option?: IDropdownOption) => { @@ -52,8 +47,7 @@ export const TriggerDropdownGroup: FC = ({ recognize return generateTriggerOptionTree( triggerUISchema, formatMessage('What is the type of this trigger?'), - formatMessage('Select a trigger type'), - (opt1, opt2) => getTriggerLabelOrder(opt1.label) - getTriggerLabelOrder(opt2.label) + formatMessage('Select a trigger type') ); }, []); diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index 2a436996c1..e2a1ab3a1b 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -1,23 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { TriggerUISchema } from '@bfc/extension-client'; - -import { TriggerUIOption } from './schema/TriggerOption'; +import { TriggerUISchema, TriggerUIOption } from '@bfc/extension-client'; export class TriggerOptionLeafNode { label: string; + order: number; $kind: string; parent: TriggerOptionGroupNode | null = null; - constructor(label: string, $kind: string) { + constructor(label: string, $kind: string, order?: number) { this.label = label; this.$kind = $kind; + this.order = order ?? Number.MAX_SAFE_INTEGER; } } export class TriggerOptionGroupNode { label: string; + order: number; /** Title of a dropdown. 'Which activity type?' */ prompt?: string; /** Placeholder of a dropdown input. 'Select an activity type' */ @@ -29,6 +30,7 @@ export class TriggerOptionGroupNode { this.label = label; this.prompt = prompt; this.placeholder = placeholder; + this.order = Number.MAX_SAFE_INTEGER; } } @@ -41,14 +43,13 @@ const getGroupKey = (submenu) => (typeof submenu === 'object' ? submenu.label : export const generateTriggerOptionTree = ( triggerUIOptions: TriggerUISchema, rootPrompt: string, - rootPlaceHolder: string, - optionCompareFn?: (a: TriggerOptionTreeNode, b: TriggerOptionTreeNode) => number + rootPlaceHolder: string ): TriggerOptionTree => { const root = new TriggerOptionGroupNode('triggerTypeDropDown', rootPrompt, rootPlaceHolder); const leafNodeList = Object.entries(triggerUIOptions) .filter(([, options]) => options && !options.submenu) - .map(([$kind, options]) => new TriggerOptionLeafNode(options?.label ?? '', $kind)); + .map(([$kind, options]) => new TriggerOptionLeafNode(options?.label ?? '', $kind, options?.order)); root.children.push(...leafNodeList); leafNodeList.forEach((leaf) => (leaf.parent = root)); @@ -70,20 +71,21 @@ export const generateTriggerOptionTree = ( Object.entries(triggerUIOptions) .filter(([, options]) => options && options.submenu) .forEach(([$kind, options]) => { - const { label, submenu } = options as TriggerUIOption; - const node = new TriggerOptionLeafNode(label, $kind); + const { label, submenu, order } = options as TriggerUIOption; + const node = new TriggerOptionLeafNode(label, $kind, order); + const groupName = getGroupKey(submenu); const groupParent = groups[groupName]; + groupParent.children.push(node); node.parent = groupParent; + groupParent.order = Math.min(groupParent.order, order ?? Number.MAX_SAFE_INTEGER); }); root.children.push(...Object.values(groups)); // sort tree nodes - if (optionCompareFn) { - root.children.sort(optionCompareFn); - Object.values(groups).forEach((x) => x.children.sort(optionCompareFn)); - } + root.children.sort((a, b) => a.order - b.order); + Object.values(groups).forEach((x) => x.children.sort((a, b) => a.order - b.order)); return root; }; diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts deleted file mode 100644 index 401e657ca8..0000000000 --- a/Composer/packages/client/src/components/TriggerCreationModal/schema/TriggerOption.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -export interface TriggerUIOption { - label: string; - submenu?: TriggerSubmenuInfo | string | false; -} -export type TriggerUIOptionMap = { [key: string]: TriggerUIOption }; - -export interface TriggerSubmenuInfo { - label: string; - prompt?: string; - placeholder?: string; -} diff --git a/Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts b/Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts deleted file mode 100644 index 453921edc2..0000000000 --- a/Composer/packages/client/src/components/TriggerCreationModal/schema/defaultTriggerOrder.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -export const defaultTriggerOrder = [ - 'Intent recognized', - 'QnA Intent recognized', - 'Unknown intent', - 'Dialog events', - 'Activities', - 'Duplicated intents recognized', - 'Custom events', -]; - -export const triggerOrderMap: { [label: string]: number } = defaultTriggerOrder.reduce((result, label, index) => { - result[label] = index; - return result; -}, {}); From 6261692e4efe527ee6fdf6900ae98f7c1d01fdd3 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 30 Nov 2020 16:02:13 +0800 Subject: [PATCH 40/45] early returning & add comments --- .../TriggerDropdownGroup.tsx | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx index 57de50649a..1b72b61d00 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerDropdownGroup.tsx @@ -71,33 +71,34 @@ export const TriggerDropdownGroup: FC = ({ recognize const getKey = (x: TriggerOptionTreeNode) => (x instanceof TriggerOptionLeafNode ? x.$kind : x.label); + // Render every group node as a dropdown until meet a leaf node. for (let i = 0; i < treePath.length; i++) { const currentNode: TriggerOptionTreeNode = treePath[i]; - if (currentNode instanceof TriggerOptionGroupNode) { - const nextNode = treePath[i + 1]; - const selectedKey = nextNode ? getKey(nextNode) : ''; - const dropdown = ( - { - return { - key: getKey(x), - text: x.label, - node: x, - }; - })} - placeholder={currentNode.placeholder} - selectedKey={selectedKey} - styles={dropdownStyles} - onChange={(e, opt: any) => { - onClickNode(opt.node); - }} - onRenderOption={renderDropdownOption} - /> - ); - dropdownList.push(dropdown); - } + if (!(currentNode instanceof TriggerOptionGroupNode)) break; + + const nextNode = treePath[i + 1]; + const selectedKey = nextNode ? getKey(nextNode) : ''; + const dropdown = ( + { + return { + key: getKey(x), + text: x.label, + node: x, + }; + })} + placeholder={currentNode.placeholder} + selectedKey={selectedKey} + styles={dropdownStyles} + onChange={(e, opt: any) => { + onClickNode(opt.node); + }} + onRenderOption={renderDropdownOption} + /> + ); + dropdownList.push(dropdown); } return dropdownList; From 3bf9618786997097ffb25682b6b89ba027b60a30 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 30 Nov 2020 16:20:41 +0800 Subject: [PATCH 41/45] avoid duplicated iteration and add comments --- .../TriggerCreationModal/TriggerOptionTree.ts | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts index e2a1ab3a1b..f342206a3b 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts +++ b/Composer/packages/client/src/components/TriggerCreationModal/TriggerOptionTree.ts @@ -47,15 +47,25 @@ export const generateTriggerOptionTree = ( ): TriggerOptionTree => { const root = new TriggerOptionGroupNode('triggerTypeDropDown', rootPrompt, rootPlaceHolder); - const leafNodeList = Object.entries(triggerUIOptions) - .filter(([, options]) => options && !options.submenu) - .map(([$kind, options]) => new TriggerOptionLeafNode(options?.label ?? '', $kind, options?.order)); + const allOptionEntries = Object.entries(triggerUIOptions).filter(([, option]) => Boolean(option)) as [ + string, + TriggerUIOption + ][]; + const leafEntries = allOptionEntries.filter(([, options]) => !options.submenu); + const nonLeafEntries = allOptionEntries.filter(([, options]) => options.submenu); + + // Build leaf nodes whose depth = 1. + const leafNodeList = leafEntries.map( + ([$kind, options]) => new TriggerOptionLeafNode(options?.label ?? '', $kind, options?.order) + ); + + // Insert depth 1 leaf nodes to tree. root.children.push(...leafNodeList); leafNodeList.forEach((leaf) => (leaf.parent = root)); - const groups = Object.values(triggerUIOptions) - .map((options) => options && options.submenu) - .filter(Boolean) + // Build group nodes. + const groups = nonLeafEntries + .map(([, options]) => options.submenu) .reduce((result, submenu) => { const name = getGroupKey(submenu); if (!result[name]) result[name] = new TriggerOptionGroupNode(name, '', ''); @@ -68,22 +78,24 @@ export const generateTriggerOptionTree = ( return result; }, {} as { [key: string]: TriggerOptionGroupNode }); - Object.entries(triggerUIOptions) - .filter(([, options]) => options && options.submenu) - .forEach(([$kind, options]) => { - const { label, submenu, order } = options as TriggerUIOption; - const node = new TriggerOptionLeafNode(label, $kind, order); + // Insert depth 1 group nodes to tree. + root.children.push(...Object.values(groups)); - const groupName = getGroupKey(submenu); - const groupParent = groups[groupName]; + // Build other leaf nodes whose depth = 2 and mount to related group node + nonLeafEntries.forEach(([$kind, options]) => { + const { label, submenu, order } = options; + const node = new TriggerOptionLeafNode(label, $kind, order); - groupParent.children.push(node); - node.parent = groupParent; - groupParent.order = Math.min(groupParent.order, order ?? Number.MAX_SAFE_INTEGER); - }); - root.children.push(...Object.values(groups)); + const groupName = getGroupKey(submenu); + const groupParent = groups[groupName]; + + groupParent.children.push(node); + node.parent = groupParent; + // Apply minimum child node order to group node for sorting. + groupParent.order = Math.min(groupParent.order, order ?? Number.MAX_SAFE_INTEGER); + }); - // sort tree nodes + // Sort by node's 'order'. root.children.sort((a, b) => a.order - b.order); Object.values(groups).forEach((x) => x.children.sort((a, b) => a.order - b.order)); From d6c5c29ba36a1912df08812d44f116a392194e4b Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 30 Nov 2020 16:51:03 +0800 Subject: [PATCH 42/45] fix trigger modal UT --- .../triggerCreationModal.test.tsx | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx index de0be459be..ff13bebc35 100644 --- a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx +++ b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { fireEvent, waitFor } from '@botframework-composer/test-utils'; +import { EditorExtension, PluginConfig } from '@bfc/extension-client'; import { TriggerCreationModal } from '../../../src/components/TriggerCreationModal'; import { renderWithRecoil } from '../../testUtils'; @@ -13,15 +14,34 @@ describe('', () => { const onSubmitMock = jest.fn(); const onDismissMock = jest.fn(); + const pluginsStub: PluginConfig = { + uiSchema: { + 'Microsoft.OnIntent': { + trigger: { + label: 'Intent recognized', + order: 1, + }, + }, + 'Microsoft.OnQnAMatch': { + trigger: { + label: 'QnA Intent recognized', + order: 2, + }, + }, + }, + }; + function renderComponent() { return renderWithRecoil( - + + + ); } From 19554e7767c805e7fb5dfae5c608133139fd3626 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 30 Nov 2020 17:11:12 +0800 Subject: [PATCH 43/45] lint --- .../TriggerCreationModal/triggerCreationModal.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx index ff13bebc35..94642b4d0b 100644 --- a/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx +++ b/Composer/packages/client/__tests__/components/TriggerCreationModal/triggerCreationModal.test.tsx @@ -33,7 +33,7 @@ describe('', () => { function renderComponent() { return renderWithRecoil( - + Date: Fri, 8 Jan 2021 14:51:08 +0800 Subject: [PATCH 44/45] replay Ben's commit --- .../TriggerCreationModal/resolveTriggerWidget.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx index d48f08091b..025908beca 100644 --- a/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx +++ b/Composer/packages/client/src/components/TriggerCreationModal/resolveTriggerWidget.tsx @@ -28,8 +28,9 @@ export function resolveTriggerWidget( const isLUISnQnA = isLUISnQnARecognizerType(dialogFile) || isPVARecognizerType(dialogFile); const showTriggerPhrase = selectedType === SDKKinds.OnIntent && !isRegEx; - const onNameChange = (e, name) => { + const onNameChange = (e: React.FormEvent, name: string | undefined) => { const errors: TriggerFormDataErrors = {}; + if (name == null) return; errors.intent = validateIntentName(selectedType, name); if (showTriggerPhrase && formData.triggerPhrases) { errors.triggerPhrases = getLuDiagnostics(name, formData.triggerPhrases); @@ -37,8 +38,9 @@ export function resolveTriggerWidget( setFormData({ ...formData, intent: name, errors: { ...formData.errors, ...errors } }); }; - const onChangeRegEx = (e, pattern) => { + const onChangeRegEx = (e: React.FormEvent, pattern: string | undefined) => { const errors: TriggerFormDataErrors = {}; + if (pattern == null) return; errors.regEx = validateRegExPattern(selectedType, isRegEx, pattern); setFormData({ ...formData, regEx: pattern, errors: { ...formData.errors, ...errors } }); }; From 2fa8db18d5269d692ca57cb070b0b2f6ad643ce8 Mon Sep 17 00:00:00 2001 From: zeye Date: Fri, 22 Jan 2021 13:01:07 +0800 Subject: [PATCH 45/45] CI fix --- Composer/cypress/integration/TriggerCreation.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composer/cypress/integration/TriggerCreation.spec.ts b/Composer/cypress/integration/TriggerCreation.spec.ts index 589f0a1324..3302c3bde6 100644 --- a/Composer/cypress/integration/TriggerCreation.spec.ts +++ b/Composer/cypress/integration/TriggerCreation.spec.ts @@ -32,7 +32,7 @@ context('Creating a new trigger', () => { cy.findAllByText('Add a trigger').click({ force: true }); cy.findByTestId('triggerTypeDropDown').click(); cy.get('[title="Dialog events"]').click(); - cy.findByTestId('eventTypeDropDown').click(); + cy.findByText('Select an event type').click(); cy.findByText('Dialog started (Begin dialog event)').click(); cy.findByTestId('triggerFormSubmit').click(); cy.findAllByText('Begin dialog event').should('exist'); @@ -55,7 +55,7 @@ context('Creating a new trigger', () => { cy.findAllByText('Add a trigger').click({ force: true }); cy.findByTestId('triggerTypeDropDown').click(); cy.get('[title="Activities"]').click(); - cy.findByTestId('activityTypeDropDown').click(); + cy.findByText('Select an activity type').click(); cy.findByText('Activities (Activity received)').click(); cy.findByTestId('triggerFormSubmit').click(); cy.findAllByText('Activities').should('exist');