diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 29c51623ae..09754e1e46 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -48,9 +48,7 @@ import { showAddSkillDialogModalState, actionsSeedState, localeState, - qnaFilesState, } from '../../recoilModel'; -import { getBaseName } from '../../utils/fileUtil'; import ImportQnAFromUrlModal from '../knowledge-base/ImportQnAFromUrlModal'; import { triggerNotSupported } from '../../utils/dialogValidator'; import { undoFunctionState, undoVersionState } from '../../recoilModel/undo/history'; @@ -121,7 +119,6 @@ const DesignPage: React.FC { - dialogs.forEach(async (dialog) => { - if (!qnaFiles || qnaFiles.length === 0 || !qnaFiles.find((qnaFile) => getBaseName(qnaFile.id) === dialog.id)) { - await createQnAFile({ id: dialog.id, content: '', projectId }); - } - }); - }, [dialogs]); - useEffect(() => { if (location && props.dialogId && props.projectId) { const { dialogId, projectId } = props; diff --git a/Composer/packages/server/__tests__/models/bot/botProject.test.ts b/Composer/packages/server/__tests__/models/bot/botProject.test.ts index a40b54cf41..56ef84919d 100644 --- a/Composer/packages/server/__tests__/models/bot/botProject.test.ts +++ b/Composer/packages/server/__tests__/models/bot/botProject.test.ts @@ -30,7 +30,7 @@ beforeEach(async () => { describe('init', () => { it('should get project successfully', () => { const project: { [key: string]: any } = proj.getProject(); - expect(project.files.length).toBe(10); + expect(project.files.length).toBe(13); }); }); @@ -103,7 +103,7 @@ describe('copyTo', () => { const newBotProject = await proj.copyTo(locationRef); await newBotProject.init(); const project: { [key: string]: any } = newBotProject.getProject(); - expect(project.files.length).toBe(10); + expect(project.files.length).toBe(13); }); }); @@ -380,7 +380,7 @@ describe('deleteAllFiles', () => { const newBotProject = await proj.copyTo(locationRef); await newBotProject.init(); const project: { [key: string]: any } = newBotProject.getProject(); - expect(project.files.length).toBe(10); + expect(project.files.length).toBe(14); await newBotProject.deleteAllFiles(); expect(fs.existsSync(copyDir)).toBe(false); }); diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index 084d40386a..f214875e75 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -24,6 +24,7 @@ import values from 'lodash/values'; import { Path } from '../../utility/path'; import { copyDir } from '../../utility/storage'; import StorageService from '../../services/storage'; +import { getDialogNameFromFile } from '../utilities/util'; import { ISettingManager, OBFUSCATED_VALUE } from '../settings'; import { DefaultSettingManager } from '../settings/defaultSettingManager'; import log from '../../logger'; @@ -728,6 +729,46 @@ export class BotProject implements IBotProject { fileList.set(file.name, file); }); + const migrationFiles = await this._createQnAFilesForOldBot(fileList); + + return new Map([...fileList, ...migrationFiles]); + }; + + // migration: create qna files for old bots + private _createQnAFilesForOldBot = async (files: Map) => { + const dialogFiles: FileInfo[] = []; + const qnaFiles: FileInfo[] = []; + files.forEach((file) => { + if (file.name.endsWith('.dialog')) { + dialogFiles.push(file); + } + if (file.name.endsWith('.qna')) { + qnaFiles.push(file); + } + }); + + const dialogNames = dialogFiles.map((file) => getDialogNameFromFile(file.name)); + const qnaNames = qnaFiles.map((file) => getDialogNameFromFile(file.name)); + const fileList = new Map(); + for (let i = 0; i < dialogNames.length; i++) { + if (!qnaNames || qnaNames.length === 0 || !qnaNames.find((qn) => qn === dialogNames[i])) { + await this.createFile(`${dialogNames[i]}.qna`, ''); + } + } + + const pattern = '**/*.qna'; + // load only from the data dir, otherwise may get "build" versions from + // deployment process + const root = this.dataDir; + const paths = await this.fileStorage.glob([pattern, '!(generated/**)', '!(runtime/**)'], root); + + for (const filePath of paths.sort()) { + const realFilePath: string = Path.join(root, filePath); + const fileInfo = await this._getFileInfo(realFilePath); + if (fileInfo) { + fileList.set(fileInfo.name, fileInfo); + } + } return fileList; }; diff --git a/Composer/packages/server/src/models/utilities/util.ts b/Composer/packages/server/src/models/utilities/util.ts new file mode 100644 index 0000000000..17109e4d94 --- /dev/null +++ b/Composer/packages/server/src/models/utilities/util.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const getDialogNameFromFile = (file: string) => { + const tokens = file.split('.'); + const length = tokens.length; + let dialogName = ''; + if (length > 1) { + const extension = tokens[length - 1]; + switch (extension) { + case 'dialog': + case 'lu': + case 'lg': + case 'qna': + dialogName = tokens[0]; + break; + } + } + return dialogName; +};