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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 77 additions & 79 deletions Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,7 @@ export const ProjectTree: React.FC<Props> = ({
const addMainDialogRef = useCallback((mainDialog) => onboardingAddCoachMarkRef({ mainDialog }), []);

const rootProjectId = useRecoilValue(rootBotProjectIdSelector);
const selectorOptions = {
showLgImports: options.showLgImports ?? false,
showLuImports: options.showLuImports ?? false,
};
const projectCollection: TreeDataPerProject[] = useRecoilValue(projectTreeSelectorFamily(selectorOptions));

const projectCollection: TreeDataPerProject[] = useRecoilValue(projectTreeSelectorFamily);
const jsonSchemaFilesByProjectId = useRecoilValue(jsonSchemaFilesByProjectIdSelector);

// TODO Refactor to make sure tree is not generated until a new trigger/dialog is added. #5462
Expand Down Expand Up @@ -504,28 +499,21 @@ export const ProjectTree: React.FC<Props> = ({
: renderTriggerList(dialog.triggers, dialog, projectId, dialogLink, 1);
};

const renderLgImport = (
item: LanguageFileImport,
dialog: DialogInfo,
projectId: string,
dialogLink: TreeLink
): React.ReactNode => {
const renderLgImport = (item: LanguageFileImport, projectId: string): React.ReactNode => {
const link: TreeLink = {
projectId: rootProjectId,
skillId: projectId === rootProjectId ? undefined : projectId,
dialogId: dialog.id,
lgFileId: item.id,
dialogId: 'all',
displayName: item.displayName ?? item.id,
diagnostics: [],
isRoot: false,
parentLink: dialogLink,
isRemote: false,
};

return (
<TreeItem
key={`lg_${item.id}`}
dialogName={dialog.displayName}
extraSpace={INDENT_PER_LEVEL}
icon={icons.DIALOG}
isActive={doesLinkMatch(link, selectedLink)}
Expand All @@ -540,36 +528,29 @@ export const ProjectTree: React.FC<Props> = ({
);
};

const renderLgImports = (dialog: DialogInfo, projectId: string, dialogLink: TreeLink) => {
const renderLgImports = (dialog: DialogInfo, projectId: string) => {
return lgImportsByProjectByDialog[projectId][dialog.id]
.filter((lgImport) => filterMatch(dialog.displayName) || filterMatch(lgImport.displayName))
.map((lgImport) => {
return renderLgImport(lgImport, dialog, projectId, dialogLink);
return renderLgImport(lgImport, projectId);
});
};

const renderLuImport = (
item: LanguageFileImport,
dialog: DialogInfo,
projectId: string,
dialogLink: TreeLink
): React.ReactNode => {
const renderLuImport = (item: LanguageFileImport, projectId: string): React.ReactNode => {
const link: TreeLink = {
projectId: rootProjectId,
skillId: projectId === rootProjectId ? undefined : projectId,
dialogId: dialog.id,
luFileId: item.id,
displayName: item.displayName ?? item.id,
dialogId: 'all',
diagnostics: [],
isRoot: false,
parentLink: dialogLink,
isRemote: false,
};

return (
<TreeItem
key={`lu_${item.id}`}
dialogName={dialog.displayName}
extraSpace={INDENT_PER_LEVEL}
icon={icons.DIALOG}
isActive={doesLinkMatch(link, selectedLink)}
Expand All @@ -584,16 +565,16 @@ export const ProjectTree: React.FC<Props> = ({
);
};

const renderLuImports = (dialog: DialogInfo, projectId: string, dialogLink: TreeLink) => {
const renderLuImports = (dialog: DialogInfo, projectId: string) => {
return luImportsByProjectByDialog[projectId][dialog.id]
.filter((luImport) => filterMatch(dialog.displayName) || filterMatch(luImport.displayName))
.map((luImport) => {
return renderLuImport(luImport, dialog, projectId, dialogLink);
return renderLuImport(luImport, projectId);
});
};

const createDetailsTree = (bot: TreeDataPerProject, startDepth: number) => {
const { projectId } = bot;
const { projectId, lgImportsList, luImportsList } = bot;
const dialogs = bot.sortedDialogs;

const filteredDialogs =
Expand All @@ -605,56 +586,73 @@ export const ProjectTree: React.FC<Props> = ({
);
const commonLink = options.showCommonLinks ? [renderCommonDialogHeader(projectId, 1)] : [];

if (options.showTriggers || options.showLgImports || options.showLuImports) {
return [
...commonLink,
...filteredDialogs.map((dialog: DialogInfo) => {
const { summaryElement, dialogLink } = renderDialogHeader(projectId, dialog, 0, bot.isPvaSchema);
const key = 'dialog-' + dialog.id;
let lgImports, luImports;
if (options.showLgImports) {
lgImports = renderLgImports(dialog, projectId, dialogLink);
}

if (options.showLuImports) {
luImports = renderLuImports(dialog, projectId, dialogLink);
}

const showExpanded =
options.showTriggers ||
(options.showLgImports && lgImports.length > 0) ||
(options.showLuImports && luImports.length > 0);
if (showExpanded) {
return (
<ExpandableNode
key={key}
defaultState={getPageElement(key)}
depth={startDepth}
detailsRef={dialog.isRoot ? addMainDialogRef : undefined}
isActive={doesLinkMatch(dialogLink, selectedLink)}
summary={summaryElement}
onToggle={(newState) => setPageElement(key, newState)}
>
<div>
{options.showTriggers && renderDialogTriggers(dialog, projectId, startDepth + 1, dialogLink)}
{options.showLgImports && lgImports}
{options.showLuImports && luImports}
</div>
</ExpandableNode>
);
} else {
return renderDialogHeader(projectId, dialog, 1, bot.isPvaSchema).summaryElement;
}
}),
];
} else {
return [
...commonLink,
...filteredDialogs.map(
(dialog: DialogInfo) => renderDialogHeader(projectId, dialog, 1, bot.isPvaSchema).summaryElement
),
];
}
const importedLgLinks = options.showLgImports ? lgImportsList.map((file) => renderLgImport(file, projectId)) : [];
const importedLuLinks = options.showLuImports ? luImportsList.map((file) => renderLuImport(file, projectId)) : [];

return [
...commonLink,
...importedLgLinks,
...importedLuLinks,
...filteredDialogs.map((dialog: DialogInfo) => {
const { summaryElement, dialogLink } = renderDialogHeader(projectId, dialog, 0, bot.isPvaSchema);
const key = 'dialog-' + dialog.id;

let lgImports, luImports;
if (options.showLgImports) {
lgImports = renderLgImports(dialog, projectId);
}

if (options.showLuImports) {
luImports = renderLuImports(dialog, projectId);
}

if (options.showTriggers) {
return (
<ExpandableNode
key={key}
defaultState={getPageElement(key)}
depth={startDepth}
detailsRef={dialog.isRoot ? addMainDialogRef : undefined}
isActive={doesLinkMatch(dialogLink, selectedLink)}
summary={summaryElement}
onToggle={(newState) => setPageElement(key, newState)}
>
<div>{renderDialogTriggers(dialog, projectId, startDepth + 1, dialogLink)}</div>
</ExpandableNode>
);
} else if (options.showLgImports && lgImports.length > 0 && dialog.isFormDialog) {
return (
<ExpandableNode
key={key}
defaultState={getPageElement(key)}
depth={startDepth}
detailsRef={dialog.isRoot ? addMainDialogRef : undefined}
isActive={doesLinkMatch(dialogLink, selectedLink)}
summary={summaryElement}
onToggle={(newState) => setPageElement(key, newState)}
>
<div>{lgImports}</div>
</ExpandableNode>
);
} else if (options.showLuImports && luImports.length > 0 && dialog.isFormDialog) {
return (
<ExpandableNode
key={key}
defaultState={getPageElement(key)}
depth={startDepth}
detailsRef={dialog.isRoot ? addMainDialogRef : undefined}
isActive={doesLinkMatch(dialogLink, selectedLink)}
summary={summaryElement}
onToggle={(newState) => setPageElement(key, newState)}
>
<div>{luImports}</div>
</ExpandableNode>
);
} else {
return renderDialogHeader(projectId, dialog, 1, bot.isPvaSchema).summaryElement;
}
}),
];
};

const createBotSubtree = (bot: TreeDataPerProject) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ const TableView: React.FC<TableViewProps> = (props) => {
return result.concat(items);
}, []);

if (!activeDialog) {
setIntents(allIntents);
} else if (luFileId && file) {
if (luFileId && file) {
const luIntents: Intent[] = [];
get(file, 'intents', []).forEach(({ Name: name, Body: phrases }) => {
const state = getIntentState(file);
Expand All @@ -118,14 +116,16 @@ const TableView: React.FC<TableViewProps> = (props) => {
phrases,
fileId: file.id,
dialogId: activeDialog?.id || '',
used: activeDialog?.referredLuIntents.some((lu) => lu.name === name), // used by it's dialog or not
used: !!activeDialog?.referredLuIntents.some((lu) => lu.name === name), // used by it's dialog or not
state,
});
});
setIntents(luIntents);
} else {
} else if (activeDialog) {
const dialogIntents = allIntents.filter((t) => t.dialogId === activeDialog.id);
setIntents(dialogIntents);
} else {
setIntents(allIntents);
}
}, [luFiles, activeDialog, actualProjectId, luFileId]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,22 @@ describe('dialogImports selectors', () => {
const fileImports = getLanguageFileImports('name1', getFile);
expect(fileImports).toEqual([
{
displayName: 'display-name2.lg',
displayName: 'name2',
id: 'name2',
importPath: '../files/name2.lg',
},
{
displayName: 'display-name3.lg',
displayName: 'name3',
id: 'name3',
importPath: '../files/name3.lg',
},
{
displayName: 'display-name4.lg',
displayName: 'name4',
id: 'name4',
importPath: '../files/name4.lg',
},
{
displayName: 'display-name5-entity.lg',
displayName: 'name5-entity',
id: 'name5-entity',
importPath: '../files/name5-entity.lg',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ export const getLanguageFileImports = <T extends LgFile | LuFile | QnAFile>(
}
const currentImports = file.imports.map((item) => {
const importedFile = getFile(getBaseName(item.id));
const displayName = item.id.substring(0, item.id.indexOf('.'));
return {
displayName: item.description,
displayName,
Comment on lines +40 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can keep this mostly as it is and just write displayName: item.id.substring(0, item.id.indexOf('.')) as a field on the object (replacing the item.description part).

importPath: item.path,
id: importedFile ? importedFile.id : '',
};
Expand Down
32 changes: 23 additions & 9 deletions Composer/packages/client/src/recoilModel/selectors/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { BotIndexer } from '@bfc/indexers';
import { BotAssets, checkForPVASchema, DialogInfo, FormDialogSchema, JsonSchemaFile } from '@bfc/shared';
import isEmpty from 'lodash/isEmpty';
import uniqBy from 'lodash/uniqBy';
import { selector, selectorFamily } from 'recoil';

import { LanguageFileImport } from '../../../../types/src';
Expand Down Expand Up @@ -48,6 +49,8 @@ export type TreeDataPerProject = {
sortedDialogs: DialogInfo[];
lgImports: Record<string, LanguageFileImport[]>;
luImports: Record<string, LanguageFileImport[]>;
lgImportsList: LanguageFileImport[]; // all imported file exclude form diloag
luImportsList: LanguageFileImport[];
name: string;
isPvaSchema: boolean;
formDialogSchemas: FormDialogSchema[];
Expand Down Expand Up @@ -271,12 +274,9 @@ export const projectDialogsMapSelector = selector<{ [key: string]: DialogInfo[]
},
});

export const projectTreeSelectorFamily = selectorFamily<
TreeDataPerProject[],
{ showLgImports: boolean; showLuImports: boolean }
>({
export const projectTreeSelectorFamily = selector<TreeDataPerProject[]>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should rename this now that it's no longer a family (just projectTreeSelector is fine).

key: 'projectTreeSelectorFamily',
get: (options) => ({ get }) => {
get: ({ get }) => {
const projectIds = get(botProjectIdsState);
return projectIds.map((projectId: string) => {
const { isRemote, isRootBot } = get(projectMetaDataState(projectId));
Expand All @@ -293,31 +293,45 @@ export const projectTreeSelectorFamily = selectorFamily<

const botError = get(botErrorState(projectId));
const name = get(botDisplayNameState(projectId));
const dialogIds = get(dialogIdsState(projectId));

const lgImports: Record<string, LanguageFileImport[]> = {};
const luImports: Record<string, LanguageFileImport[]> = {};

// flatten imported file list
let lgImportsList: LanguageFileImport[] = [];
let luImportsList: LanguageFileImport[] = [];

dialogs.forEach((d) => {
if (options.showLgImports) {
lgImports[d.id] = get(lgImportsSelectorFamily({ projectId, dialogId: d.id })) ?? [];
const currentLgImports = get(lgImportsSelectorFamily({ projectId, dialogId: d.id })) ?? [];
lgImports[d.id] = currentLgImports;
if (!d.isFormDialog) {
lgImportsList.push(...currentLgImports);
}

if (options.showLuImports) {
luImports[d.id] = get(luImportsSelectorFamily({ projectId, dialogId: d.id })) ?? [];
const currentLuImports = get(luImportsSelectorFamily({ projectId, dialogId: d.id })) ?? [];
luImports[d.id] = currentLuImports;
if (!d.isFormDialog) {
luImportsList.push(...currentLuImports);
}
});

const schemas = get(schemasState(projectId));
const isPvaSchema = schemas && checkForPVASchema(schemas.sdk);
const formDialogSchemas = get(formDialogSchemasSelectorFamily(projectId));

lgImportsList = uniqBy(lgImportsList, 'id').filter((item) => !dialogIds.includes(item.displayName) && item.id);
luImportsList = uniqBy(luImportsList, 'id').filter((item) => !dialogIds.includes(item.displayName) && item.id);

return {
projectId,
isRemote,
isRootBot,
sortedDialogs,
luImports,
lgImports,
lgImportsList,
luImportsList,
name,
isPvaSchema,
formDialogSchemas,
Expand Down