Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const luisConfig = {
defaultLanguage: 'en-us',
environment: 'composer',
};
const config = { subscriptionKey: '12345', ...luisConfig };
const qnaConfig = { subscriptionKey: '12345', endpointKey: '12345' };
describe('<PublishDialog />', () => {
it('should render the <PublishDialog />', () => {
const onDismiss = jest.fn(() => {});
Expand All @@ -31,10 +33,11 @@ describe('<PublishDialog />', () => {
set(botNameState, 'sampleBot0');
set(settingsState, {
luis: luisConfig,
qna: qnaConfig,
});
};
const { getByText } = renderWithRecoil(
<PublishDialog isOpen botName={'sampleBot0'} config={luisConfig} onDismiss={onDismiss} onPublish={onPublish} />,
<PublishDialog isOpen botName={'sampleBot0'} config={config} onDismiss={onDismiss} onPublish={onPublish} />,
recoilInitState
);

Expand Down
34 changes: 2 additions & 32 deletions Composer/packages/client/__tests__/utils/luUtil.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { LuFile, DialogInfo, Diagnostic, DiagnosticSeverity } from '@bfc/shared';
import { LuFile, DialogInfo } from '@bfc/shared';

import { getReferredLuFiles, checkLuisPublish, createCrossTrainConfig } from '../../src/utils/luUtil';
import { getReferredLuFiles, createCrossTrainConfig } from '../../src/utils/luUtil';

describe('getReferredLuFiles', () => {
it('returns referred luFiles from dialog', () => {
Expand Down Expand Up @@ -93,34 +93,4 @@ describe('getReferredLuFiles', () => {
expect(config.triggerRules['dia1.en-us.lu']['dia4.en-us.lu']).toBeUndefined();
expect(config.triggerRules['main.en-us.lu'].dialog_without_lu).toEqual('');
});

it('check the lu files before publish', () => {
const dialogs = [{ luFile: 'a' }] as DialogInfo[];
const diagnostics: Diagnostic[] = [];
const luFiles = [
{ id: 'a.en-us', diagnostics, content: 'test', intents: [{ Name: '1', Body: '1' }], empty: false },
{ id: 'b.en-us', diagnostics },
{ id: 'c.en-us', diagnostics },
] as LuFile[];
const referred = checkLuisPublish(luFiles, dialogs);
expect(referred.length).toEqual(1);

expect(referred[0].id).toEqual('a.en-us');

luFiles[0].diagnostics = [{ message: 'wrong', severity: DiagnosticSeverity.Error }] as Diagnostic[];
expect(() => {
checkLuisPublish(luFiles, dialogs);
}).toThrowError(/wrong/);

luFiles[0].diagnostics = [];
luFiles[0].intents = [];
luFiles[0].empty = true;
expect(() => {
checkLuisPublish(luFiles, dialogs);
}).toThrowError('You have the following empty LuFile(s): a.en-us');

luFiles[0].empty = false;

expect(checkLuisPublish(luFiles, dialogs)[0].id).toEqual('a.en-us');
});
});
8 changes: 3 additions & 5 deletions Composer/packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,13 @@
"@bfc/ui-plugin-select-dialog": "*",
"@bfc/ui-plugin-select-skill-dialog": "*",
"@emotion/core": "^10.0.27",
"@microsoft/bf-lu": "^4.10.0-preview.141651",
"@reach/router": "^1.2.1",
"@uifabric/fluent-theme": "^7.1.107",
"@uifabric/icons": "^7.3.59",
"@uifabric/react-hooks": "^7.4.12",
"@uifabric/styling": "^7.13.7",
"axios": "^0.19.2",
"babel-plugin-extract-format-message": "^6.2.3",
"botbuilder-lg": "4.10.0-preview-147186",
"format-message": "^6.2.3",
"immer": "^5.2.0",
"jwt-decode": "^2.2.0",
Expand All @@ -57,8 +55,8 @@
"react-frame-component": "^4.0.2",
"react-timeago": "^4.4.0",
"recoil": "^0.0.10",
"webpack-bundle-analyzer": "^3.8.0",
"styled-components": "^4.1.3"
"styled-components": "^4.1.3",
"webpack-bundle-analyzer": "^3.8.0"
},
"browserslist": [
">0.2%",
Expand All @@ -67,7 +65,6 @@
"not op_mini all"
],
"devDependencies": {
"@types/recoil": "^0.0.1",
"@babel/cli": "7.2.3",
"@babel/core": "7.3.4",
"@babel/runtime": "7.3.4",
Expand All @@ -79,6 +76,7 @@
"@types/reach__router": "^1.2.4",
"@types/react": "16.9.23",
"@types/react-dom": "16.9.5",
"@types/recoil": "^0.0.1",
"@types/webpack-env": "^1.15.2",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ 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 { DialogInfo, SDKKinds } from '@bfc/shared';
import { DialogInfo, SDKKinds, LuIntentSection } 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 get from 'lodash/get';
import { generateUniqueId } from '@bfc/shared';
import { generateDesignerId, LgTemplate } from '@bfc/shared';

import {
generateNewDialog,
Expand All @@ -38,16 +38,8 @@ import {
qnaMatcherKey,
onChooseIntentKey,
} from '../../utils/dialogUtil';
import { addIntent } from '../../utils/luUtil';
import { addTemplate } from '../../utils/lgUtil';
import {
dialogsState,
luFilesState,
lgFilesState,
localeState,
projectIdState,
schemasState,
} from '../../recoilModel/atoms/botState';

import { dialogsState, projectIdState, schemasState } from '../../recoilModel/atoms/botState';
import { userSettingsState } from '../../recoilModel';
import { nameRegex } from '../../constants';

Expand Down Expand Up @@ -224,25 +216,15 @@ interface TriggerCreationModalProps {
dialogId: string;
isOpen: boolean;
onDismiss: () => void;
onSubmit: (
dialog: DialogInfo,
luFilePayload?: LuFilePayload,
lgFilePayload?: LgFilePayload,
QnAFilePayload?: QnAFilePayload
) => void;
onSubmit: (dialog: DialogInfo, intent?: LuIntentSection, lgTemplate?: LgTemplate[]) => void;
}

export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props) => {
const { isOpen, onDismiss, onSubmit, dialogId } = props;
const dialogs = useRecoilValue(dialogsState);
const luFiles = useRecoilValue(luFilesState);
const lgFiles = useRecoilValue(lgFilesState);
const locale = useRecoilValue(localeState);
const projectId = useRecoilValue(projectIdState);
const schemas = useRecoilValue(schemasState);
const userSettings = useRecoilValue(userSettingsState);
const luFile = luFiles.find(({ id }) => id === `${dialogId}.${locale}`);
const lgFile = lgFiles.find(({ id }) => id === `${dialogId}.${locale}`);
const dialogFile = dialogs.find((dialog) => dialog.id === dialogId);
const isRegEx = (dialogFile?.content?.recognizer?.$kind ?? '') === regexRecognizerKey;
const recognizer = get(dialogFile, 'content.recognizer', '');
Expand Down Expand Up @@ -303,39 +285,31 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props)

if (formData.$kind === intentTypeKey && isLUISnQnA && formData.triggerPhrases) {
const newDialog = generateNewDialog(dialogs, dialogId, formData, schemas.sdk?.content, {});
const content = luFile?.content ?? '';
const luFileId = luFile?.id || `${dialogId}.${locale}`;
const newContent = addIntent(content, { Name: formData.intent, Body: formData.triggerPhrases });
const updateLuFile = {
id: luFileId,
content: newContent,
};
onSubmit(newDialog, updateLuFile);
const newIntent = { Name: formData.intent, Body: formData.triggerPhrases };
onSubmit(newDialog, newIntent);
} else if (formData.$kind === qnaMatcherKey || formData.$kind === onChooseIntentKey) {
const lgTemplateId = generateUniqueId(6);
const extraTriggerAttributes = { lgTemplateId };
const newDialog = generateNewDialog(dialogs, dialogId, formData, schemas.sdk?.content, extraTriggerAttributes);
const content = lgFile?.content ?? '';
const lgFileId = lgFile?.id || `common.${locale}`;
let body = '';
let name = '';
if (formData.$kind === qnaMatcherKey) {
name = `SendActivity_${lgTemplateId}`;
body = '- ${@answer}';
} else if (formData.$kind === onChooseIntentKey) {
name = `ChoiceInput_Prompt_${lgTemplateId}`;
body = '- Multiple intents detected, please choose which intent do you want to trigger?';
const lgTemplateId1 = generateDesignerId();
const lgTemplateId2 = generateDesignerId();
let extraTriggerAttributes = {};
let lgTemplates: LgTemplate[] = [];
if (formData.$kind === onChooseIntentKey) {
extraTriggerAttributes['actions[4].prompt'] = `\${TextInput_Prompt_${lgTemplateId1}()}`;
extraTriggerAttributes['actions[5].elseActions[0].activity'] = `\${SendActivity_${lgTemplateId2}()}`;
lgTemplates = [
{
name: `TextInput_Prompt_${lgTemplateId1}`,
body: `[Activity\n
Attachments = \${json(AdaptiveCardJson())}\n
]\n`,
} as LgTemplate,
{
name: `SendActivity_${lgTemplateId2}`,
body: '- Sure, no worries.',
} as LgTemplate,
];
}
const newLgFile = addTemplate(lgFileId, content, {
name,
parameters: [],
body,
});
const updateLgFile = {
id: newLgFile.id,
content: newLgFile.content,
};
onSubmit(newDialog, undefined, updateLgFile);
const newDialog = generateNewDialog(dialogs, dialogId, formData, schemas.sdk?.content, extraTriggerAttributes);
onSubmit(newDialog, undefined, lgTemplates);
} else {
const newDialog = generateNewDialog(dialogs, dialogId, formData, schemas.sdk?.content, {});
onSubmit(newDialog);
Expand All @@ -351,7 +325,10 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props)
if (isCompound) {
newFormData = { ...newFormData, $kind: '' };
} else {
newFormData = { ...newFormData, $kind: option.key === customEventKey ? SDKKinds.OnDialogEvent : option.key };
newFormData = {
...newFormData,
$kind: option.key === customEventKey ? SDKKinds.OnDialogEvent : option.key,
};
}
setFormData({ ...newFormData, errors: initialFormDataErrors });
};
Expand All @@ -371,7 +348,11 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props)
if (option) {
const errors: TriggerFormDataErrors = {};
errors.event = validateEventKind(selectedType, option.key as string);
setFormData({ ...formData, $kind: option.key as string, errors: { ...formData.errors, ...errors } });
setFormData({
...formData,
$kind: option.key as string,
errors: { ...formData.errors, ...errors },
});
}
};

Expand All @@ -381,13 +362,21 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props)
if (showTriggerPhrase && formData.triggerPhrases) {
errors.triggerPhrases = getLuDiagnostics(name, formData.triggerPhrases);
}
setFormData({ ...formData, intent: name, errors: { ...formData.errors, ...errors } });
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 } });
setFormData({
...formData,
regEx: pattern,
errors: { ...formData.errors, ...errors },
});
};

//Trigger phrase is optional
Expand All @@ -398,7 +387,11 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props)
} else {
errors.triggerPhrases = '';
}
setFormData({ ...formData, triggerPhrases: body, errors: { ...formData.errors, ...errors } });
setFormData({
...formData,
triggerPhrases: body,
errors: { ...formData.errors, ...errors },
});
};
const errors = validateForm(selectedType, formData, isRegEx, regexIntents);
const disable = shouldDisable(errors);
Expand Down
39 changes: 39 additions & 0 deletions Composer/packages/client/src/hooks/useElectronFeatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { useEffect } from 'react';
import get from 'lodash/get';
import { getEditorAPI } from '@bfc/shared';

export const useElectronFeatures = (actionSelected: boolean) => {
// Sync selection state to Electron main process
useEffect(() => {
if (!window.__IS_ELECTRON__) return;
if (!window.ipcRenderer || typeof window.ipcRenderer.send !== 'function') return;

window.ipcRenderer.send('composer-state-change', { actionSelected });
}, [actionSelected]);

// Subscribe Electron app menu events (copy/cut/del/undo/redo)
useEffect(() => {
if (!window.__IS_ELECTRON__) return;
if (!window.ipcRenderer || typeof window.ipcRenderer.on !== 'function') return;

const EditorAPI = getEditorAPI();
window.ipcRenderer.on('electron-menu-clicked', (e, data) => {
const label = get(data, 'label', '');
switch (label) {
case 'undo':
return EditorAPI.Editing.Undo();
case 'redo':
return EditorAPI.Editing.Redo();
case 'cut':
return EditorAPI.Actions.CutSelection();
case 'copy':
return EditorAPI.Actions.CopySelection();
case 'delete':
return EditorAPI.Actions.DeleteSelection();
}
});
}, []);
};
Loading