diff --git a/Composer/packages/client/__tests__/pages/language-generation/LGPage.test.tsx b/Composer/packages/client/__tests__/pages/language-generation/LGPage.test.tsx
index 34e95360e0..45b296d81a 100644
--- a/Composer/packages/client/__tests__/pages/language-generation/LGPage.test.tsx
+++ b/Composer/packages/client/__tests__/pages/language-generation/LGPage.test.tsx
@@ -9,7 +9,7 @@ import TableView from '../../../src/pages/language-generation/table-view';
import CodeEditor from '../../../src/pages/language-generation/code-editor';
import {
localeState,
- lgFilesState,
+ lgFilesSelectorFamily,
settingsState,
schemasState,
dialogsSelectorFamily,
@@ -51,7 +51,7 @@ const initRecoilState = ({ set }) => {
set(currentProjectIdState, state.projectId);
set(localeState(state.projectId), state.locale);
set(dialogsSelectorFamily(state.projectId), state.dialogs);
- set(lgFilesState(state.projectId), state.lgFiles);
+ set(lgFilesSelectorFamily(state.projectId), state.lgFiles);
set(settingsState(state.projectId), state.settings);
set(schemasState(state.projectId), mockProjectResponse.schemas);
};
diff --git a/Composer/packages/client/__tests__/pages/notifications/diagnosticList.test.tsx b/Composer/packages/client/__tests__/pages/notifications/diagnosticList.test.tsx
index 109dd25c97..91e6c46e38 100644
--- a/Composer/packages/client/__tests__/pages/notifications/diagnosticList.test.tsx
+++ b/Composer/packages/client/__tests__/pages/notifications/diagnosticList.test.tsx
@@ -13,7 +13,7 @@ import {
dialogIdsState,
formDialogSchemaIdsState,
jsonSchemaFilesState,
- lgFilesState,
+ lgFilesSelectorFamily,
luFilesState,
schemasState,
settingsState,
@@ -111,7 +111,7 @@ describe('', () => {
set(botProjectIdsState, [state.projectId]);
set(dialogIdsState(state.projectId), []);
set(luFilesState(state.projectId), state.luFiles);
- set(lgFilesState(state.projectId), state.lgFiles);
+ set(lgFilesSelectorFamily(state.projectId), state.lgFiles);
set(jsonSchemaFilesState(state.projectId), state.jsonSchemaFiles);
set(botDiagnosticsState(state.projectId), state.diagnostics);
set(settingsState(state.projectId), state.settings);
diff --git a/Composer/packages/client/__tests__/pages/notifications/diagnostics.test.tsx b/Composer/packages/client/__tests__/pages/notifications/diagnostics.test.tsx
index ffe2d9e7b8..99bef63d4d 100644
--- a/Composer/packages/client/__tests__/pages/notifications/diagnostics.test.tsx
+++ b/Composer/packages/client/__tests__/pages/notifications/diagnostics.test.tsx
@@ -13,7 +13,7 @@ import {
dialogState,
formDialogSchemaIdsState,
jsonSchemaFilesState,
- lgFilesState,
+ lgFilesSelectorFamily,
luFilesState,
qnaFilesState,
schemasState,
@@ -142,7 +142,7 @@ describe('', () => {
set(dialogIdsState(state.projectId), ['test']);
set(dialogState({ projectId: state.projectId, dialogId: 'test' }), state.dialogs[0]);
set(luFilesState(state.projectId), state.luFiles);
- set(lgFilesState(state.projectId), state.lgFiles);
+ set(lgFilesSelectorFamily(state.projectId), state.lgFiles);
set(qnaFilesState(state.projectId), state.qnaFiles);
set(jsonSchemaFilesState(state.projectId), state.jsonSchemaFiles);
set(botDiagnosticsState(state.projectId), state.diagnostics);
diff --git a/Composer/packages/client/__tests__/shell/lgApi.test.tsx b/Composer/packages/client/__tests__/shell/lgApi.test.tsx
index 007c0f6da7..5dd26e2814 100644
--- a/Composer/packages/client/__tests__/shell/lgApi.test.tsx
+++ b/Composer/packages/client/__tests__/shell/lgApi.test.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
import { RecoilRoot } from 'recoil';
import { useLgApi } from '../../src/shell/lgApi';
-import { lgFilesState, localeState, dispatcherState, currentProjectIdState } from '../../src/recoilModel';
+import { lgFilesSelectorFamily, localeState, dispatcherState, currentProjectIdState } from '../../src/recoilModel';
import { Dispatcher } from '../../src/recoilModel/dispatchers';
const state = {
@@ -42,7 +42,7 @@ describe('use lgApi hooks', () => {
initRecoilState = ({ set }) => {
set(currentProjectIdState, state.projectId);
set(localeState(state.projectId), 'en-us');
- set(lgFilesState(state.projectId), state.lgFiles);
+ set(lgFilesSelectorFamily(state.projectId), state.lgFiles);
set(dispatcherState, (current: Dispatcher) => ({
...current,
updateLgTemplate: updateLgTemplateMock,
diff --git a/Composer/packages/client/__tests__/shell/triggerApi.test.tsx b/Composer/packages/client/__tests__/shell/triggerApi.test.tsx
index 4557f5df7d..f40e61ce11 100644
--- a/Composer/packages/client/__tests__/shell/triggerApi.test.tsx
+++ b/Composer/packages/client/__tests__/shell/triggerApi.test.tsx
@@ -9,7 +9,7 @@ import { useTriggerApi } from '../../src/shell/triggerApi';
import {
localeState,
luFilesState,
- lgFilesState,
+ lgFilesSelectorFamily,
dialogsSelectorFamily,
schemasState,
dispatcherState,
@@ -53,7 +53,7 @@ describe('use triggerApi hooks', () => {
set(currentProjectIdState, state.projectId);
set(localeState(state.projectId), 'en-us');
set(luFilesState(state.projectId), state.luFiles);
- set(lgFilesState(state.projectId), state.lgFiles);
+ set(lgFilesSelectorFamily(state.projectId), state.lgFiles);
set(dialogsSelectorFamily(state.projectId), state.dialogs);
set(schemasState(state.projectId), state.schemas);
set(dispatcherState, (current: Dispatcher) => ({
diff --git a/Composer/packages/client/src/Onboarding/Onboarding.tsx b/Composer/packages/client/src/Onboarding/Onboarding.tsx
index 0259a12a9a..2a80ba13f7 100644
--- a/Composer/packages/client/src/Onboarding/Onboarding.tsx
+++ b/Composer/packages/client/src/Onboarding/Onboarding.tsx
@@ -9,7 +9,7 @@ import { OpenConfirmModal } from '@bfc/ui-shared';
import onboardingStorage from '../utils/onboardingStorage';
import { useLocation } from '../utils/hooks';
-import { dispatcherState, onboardingState, botProjectIdsState, validateDialogsSelectorFamily } from '../recoilModel';
+import { dispatcherState, onboardingState, botProjectIdsState, dialogsSelectorFamily } from '../recoilModel';
import OnboardingContext from './OnboardingContext';
import TeachingBubbles from './TeachingBubbles/TeachingBubbles';
@@ -22,7 +22,7 @@ const Onboarding: React.FC = () => {
const didMount = useRef(false);
const botProjects = useRecoilValue(botProjectIdsState);
const rootBotProjectId = botProjects[0];
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(rootBotProjectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(rootBotProjectId));
const { onboardingSetComplete } = useRecoilValue(dispatcherState);
const onboarding = useRecoilValue(onboardingState);
const complete = onboarding.complete;
diff --git a/Composer/packages/client/src/components/BotRuntimeController/publishDialog.tsx b/Composer/packages/client/src/components/BotRuntimeController/publishDialog.tsx
index 9e6d359039..a592d16a25 100644
--- a/Composer/packages/client/src/components/BotRuntimeController/publishDialog.tsx
+++ b/Composer/packages/client/src/components/BotRuntimeController/publishDialog.tsx
@@ -22,7 +22,7 @@ import { Text, Tips, Links, nameRegex, LUIS_REGIONS } from '../../constants';
import { FieldConfig, useForm } from '../../hooks/useForm';
import { getReferredQnaFiles } from '../../utils/qnaUtil';
import { getLuisBuildLuFiles } from '../../utils/luUtil';
-import { luFilesState, qnaFilesState, validateDialogsSelectorFamily } from '../../recoilModel';
+import { luFilesState, qnaFilesState, dialogsSelectorFamily } from '../../recoilModel';
// -------------------- Styles -------------------- //
const textFieldLabel = css`
@@ -90,7 +90,7 @@ interface IPublishDialogProps {
export const PublishDialog: React.FC = (props) => {
const { isOpen, onDismiss, onPublish, botName, config, projectId } = props;
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
const qnaConfigShow = getReferredQnaFiles(qnaFiles, dialogs).length > 0;
diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx
index 948b967db2..0f20a0ba41 100644
--- a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx
@@ -38,7 +38,7 @@ import {
import { schemasState, userSettingsState } from '../../recoilModel/atoms';
import { nameRegex } from '../../constants';
import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator';
-import { validateDialogsSelectorFamily } from '../../recoilModel';
+import { dialogsSelectorFamily } from '../../recoilModel';
import TelemetryClient from '../../telemetry/TelemetryClient';
// -------------------- Styles -------------------- //
@@ -210,7 +210,7 @@ interface TriggerCreationModalProps {
export const TriggerCreationModal: React.FC = (props) => {
const { isOpen, onDismiss, onSubmit, dialogId, projectId } = props;
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const schemas = useRecoilValue(schemasState(projectId));
const userSettings = useRecoilValue(userSettingsState);
const dialogFile = dialogs.find((dialog) => dialog.id === dialogId);
diff --git a/Composer/packages/client/src/hooks/useResolver.ts b/Composer/packages/client/src/hooks/useResolver.ts
index f9eb2cf9c4..ac89aa885c 100644
--- a/Composer/packages/client/src/hooks/useResolver.ts
+++ b/Composer/packages/client/src/hooks/useResolver.ts
@@ -4,12 +4,13 @@ import { useRef } from 'react';
import { lgImportResolverGenerator } from '@bfc/shared';
import { useRecoilValue } from 'recoil';
-import { dialogsSelectorFamily, luFilesState, lgFilesState, localeState, qnaFilesState } from '../recoilModel';
+import { dialogsSelectorFamily, luFilesState, localeState, qnaFilesState } from '../recoilModel';
+import { lgFilesSelectorFamily } from '../recoilModel/selectors/lg';
export const useResolvers = (projectId: string) => {
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
- const lgFiles = useRecoilValue(lgFilesState(projectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(projectId));
const locale = useRecoilValue(localeState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
diff --git a/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx b/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx
index d455f706cc..ecebaeb300 100644
--- a/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx
+++ b/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx
@@ -22,7 +22,7 @@ import {
settingsState,
luFilesState,
qnaFilesState,
- validateDialogsSelectorFamily,
+ dialogsSelectorFamily,
botDisplayNameState,
} from '../../recoilModel';
import settingStorage from '../../utils/dialogSettingStorage';
@@ -171,7 +171,7 @@ export const RootBotExternalService: React.FC = (pr
const groupQnAKey = get(sensitiveGroupManageProperty, 'qna.subscriptionKey', {});
const rootqnaKey = groupQnAKey.root;
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
const botName = useRecoilValue(botDisplayNameState(projectId));
diff --git a/Composer/packages/client/src/pages/botProject/SkillBotExternalService.tsx b/Composer/packages/client/src/pages/botProject/SkillBotExternalService.tsx
index 8d240b9551..53b535753f 100644
--- a/Composer/packages/client/src/pages/botProject/SkillBotExternalService.tsx
+++ b/Composer/packages/client/src/pages/botProject/SkillBotExternalService.tsx
@@ -17,7 +17,7 @@ import {
settingsState,
luFilesState,
qnaFilesState,
- validateDialogsSelectorFamily,
+ dialogsSelectorFamily,
botDisplayNameState,
} from '../../recoilModel';
import settingStorage from '../../utils/dialogSettingStorage';
@@ -71,7 +71,7 @@ export const SkillBotExternalService: React.FC = (
const rootQnAKey = groupQnAKey.root;
const skillQnAKey = groupQnAKey[projectId];
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index dea2048fa7..b44cce95c5 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -33,7 +33,7 @@ import {
userSettingsState,
dispatcherState,
schemasState,
- validateDialogsSelectorFamily,
+ dialogsSelectorFamily,
focusPathState,
showCreateDialogModalState,
localeState,
@@ -133,7 +133,7 @@ const DesignPage: React.FC {
const { onFocusSteps } = shellApi;
const botName = useRecoilValue(botDisplayNameState(projectId));
const projectData = useRecoilValue(projectMetaDataState(projectId));
+ const diagnostics = useRecoilValue(dialogDiagnosticsSelectorFamily({ projectId, dialogId: currentDialog.id }));
const dialogData = useMemo(() => {
if (currentDialog?.content) {
@@ -81,8 +82,7 @@ const PropertyEditor: React.FC = () => {
}, [$schema, formUIOptions]);
const errors = useMemo(() => {
- const diagnostics = currentDialog?.diagnostics;
- if (diagnostics) {
+ if (diagnostics?.length) {
const currentPath = focusPath.replace('#', '');
return diagnostics.reduce((errors, d) => {
diff --git a/Composer/packages/client/src/pages/design/VisualEditor.tsx b/Composer/packages/client/src/pages/design/VisualEditor.tsx
index b75bd730c7..74ffc7a710 100644
--- a/Composer/packages/client/src/pages/design/VisualEditor.tsx
+++ b/Composer/packages/client/src/pages/design/VisualEditor.tsx
@@ -13,12 +13,7 @@ import { useFormConfig, useShellApi } from '@bfc/extension-client';
import cloneDeep from 'lodash/cloneDeep';
import grayComposerIcon from '../../images/grayComposerIcon.svg';
-import {
- dispatcherState,
- validateDialogsSelectorFamily,
- schemasState,
- designPageLocationState,
-} from '../../recoilModel';
+import { dispatcherState, dialogsSelectorFamily, schemasState, designPageLocationState } from '../../recoilModel';
import { middleTriggerContainer, middleTriggerElements, triggerButton, visualEditor } from './styles';
@@ -72,7 +67,7 @@ const VisualEditor: React.FC = (props) => {
const { openNewTriggerModal, onFocus, onBlur, isRemoteSkill } = props;
const [triggerButtonVisible, setTriggerButtonVisibility] = useState(false);
const { onboardingAddCoachMarkRef } = useRecoilValue(dispatcherState);
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const schemas = useRecoilValue(schemasState(projectId));
const designPageLocation = useRecoilValue(designPageLocationState(projectId));
const { dialogId, selected } = designPageLocation;
diff --git a/Composer/packages/client/src/pages/design/createDialogModal.tsx b/Composer/packages/client/src/pages/design/createDialogModal.tsx
index efaa623203..987f28ccdd 100644
--- a/Composer/packages/client/src/pages/design/createDialogModal.tsx
+++ b/Composer/packages/client/src/pages/design/createDialogModal.tsx
@@ -14,7 +14,7 @@ import { DialogWrapper, DialogTypes } from '@bfc/ui-shared';
import { DialogCreationCopy } from '../../constants';
import { StorageFolder } from '../../recoilModel/types';
import { FieldConfig, useForm } from '../../hooks/useForm';
-import { actionsSeedState, botDisplayNameState, schemasState, validateDialogsSelectorFamily } from '../../recoilModel';
+import { actionsSeedState, botDisplayNameState, schemasState, dialogsSelectorFamily } from '../../recoilModel';
import TelemetryClient from '../../telemetry/TelemetryClient';
import { name, description, styles as wizardStyles } from './styles';
@@ -37,7 +37,7 @@ export const CreateDialogModal: React.FC = (props) => {
const { onSubmit, onDismiss, isOpen, projectId } = props;
const schemas = useRecoilValue(schemasState(projectId));
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const botName = useRecoilValue(botDisplayNameState(projectId));
const actionsSeed = useRecoilValue(actionsSeedState(projectId));
const { shellApi, ...shellData } = useShellApi();
diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx
index 7df85c3eab..ee90f5fd68 100644
--- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx
+++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectDialogs.tsx
@@ -11,7 +11,7 @@ import debounce from 'lodash/debounce';
import formatMessage from 'format-message';
import { ContentProps } from '../constants';
-import { dispatcherState, validateDialogsSelectorFamily } from '../../../../recoilModel';
+import { dispatcherState, dialogsSelectorFamily } from '../../../../recoilModel';
import { SelectItems } from './SelectItems';
@@ -35,7 +35,7 @@ interface DescriptionColumnProps extends DialogInfo {
const DescriptionColumn: React.FC = (props) => {
const { id, displayName, projectId } = props;
- const items = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const items = useRecoilValue(dialogsSelectorFamily(projectId));
const { content } = items.find(({ id: dialogId }) => dialogId === id) || {};
const [value, setValue] = useState(content?.$designer?.description);
@@ -94,7 +94,7 @@ const DescriptionColumn: React.FC = (props) => {
};
export const SelectDialogs: React.FC = ({ setSelectedDialogs, projectId }) => {
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const items = useMemo(() => dialogs.map(({ id, content, displayName }) => ({ id, content, displayName })), [
projectId,
]);
diff --git a/Composer/packages/client/src/pages/language-generation/code-editor.tsx b/Composer/packages/client/src/pages/language-generation/code-editor.tsx
index 405fa796d8..088f3eed09 100644
--- a/Composer/packages/client/src/pages/language-generation/code-editor.tsx
+++ b/Composer/packages/client/src/pages/language-generation/code-editor.tsx
@@ -15,9 +15,10 @@ import { CodeEditorSettings } from '@bfc/shared';
import { useRecoilValue } from 'recoil';
import { LgFile } from '@bfc/extension-client';
-import { localeState, lgFilesState, settingsState } from '../../recoilModel/atoms/botState';
+import { localeState, settingsState } from '../../recoilModel/atoms/botState';
import { userSettingsState, dispatcherState } from '../../recoilModel';
import { DiffCodeEditor } from '../language-understanding/diff-editor';
+import { lgFilesSelectorFamily } from '../../recoilModel/selectors/lg';
const lspServerPath = '/lg-language-server';
@@ -34,7 +35,7 @@ const CodeEditor: React.FC = (props) => {
const userSettings = useRecoilValue(userSettingsState);
const locale = useRecoilValue(localeState(actualProjectId));
- const lgFiles = useRecoilValue(lgFilesState(actualProjectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(actualProjectId));
const settings = useRecoilValue(settingsState(actualProjectId));
const { languages, defaultLanguage } = settings;
diff --git a/Composer/packages/client/src/pages/language-generation/table-view.tsx b/Composer/packages/client/src/pages/language-generation/table-view.tsx
index dce9adf08b..18274b064c 100644
--- a/Composer/packages/client/src/pages/language-generation/table-view.tsx
+++ b/Composer/packages/client/src/pages/language-generation/table-view.tsx
@@ -22,15 +22,10 @@ import { lgUtil } from '@bfc/indexers';
import { EditableField } from '../../components/EditableField';
import { navigateTo } from '../../utils/navigation';
import { actionButton, formCell, editableFieldContainer } from '../language-understanding/styles';
-import {
- dispatcherState,
- lgFilesState,
- localeState,
- settingsState,
- validateDialogsSelectorFamily,
-} from '../../recoilModel';
+import { dispatcherState, localeState, settingsState, dialogsSelectorFamily } from '../../recoilModel';
import { languageListTemplates } from '../../components/MultiLanguage';
import TelemetryClient from '../../telemetry/TelemetryClient';
+import { lgFilesSelectorFamily } from '../../recoilModel/selectors/lg';
interface TableViewProps extends RouteComponentProps<{ dialogId: string; skillId: string; projectId: string }> {
projectId: string;
@@ -44,10 +39,10 @@ const TableView: React.FC = (props) => {
const actualProjectId = skillId ?? projectId;
- const lgFiles = useRecoilValue(lgFilesState(actualProjectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(actualProjectId));
const locale = useRecoilValue(localeState(actualProjectId));
const settings = useRecoilValue(settingsState(actualProjectId));
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(actualProjectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(actualProjectId));
const { createLgTemplate, copyLgTemplate, removeLgTemplate, setMessage, updateLgTemplate } = useRecoilValue(
dispatcherState
);
diff --git a/Composer/packages/client/src/pages/language-understanding/table-view.tsx b/Composer/packages/client/src/pages/language-understanding/table-view.tsx
index 1a146debe0..a974ba276d 100644
--- a/Composer/packages/client/src/pages/language-understanding/table-view.tsx
+++ b/Composer/packages/client/src/pages/language-understanding/table-view.tsx
@@ -24,13 +24,7 @@ import { EditableField } from '../../components/EditableField';
import { getExtension } from '../../utils/fileUtil';
import { languageListTemplates } from '../../components/MultiLanguage';
import { navigateTo } from '../../utils/navigation';
-import {
- dispatcherState,
- luFilesState,
- localeState,
- settingsState,
- validateDialogsSelectorFamily,
-} from '../../recoilModel';
+import { dispatcherState, luFilesState, localeState, settingsState, dialogsSelectorFamily } from '../../recoilModel';
import { formCell, luPhraseCell, tableCell, editableFieldContainer } from './styles';
interface TableViewProps extends RouteComponentProps<{ dialogId: string; skillId: string; projectId: string }> {
@@ -60,7 +54,7 @@ const TableView: React.FC = (props) => {
const luFiles = useRecoilValue(luFilesState(actualProjectId));
const locale = useRecoilValue(localeState(actualProjectId));
const settings = useRecoilValue(settingsState(actualProjectId));
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(actualProjectId));
+ const dialogs = useRecoilValue(dialogsSelectorFamily(actualProjectId));
const { languages, defaultLanguage } = settings;
diff --git a/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx b/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx
index fd44c2b34d..3bef276548 100644
--- a/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx
+++ b/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx
@@ -17,7 +17,6 @@ import createDispatchers, { Dispatcher } from './dispatchers';
import {
luFilesState,
qnaFilesState,
- lgFilesState,
skillManifestsState,
dialogSchemasState,
settingsState,
@@ -29,11 +28,12 @@ import {
import { localBotsWithoutErrorsSelector, formDialogSchemasSelectorFamily } from './selectors';
import { Recognizer } from './Recognizers';
import { recognizersSelectorFamily } from './selectors/recognizers';
+import { lgFilesSelectorFamily } from './selectors/lg';
const getBotAssets = async (projectId, snapshot: Snapshot): Promise => {
const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId));
const luFiles = await snapshot.getPromise(luFilesState(projectId));
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const skillManifests = await snapshot.getPromise(skillManifestsState(projectId));
const setting = await snapshot.getPromise(settingsState(projectId));
const botProjectFile = await snapshot.getPromise(botProjectFileState(projectId));
diff --git a/Composer/packages/client/src/recoilModel/atoms/botState.ts b/Composer/packages/client/src/recoilModel/atoms/botState.ts
index b9c94bff5c..ef59d2f431 100644
--- a/Composer/packages/client/src/recoilModel/atoms/botState.ts
+++ b/Composer/packages/client/src/recoilModel/atoms/botState.ts
@@ -47,6 +47,32 @@ const emptyDialog: DialogInfo = {
skills: [],
isFormDialog: false,
};
+
+const emptyLg: LgFile = {
+ id: '',
+ content: '',
+ diagnostics: [],
+ templates: [],
+ allTemplates: [],
+ imports: [],
+};
+
+type LgStateParams = { projectId: string; lgFileId: string };
+
+export const lgFileState = atomFamily({
+ key: getFullyQualifiedKey('lg'),
+ default: () => {
+ return emptyLg;
+ },
+});
+
+export const lgFileIdsState = atomFamily({
+ key: getFullyQualifiedKey('lgFileIds'),
+ default: () => {
+ return [];
+ },
+});
+
type dialogStateParams = { projectId: string; dialogId: string };
export const dialogState = atomFamily({
key: getFullyQualifiedKey('dialog'),
@@ -124,13 +150,6 @@ export const botRuntimeErrorState = atomFamily({
},
});
-export const lgFilesState = atomFamily({
- key: getFullyQualifiedKey('lgFiles'),
- default: (id) => {
- return [];
- },
-});
-
export const luFilesState = atomFamily({
key: getFullyQualifiedKey('luFiles'),
default: (id) => {
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/dialog.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/dialog.test.tsx
index 1b4d232dc0..f650216c1f 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/dialog.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/dialog.test.tsx
@@ -8,7 +8,6 @@ import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
import { dialogsDispatcher } from '../dialogs';
import { renderRecoilHook } from '../../../../__tests__/testUtils';
import {
- lgFilesState,
luFilesState,
schemasState,
dialogSchemasState,
@@ -17,7 +16,7 @@ import {
showCreateDialogModalState,
qnaFilesState,
} from '../../atoms';
-import { dialogsSelectorFamily } from '../../selectors';
+import { dialogsSelectorFamily, lgFilesSelectorFamily } from '../../selectors';
import { dispatcherState } from '../../../recoilModel/DispatcherWrapper';
import { Dispatcher } from '..';
@@ -106,7 +105,7 @@ describe('dialog dispatcher', () => {
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const dialogSchemas = useRecoilValue(dialogSchemasState(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
- const lgFiles = useRecoilValue(lgFilesState(projectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(projectId));
const actionsSeed = useRecoilValue(actionsSeedState(projectId));
const onCreateDialogComplete = useRecoilValue(onCreateDialogCompleteState(projectId));
const showCreateDialogModal = useRecoilValue(showCreateDialogModalState);
@@ -133,7 +132,7 @@ describe('dialog dispatcher', () => {
states: [
{ recoilState: dialogsSelectorFamily(projectId), initialValue: [{ id: '1' }, { id: '2' }] },
{ recoilState: dialogSchemasState(projectId), initialValue: [{ id: '1' }, { id: '2' }] },
- { recoilState: lgFilesState(projectId), initialValue: [{ id: '1.en-us' }, { id: '2.en-us' }] },
+ { recoilState: lgFilesSelectorFamily(projectId), initialValue: [{ id: '1.en-us' }, { id: '2.en-us' }] },
{ recoilState: luFilesState(projectId), initialValue: [{ id: '1.en-us' }, { id: '2.en-us' }] },
{ recoilState: qnaFilesState(projectId), initialValue: [{ id: '1.en-us' }, { id: '2.en-us' }] },
{ recoilState: schemasState(projectId), initialValue: { sdk: { content: '' } } },
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/lg.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/lg.test.tsx
index f066164769..3dd15f7e20 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/lg.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/lg.test.tsx
@@ -8,9 +8,10 @@ import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
import { lgDispatcher } from '../lg';
import { renderRecoilHook } from '../../../../__tests__/testUtils';
-import { lgFilesState, currentProjectIdState } from '../../atoms';
+import { currentProjectIdState } from '../../atoms';
import { dispatcherState } from '../../../recoilModel/DispatcherWrapper';
import { Dispatcher } from '..';
+import { lgFilesSelectorFamily } from '../../selectors';
const projectId = '123asad.123sad';
@@ -57,7 +58,7 @@ const getLgTemplate = (name, body): LgTemplate =>
describe('Lg dispatcher', () => {
const useRecoilTestHook = () => {
- const [lgFiles, setLgFiles] = useRecoilState(lgFilesState(projectId));
+ const [lgFiles, setLgFiles] = useRecoilState(lgFilesSelectorFamily(projectId));
const currentDispatcher = useRecoilValue(dispatcherState);
return {
@@ -72,7 +73,7 @@ describe('Lg dispatcher', () => {
beforeEach(() => {
const { result } = renderRecoilHook(useRecoilTestHook, {
states: [
- { recoilState: lgFilesState(projectId), initialValue: lgFiles },
+ { recoilState: lgFilesSelectorFamily(projectId), initialValue: lgFiles },
{ recoilState: currentProjectIdState, initialValue: projectId },
],
dispatcher: {
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/multilang.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/multilang.test.tsx
index c6ba250880..a82ba6e692 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/multilang.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/multilang.test.tsx
@@ -7,7 +7,6 @@ import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
import { renderRecoilHook } from '../../../../__tests__/testUtils';
import {
luFilesState,
- lgFilesState,
settingsState,
localeState,
actionsSeedState,
@@ -15,7 +14,7 @@ import {
onDelLanguageDialogCompleteState,
currentProjectIdState,
} from '../../atoms';
-import { dialogsSelectorFamily } from '../../selectors';
+import { dialogsSelectorFamily, lgFilesSelectorFamily } from '../../selectors';
import { dispatcherState } from '../../../recoilModel/DispatcherWrapper';
import { Dispatcher } from '..';
import { multilangDispatcher } from '../multilang';
@@ -45,7 +44,7 @@ describe('Multilang dispatcher', () => {
const locale = useRecoilValue(localeState(state.projectId));
const settings = useRecoilValue(settingsState(state.projectId));
const luFiles = useRecoilValue(luFilesState(state.projectId));
- const lgFiles = useRecoilValue(lgFilesState(state.projectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(state.projectId));
const onAddLanguageDialogComplete = useRecoilValue(onAddLanguageDialogCompleteState(state.projectId));
const onDelLanguageDialogComplete = useRecoilValue(onDelLanguageDialogCompleteState(state.projectId));
@@ -72,7 +71,7 @@ describe('Multilang dispatcher', () => {
{ recoilState: currentProjectIdState, initialValue: state.projectId },
{ recoilState: dialogsSelectorFamily(state.projectId), initialValue: state.dialogs },
{ recoilState: localeState(state.projectId), initialValue: state.locale },
- { recoilState: lgFilesState(state.projectId), initialValue: state.lgFiles },
+ { recoilState: lgFilesSelectorFamily(state.projectId), initialValue: state.lgFiles },
{ recoilState: luFilesState(state.projectId), initialValue: state.luFiles },
{ recoilState: settingsState(state.projectId), initialValue: state.settings },
],
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/project.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/project.test.tsx
index 3126536cb2..0b193c7953 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/project.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/project.test.tsx
@@ -25,7 +25,6 @@ import {
currentProjectIdState,
skillManifestsState,
luFilesState,
- lgFilesState,
settingsState,
botEnvironmentState,
botDiagnosticsState,
@@ -41,7 +40,7 @@ import {
botErrorState,
botProjectSpaceLoadedState,
} from '../../atoms';
-import { dialogsSelectorFamily } from '../../selectors';
+import { dialogsSelectorFamily, lgFilesSelectorFamily } from '../../selectors';
import { dispatcherState } from '../../../recoilModel/DispatcherWrapper';
import { Dispatcher } from '../../dispatchers';
import { BotStatus } from '../../../constants';
@@ -115,7 +114,7 @@ describe('Project dispatcher', () => {
const botName = useRecoilValue(botDisplayNameState(projectId));
const skillManifests = useRecoilValue(skillManifestsState(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
- const lgFiles = useRecoilValue(lgFilesState(projectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(projectId));
const settings = useRecoilValue(settingsState(projectId));
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const botEnvironment = useRecoilValue(botEnvironmentState(projectId));
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/trigger.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/trigger.test.tsx
index a51c1d42cf..6c994cde9e 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/trigger.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/trigger.test.tsx
@@ -10,15 +10,8 @@ import { lgDispatcher } from '../lg';
import { luDispatcher } from '../lu';
import { navigationDispatcher } from '../navigation';
import { renderRecoilHook } from '../../../../__tests__/testUtils';
-import {
- lgFilesState,
- luFilesState,
- schemasState,
- dialogSchemasState,
- actionsSeedState,
- qnaFilesState,
-} from '../../atoms';
-import { dialogsSelectorFamily } from '../../selectors';
+import { luFilesState, schemasState, dialogSchemasState, actionsSeedState, qnaFilesState } from '../../atoms';
+import { dialogsSelectorFamily, lgFilesSelectorFamily } from '../../selectors';
import { dispatcherState } from '../../../recoilModel/DispatcherWrapper';
import { Dispatcher } from '..';
import { DialogInfo } from '../../../../../types';
@@ -106,7 +99,7 @@ describe('trigger dispatcher', () => {
const dialogs: DialogInfo[] = useRecoilValue(dialogsSelectorFamily(projectId));
const dialogSchemas = useRecoilValue(dialogSchemasState(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
- const lgFiles = useRecoilValue(lgFilesState(projectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(projectId));
const actionsSeed = useRecoilValue(actionsSeedState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
const currentDispatcher = useRecoilValue(dispatcherState);
@@ -135,7 +128,7 @@ describe('trigger dispatcher', () => {
},
{ recoilState: dialogSchemasState(projectId), initialValue: [{ id: '1' }, { id: '2' }] },
{
- recoilState: lgFilesState(projectId),
+ recoilState: lgFilesSelectorFamily(projectId),
initialValue: [
{ id: '1.en-us', content: '' },
{ id: '2.en-us', content: '' },
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts b/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts
index 12c61e6422..283be2a732 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts
@@ -2,15 +2,12 @@
// Licensed under the MIT License.
/* eslint-disable react-hooks/rules-of-hooks */
import { useRecoilCallback, CallbackInterface } from 'recoil';
-import { dialogIndexer, autofixReferInDialog, validateDialog } from '@bfc/indexers';
+import { dialogIndexer, autofixReferInDialog } from '@bfc/indexers';
import { DialogInfo, checkForPVASchema } from '@bfc/shared';
import {
- lgFilesState,
- luFilesState,
dialogIdsState,
schemasState,
- settingsState,
onCreateDialogCompleteState,
actionsSeedState,
showCreateDialogModalState,
@@ -83,11 +80,8 @@ export const dialogsDispatcher = () => {
const { set, snapshot } = callbackHelpers;
const fixedContent = JSON.parse(autofixReferInDialog(id, JSON.stringify(content)));
const schemas = await snapshot.getPromise(schemasState(projectId));
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
- const luFiles = await snapshot.getPromise(luFilesState(projectId));
- const settings = await snapshot.getPromise(settingsState(projectId));
const dialog = { isRoot: false, displayName: id, ...dialogIndexer.parse(id, fixedContent) };
- dialog.diagnostics = validateDialog(dialog, schemas.sdk.content, settings, lgFiles, luFiles);
+
if (typeof dialog.content === 'object') {
dialog.content.id = id;
}
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/lg.ts b/Composer/packages/client/src/recoilModel/dispatchers/lg.ts
index b9948763cc..333275ea95 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/lg.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/lg.ts
@@ -8,11 +8,12 @@ import formatMessage from 'format-message';
import { getBaseName, getExtension } from '../../utils/fileUtil';
import { dispatcherState } from '../DispatcherWrapper';
+import { lgFilesSelectorFamily } from '../selectors/lg';
import { setError } from './shared';
import LgWorker from './../parsers/lgWorker';
import LgDiagnosticWorker from './../parsers/lgDiagnosticWorker';
-import { lgFilesState, localeState, settingsState } from './../atoms/botState';
+import { lgFileIdsState, lgFileState, localeState, settingsState } from './../atoms/botState';
const templateIsNotEmpty = ({ name, body }) => {
return !!name && !!body;
@@ -29,37 +30,39 @@ const initialBody = '- ';
*
*/
-const lgFilesAtomUpdater = (
+const updateLgFiles = (
+ { set }: CallbackInterface,
+ projectId: string,
changes: {
adds?: LgFile[];
deletes?: LgFile[];
updates?: LgFile[];
},
- filter?: (oldList: LgFile[]) => (changeItem: LgFile) => boolean
+ needUpdate?: (current: LgFile, changed: LgFile) => boolean
) => {
- return (oldList: LgFile[]) => {
- const updates = changes.updates ? (filter ? changes.updates.filter(filter(oldList)) : changes.updates) : [];
- const adds = changes.adds ? (filter ? changes.adds.filter(filter(oldList)) : changes.adds) : [];
- const deletes = changes.deletes
- ? filter
- ? changes.deletes.filter(filter(oldList)).map(({ id }) => id)
- : changes.deletes.map(({ id }) => id)
- : [];
-
- // updates
- let newList = oldList.map((file) => {
- const changedFile = updates.find(({ id }) => id === file.id);
- return changedFile ?? file;
- });
+ const { updates, adds, deletes } = changes;
- // deletes
- newList = newList.filter((file) => !deletes.includes(file.id));
+ // updates
+ updates?.forEach((lgFile) => {
+ set(lgFileState({ projectId, lgFileId: lgFile.id }), (preFile) =>
+ needUpdate ? (needUpdate(preFile, lgFile) ? lgFile : preFile) : lgFile
+ );
+ });
- // adds
- newList = adds.concat(newList);
+ // deletes
+ if (deletes?.length) {
+ set(lgFileIdsState(projectId), (ids) => ids.filter((id) => !deletes.map((file) => file.id).includes(id)));
+ }
- return newList;
- };
+ // adds
+ if (adds?.length) {
+ set(lgFileIdsState(projectId), (ids) => ids.concat(adds.map((file) => file.id)));
+ adds.forEach((lgFile) => {
+ set(lgFileState({ projectId, lgFileId: lgFile.id }), (preFile) =>
+ needUpdate ? (needUpdate(preFile, lgFile) ? lgFile : preFile) : lgFile
+ );
+ });
+ }
};
// sync lg file structure across locales, it take times, computed changes may be expired at next tick.
@@ -121,8 +124,8 @@ export const createLgFileState = async (
{ id, content, projectId }: { id: string; content: string; projectId: string }
) => {
try {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const locale = await snapshot.getPromise(localeState(projectId));
const { languages } = await snapshot.getPromise(settingsState(projectId));
const createdLgId = `${id}.${locale}`;
@@ -147,7 +150,7 @@ export const createLgFileState = async (
});
});
- set(lgFilesState(projectId), lgFilesAtomUpdater({ adds: changes }));
+ updateLgFiles(callbackHelpers, projectId, { adds: changes });
} catch (error) {
setError(callbackHelpers, error);
}
@@ -157,8 +160,8 @@ export const removeLgFileState = async (
callbackHelpers: CallbackInterface,
{ id, projectId }: { id: string; projectId: string }
) => {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const locale = await snapshot.getPromise(localeState(projectId));
const targetLgFile = lgFiles.find((item) => item.id === id) || lgFiles.find((item) => item.id === `${id}.${locale}`);
@@ -167,7 +170,7 @@ export const removeLgFileState = async (
return;
}
- set(lgFilesState(projectId), lgFilesAtomUpdater({ deletes: [targetLgFile] }));
+ updateLgFiles(callbackHelpers, projectId, { deletes: [targetLgFile] });
};
export const lgDispatcher = () => {
@@ -208,40 +211,27 @@ export const lgDispatcher = () => {
try {
const { set, snapshot } = callbackHelpers;
//set content first
- set(lgFilesState(projectId), (prevLgFiles) => {
- return prevLgFiles.map((file) => {
- if (file.id === id) {
- return {
- ...file,
- content,
- };
- }
- return file;
- });
+ set(lgFileState({ projectId, lgFileId: id }), (prevLgFile) => {
+ return {
+ ...prevLgFile,
+ content,
+ };
});
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const updatedFile = (await LgWorker.parse(projectId, id, content, lgFiles)) as LgFile;
const updatedFiles = await getRelatedLgFileChanges(projectId, lgFiles, updatedFile);
// compare to drop expired change on current id lg file.
/**
* Why other methods do not need double check content?
- * Because this method already did set content before call lgFilesAtomUpdater.
+ * Because this method already did set content before call updateLgFiles.
*/
- set(
- lgFilesState(projectId),
- lgFilesAtomUpdater({ updates: updatedFiles }, (prevLgFiles) => {
- const targetInState = prevLgFiles.find((file) => file.id === updatedFile.id);
- const targetInCurrentChange = updatedFiles.find((file) => file.id === updatedFile.id);
- // compare to drop expired content already setted above.
- if (targetInState?.content !== targetInCurrentChange?.content) {
- return (lgFile) => lgFile.id !== updatedFile.id;
- } else {
- return () => true;
- }
- })
- );
+
+ updateLgFiles(callbackHelpers, projectId, { updates: updatedFiles }, (current, changed) => {
+ // compare to drop expired content already setted above.
+ return current?.content === changed?.content;
+ });
// if changes happen on common.lg, async re-parse all.
if (getBaseName(id) === 'common') {
@@ -266,8 +256,8 @@ export const lgDispatcher = () => {
template: LgTemplate;
projectId: string;
}) => {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const lgFile = lgFiles.find((file) => file.id === id);
if (!lgFile) return lgFiles;
const sameIdOtherLocaleFiles = lgFiles.filter((file) => getBaseName(file.id) === getBaseName(id));
@@ -294,7 +284,7 @@ export const lgDispatcher = () => {
)) as LgFile;
changes.push(updatedFile);
}
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: changes }));
+ updateLgFiles(callbackHelpers, projectId, { updates: changes });
} else {
// body change, only update current locale file
const updatedFile = (await LgWorker.updateTemplate(
@@ -304,7 +294,7 @@ export const lgDispatcher = () => {
{ body: template.body },
lgFiles
)) as LgFile;
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: [updatedFile] }));
+ updateLgFiles(callbackHelpers, projectId, { updates: [updatedFile] });
}
} catch (error) {
setError(callbackHelpers, error);
@@ -320,7 +310,7 @@ export const lgDispatcher = () => {
);
const createLgTemplate = useRecoilCallback(
- ({ set, snapshot }: CallbackInterface) => async ({
+ (callbackHelpers: CallbackInterface) => async ({
id,
template,
projectId,
@@ -329,12 +319,13 @@ export const lgDispatcher = () => {
template: LgTemplate;
projectId: string;
}) => {
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const lgFile = lgFiles.find((file) => file.id === id);
if (!lgFile) return lgFiles;
const updatedFile = (await LgWorker.addTemplate(projectId, lgFile, template, lgFiles)) as LgFile;
const updatedFiles = await getRelatedLgFileChanges(projectId, lgFiles, updatedFile);
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: updatedFiles }));
+ updateLgFiles(callbackHelpers, projectId, { updates: updatedFiles });
}
);
@@ -349,13 +340,13 @@ export const lgDispatcher = () => {
projectId: string;
}) => {
try {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const lgFile = lgFiles.find((file) => file.id === id);
if (!lgFile) return lgFiles;
const updatedFile = (await LgWorker.addTemplates(projectId, lgFile, templates, lgFiles)) as LgFile;
const updatedFiles = await getRelatedLgFileChanges(projectId, lgFiles, updatedFile);
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: updatedFiles }));
+ updateLgFiles(callbackHelpers, projectId, { updates: updatedFiles });
} catch (error) {
setError(callbackHelpers, error);
}
@@ -372,15 +363,15 @@ export const lgDispatcher = () => {
templateName: string;
projectId: string;
}) => {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const lgFile = lgFiles.find((file) => file.id === id);
if (!lgFile) return lgFiles;
try {
const updatedFile = (await LgWorker.removeTemplate(projectId, lgFile, templateName, lgFiles)) as LgFile;
const updatedFiles = await getRelatedLgFileChanges(projectId, lgFiles, updatedFile);
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: updatedFiles }));
+ updateLgFiles(callbackHelpers, projectId, { updates: updatedFiles });
} catch (error) {
setError(callbackHelpers, error);
}
@@ -398,15 +389,15 @@ export const lgDispatcher = () => {
projectId: string;
}) => {
try {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const lgFile = lgFiles.find((file) => file.id === id);
if (!lgFile) return lgFiles;
const updatedFile = (await LgWorker.removeTemplates(projectId, lgFile, templateNames, lgFiles)) as LgFile;
const updatedFiles = await getRelatedLgFileChanges(projectId, lgFiles, updatedFile);
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: updatedFiles }));
+ updateLgFiles(callbackHelpers, projectId, { updates: updatedFiles });
} catch (error) {
setError(callbackHelpers, error);
}
@@ -426,8 +417,8 @@ export const lgDispatcher = () => {
projectId: string;
}) => {
try {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const lgFile = lgFiles.find((file) => file.id === id);
if (!lgFile) return lgFiles;
const updatedFile = (await LgWorker.copyTemplate(
@@ -438,7 +429,7 @@ export const lgDispatcher = () => {
lgFiles
)) as LgFile;
const updatedFiles = await getRelatedLgFileChanges(projectId, lgFiles, updatedFile);
- set(lgFilesState(projectId), lgFilesAtomUpdater({ updates: updatedFiles }));
+ updateLgFiles(callbackHelpers, projectId, { updates: updatedFiles });
} catch (error) {
setError(callbackHelpers, error);
}
@@ -448,24 +439,17 @@ export const lgDispatcher = () => {
const reparseAllLgFiles = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async ({ projectId }: { projectId: string }) => {
try {
- const { set, snapshot } = callbackHelpers;
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const { snapshot } = callbackHelpers;
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const reparsedLgFiles: LgFile[] = [];
for (const file of lgFiles) {
const reparsedFile = (await LgDiagnosticWorker.parse(projectId, file.id, file.content, lgFiles)) as LgFile;
reparsedLgFiles.push({ ...file, diagnostics: reparsedFile.diagnostics });
}
- set(
- lgFilesState(projectId),
- lgFilesAtomUpdater({ updates: reparsedLgFiles }, (prevLgFiles) => {
- // compare to drop expired content already setted above.
- return (target) => {
- const targetInState = prevLgFiles.find((file) => file.id === target.id);
- const targetInCurrentChange = reparsedLgFiles.find((file) => file.id === target.id);
- return targetInState?.content === targetInCurrentChange?.content;
- };
- })
- );
+ updateLgFiles(callbackHelpers, projectId, { updates: reparsedLgFiles }, (current, changed) => {
+ // compare to drop expired content already setted above.
+ return current?.content === changed?.content;
+ });
} catch (error) {
setError(callbackHelpers, error);
}
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/multilang.ts b/Composer/packages/client/src/recoilModel/dispatchers/multilang.ts
index d4519b5856..459cf85e39 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/multilang.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/multilang.ts
@@ -9,9 +9,9 @@ import difference from 'lodash/difference';
import languageStorage from '../../utils/languageStorage';
import { getExtension } from '../../utils/fileUtil';
import { localBotsDataSelector, rootBotProjectIdSelector } from '../selectors/project';
+import { lgFilesSelectorFamily } from '../selectors/lg';
import {
- lgFilesState,
luFilesState,
localeState,
settingsState,
@@ -93,7 +93,7 @@ export const multilangDispatcher = () => {
const onAddLanguageDialogComplete = (await snapshot.getPromise(onAddLanguageDialogCompleteState(projectId))).func;
// copy files from default language
- set(lgFilesState(projectId), (prevlgFiles) => {
+ set(lgFilesSelectorFamily(projectId), (prevlgFiles) => {
const addedLgFiles = copyLanguageResources(prevlgFiles, defaultLang, languages);
return [...prevlgFiles, ...addedLgFiles];
});
@@ -136,7 +136,7 @@ export const multilangDispatcher = () => {
const onDelLanguageDialogComplete = (await snapshot.getPromise(onDelLanguageDialogCompleteState(projectId))).func;
// copy files from default language
- set(lgFilesState(projectId), (prevlgFiles) => {
+ set(lgFilesSelectorFamily(projectId), (prevlgFiles) => {
const { left: leftLgFiles } = deleteLanguageResources(prevlgFiles, languages);
return leftLgFiles;
});
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/trigger.ts b/Composer/packages/client/src/recoilModel/dispatchers/trigger.ts
index 20d096ba31..f5173fc3f6 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/trigger.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/trigger.ts
@@ -5,7 +5,7 @@ import { useRecoilCallback, CallbackInterface } from 'recoil';
import { BaseSchema, deleteActions, ITriggerCondition, LgTemplate, LgTemplateSamples, SDKKinds } from '@bfc/shared';
import get from 'lodash/get';
-import { lgFilesState, luFilesState, schemasState, dialogState, localeState } from '../atoms/botState';
+import { luFilesState, schemasState, dialogState, localeState } from '../atoms/botState';
import { dispatcherState } from '../DispatcherWrapper';
import { dialogsSelectorFamily } from '../selectors';
import {
@@ -15,6 +15,7 @@ import {
qnaMatcherKey,
TriggerFormData,
} from '../../utils/dialogUtil';
+import { lgFilesSelectorFamily } from '../selectors/lg';
import { setError } from './shared';
@@ -47,7 +48,7 @@ export const triggerDispatcher = () => {
try {
const { snapshot } = callbackHelpers;
const dispatcher = await snapshot.getPromise(dispatcherState);
- const lgFiles = await snapshot.getPromise(lgFilesState(projectId));
+ const lgFiles = await snapshot.getPromise(lgFilesSelectorFamily(projectId));
const luFiles = await snapshot.getPromise(luFilesState(projectId));
const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId));
const dialog = await snapshot.getPromise(dialogState({ projectId, dialogId }));
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
index 91a15653fd..1df6055f3d 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
@@ -3,7 +3,7 @@
import path from 'path';
-import { indexer, validateDialog } from '@bfc/indexers';
+import { indexer } from '@bfc/indexers';
import {
BotProjectFile,
BotProjectSpace,
@@ -58,7 +58,6 @@ import {
formDialogSchemaIdsState,
formDialogSchemaState,
jsonSchemaFilesState,
- lgFilesState,
localeState,
locationState,
luFilesState,
@@ -83,6 +82,7 @@ import { undoHistoryState } from '../../undo/history';
import UndoHistory from '../../undo/undoHistory';
import { logMessage, setError } from '../shared';
import { setRootBotSettingState } from '../setting';
+import { lgFilesSelectorFamily } from '../../selectors/lg';
import { crossTrainConfigState } from './../../atoms/botState';
import { recognizersSelectorFamily } from './../../selectors/recognizers';
@@ -357,14 +357,15 @@ export const initBotState = async (callbackHelpers: CallbackInterface, data: any
let mainDialog = '';
const dialogIds: string[] = [];
- dialogs.forEach((dialog) => {
+
+ for (const dialog of dialogs) {
if (dialog.isRoot) {
mainDialog = dialog.id;
}
- dialog.diagnostics = validateDialog(dialog, schemas.sdk.content, settings, lgFiles, luFiles);
+
set(dialogState({ projectId, dialogId: dialog.id }), dialog);
dialogIds.push(dialog.id);
- });
+ }
set(dialogIdsState(projectId), dialogIds);
set(recognizersSelectorFamily(projectId), recognizers);
@@ -383,7 +384,7 @@ export const initBotState = async (callbackHelpers: CallbackInterface, data: any
set(skillManifestsState(projectId), skillManifests);
set(luFilesState(projectId), initLuFilesStatus(botName, luFiles, dialogs));
- set(lgFilesState(projectId), lgFiles);
+ set(lgFilesSelectorFamily(projectId), lgFiles);
set(jsonSchemaFilesState(projectId), jsonSchemaFiles);
set(dialogSchemasState(projectId), dialogSchemas);
diff --git a/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts b/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts
index fb6a4d0587..4b2e429718 100644
--- a/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/diagnosticsPageSelector.ts
@@ -8,7 +8,7 @@ import formatMessage from 'format-message';
import { getReferredLuFiles } from '../../utils/luUtil';
import { INavTreeItem } from '../../components/NavTree';
-import { botDisplayNameState, qnaFilesState } from '../atoms/botState';
+import { botDisplayNameState, dialogIdsState, qnaFilesState } from '../atoms/botState';
import {
DialogDiagnostic,
LgDiagnostic,
@@ -25,7 +25,6 @@ import {
botProjectIdsState,
dialogSchemasState,
jsonSchemaFilesState,
- lgFilesState,
luFilesState,
projectMetaDataState,
settingsState,
@@ -34,8 +33,10 @@ import {
import { crossTrainConfigState } from './../atoms/botState';
import { formDialogSchemasSelectorFamily, rootBotProjectIdSelector } from './project';
-import { validateDialogsSelectorFamily } from './validatedDialogs';
+import { dialogsSelectorFamily } from './dialogs';
import { recognizersSelectorFamily } from './recognizers';
+import { dialogDiagnosticsSelectorFamily } from './validatedDialogs';
+import { lgFilesSelectorFamily } from './lg';
export const botAssetsSelectFamily = selectorFamily({
key: 'botAssetsSelectFamily',
@@ -43,9 +44,9 @@ export const botAssetsSelectFamily = selectorFamily({
const projectsMetaData = get(projectMetaDataState(projectId));
if (!projectsMetaData || projectsMetaData.isRemote) return null;
- const dialogs = get(validateDialogsSelectorFamily(projectId));
+ const dialogs = get(dialogsSelectorFamily(projectId));
const luFiles = get(luFilesState(projectId));
- const lgFiles = get(lgFilesState(projectId));
+ const lgFiles = get(lgFilesSelectorFamily(projectId));
const setting = get(settingsState(projectId));
const skillManifests = get(skillManifestsState(projectId));
const dialogSchemas = get(dialogSchemasState(projectId));
@@ -151,24 +152,25 @@ export const settingDiagnosticsSelectorFamily = selectorFamily({
},
});
-export const dialogDiagnosticsSelectorFamily = selectorFamily({
- key: 'dialogDiagnosticsSelectorFamily',
+export const dialogsDiagnosticsSelectorFamily = selectorFamily({
+ key: 'dialogsDiagnosticsSelectorFamily',
get: (projectId: string) => ({ get }) => {
const botAssets = get(botAssetsSelectFamily(projectId));
if (botAssets === null) return [];
const rootProjectId = get(rootBotProjectIdSelector) ?? projectId;
+ const dialogIds = get(dialogIdsState(projectId));
const diagnosticList: DiagnosticInfo[] = [];
- const { dialogs } = botAssets;
- dialogs.forEach((dialog) => {
- dialog.diagnostics.forEach((diagnostic) => {
- const location = `${dialog.id}.dialog`;
- diagnosticList.push(new DialogDiagnostic(rootProjectId, projectId, dialog.id, location, diagnostic));
+ dialogIds.forEach((dialogId: string) => {
+ const diagnostics = get(dialogDiagnosticsSelectorFamily({ projectId, dialogId })) || [];
+ diagnostics.forEach((diagnostic) => {
+ const location = `${dialogId}.dialog`;
+ diagnosticList.push(new DialogDiagnostic(rootProjectId, projectId, dialogId, location, diagnostic));
});
});
- return diagnosticList;
+ return [];
},
});
@@ -242,7 +244,7 @@ export const qnaDiagnosticsSelectorFamily = selectorFamily({
export const diagnosticsSelectorFamily = selectorFamily({
key: 'diagnosticsSelector',
get: (projectId: string) => ({ get }) => [
- ...get(dialogDiagnosticsSelectorFamily(projectId)),
+ ...get(dialogsDiagnosticsSelectorFamily(projectId)),
...get(botDiagnosticsSelectorFamily(projectId)),
...get(skillSettingDiagnosticsSelectorFamily(projectId)),
...get(settingDiagnosticsSelectorFamily(projectId)),
diff --git a/Composer/packages/client/src/recoilModel/selectors/dialogImports.ts b/Composer/packages/client/src/recoilModel/selectors/dialogImports.ts
index ff92849130..fc49a8a07a 100644
--- a/Composer/packages/client/src/recoilModel/selectors/dialogImports.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/dialogImports.ts
@@ -6,7 +6,9 @@ import uniqBy from 'lodash/uniqBy';
import { selectorFamily } from 'recoil';
import { getBaseName } from '../../utils/fileUtil';
-import { localeState, lgFilesState, luFilesState } from '../atoms';
+import { localeState, luFilesState } from '../atoms';
+
+import { lgFilesSelectorFamily } from './lg';
// Finds all the file imports starting from a given dialog file.
export const getLanguageFileImports = (
@@ -57,7 +59,7 @@ export const lgImportsSelectorFamily = selectorFamily
- get(lgFilesState(projectId)).find((f) => f.id === fileId || f.id === `${fileId}.${locale}`) as LgFile;
+ get(lgFilesSelectorFamily(projectId)).find((f) => f.id === fileId || f.id === `${fileId}.${locale}`) as LgFile;
// Have to exclude common as a special case
return getLanguageFileImports(dialogId, getFile).filter((i) => i.id !== 'common');
diff --git a/Composer/packages/client/src/recoilModel/selectors/index.ts b/Composer/packages/client/src/recoilModel/selectors/index.ts
index 573cc0c578..f3c6ed49af 100644
--- a/Composer/packages/client/src/recoilModel/selectors/index.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/index.ts
@@ -11,3 +11,4 @@ export * from './projectTemplates';
export * from './skills';
export * from './localRuntimeBuilder';
export * from './diagnosticsPageSelector';
+export * from './lg';
diff --git a/Composer/packages/client/src/recoilModel/selectors/lg.ts b/Composer/packages/client/src/recoilModel/selectors/lg.ts
new file mode 100644
index 0000000000..a6e6d272a9
--- /dev/null
+++ b/Composer/packages/client/src/recoilModel/selectors/lg.ts
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { LgFile } from '@bfc/shared';
+import { selectorFamily } from 'recoil';
+
+import { lgFileIdsState, lgFileState } from '../atoms';
+
+export const lgFilesSelectorFamily = selectorFamily({
+ key: 'lgFiles',
+ get: (projectId: string) => ({ get }) => {
+ const lgFileIds = get(lgFileIdsState(projectId));
+
+ return lgFileIds.map((lgFileId) => {
+ return get(lgFileState({ projectId, lgFileId }));
+ });
+ },
+ set: (projectId: string) => ({ set }, newLgFiles) => {
+ const newLgFileArray = newLgFiles as LgFile[];
+
+ set(
+ lgFileIdsState(projectId),
+ newLgFileArray.map((lgFile) => lgFile.id)
+ );
+ newLgFileArray.forEach((lgFile) => set(lgFileState({ projectId, lgFileId: lgFile.id }), lgFile));
+ },
+});
diff --git a/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts b/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts
index c93b349c72..ead81344dc 100644
--- a/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/localRuntimeBuilder.ts
@@ -20,7 +20,7 @@ import { dispatcherState } from '../DispatcherWrapper';
import { isBuildConfigComplete as isBuildConfigurationComplete, needsBuild } from '../../utils/buildUtil';
import { getSensitiveProperties } from '../dispatchers/utils/project';
-import { validateDialogsSelectorFamily } from './validatedDialogs';
+import { dialogsSelectorFamily } from './dialogs';
import { localBotsWithoutErrorsSelector, rootBotProjectIdSelector } from './project';
export const trackBotStatusesSelector = selectorFamily({
@@ -40,7 +40,7 @@ export const trackBotStatusesSelector = selectorFamily({
export const botBuildRequiredSelector = selectorFamily({
key: 'botBuildRequiredSelector',
get: (projectId: string) => ({ get }) => {
- const dialogs = get(validateDialogsSelectorFamily(projectId));
+ const dialogs = get(dialogsSelectorFamily(projectId));
return !isAbsHosted() && needsBuild(dialogs);
},
});
@@ -53,7 +53,7 @@ export const buildEssentialsSelector = selectorFamily({
luis: settings.luis,
qna: settings.qna,
};
- const dialogs = get(validateDialogsSelectorFamily(projectId));
+ const dialogs = get(dialogsSelectorFamily(projectId));
const luFiles = get(luFilesState(projectId));
const qnaFiles = get(qnaFilesState(projectId));
const buildRequired = get(botBuildRequiredSelector(projectId));
@@ -84,7 +84,7 @@ export const buildConfigurationSelector = selector({
.map((projectId: string) => {
const result = get(buildEssentialsSelector(projectId));
const name = get(botDisplayNameState(projectId));
- const dialogs = get(validateDialogsSelectorFamily(projectId));
+ const dialogs = get(dialogsSelectorFamily(projectId));
const settings = get(settingsState(projectId));
let sensitiveSettings = {};
if (rootBotId) {
diff --git a/Composer/packages/client/src/recoilModel/selectors/project.ts b/Composer/packages/client/src/recoilModel/selectors/project.ts
index 306d210d34..b203820022 100644
--- a/Composer/packages/client/src/recoilModel/selectors/project.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/project.ts
@@ -16,7 +16,6 @@ import {
formDialogSchemaIdsState,
formDialogSchemaState,
luFilesState,
- lgFilesState,
qnaFilesState,
skillManifestsState,
dialogSchemasState,
@@ -32,11 +31,11 @@ import {
import {
dialogsSelectorFamily,
buildEssentialsSelector,
- validateDialogsSelectorFamily,
lgImportsSelectorFamily,
luImportsSelectorFamily,
} from '../selectors';
+import { lgFilesSelectorFamily } from './lg';
// Selector return types
export type TreeDataPerProject = {
isRemote: boolean;
@@ -127,9 +126,9 @@ export const botProjectSpaceSelector = selector({
const botProjects = get(botProjectIdsState);
const result = botProjects.map((projectId: string) => {
const { isRemote, isRootBot } = get(projectMetaDataState(projectId));
- const dialogs = get(validateDialogsSelectorFamily(projectId));
+ const dialogs = get(dialogsSelectorFamily(projectId));
const luFiles = get(luFilesState(projectId));
- const lgFiles = get(lgFilesState(projectId));
+ const lgFiles = get(lgFilesSelectorFamily(projectId));
const qnaFiles = get(qnaFilesState(projectId));
const formDialogSchemas = get(formDialogSchemasSelectorFamily(projectId));
const botProjectFile = get(botProjectFileState(projectId));
@@ -216,7 +215,7 @@ export const perProjectDiagnosticsSelectorFamily = selectorFamily({
const dialogs = get(dialogsSelectorFamily(projectId));
const formDialogSchemas = get(formDialogSchemasSelectorFamily(projectId));
const luFiles = get(luFilesState(projectId));
- const lgFiles = get(lgFilesState(projectId));
+ const lgFiles = get(lgFilesSelectorFamily(projectId));
const setting = get(settingsState(projectId));
const skillManifests = get(skillManifestsState(projectId));
const dialogSchemas = get(dialogSchemasState(projectId));
@@ -277,7 +276,7 @@ export const projectTreeSelectorFamily = selectorFamily<
const projectIds = get(botProjectIdsState);
return projectIds.map((projectId: string) => {
const { isRemote, isRootBot } = get(projectMetaDataState(projectId));
- const dialogs = get(validateDialogsSelectorFamily(projectId));
+ const dialogs = get(dialogsSelectorFamily(projectId));
const sortedDialogs = [...dialogs].sort((x, y) => {
if (x.isRoot) {
return -1;
diff --git a/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts b/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts
index c6b829043d..39d0475f64 100644
--- a/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts
+++ b/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts
@@ -2,52 +2,54 @@
// Licensed under the MIT License.
import { selectorFamily } from 'recoil';
+import { DialogInfo, BotSchemas, LgFile, DialogSetting, RecognizerFile } from '@bfc/shared';
import { validateDialog } from '@bfc/indexers';
-import { DialogInfo, BotSchemas, LgFile, LuFile, DialogSetting, RecognizerFile } from '@bfc/shared';
-
-import {
- botProjectIdsState,
- dialogIdsState,
- schemasState,
- lgFilesState,
- luFilesState,
- dialogState,
- settingsState,
-} from '../atoms';
+
+import { schemasState, dialogState, settingsState, localeState, lgFileState, projectMetaDataState } from '../atoms';
import { getLuProvider } from '../../utils/dialogUtil';
import { recognizersSelectorFamily } from './recognizers';
+import { dialogsSelectorFamily } from './dialogs';
+import { ClientStorage } from './../../utils/storage';
+const dialogCache = new ClientStorage(window.sessionStorage, 'dialogCache');
type validateDialogSelectorFamilyParams = { projectId: string; dialogId: string };
-const validateDialogSelectorFamily = selectorFamily({
- key: 'validateDialogSelectorFamily',
+export const dialogsWithLuProviderSelectorFamily = selectorFamily({
+ key: 'dialogLuProviderSelectorFamily',
+ get: (projectId: string) => ({ get }) => {
+ const dialogs = get(dialogsSelectorFamily(projectId));
+ const recognizers: RecognizerFile[] = get(recognizersSelectorFamily(projectId));
+ return dialogs.map((dialog) => {
+ return {
+ ...dialog,
+ luProvider: getLuProvider(dialog.id, recognizers),
+ };
+ });
+ },
+});
+
+export const dialogDiagnosticsSelectorFamily = selectorFamily({
+ key: 'dialogDiagnosticsSelectorFamily',
get: ({ projectId, dialogId }: validateDialogSelectorFamilyParams) => ({ get }) => {
+ if (get(projectMetaDataState(projectId)).isRemote) return [];
+
const dialog: DialogInfo = get(dialogState({ projectId, dialogId }));
const schemas: BotSchemas = get(schemasState(projectId));
- const lgFiles: LgFile[] = get(lgFilesState(projectId));
- const luFiles: LuFile[] = get(luFilesState(projectId));
+ const locale = get(localeState(projectId));
+ const lgFile: LgFile = get(lgFileState({ projectId, lgFileId: `${dialogId}.${locale}` }));
const settings: DialogSetting = get(settingsState(projectId));
- const recognizers: RecognizerFile[] = get(recognizersSelectorFamily(projectId));
- const luProvider = getLuProvider(dialogId, recognizers);
- return {
- ...dialog,
- diagnostics: validateDialog(dialog, schemas.sdk.content, settings, lgFiles, luFiles),
- luProvider,
- };
- },
-});
+ const cacheId = `${projectId}-${dialogId}`;
-export const validateDialogsSelectorFamily = selectorFamily({
- key: 'validateDialogsSelectorFamily',
- get: (projectId: string) => ({ get }) => {
- const loadedProjects = get(botProjectIdsState);
- if (!loadedProjects.includes(projectId)) {
- return [];
- }
- const dialogIds = get(dialogIdsState(projectId));
-
- return dialogIds.map((dialogId) => {
- return get(validateDialogSelectorFamily({ projectId, dialogId }));
- });
+ const { diagnostics, cache } = validateDialog(
+ dialog,
+ schemas.sdk.content,
+ settings,
+ [lgFile],
+ [],
+ dialogCache.get(cacheId)
+ );
+ dialogCache.set(cacheId, cache);
+
+ return diagnostics;
},
});
diff --git a/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx b/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx
index d4e781d4e9..26f6f9f443 100644
--- a/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx
+++ b/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx
@@ -9,7 +9,6 @@ import { HookResult } from '@botframework-composer/test-utils/lib/hooks';
import { UndoRoot, undoFunctionState, undoHistoryState } from '../history';
import {
- lgFilesState,
luFilesState,
projectMetaDataState,
currentProjectIdState,
@@ -18,7 +17,7 @@ import {
canUndoState,
canRedoState,
} from '../../atoms';
-import { dialogsSelectorFamily } from '../../selectors';
+import { dialogsSelectorFamily, lgFilesSelectorFamily } from '../../selectors';
import { renderRecoilHook } from '../../../../__tests__/testUtils/react-recoil-hooks-testing-library';
import UndoHistory from '../undoHistory';
import { undoStatusSelectorFamily } from '../../selectors/undo';
@@ -72,7 +71,7 @@ describe('', () => {
states: [
{ recoilState: botProjectIdsState, initialValue: [projectId] },
{ recoilState: dialogsSelectorFamily(projectId), initialValue: [{ id: '1', content: '' }] },
- { recoilState: lgFilesState(projectId), initialValue: [{ id: '1.lg' }, { id: '2' }] },
+ { recoilState: lgFilesSelectorFamily(projectId), initialValue: [{ id: '1.lg' }, { id: '2' }] },
{ recoilState: luFilesState(projectId), initialValue: [{ id: '1.lu' }, { id: '2' }] },
{ recoilState: currentProjectIdState, initialValue: projectId },
{ recoilState: undoHistoryState(projectId), initialValue: new UndoHistory(projectId) },
diff --git a/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts b/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts
index 3ed1f116d0..566f6c493f 100644
--- a/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts
+++ b/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts
@@ -3,11 +3,12 @@
import { RecoilState } from 'recoil';
-import { luFilesState, lgFilesState } from '../atoms';
+import { luFilesState } from '../atoms';
import { dialogsSelectorFamily } from '../selectors';
+import { lgFilesSelectorFamily } from '../selectors/lg';
export type AtomAssetsMap = Map, any>;
export const trackedAtoms = (projectId: string): RecoilState[] => {
- return [dialogsSelectorFamily(projectId), luFilesState(projectId), lgFilesState(projectId)];
+ return [dialogsSelectorFamily(projectId), luFilesState(projectId), lgFilesSelectorFamily(projectId)];
};
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index 65ae0c9dde..af3627dfed 100644
--- a/Composer/packages/client/src/shell/useShell.ts
+++ b/Composer/packages/client/src/shell/useShell.ts
@@ -27,23 +27,22 @@ import {
settingsState,
clipboardActionsState,
schemasState,
- validateDialogsSelectorFamily,
focusPathState,
localeState,
qnaFilesState,
designPageLocationState,
botDisplayNameState,
dialogSchemasState,
- lgFilesState,
luFilesState,
rateInfoState,
rootBotProjectIdSelector,
featureFlagsState,
} from '../recoilModel';
import { undoFunctionState } from '../recoilModel/undo/history';
-import { skillsStateSelector } from '../recoilModel/selectors';
+import { dialogsWithLuProviderSelectorFamily, skillsStateSelector } from '../recoilModel/selectors';
import { navigateTo } from '../utils/navigation';
import TelemetryClient from '../telemetry/TelemetryClient';
+import { lgFilesSelectorFamily } from '../recoilModel/selectors/lg';
import { useLgApi } from './lgApi';
import { useLuApi } from './luApi';
@@ -79,7 +78,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
const dialogMapRef = useRef({});
const schemas = useRecoilValue(schemasState(projectId));
- const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
+ const dialogs = useRecoilValue(dialogsWithLuProviderSelectorFamily(projectId));
const focusPath = useRecoilValue(focusPathState(projectId));
const skills = useRecoilValue(skillsStateSelector);
const locale = useRecoilValue(localeState(projectId));
@@ -88,7 +87,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
const designPageLocation = useRecoilValue(designPageLocationState(projectId));
const { undo, redo, commitChanges } = undoFunction;
const luFiles = useRecoilValue(luFilesState(projectId));
- const lgFiles = useRecoilValue(lgFilesState(projectId));
+ const lgFiles = useRecoilValue(lgFilesSelectorFamily(projectId));
const dialogSchemas = useRecoilValue(dialogSchemasState(projectId));
const botName = useRecoilValue(botDisplayNameState(projectId));
const settings = useRecoilValue(settingsState(projectId));
diff --git a/Composer/packages/lib/indexers/src/validations/expressionValidation/index.ts b/Composer/packages/lib/indexers/src/validations/expressionValidation/index.ts
index e103523010..23013ccd81 100644
--- a/Composer/packages/lib/indexers/src/validations/expressionValidation/index.ts
+++ b/Composer/packages/lib/indexers/src/validations/expressionValidation/index.ts
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-import { MicrosoftIDialog, Diagnostic, LgFile, DialogSetting } from '@bfc/shared';
+import { MicrosoftIDialog, Diagnostic, LgFile, DialogSetting, DiagnosticSeverity, LuFile } from '@bfc/shared';
import { SchemaDefinitions } from '@bfc/shared/lib/schemaUtils/types';
import { extractOptionByKey } from '../../utils/lgUtil';
import { searchExpressions } from './searchExpression';
-import { ValidateFunc } from './types';
-import { validate } from './validation';
+import { ExpressionParseResult, ValidateFunc } from './types';
+import { checkExpression, checkReturnType, filterCustomFunctionError } from './validation';
const NamespaceKey = '@namespace';
const ExportsKey = '@exports';
@@ -37,16 +37,29 @@ export const validateExpressions: ValidateFunc = (
type: string,
schema: SchemaDefinitions,
settings: DialogSetting,
- lgFiles: LgFile[]
+ lgFiles: LgFile[],
+ luFiles: LuFile[],
+ cache?: ExpressionParseResult
) => {
const expressions = searchExpressions(path, value, type, schema);
const customFunctions = searchLgCustomFunction(lgFiles).concat(settings.customFunctions);
-
+ const newCache = {};
const diagnostics = expressions.reduce((diagnostics: Diagnostic[], expression) => {
- const diagnostic = validate(expression, customFunctions);
- if (diagnostic) diagnostics.push(diagnostic);
+ const { required, path, types, value } = expression;
+ let errorMessage = '';
+
+ try {
+ newCache[value] = cache?.[value] ? cache[value] : checkExpression(value, required);
+ errorMessage = checkReturnType(newCache[value], types);
+ } catch (error) {
+ errorMessage = filterCustomFunctionError(error.message, customFunctions);
+ }
+
+ if (!errorMessage) return diagnostics;
+
+ diagnostics.push(new Diagnostic(errorMessage, '', DiagnosticSeverity.Error, path));
+
return diagnostics;
}, []);
-
- return diagnostics;
+ return { diagnostics, cache: newCache };
};
diff --git a/Composer/packages/lib/indexers/src/validations/expressionValidation/types.ts b/Composer/packages/lib/indexers/src/validations/expressionValidation/types.ts
index 8915244b6d..21201f655d 100644
--- a/Composer/packages/lib/indexers/src/validations/expressionValidation/types.ts
+++ b/Composer/packages/lib/indexers/src/validations/expressionValidation/types.ts
@@ -13,6 +13,8 @@ export const StringMapExpressionType = {
integer: ReturnType.Number,
};
+export type ExpressionParseResult = { [content: string]: number };
+
export type ValidateFunc = (
path: string,
value: MicrosoftIDialog,
@@ -20,8 +22,9 @@ export type ValidateFunc = (
schema: SchemaDefinitions,
setting: DialogSetting,
lgFiles: LgFile[],
- luFiles: LuFile[]
-) => Diagnostic[] | null; // error msg
+ luFiles: LuFile[],
+ cache?: ExpressionParseResult
+) => { diagnostics: Diagnostic[] | null; cache: ExpressionParseResult }; // error msg
export type ExpressionProperty = {
value: any;
diff --git a/Composer/packages/lib/indexers/src/validations/expressionValidation/validation.ts b/Composer/packages/lib/indexers/src/validations/expressionValidation/validation.ts
index 2ee43207bc..f7e4b2b9f6 100644
--- a/Composer/packages/lib/indexers/src/validations/expressionValidation/validation.ts
+++ b/Composer/packages/lib/indexers/src/validations/expressionValidation/validation.ts
@@ -4,9 +4,6 @@
/* eslint-disable no-bitwise */
import { Expression, ReturnType } from 'adaptive-expressions';
import formatMessage from 'format-message';
-import { Diagnostic } from '@bfc/shared';
-
-import { ExpressionProperty } from './types';
const EMPTY = formatMessage(`is missing or empty`);
const RETURNTYPE_NOT_MATCH = formatMessage('the return type does not match');
@@ -69,7 +66,7 @@ export const checkExpression = (exp: any, required: boolean): number => {
//The return type should match the schema type
// the return type use binary number to store
// if returnType = 24, the expression result is 16+8. so the type is string or array
-const checkReturnType = (returnType: number, types: number[]): string => {
+export const checkReturnType = (returnType: number, types: number[]): string => {
// if return type contain object do nothing.
if (returnType & ReturnType.Object) return '';
@@ -77,7 +74,7 @@ const checkReturnType = (returnType: number, types: number[]): string => {
};
//string match or * match
-const checkCustomFunctions = (currentFunction: string, customFunction: string) => {
+export const checkCustomFunctions = (currentFunction: string, customFunction: string) => {
//.* or * => #
let customReg = customFunction.replace(/\.\*|\*/g, '#');
//. => [.]
@@ -90,7 +87,7 @@ const checkCustomFunctions = (currentFunction: string, customFunction: string) =
return reg.test(currentFunction);
};
-const filterCustomFunctionError = (error: string, CustomFunctions: string[]): string => {
+export const filterCustomFunctionError = (error: string, CustomFunctions: string[]): string => {
//Now all customFunctions is from lg file content.
//If the custom functions are defined in runtime, use the field from settings to filter
if (error.endsWith(BUILT_IN_FUNCTION_ERROR)) {
@@ -104,21 +101,3 @@ const filterCustomFunctionError = (error: string, CustomFunctions: string[]): st
return expressionErrorMessage(error);
};
-
-export const validate = (expression: ExpressionProperty, customFunctions: string[]): Diagnostic | null => {
- const { required, path, types, value } = expression;
- let errorMessage = '';
-
- try {
- const returnType = checkExpression(value, required);
- errorMessage = checkReturnType(returnType, types);
- } catch (error) {
- errorMessage = filterCustomFunctionError(error.message, customFunctions);
- }
-
- if (!errorMessage) return null;
-
- const diagnostic = new Diagnostic(errorMessage, '');
- diagnostic.path = path;
- return diagnostic;
-};
diff --git a/Composer/packages/lib/indexers/src/validations/index.ts b/Composer/packages/lib/indexers/src/validations/index.ts
index 4a8787ab65..d88ead354e 100644
--- a/Composer/packages/lib/indexers/src/validations/index.ts
+++ b/Composer/packages/lib/indexers/src/validations/index.ts
@@ -6,7 +6,7 @@ import has from 'lodash/has';
import { JsonWalk, VisitorFunc } from '..';
import { validateExpressions } from './expressionValidation/index';
-import { ValidateFunc } from './expressionValidation/types';
+import { ExpressionParseResult, ValidateFunc } from './expressionValidation/types';
export const validateFuncs: { [type: string]: ValidateFunc[] } = {
'.': [validateExpressions], //this will check all types
@@ -17,11 +17,13 @@ export function validateDialog(
schema: SchemaDefinitions,
settings: DialogSetting,
lgFiles: LgFile[],
- luFiles: LuFile[]
-): Diagnostic[] {
+ luFiles: LuFile[],
+ cache?: ExpressionParseResult
+): { diagnostics: Diagnostic[] | null; cache?: ExpressionParseResult } {
const { id, content } = dialog;
try {
const diagnostics: Diagnostic[] = [];
+ let newCache: ExpressionParseResult = {};
/**
*
* @param path , jsonPath string
@@ -29,7 +31,7 @@ export function validateDialog(
* @return boolean, true to stop walk
* */
const visitor: VisitorFunc = (path: string, value: any): boolean => {
- if (has(value, '$kind')) {
+ if (has(value, '$kind') && value.$kind) {
const allChecks = [...validateFuncs['.']];
const checkerFunc = validateFuncs[value.$kind];
if (checkerFunc) {
@@ -37,35 +39,33 @@ export function validateDialog(
}
allChecks.forEach((func) => {
- const result = func(path, value, value.$kind, schema.definitions[value.$kind], settings, lgFiles, luFiles);
- if (result) {
- diagnostics.push(...result);
+ const result = func(
+ path,
+ value,
+ value.$kind,
+ schema.definitions[value.$kind],
+ settings,
+ lgFiles,
+ luFiles,
+ cache
+ );
+ if (result.diagnostics) {
+ diagnostics.push(...result.diagnostics);
}
+ newCache = { ...newCache, ...result.cache };
});
}
return false;
};
JsonWalk(id, content, visitor);
- return diagnostics.map((e) => {
- e.source = id;
- return e;
- });
+ return {
+ diagnostics: diagnostics.map((e) => {
+ e.source = id;
+ return e;
+ }),
+ cache: newCache,
+ };
} catch (error) {
- return [new Diagnostic(error.message, id)];
+ return { diagnostics: [new Diagnostic(error.message, id)], cache };
}
}
-
-export function validateDialogs(
- dialogs: DialogInfo[],
- schema: SchemaDefinitions,
- lgFiles: LgFile[],
- luFiles: LuFile[],
- settings: DialogSetting
-): { [id: string]: Diagnostic[] } {
- const diagnosticsMap = {};
- dialogs.forEach((dialog) => {
- diagnosticsMap[dialog.id] = validateDialog(dialog, schema, settings, lgFiles, luFiles);
- });
-
- return diagnosticsMap;
-}