forked from devcode1981/BotFramework-Composer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Selector to get all lg and lu files imported by a form dialog (m…
…icrosoft#4515) * selector to get all lg and lu files imported by a form dialog * export as selector * rename type * ProjectTree: env variable flag -> feature flag * adds test and rename * fix Co-authored-by: Soroush <[email protected]>
- Loading branch information
Showing
5 changed files
with
154 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
Composer/packages/client/src/recoilModel/selectors/__test__/dialogImports.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { getBaseName } from '../../../utils/fileUtil'; | ||
import { getLanguageFileImports } from '../dialogImports'; | ||
|
||
const files = [ | ||
{ | ||
id: 'name1.lg', | ||
content: '[name2.lg](../files/name2.lg)\n[name3.lg](../files/name3.lg)\n', | ||
}, | ||
{ | ||
id: 'id.lg', | ||
content: '', | ||
}, | ||
{ | ||
id: 'gender.lg', | ||
content: '', | ||
}, | ||
{ | ||
id: 'name2.lg', | ||
content: '[name4.lg](../files/name4.lg)\n[name5-entity.lg](../files/name5-entity.lg)\n', | ||
}, | ||
{ | ||
id: 'name3.lg', | ||
content: '- Enter a value for name3', | ||
}, | ||
{ | ||
id: 'name4.lg', | ||
content: '[name5-entity.lg](../files/name5-entity.lg)', | ||
}, | ||
{ | ||
id: 'name5-entity.lg', | ||
content: '- Enter a value for name5', | ||
}, | ||
]; | ||
|
||
describe('dialogImports selectors', () => { | ||
it('should follow all imports and list all unique imports', () => { | ||
const getFile = (id) => files.find((f) => getBaseName(f.id) === id) as { id: string; content: string }; | ||
|
||
const fileImports = getLanguageFileImports('name1', getFile); | ||
expect(fileImports).toEqual([ | ||
{ | ||
id: 'name2.lg', | ||
content: '[name4.lg](../files/name4.lg)\n[name5-entity.lg](../files/name5-entity.lg)\n', | ||
}, | ||
{ | ||
id: 'name3.lg', | ||
content: '- Enter a value for name3', | ||
}, | ||
{ | ||
id: 'name4.lg', | ||
content: '[name5-entity.lg](../files/name5-entity.lg)', | ||
}, | ||
{ | ||
id: 'name5-entity.lg', | ||
content: '- Enter a value for name5', | ||
}, | ||
]); | ||
}); | ||
}); |
83 changes: 83 additions & 0 deletions
83
Composer/packages/client/src/recoilModel/selectors/dialogImports.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { LanguageFileImport, LgFile, LuFile } from '@bfc/shared'; | ||
import uniqBy from 'lodash/uniqBy'; | ||
import { selectorFamily } from 'recoil'; | ||
|
||
import { getBaseName } from '../../utils/fileUtil'; | ||
import { localeState, lgFilesState, luFilesState } from '../atoms'; | ||
|
||
// eslint-disable-next-line security/detect-unsafe-regex | ||
const importRegex = /\[(?<id>.*?)]\((?<importPath>.*?)(?="|\))(?<optionalpart>".*")?\)/g; | ||
|
||
const getImportsHelper = (content: string): LanguageFileImport[] => { | ||
const lines = content.split(/\r?\n/g).filter((l) => !!l) ?? []; | ||
|
||
return (lines | ||
.map((l) => { | ||
importRegex.lastIndex = 0; | ||
return importRegex.exec(l) as RegExpExecArray; | ||
}) | ||
.filter(Boolean) as RegExpExecArray[]).map((regExecArr) => ({ | ||
id: getBaseName(regExecArr.groups?.id ?? ''), | ||
importPath: regExecArr.groups?.importPath ?? '', | ||
})); | ||
}; | ||
|
||
// Finds all the file imports starting from a given dialog file. | ||
export const getLanguageFileImports = <T extends { id: string; content: string }>( | ||
rootDialogId: string, | ||
getFile: (fileId: string) => T | ||
): T[] => { | ||
const imports: LanguageFileImport[] = []; | ||
|
||
const visitedIds: string[] = []; | ||
const fileIds = [rootDialogId]; | ||
|
||
while (fileIds.length) { | ||
const currentId = fileIds.pop() as string; | ||
// If this file is already visited, then continue. | ||
if (visitedIds.includes(currentId)) { | ||
continue; | ||
} | ||
const file = getFile(currentId); | ||
// If file is not found or file content is empty, then continue. | ||
if (!file || !file.content) { | ||
continue; | ||
} | ||
const currentImports = getImportsHelper(file.content); | ||
visitedIds.push(currentId); | ||
imports.push(...currentImports); | ||
const newIds = currentImports.map((ci) => getBaseName(ci.id)); | ||
fileIds.push(...newIds); | ||
} | ||
|
||
return uniqBy(imports, 'id').map((impExpr) => getFile(impExpr.id)); | ||
}; | ||
|
||
// Returns all the lg files referenced by a dialog file and its referenced lg files. | ||
export const lgImportsSelectorFamily = selectorFamily<LgFile[], { projectId: string; dialogId: string }>({ | ||
key: 'lgImports', | ||
get: ({ projectId, dialogId }) => ({ get }) => { | ||
const locale = get(localeState(projectId)); | ||
|
||
const getFile = (fileId: string) => | ||
get(lgFilesState(projectId)).find((f) => f.id === fileId || f.id === `${fileId}.${locale}`) as LgFile; | ||
|
||
return getLanguageFileImports(dialogId, getFile); | ||
}, | ||
}); | ||
|
||
// Returns all the lu files referenced by a dialog file and its referenced lu files. | ||
export const luImportsSelectorFamily = selectorFamily<LuFile[], { projectId: string; dialogId: string }>({ | ||
key: 'luImports', | ||
get: ({ projectId, dialogId }) => ({ get }) => { | ||
const locale = get(localeState(projectId)); | ||
|
||
const getFile = (fileId: string) => | ||
get(luFilesState(projectId)).find((f) => f.id === fileId || f.id === `${fileId}.${locale}`) as LuFile; | ||
|
||
return getLanguageFileImports(dialogId, getFile); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters