diff --git a/Composer/packages/client/__tests__/components/design.test.tsx b/Composer/packages/client/__tests__/components/design.test.tsx index c893547ab9..aa4ad6edf5 100644 --- a/Composer/packages/client/__tests__/components/design.test.tsx +++ b/Composer/packages/client/__tests__/components/design.test.tsx @@ -9,7 +9,7 @@ import { SAMPLE_DIALOG } from '../mocks/sampleDialog'; import { ProjectTree } from '../../src/components/ProjectTree/ProjectTree'; import { TriggerCreationModal } from '../../src/components/ProjectTree/TriggerCreationModal'; import { CreateDialogModal } from '../../src/pages/design/createDialogModal'; -import { dialogsState, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel'; +import { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel'; jest.mock('@bfc/code-editor', () => { return { @@ -22,7 +22,7 @@ const dialogs = [SAMPLE_DIALOG]; const initRecoilState = ({ set }) => { set(currentProjectIdState, projectId); set(botProjectIdsState, [projectId]); - set(dialogsState(projectId), dialogs); + set(dialogsSelectorFamily(projectId), dialogs); set(schemasState(projectId), { sdk: { content: {} } }); }; diff --git a/Composer/packages/client/__tests__/components/projecttree.test.tsx b/Composer/packages/client/__tests__/components/projecttree.test.tsx index b23859a3d4..c287e5e39a 100644 --- a/Composer/packages/client/__tests__/components/projecttree.test.tsx +++ b/Composer/packages/client/__tests__/components/projecttree.test.tsx @@ -7,7 +7,7 @@ import { fireEvent } from '@botframework-composer/test-utils'; import { ProjectTree } from '../../src/components/ProjectTree/ProjectTree'; import { renderWithRecoil } from '../testUtils'; import { SAMPLE_DIALOG } from '../mocks/sampleDialog'; -import { dialogsState, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel'; +import { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel'; const projectId = '12345.6789'; const dialogs = [SAMPLE_DIALOG]; @@ -15,7 +15,7 @@ const dialogs = [SAMPLE_DIALOG]; const initRecoilState = ({ set }) => { set(currentProjectIdState, projectId); set(botProjectIdsState, [projectId]); - set(dialogsState(projectId), dialogs); + set(dialogsSelectorFamily(projectId), dialogs); set(schemasState(projectId), { sdk: { content: {} } }); }; diff --git a/Composer/packages/client/__tests__/pages/knowledge-base/QnAPage.test.tsx b/Composer/packages/client/__tests__/pages/knowledge-base/QnAPage.test.tsx index 62946c4246..a9b1a40002 100644 --- a/Composer/packages/client/__tests__/pages/knowledge-base/QnAPage.test.tsx +++ b/Composer/packages/client/__tests__/pages/knowledge-base/QnAPage.test.tsx @@ -9,7 +9,7 @@ import CodeEditor from '../../../src/pages/knowledge-base/code-editor'; import { renderWithRecoil } from '../../testUtils'; import { localeState, - dialogsState, + dialogsSelectorFamily, qnaFilesState, settingsState, schemasState, @@ -57,7 +57,7 @@ const updateQnAFileMock = jest.fn(); const initRecoilState = ({ set }) => { set(currentProjectIdState, state.projectId); set(localeState(state.projectId), state.locale); - set(dialogsState(state.projectId), state.dialogs); + set(dialogsSelectorFamily(state.projectId), state.dialogs); set(qnaFilesState(state.projectId), state.qnaFiles); set(settingsState(state.projectId), state.settings); set(schemasState(state.projectId), mockProjectResponse.schemas); 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 81a976f4d7..34e95360e0 100644 --- a/Composer/packages/client/__tests__/pages/language-generation/LGPage.test.tsx +++ b/Composer/packages/client/__tests__/pages/language-generation/LGPage.test.tsx @@ -12,7 +12,7 @@ import { lgFilesState, settingsState, schemasState, - dialogsState, + dialogsSelectorFamily, currentProjectIdState, } from '../../../src/recoilModel'; import mockProjectResponse from '../../../src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json'; @@ -50,7 +50,7 @@ const state = { const initRecoilState = ({ set }) => { set(currentProjectIdState, state.projectId); set(localeState(state.projectId), state.locale); - set(dialogsState(state.projectId), state.dialogs); + set(dialogsSelectorFamily(state.projectId), state.dialogs); set(lgFilesState(state.projectId), state.lgFiles); set(settingsState(state.projectId), state.settings); set(schemasState(state.projectId), mockProjectResponse.schemas); diff --git a/Composer/packages/client/__tests__/pages/language-understanding/LUPage.test.tsx b/Composer/packages/client/__tests__/pages/language-understanding/LUPage.test.tsx index 8c7927ddac..269416c1d9 100644 --- a/Composer/packages/client/__tests__/pages/language-understanding/LUPage.test.tsx +++ b/Composer/packages/client/__tests__/pages/language-understanding/LUPage.test.tsx @@ -9,7 +9,7 @@ import CodeEditor from '../../../src/pages/language-understanding/code-editor'; import { renderWithRecoil } from '../../testUtils'; import { localeState, - dialogsState, + dialogsSelectorFamily, luFilesState, settingsState, schemasState, @@ -49,7 +49,7 @@ const state = { const initRecoilState = ({ set }) => { set(currentProjectIdState, state.projectId); set(localeState(state.projectId), state.locale); - set(dialogsState(state.projectId), state.dialogs); + set(dialogsSelectorFamily(state.projectId), state.dialogs); set(luFilesState(state.projectId), state.luFiles); set(settingsState(state.projectId), state.settings); set(schemasState(state.projectId), mockProjectResponse.schemas); diff --git a/Composer/packages/client/__tests__/pages/notifications/useNotifications.test.tsx b/Composer/packages/client/__tests__/pages/notifications/useNotifications.test.tsx index 7cf428539d..d6d060c28d 100644 --- a/Composer/packages/client/__tests__/pages/notifications/useNotifications.test.tsx +++ b/Composer/packages/client/__tests__/pages/notifications/useNotifications.test.tsx @@ -8,7 +8,7 @@ import { Range, Position } from '@bfc/shared'; import useNotifications from '../../../src/pages/notifications/useNotifications'; import { - dialogsState, + dialogsSelectorFamily, luFilesState, lgFilesState, settingsState, @@ -109,7 +109,7 @@ const state = { const initRecoilState = ({ set }) => { set(currentProjectIdState, state.projectId); set(botProjectIdsState, [state.projectId]); - set(dialogsState(state.projectId), state.dialogs); + set(dialogsSelectorFamily(state.projectId), state.dialogs); set(luFilesState(state.projectId), state.luFiles); set(lgFilesState(state.projectId), state.lgFiles); set(jsonSchemaFilesState(state.projectId), state.jsonSchemaFiles); diff --git a/Composer/packages/client/__tests__/shell/triggerApi.test.tsx b/Composer/packages/client/__tests__/shell/triggerApi.test.tsx index 92aef42c06..6ccd943fc6 100644 --- a/Composer/packages/client/__tests__/shell/triggerApi.test.tsx +++ b/Composer/packages/client/__tests__/shell/triggerApi.test.tsx @@ -10,7 +10,7 @@ import { localeState, luFilesState, lgFilesState, - dialogsState, + dialogsSelectorFamily, schemasState, dispatcherState, currentProjectIdState, @@ -57,7 +57,7 @@ describe('use triggerApi hooks', () => { set(localeState(state.projectId), 'en-us'); set(luFilesState(state.projectId), state.luFiles); set(lgFilesState(state.projectId), state.lgFiles); - set(dialogsState(state.projectId), state.dialogs); + set(dialogsSelectorFamily(state.projectId), state.dialogs); set(schemasState(state.projectId), state.schemas); set(dispatcherState, (current: Dispatcher) => ({ ...current, diff --git a/Composer/packages/client/src/Onboarding/Onboarding.tsx b/Composer/packages/client/src/Onboarding/Onboarding.tsx index 50817bf654..49eb30266d 100644 --- a/Composer/packages/client/src/Onboarding/Onboarding.tsx +++ b/Composer/packages/client/src/Onboarding/Onboarding.tsx @@ -9,7 +9,7 @@ import { useRecoilValue } from 'recoil'; import onboardingStorage from '../utils/onboardingStorage'; import { OpenConfirmModal } from '../components/Modal/ConfirmDialog'; import { useLocation } from '../utils/hooks'; -import { dispatcherState, onboardingState, botProjectIdsState, validateDialogSelectorFamily } from '../recoilModel'; +import { dispatcherState, onboardingState, botProjectIdsState, validateDialogsSelectorFamily } 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(validateDialogSelectorFamily(rootBotProjectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(rootBotProjectId)); const { onboardingSetComplete } = useRecoilValue(dispatcherState); const onboarding = useRecoilValue(onboardingState); const complete = onboarding.complete; diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx index ff4707b4be..73cdf3a95e 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 { userSettingsState } from '../../recoilModel/atoms'; import { nameRegex } from '../../constants'; import { isRegExRecognizerType, isLUISnQnARecognizerType } from '../../utils/dialogValidator'; -import { validateDialogSelectorFamily } from '../../recoilModel'; +import { validateDialogsSelectorFamily } from '../../recoilModel'; // -------------------- Styles -------------------- // const styles = { @@ -209,7 +209,7 @@ interface TriggerCreationModalProps { export const TriggerCreationModal: React.FC = (props) => { const { isOpen, onDismiss, onSubmit, dialogId, projectId } = props; - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const userSettings = useRecoilValue(userSettingsState); const dialogFile = dialogs.find((dialog) => dialog.id === dialogId); const isRegEx = isRegExRecognizerType(dialogFile); diff --git a/Composer/packages/client/src/components/TestController/TestController.tsx b/Composer/packages/client/src/components/TestController/TestController.tsx index 1bdd05e62d..636da59093 100644 --- a/Composer/packages/client/src/components/TestController/TestController.tsx +++ b/Composer/packages/client/src/components/TestController/TestController.tsx @@ -14,7 +14,7 @@ import { useRecognizerConfig } from '@bfc/extension-client'; import { botEndpointsState, dispatcherState, - validateDialogSelectorFamily, + validateDialogsSelectorFamily, botStatusState, botDisplayNameState, luFilesState, @@ -61,7 +61,7 @@ export const TestController: React.FC<{ projectId: string }> = (props) => { const botActionRef = useRef(null); const notifications = useNotifications(projectId); - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const botStatus = useRecoilValue(botStatusState(projectId)); const botName = useRecoilValue(botDisplayNameState(projectId)); const luFiles = useRecoilValue(luFilesState(projectId)); diff --git a/Composer/packages/client/src/components/TestController/publishDialog.tsx b/Composer/packages/client/src/components/TestController/publishDialog.tsx index b4464b3898..45a439c511 100644 --- a/Composer/packages/client/src/components/TestController/publishDialog.tsx +++ b/Composer/packages/client/src/components/TestController/publishDialog.tsx @@ -22,7 +22,7 @@ import { Text, Tips, Links, nameRegex } from '../../constants'; import { FieldConfig, useForm } from '../../hooks/useForm'; import { getReferredQnaFiles } from '../../utils/qnaUtil'; import { getReferredLuFiles } from '../../utils/luUtil'; -import { dialogsState, luFilesState, qnaFilesState } from '../../recoilModel'; +import { dialogsSelectorFamily, luFilesState, qnaFilesState } from '../../recoilModel'; // -------------------- Styles -------------------- // const textFieldLabel = css` @@ -105,7 +105,7 @@ interface IPublishDialogProps { export const PublishDialog: React.FC = (props) => { const { isOpen, onDismiss, onPublish, botName, config, projectId } = props; - const dialogs = useRecoilValue(dialogsState(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/hooks/useResolver.ts b/Composer/packages/client/src/hooks/useResolver.ts index 7637ceb894..f9eb2cf9c4 100644 --- a/Composer/packages/client/src/hooks/useResolver.ts +++ b/Composer/packages/client/src/hooks/useResolver.ts @@ -4,10 +4,10 @@ import { useRef } from 'react'; import { lgImportResolverGenerator } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; -import { dialogsState, luFilesState, lgFilesState, localeState, qnaFilesState } from '../recoilModel'; +import { dialogsSelectorFamily, luFilesState, lgFilesState, localeState, qnaFilesState } from '../recoilModel'; export const useResolvers = (projectId: string) => { - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const luFiles = useRecoilValue(luFilesState(projectId)); const lgFiles = useRecoilValue(lgFilesState(projectId)); const locale = useRecoilValue(localeState(projectId)); diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 8b2f3b97d5..85426c2df2 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -42,7 +42,7 @@ import { dispatcherState, schemasState, displaySkillManifestState, - validateDialogSelectorFamily, + validateDialogsSelectorFamily, breadcrumbState, focusPathState, showCreateDialogModalState, @@ -116,7 +116,7 @@ const DesignPage: React.FC = (props) => { const { openNewTriggerModal, onFocus, onBlur } = props; const [triggerButtonVisible, setTriggerButtonVisibility] = useState(false); const { onboardingAddCoachMarkRef } = useRecoilValue(dispatcherState); - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(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 3e469ac141..d8373a7f80 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, schemasState, validateDialogSelectorFamily } from '../../recoilModel'; +import { actionsSeedState, schemasState, validateDialogsSelectorFamily } from '../../recoilModel'; import { name, description, styles as wizardStyles } from './styles'; @@ -36,7 +36,7 @@ export const CreateDialogModal: React.FC = (props) => { const { onSubmit, onDismiss, isOpen, projectId } = props; const schemas = useRecoilValue(schemasState(projectId)); - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(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 e6373e4e26..7df85c3eab 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, validateDialogSelectorFamily } from '../../../../recoilModel'; +import { dispatcherState, validateDialogsSelectorFamily } 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(validateDialogSelectorFamily(projectId)); + const items = useRecoilValue(validateDialogsSelectorFamily(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(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const items = useMemo(() => dialogs.map(({ id, content, displayName }) => ({ id, content, displayName })), [ projectId, ]); diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx index 2f0f8b220f..56d8cd156d 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/content/SelectTriggers.tsx @@ -12,7 +12,7 @@ import formatMessage from 'format-message'; import { ContentProps } from '../constants'; import { getFriendlyName } from '../../../../utils/dialogUtil'; import { isSupportedTrigger } from '../generateSkillManifest'; -import { dialogsState, schemasState } from '../../../../recoilModel'; +import { dialogsSelectorFamily, schemasState } from '../../../../recoilModel'; import { SelectItems } from './SelectItems'; @@ -22,7 +22,7 @@ const getLabel = (kind: SDKKinds, uiSchema) => { }; export const SelectTriggers: React.FC = ({ setSelectedTriggers, projectId }) => { - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const schemas = useRecoilValue(schemasState(projectId)); const items = useMemo(() => { diff --git a/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx b/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx index 55a418fd90..644e97d970 100644 --- a/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx +++ b/Composer/packages/client/src/pages/design/exportSkillModal/index.tsx @@ -16,7 +16,7 @@ import { dispatcherState, skillManifestsState, qnaFilesState, - dialogsState, + dialogsSelectorFamily, dialogSchemasState, luFilesState, } from '../../../recoilModel'; @@ -33,7 +33,7 @@ interface ExportSkillModalProps { } const ExportSkillModal: React.FC = ({ onSubmit, onDismiss: handleDismiss, projectId }) => { - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const dialogSchemas = useRecoilValue(dialogSchemasState(projectId)); const luFiles = useRecoilValue(luFilesState(projectId)); const qnaFiles = useRecoilValue(qnaFilesState(projectId)); diff --git a/Composer/packages/client/src/pages/form-dialog/CreateFormDialogSchemaModal.tsx b/Composer/packages/client/src/pages/form-dialog/CreateFormDialogSchemaModal.tsx index 5de3c00521..4d7ad9ae97 100644 --- a/Composer/packages/client/src/pages/form-dialog/CreateFormDialogSchemaModal.tsx +++ b/Composer/packages/client/src/pages/form-dialog/CreateFormDialogSchemaModal.tsx @@ -12,7 +12,7 @@ import { useRecoilValue } from 'recoil'; import { nameRegex } from '../../constants'; import { FieldConfig, useForm } from '../../hooks/useForm'; -import { dialogsState } from '../../recoilModel'; +import { dialogsSelectorFamily } from '../../recoilModel'; type FormDialogSchemaFormData = { name: string; @@ -28,7 +28,7 @@ type Props = { const CreateFormDialogSchemaModal: React.FC = (props) => { const { isOpen, projectId, onSubmit, onDismiss } = props; - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const formConfig: FieldConfig = { name: { diff --git a/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx b/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx index e3a7bdaa32..13dfc69ab6 100644 --- a/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx +++ b/Composer/packages/client/src/pages/knowledge-base/QnAPage.tsx @@ -14,7 +14,7 @@ import { navigateTo } from '../../utils/navigation'; import { TestController } from '../../components/TestController/TestController'; import { INavTreeItem } from '../../components/NavTree'; import { Page } from '../../components/Page'; -import { dialogsState, qnaFilesState } from '../../recoilModel/atoms/botState'; +import { dialogsSelectorFamily, qnaFilesState } from '../../recoilModel'; import { dispatcherState } from '../../recoilModel'; import { CreateQnAModal } from '../../components/QnA'; @@ -31,7 +31,7 @@ const QnAPage: React.FC = (props) => { const { dialogId = '', projectId = '' } = props; const actions = useRecoilValue(dispatcherState); - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const qnaFiles = useRecoilValue(qnaFilesState(projectId)); //To do: support other languages const locale = 'en-us'; diff --git a/Composer/packages/client/src/pages/knowledge-base/table-view.tsx b/Composer/packages/client/src/pages/knowledge-base/table-view.tsx index fa124aa594..e45b633fb1 100644 --- a/Composer/packages/client/src/pages/knowledge-base/table-view.tsx +++ b/Composer/packages/client/src/pages/knowledge-base/table-view.tsx @@ -32,7 +32,7 @@ import { NeutralColors } from '@uifabric/fluent-theme'; import emptyQnAIcon from '../../images/emptyQnAIcon.svg'; import { navigateTo } from '../../utils/navigation'; -import { dialogsState, qnaFilesState, localeState } from '../../recoilModel/atoms/botState'; +import { dialogsSelectorFamily, qnaFilesState, localeState } from '../../recoilModel'; import { dispatcherState } from '../../recoilModel'; import { getBaseName } from '../../utils/fileUtil'; import { EditableField } from '../../components/EditableField'; @@ -81,7 +81,7 @@ interface TableViewProps extends RouteComponentProps<{}> { const TableView: React.FC = (props) => { const { dialogId = '', projectId = '' } = props; const actions = useRecoilValue(dispatcherState); - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const qnaFiles = useRecoilValue(qnaFilesState(projectId)); const locale = useRecoilValue(localeState(projectId)); const { diff --git a/Composer/packages/client/src/pages/language-generation/LGPage.tsx b/Composer/packages/client/src/pages/language-generation/LGPage.tsx index 66921b4237..b3f914871d 100644 --- a/Composer/packages/client/src/pages/language-generation/LGPage.tsx +++ b/Composer/packages/client/src/pages/language-generation/LGPage.tsx @@ -14,7 +14,7 @@ import { navigateTo } from '../../utils/navigation'; import { TestController } from '../../components/TestController/TestController'; import { INavTreeItem } from '../../components/NavTree'; import { Page } from '../../components/Page'; -import { validateDialogSelectorFamily } from '../../recoilModel'; +import { validateDialogsSelectorFamily } from '../../recoilModel'; import TableView from './table-view'; const CodeEditor = React.lazy(() => import('./code-editor')); @@ -26,7 +26,7 @@ interface LGPageProps { const LGPage: React.FC> = (props: RouteComponentProps) => { const { dialogId = '', projectId = '' } = props; - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const path = props.location?.pathname ?? ''; 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 cdead54f70..a020bfc8d9 100644 --- a/Composer/packages/client/src/pages/language-generation/table-view.tsx +++ b/Composer/packages/client/src/pages/language-generation/table-view.tsx @@ -27,7 +27,7 @@ import { lgFilesState, localeState, settingsState, - validateDialogSelectorFamily, + validateDialogsSelectorFamily, } from '../../recoilModel'; import { languageListTemplates } from '../../components/MultiLanguage'; @@ -42,7 +42,7 @@ const TableView: React.FC = (props) => { const lgFiles = useRecoilValue(lgFilesState(projectId)); const locale = useRecoilValue(localeState(projectId)); const settings = useRecoilValue(settingsState(projectId)); - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const { createLgTemplate, copyLgTemplate, removeLgTemplate, setMessage, updateLgTemplate } = useRecoilValue( dispatcherState ); diff --git a/Composer/packages/client/src/pages/language-understanding/LUPage.tsx b/Composer/packages/client/src/pages/language-understanding/LUPage.tsx index 87ed81d6be..8460bac7c1 100644 --- a/Composer/packages/client/src/pages/language-understanding/LUPage.tsx +++ b/Composer/packages/client/src/pages/language-understanding/LUPage.tsx @@ -13,7 +13,7 @@ import { LoadingSpinner } from '../../components/LoadingSpinner'; import { TestController } from '../../components/TestController/TestController'; import { INavTreeItem } from '../../components/NavTree'; import { Page } from '../../components/Page'; -import { validateDialogSelectorFamily } from '../../recoilModel'; +import { validateDialogsSelectorFamily } from '../../recoilModel'; import TableView from './table-view'; const CodeEditor = React.lazy(() => import('./code-editor')); @@ -23,7 +23,7 @@ const LUPage: React.FC> = (props) => { const { dialogId = '', projectId = '' } = props; - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const path = props.location?.pathname ?? ''; const edit = /\/edit(\/)?$/.test(path); 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 9466ec6644..20c34ffc32 100644 --- a/Composer/packages/client/src/pages/language-understanding/table-view.tsx +++ b/Composer/packages/client/src/pages/language-understanding/table-view.tsx @@ -29,7 +29,7 @@ import { luFilesState, localeState, settingsState, - validateDialogSelectorFamily, + validateDialogsSelectorFamily, } from '../../recoilModel'; import { formCell, luPhraseCell, tableCell, editableFieldContainer } from './styles'; @@ -54,7 +54,7 @@ const TableView: React.FC = (props) => { const luFiles = useRecoilValue(luFilesState(projectId)); const locale = useRecoilValue(localeState(projectId)); const settings = useRecoilValue(settingsState(projectId)); - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const { languages, defaultLanguage } = settings; diff --git a/Composer/packages/client/src/pages/notifications/useNotifications.tsx b/Composer/packages/client/src/pages/notifications/useNotifications.tsx index 2d07abd037..bc3dbd08c6 100644 --- a/Composer/packages/client/src/pages/notifications/useNotifications.tsx +++ b/Composer/packages/client/src/pages/notifications/useNotifications.tsx @@ -18,7 +18,7 @@ import { qnaFilesState, settingsState, skillManifestsState, - validateDialogSelectorFamily, + validateDialogsSelectorFamily, } from '../../recoilModel'; import { getReferredLuFiles } from './../../utils/luUtil'; @@ -34,7 +34,7 @@ import { } from './types'; export default function useNotifications(projectId: string, filter?: string) { - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const luFiles = useRecoilValue(luFilesState(projectId)); const lgFiles = useRecoilValue(lgFilesState(projectId)); const diagnostics = useRecoilValue(botDiagnosticsState(projectId)); diff --git a/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx b/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx index c6f7852039..5480f16d67 100644 --- a/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx +++ b/Composer/packages/client/src/recoilModel/DispatcherWrapper.tsx @@ -10,11 +10,11 @@ import { BotAssets } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; import isEmpty from 'lodash/isEmpty'; +import { dialogsSelectorFamily } from './selectors'; import { UndoRoot } from './undo/history'; import { prepareAxios } from './../utils/auth'; import createDispatchers, { Dispatcher } from './dispatchers'; import { - dialogsState, luFilesState, qnaFilesState, lgFilesState, @@ -29,7 +29,7 @@ import { botsForFilePersistenceSelector, formDialogSchemasSelectorFamily } from const getBotAssets = async (projectId, snapshot: Snapshot): Promise => { const result = await Promise.all([ - snapshot.getPromise(dialogsState(projectId)), + snapshot.getPromise(dialogsSelectorFamily(projectId)), snapshot.getPromise(luFilesState(projectId)), snapshot.getPromise(qnaFilesState(projectId)), snapshot.getPromise(lgFilesState(projectId)), diff --git a/Composer/packages/client/src/recoilModel/atoms/botState.ts b/Composer/packages/client/src/recoilModel/atoms/botState.ts index 0ecd54c188..db2582625d 100644 --- a/Composer/packages/client/src/recoilModel/atoms/botState.ts +++ b/Composer/packages/client/src/recoilModel/atoms/botState.ts @@ -28,9 +28,33 @@ const getFullyQualifiedKey = (value: string) => { return `Bot_${value}_State`; }; -export const dialogsState = atomFamily({ - key: getFullyQualifiedKey('dialogs'), - default: (id) => { +const emptyDialog: DialogInfo = { + content: { $kind: '' }, + diagnostics: [], + displayName: '', + id: '', + isRoot: false, + lgFile: '', + lgTemplates: [], + luFile: '', + qnaFile: '', + referredLuIntents: [], + referredDialogs: [], + triggers: [], + intentTriggers: [], + skills: [], +}; +type dialogStateParams = { projectId: string; dialogId: string }; +export const dialogState = atomFamily({ + key: getFullyQualifiedKey('dialog'), + default: () => { + return emptyDialog; + }, +}); + +export const dialogIdsState = atomFamily({ + key: getFullyQualifiedKey('dialogIds'), + default: () => { return []; }, }); 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 ba7ea87a79..2cff90f10f 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 } from '@botframework-composer/test-utils/lib/hooks'; import { dialogsDispatcher } from '../dialogs'; import { renderRecoilHook } from '../../../../__tests__/testUtils'; import { - dialogsState, lgFilesState, luFilesState, schemasState, @@ -18,6 +17,7 @@ import { showCreateDialogModalState, qnaFilesState, } from '../../atoms'; +import { dialogsSelectorFamily } from '../../selectors'; import { dispatcherState } from '../../../recoilModel/DispatcherWrapper'; import { Dispatcher } from '..'; @@ -105,7 +105,7 @@ describe('dialog dispatcher', () => { let renderedComponent, dispatcher: Dispatcher; beforeEach(() => { const useRecoilTestHook = () => { - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const dialogSchemas = useRecoilValue(dialogSchemasState(projectId)); const luFiles = useRecoilValue(luFilesState(projectId)); const lgFiles = useRecoilValue(lgFilesState(projectId)); @@ -130,7 +130,7 @@ describe('dialog dispatcher', () => { const { result } = renderRecoilHook(useRecoilTestHook, { states: [ - { recoilState: dialogsState(projectId), initialValue: [{ id: '1' }, { id: '2' }] }, + { 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: luFilesState(projectId), initialValue: [{ id: '1.en-us' }, { id: '2.en-us' }] }, 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 4d0cd03677..57a563d04f 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/multilang.test.tsx +++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/multilang.test.tsx @@ -9,13 +9,13 @@ import { luFilesState, lgFilesState, settingsState, - dialogsState, localeState, actionsSeedState, onAddLanguageDialogCompleteState, onDelLanguageDialogCompleteState, currentProjectIdState, } from '../../atoms'; +import { dialogsSelectorFamily } from '../../selectors'; import { dispatcherState } from '../../../recoilModel/DispatcherWrapper'; import { Dispatcher } from '..'; import { multilangDispatcher } from '../multilang'; @@ -43,7 +43,7 @@ describe('Multilang dispatcher', () => { beforeEach(() => { const useRecoilTestHook = () => { const actionsSeed = useRecoilValue(actionsSeedState(state.projectId)); - const dialogs = useRecoilValue(dialogsState(state.projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(state.projectId)); const locale = useRecoilValue(localeState(state.projectId)); const settings = useRecoilValue(settingsState(state.projectId)); const luFiles = useRecoilValue(luFilesState(state.projectId)); @@ -69,7 +69,7 @@ describe('Multilang dispatcher', () => { const { result } = renderRecoilHook(useRecoilTestHook, { states: [ { recoilState: currentProjectIdState, initialValue: state.projectId }, - { recoilState: dialogsState(state.projectId), initialValue: state.dialogs }, + { recoilState: dialogsSelectorFamily(state.projectId), initialValue: state.dialogs }, { recoilState: localeState(state.projectId), initialValue: state.locale }, { recoilState: lgFilesState(state.projectId), initialValue: state.lgFiles }, { recoilState: luFilesState(state.projectId), initialValue: state.luFiles }, diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx index 0ad85f9161..f931e55c2f 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx +++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx @@ -7,7 +7,8 @@ import { SDKKinds } from '@bfc/shared'; import { navigationDispatcher } from '../navigation'; import { renderRecoilHook } from '../../../../__tests__/testUtils'; -import { focusPathState, breadcrumbState, designPageLocationState, dialogsState } from '../../atoms/botState'; +import { focusPathState, breadcrumbState, designPageLocationState } from '../../atoms/botState'; +import { dialogsSelectorFamily } from '../../selectors'; import { dispatcherState } from '../../../recoilModel/DispatcherWrapper'; import { Dispatcher } from '../../../recoilModel/dispatchers'; import { @@ -54,7 +55,7 @@ describe('navigation dispatcher', () => { const focusPath = useRecoilValue(focusPathState(projectId)); const breadcrumb = useRecoilValue(breadcrumbState(projectId)); const designPageLocation = useRecoilValue(designPageLocationState(projectId)); - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const currentDispatcher = useRecoilValue(dispatcherState); return { @@ -81,7 +82,7 @@ describe('navigation dispatcher', () => { }, { recoilState: currentProjectIdState, initialValue: projectId }, { - recoilState: dialogsState(projectId), + recoilState: dialogsSelectorFamily(projectId), initialValue: [{ id: 'newDialogId', triggers: [{ type: SDKKinds.OnBeginDialog }] }], }, ], 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 11941d6515..f55ad6b515 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/project.test.tsx +++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/project.test.tsx @@ -26,7 +26,6 @@ import { luFilesState, lgFilesState, settingsState, - dialogsState, botEnvironmentState, botDiagnosticsState, localeState, @@ -42,6 +41,7 @@ import { botErrorState, botProjectSpaceLoadedState, } from '../../atoms'; +import { dialogsSelectorFamily } from '../../selectors'; import { dispatcherState } from '../../../recoilModel/DispatcherWrapper'; import { Dispatcher } from '../../dispatchers'; import { BotStatus } from '../../../constants'; @@ -118,7 +118,7 @@ describe('Project dispatcher', () => { const luFiles = useRecoilValue(luFilesState(projectId)); const lgFiles = useRecoilValue(lgFilesState(projectId)); const settings = useRecoilValue(settingsState(projectId)); - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const botEnvironment = useRecoilValue(botEnvironmentState(projectId)); const diagnostics = useRecoilValue(botDiagnosticsState(projectId)); const locale = useRecoilValue(localeState(projectId)); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/builder.ts b/Composer/packages/client/src/recoilModel/dispatchers/builder.ts index 399ce9b592..d78a9a07c7 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/builder.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/builder.ts @@ -11,7 +11,8 @@ import { Text, BotStatus } from '../../constants'; import httpClient from '../../utils/httpUtil'; import luFileStatusStorage from '../../utils/luFileStatusStorage'; import qnaFileStatusStorage from '../../utils/qnaFileStatusStorage'; -import { luFilesState, qnaFilesState, dialogsState, botStatusState, botLoadErrorState } from '../atoms'; +import { luFilesState, qnaFilesState, botStatusState, botLoadErrorState } from '../atoms'; +import { dialogsSelectorFamily } from '../selectors'; import { settingsState } from '../atoms/botState'; const checkEmptyQuestionOrAnswerInQnAFile = (sections) => { @@ -26,7 +27,7 @@ export const builderDispatcher = () => { recognizerTypes: { [fileName: string]: string }, projectId: string ) => { - const dialogs = await snapshot.getPromise(dialogsState(projectId)); + const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId)); const luFiles = await snapshot.getPromise(luFilesState(projectId)); const qnaFiles = await snapshot.getPromise(qnaFilesState(projectId)); const settings = await snapshot.getPromise(settingsState(projectId)); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts b/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts index ed873d7037..fe724e576b 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/dialogs.ts @@ -3,15 +3,17 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { useRecoilCallback, CallbackInterface } from 'recoil'; import { dialogIndexer, autofixReferInDialog, validateDialog } from '@bfc/indexers'; +import { DialogInfo } from '@bfc/shared'; import { - dialogsState, lgFilesState, luFilesState, + dialogIdsState, schemasState, onCreateDialogCompleteState, actionsSeedState, showCreateDialogModalState, + dialogState, } from '../atoms/botState'; import { createLgFileState, removeLgFileState } from './lg'; @@ -22,10 +24,11 @@ import { removeDialogSchema } from './dialogSchema'; export const dialogsDispatcher = () => { const removeDialog = useRecoilCallback( (callbackHelpers: CallbackInterface) => async (id: string, projectId: string) => { - const { set, snapshot } = callbackHelpers; - let dialogs = await snapshot.getPromise(dialogsState(projectId)); - dialogs = dialogs.filter((dialog) => dialog.id !== id); - set(dialogsState(projectId), dialogs); + const { set, reset } = callbackHelpers; + + reset(dialogState({ projectId, dialogId: id })); + set(dialogIdsState(projectId), (previousDialogIds) => previousDialogIds.filter((dialogId) => dialogId !== id)); + //remove dialog should remove all locales lu and lg files and the dialog schema file await removeLgFileState(callbackHelpers, { id, projectId }); await removeLuFileState(callbackHelpers, { id, projectId }); @@ -34,21 +37,20 @@ export const dialogsDispatcher = () => { } ); - const updateDialog = useRecoilCallback(({ set }: CallbackInterface) => ({ id, content, projectId }) => { - // migration: add id for dialog - if (typeof content === 'object' && !content.id) { - content.id = id; + const updateDialog = useRecoilCallback( + ({ snapshot, set }: CallbackInterface) => async ({ id, content, projectId }) => { + // migration: add id for dialog + if (typeof content === 'object' && !content.id) { + content.id = id; + } + + const fixedContent = JSON.parse(autofixReferInDialog(id, JSON.stringify(content))); + + const dialog = await snapshot.getPromise(dialogState({ projectId, dialogId: id })); + const newDialog: DialogInfo = { ...dialog, ...dialogIndexer.parse(dialog.id, fixedContent) }; + set(dialogState({ projectId, dialogId: id }), newDialog); } - set(dialogsState(projectId), (dialogs) => { - return dialogs.map((dialog) => { - if (dialog.id === id) { - const fixedContent = JSON.parse(autofixReferInDialog(id, JSON.stringify(content))); - return { ...dialog, ...dialogIndexer.parse(dialog.id, fixedContent) }; - } - return dialog; - }); - }); - }); + ); const createDialogBegin = useRecoilCallback( (callbackHelpers: CallbackInterface) => (actions, onComplete, projectId: string) => { @@ -81,7 +83,8 @@ export const dialogsDispatcher = () => { await createLuFileState(callbackHelpers, { id, content: '', projectId }); await createQnAFileState(callbackHelpers, { id, content: '', projectId }); - set(dialogsState(projectId), (dialogs) => [...dialogs, dialog]); + set(dialogState({ projectId, dialogId: dialog.id }), dialog); + set(dialogIdsState(projectId), (dialogsIds) => [...dialogsIds, dialog.id]); set(actionsSeedState(projectId), []); set(showCreateDialogModalState(projectId), false); const onComplete = (await snapshot.getPromise(onCreateDialogCompleteState(projectId))).func; diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts index 95ee4d4598..23126d1946 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts @@ -9,10 +9,11 @@ import cloneDeep from 'lodash/cloneDeep'; import { currentProjectIdState } from '../atoms'; import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designerPathEncoder'; +import { dialogsSelectorFamily } from '../selectors'; import { createSelectedPath, getSelected } from './../../utils/dialogUtil'; import { BreadcrumbItem } from './../../recoilModel/types'; -import { breadcrumbState, designPageLocationState, focusPathState, dialogsState } from './../atoms/botState'; +import { breadcrumbState, designPageLocationState, focusPathState } from './../atoms/botState'; import { BreadcrumbUpdateType, checkUrl, @@ -54,7 +55,7 @@ export const navigationDispatcher = () => { dialogId: string, breadcrumb: BreadcrumbItem[] = [] ) => { - const dialogs = await snapshot.getPromise(dialogsState(projectId)); + const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId)); const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId)); const updatedBreadcrumb = cloneDeep(breadcrumb); set(currentProjectIdState, projectId); @@ -94,7 +95,7 @@ export const navigationDispatcher = () => { // target dialogId, projectId maybe empty string "" const dialogId = destinationDialogId ?? designPageLocation.dialogId ?? 'Main'; - const dialogs = await snapshot.getPromise(dialogsState(projectId)); + const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId)); const currentDialog = dialogs.find(({ id }) => id === dialogId); const encodedSelectPath = encodeArrayPathToDesignerPath(currentDialog?.content, selectPath); const currentUri = convertPathToUrl(projectId, skillId, dialogId, encodedSelectPath); @@ -115,7 +116,7 @@ export const navigationDispatcher = () => { let currentUri = `/bot/${projectId}/dialogs/${dialogId}`; if (focusPath) { - const dialogs = await snapshot.getPromise(dialogsState(projectId)); + const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId)); const currentDialog = dialogs.find(({ id }) => id === dialogId); const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog?.content, focusPath); @@ -150,7 +151,7 @@ export const navigationDispatcher = () => { ) => { set(currentProjectIdState, projectId); - const dialogs = await snapshot.getPromise(dialogsState(projectId)); + const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId)); const currentDialog = dialogs.find(({ id }) => id === dialogId)?.content; const encodedSelectPath = encodeArrayPathToDesignerPath(currentDialog, selectPath); const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog, focusPath); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts index 8a94b940ff..8ce490ee03 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts @@ -46,7 +46,7 @@ import { botStatusState, currentProjectIdState, dialogSchemasState, - dialogsState, + dialogState, filePersistenceState, formDialogSchemaIdsState, formDialogSchemaState, @@ -62,6 +62,7 @@ import { settingsState, skillManifestsState, skillsState, + dialogIdsState, showCreateQnAFromUrlDialogState, } from '../../atoms'; import * as botstates from '../../atoms/botState'; @@ -285,14 +286,18 @@ export const initBotState = async (callbackHelpers: CallbackInterface, data: any } let mainDialog = ''; - const verifiedDialogs = dialogs.map((dialog) => { + const dialogIds: string[] = []; + dialogs.forEach((dialog) => { if (dialog.isRoot) { mainDialog = dialog.id; } dialog.diagnostics = validateDialog(dialog, schemas.sdk.content, lgFiles, luFiles); - return dialog; + set(dialogState({ projectId, dialogId: dialog.id }), dialog); + dialogIds.push(dialog.id); }); + set(dialogIdsState(projectId), dialogIds); + await lgWorker.addProject(projectId, lgFiles); // Form dialogs @@ -308,7 +313,7 @@ export const initBotState = async (callbackHelpers: CallbackInterface, data: any set(luFilesState(projectId), initLuFilesStatus(botName, luFiles, dialogs)); set(lgFilesState(projectId), lgFiles); set(jsonSchemaFilesState(projectId), jsonSchemaFiles); - set(dialogsState(projectId), verifiedDialogs); + set(dialogSchemasState(projectId), dialogSchemas); set(botEnvironmentState(projectId), botEnvironment); set(botDisplayNameState(projectId), botName); diff --git a/Composer/packages/client/src/recoilModel/selectors/dialogs.ts b/Composer/packages/client/src/recoilModel/selectors/dialogs.ts new file mode 100644 index 0000000000..06f0173b3f --- /dev/null +++ b/Composer/packages/client/src/recoilModel/selectors/dialogs.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { DialogInfo } from '@bfc/shared'; +import { selectorFamily } from 'recoil'; + +import { dialogIdsState, dialogState } from '../atoms'; + +export const dialogsSelectorFamily = selectorFamily({ + key: 'dialogs', + get: (projectId: string) => ({ get }) => { + const dialogIds = get(dialogIdsState(projectId)); + + return dialogIds.map((dialogId) => { + return get(dialogState({ projectId, dialogId })); + }); + }, + set: (projectId: string) => ({ set }, newDialogs) => { + const newDialogArray = newDialogs as DialogInfo[]; + + set( + dialogIdsState(projectId), + newDialogArray.map((dialog) => dialog.id) + ); + newDialogArray.forEach((dialog) => set(dialogState({ projectId, dialogId: dialog.id }), dialog)); + }, +}); diff --git a/Composer/packages/client/src/recoilModel/selectors/index.ts b/Composer/packages/client/src/recoilModel/selectors/index.ts index 2dbb6d83cb..6e1afda06f 100644 --- a/Composer/packages/client/src/recoilModel/selectors/index.ts +++ b/Composer/packages/client/src/recoilModel/selectors/index.ts @@ -5,3 +5,4 @@ export * from './project'; export * from './eject'; export * from './extensions'; export * from './validatedDialogs'; +export * from './dialogs'; diff --git a/Composer/packages/client/src/recoilModel/selectors/project.ts b/Composer/packages/client/src/recoilModel/selectors/project.ts index 43468508c0..e35cde28e2 100644 --- a/Composer/packages/client/src/recoilModel/selectors/project.ts +++ b/Composer/packages/client/src/recoilModel/selectors/project.ts @@ -10,14 +10,13 @@ import { botDisplayNameState, botProjectFileState, botProjectIdsState, - dialogsState, projectMetaDataState, botNameIdentifierState, formDialogSchemaIdsState, formDialogSchemaState, jsonSchemaFilesState, } from '../atoms'; - +import { dialogsSelectorFamily } from '../selectors'; // Actions export const botsForFilePersistenceSelector = selector({ key: 'botsForFilePersistenceSelector', @@ -37,7 +36,7 @@ export const botProjectSpaceSelector = selector({ get: ({ get }) => { const botProjects = get(botProjectIdsState); const result = botProjects.map((projectId: string) => { - const dialogs = get(dialogsState(projectId)); + const dialogs = get(dialogsSelectorFamily(projectId)); const metaData = get(projectMetaDataState(projectId)); const botError = get(botErrorState(projectId)); const name = get(botDisplayNameState(projectId)); @@ -73,7 +72,7 @@ export const formDialogSchemasSelectorFamily = selectorFamily({ key: 'formDialogSchemasSelector', get: ({ projectId, schemaId }: { projectId: string; schemaId: string }) => ({ get }) => { - const dialogs = get(dialogsState(projectId)); + const dialogs = get(dialogsSelectorFamily(projectId)); return !!dialogs.find((d) => d.id === schemaId); }, }); diff --git a/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts b/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts index 131926fbd6..9f87b2c64f 100644 --- a/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts +++ b/Composer/packages/client/src/recoilModel/selectors/validatedDialogs.ts @@ -4,21 +4,32 @@ import { selectorFamily } from 'recoil'; import { validateDialog } from '@bfc/indexers'; -import { botProjectIdsState, dialogsState, schemasState, lgFilesState, luFilesState } from '../atoms'; +import { botProjectIdsState, dialogIdsState, schemasState, lgFilesState, luFilesState, dialogState } from '../atoms'; -export const validateDialogSelectorFamily = selectorFamily({ +type validateDialogSelectorFamilyParams = { projectId: string; dialogId: string }; +const validateDialogSelectorFamily = selectorFamily({ key: 'validateDialogSelectorFamily', + get: ({ projectId, dialogId }: validateDialogSelectorFamilyParams) => ({ get }) => { + const dialog = get(dialogState({ projectId, dialogId })); + const schemas = get(schemasState(projectId)); + const lgFiles = get(lgFilesState(projectId)); + const luFiles = get(luFilesState(projectId)); + + return { ...dialog, diagnostics: validateDialog(dialog, schemas.sdk.content, lgFiles, luFiles) }; + }, +}); + +export const validateDialogsSelectorFamily = selectorFamily({ + key: 'validateDialogsSelectorFamily', get: (projectId: string) => ({ get }) => { const loadedProjects = get(botProjectIdsState); if (!loadedProjects.includes(projectId)) { return []; } - const dialogs = get(dialogsState(projectId)); - const schemas = get(schemasState(projectId)); - const lgFiles = get(lgFilesState(projectId)); - const luFiles = get(luFilesState(projectId)); - return dialogs.map((dialog) => { - return { ...dialog, diagnostics: validateDialog(dialog, schemas.sdk.content, lgFiles, luFiles) }; + const dialogIds = get(dialogIdsState(projectId)); + + return dialogIds.map((dialogId) => { + return get(validateDialogSelectorFamily({ projectId, dialogId })); }); }, }); 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 b1ce067af0..5d561b06bf 100644 --- a/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx +++ b/Composer/packages/client/src/recoilModel/undo/__test__/history.test.tsx @@ -8,13 +8,13 @@ import { useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil'; import { UndoRoot, undoFunctionState, undoHistoryState } from '../history'; import { - dialogsState, lgFilesState, luFilesState, projectMetaDataState, currentProjectIdState, botProjectIdsState, } from '../../atoms'; +import { dialogsSelectorFamily } from '../../selectors'; import { renderRecoilHook } from '../../../../__tests__/testUtils/react-recoil-hooks-testing-library'; import UndoHistory from '../undoHistory'; const projectId = '123-asd'; @@ -31,7 +31,7 @@ describe('', () => { beforeEach(() => { const useRecoilTestHook = () => { const { undo, redo, canRedo, canUndo, commitChanges, clearUndo } = useRecoilValue(undoFunctionState(projectId)); - const [dialogs, setDialogs] = useRecoilState(dialogsState(projectId)); + const [dialogs, setDialogs] = useRecoilState(dialogsSelectorFamily(projectId)); const setProjectIdState = useSetRecoilState(currentProjectIdState); const history = useRecoilValue(undoHistoryState(projectId)); @@ -60,7 +60,7 @@ describe('', () => { }, states: [ { recoilState: botProjectIdsState, initialValue: [projectId] }, - { recoilState: dialogsState(projectId), initialValue: [{ id: '1' }] }, + { recoilState: dialogsSelectorFamily(projectId), initialValue: [{ id: '1' }] }, { recoilState: lgFilesState(projectId), initialValue: [{ id: '1.lg' }, { id: '2' }] }, { recoilState: luFilesState(projectId), initialValue: [{ id: '1.lu' }, { id: '2' }] }, { recoilState: currentProjectIdState, initialValue: projectId }, diff --git a/Composer/packages/client/src/recoilModel/undo/__test__/undoHistory.test.ts b/Composer/packages/client/src/recoilModel/undo/__test__/undoHistory.test.ts index 1da24eeba7..eade497c41 100644 --- a/Composer/packages/client/src/recoilModel/undo/__test__/undoHistory.test.ts +++ b/Composer/packages/client/src/recoilModel/undo/__test__/undoHistory.test.ts @@ -3,30 +3,30 @@ import undoHistoryImpl from '../undoHistory'; -import { dialogsState } from './../../atoms/botState'; +import { dialogsSelectorFamily } from './../../selectors'; const projectId = '12a-sdaas'; const undoHistory = new undoHistoryImpl(projectId); describe('undoHistory class', () => { it('should add value to stack', () => { - undoHistory.add(new Map().set(dialogsState(projectId), 'stack 1')); + undoHistory.add(new Map().set(dialogsSelectorFamily(projectId), 'stack 1')); expect(undoHistory.canUndo()).toBeFalsy(); - undoHistory.add(new Map().set(dialogsState(projectId), 'stack 2')); + undoHistory.add(new Map().set(dialogsSelectorFamily(projectId), 'stack 2')); expect(undoHistory.canUndo()).toBeTruthy(); - expect(undoHistory.getPresentAssets()?.get(dialogsState(projectId))).toBe('stack 2'); + expect(undoHistory.getPresentAssets()?.get(dialogsSelectorFamily(projectId))).toBe('stack 2'); }); it('should do undo', () => { expect(undoHistory.canUndo()).toBeTruthy(); const result = undoHistory.undo(); - expect(result.get(dialogsState(projectId))).toBe('stack 1'); + expect(result.get(dialogsSelectorFamily(projectId))).toBe('stack 1'); expect(undoHistory.stack.length).toBe(2); }); it('should remove the tail stack value when add a new one after undo ', () => { - undoHistory.add(new Map().set(dialogsState(projectId), 'stack 3')); - expect(undoHistory.getPresentAssets()?.get(dialogsState(projectId))).toBe('stack 3'); + undoHistory.add(new Map().set(dialogsSelectorFamily(projectId), 'stack 3')); + expect(undoHistory.getPresentAssets()?.get(dialogsSelectorFamily(projectId))).toBe('stack 3'); expect(undoHistory.stack.length).toBe(2); }); @@ -35,13 +35,13 @@ describe('undoHistory class', () => { undoHistory.undo(); expect(undoHistory.canRedo()).toBeTruthy(); const result = undoHistory.redo(); - expect(result.get(dialogsState(projectId))).toBe('stack 3'); + expect(result.get(dialogsSelectorFamily(projectId))).toBe('stack 3'); expect(undoHistory.stack.length).toBe(2); }); it('should replace the last stack value', () => { - undoHistory.replace(new Map().set(dialogsState(projectId), 'stack 4')); - expect(undoHistory.getPresentAssets()?.get(dialogsState(projectId))).toBe('stack 4'); + undoHistory.replace(new Map().set(dialogsSelectorFamily(projectId), 'stack 4')); + expect(undoHistory.getPresentAssets()?.get(dialogsSelectorFamily(projectId))).toBe('stack 4'); expect(undoHistory.stack.length).toBe(2); }); @@ -52,10 +52,10 @@ describe('undoHistory class', () => { it('should only support 30 actions in history', () => { for (let i = 0; i < 40; i++) { - undoHistory.add(new Map().set(dialogsState(projectId), `${i}`)); + undoHistory.add(new Map().set(dialogsSelectorFamily(projectId), `${i}`)); } expect(undoHistory.stack.length).toBe(30); - expect(undoHistory.getPresentAssets()?.get(dialogsState(projectId))).toBe('39'); - expect(undoHistory.stack[0].get(dialogsState(projectId))).toBe('10'); + expect(undoHistory.getPresentAssets()?.get(dialogsSelectorFamily(projectId))).toBe('39'); + expect(undoHistory.stack[0].get(dialogsSelectorFamily(projectId))).toBe('10'); }); }); diff --git a/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts b/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts index 3399bb247f..3ed1f116d0 100644 --- a/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts +++ b/Composer/packages/client/src/recoilModel/undo/trackedAtoms.ts @@ -3,10 +3,11 @@ import { RecoilState } from 'recoil'; -import { dialogsState, luFilesState, lgFilesState } from '../atoms'; +import { luFilesState, lgFilesState } from '../atoms'; +import { dialogsSelectorFamily } from '../selectors'; export type AtomAssetsMap = Map, any>; export const trackedAtoms = (projectId: string): RecoilState[] => { - return [dialogsState(projectId), luFilesState(projectId), lgFilesState(projectId)]; + return [dialogsSelectorFamily(projectId), luFilesState(projectId), lgFilesState(projectId)]; }; diff --git a/Composer/packages/client/src/shell/triggerApi.ts b/Composer/packages/client/src/shell/triggerApi.ts index 48e6fb7a84..82ef314a2e 100644 --- a/Composer/packages/client/src/shell/triggerApi.ts +++ b/Composer/packages/client/src/shell/triggerApi.ts @@ -18,7 +18,7 @@ import get from 'lodash/get'; import { useResolvers } from '../hooks/useResolver'; import { onChooseIntentKey, generateNewDialog, intentTypeKey, qnaMatcherKey } from '../utils/dialogUtil'; -import { schemasState, lgFilesState, dialogsState, localeState } from '../recoilModel'; +import { schemasState, lgFilesState, dialogsSelectorFamily, localeState } from '../recoilModel'; import { Dispatcher } from '../recoilModel/dispatchers'; import { dispatcherState } from './../recoilModel/DispatcherWrapper'; @@ -132,7 +132,7 @@ function createTriggerApi( export function useTriggerApi(projectId: string) { const schemas = useRecoilValue(schemasState(projectId)); const lgFiles = useRecoilValue(lgFilesState(projectId)); - const dialogs = useRecoilValue(dialogsState(projectId)); + const dialogs = useRecoilValue(dialogsSelectorFamily(projectId)); const locale = useRecoilValue(localeState(projectId)); const { deleteActions } = useActionApi(projectId); const { removeLuIntent } = useLuApi(projectId); diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts index 624cfc90e1..13c6d19a00 100644 --- a/Composer/packages/client/src/shell/useShell.ts +++ b/Composer/packages/client/src/shell/useShell.ts @@ -15,7 +15,7 @@ import { settingsState, clipboardActionsState, schemasState, - validateDialogSelectorFamily, + validateDialogsSelectorFamily, breadcrumbState, focusPathState, skillsState, @@ -63,7 +63,7 @@ export function useShell(source: EventSource, projectId: string): Shell { const dialogMapRef = useRef({}); const schemas = useRecoilValue(schemasState(projectId)); - const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId)); + const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId)); const breadcrumb = useRecoilValue(breadcrumbState(projectId)); const focusPath = useRecoilValue(focusPathState(projectId)); const skills = useRecoilValue(skillsState(projectId));