{onAllSelected != null ? (
) : null}
diff --git a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
index 55980b7380..a4ba777ae9 100644
--- a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
@@ -146,7 +146,7 @@ interface ITreeItemProps {
icon?: string;
dialogName?: string;
showProps?: boolean;
- shiftOut?: number; // needed to make an outline look right; should be the size of the "details" reveal arrow
+ forceIndent?: number; // needed to make an outline look right; should be the size of the "details" reveal arrow
}
const renderTreeMenuItem = (link: TreeLink) => (item: TreeMenuItem) => {
@@ -162,7 +162,7 @@ const renderTreeMenuItem = (link: TreeLink) => (item: TreeMenuItem) => {
text: item.label,
iconProps: { iconName: item.icon },
onClick: () => {
- item.action?.(link);
+ item.onClick?.(link);
},
};
};
@@ -240,7 +240,7 @@ export const TreeItem: React.FC
= ({
isActive = false,
icon,
dialogName,
- shiftOut,
+ forceIndent: shiftOut,
onSelect,
menu = [],
}) => {
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index 5ac44328e8..9947337650 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -204,7 +204,7 @@ const DesignPage: React.FC = (props) => {
);
useEffect(() => {
- actions.setCurrentMode('qna');
+ actions.setCurrentPageMode('qna');
}, []);
const toolbarItems = [
diff --git a/Composer/packages/client/src/pages/publish/Publish.tsx b/Composer/packages/client/src/pages/publish/Publish.tsx
index 71d9e43aa4..fd02c2523a 100644
--- a/Composer/packages/client/src/pages/publish/Publish.tsx
+++ b/Composer/packages/client/src/pages/publish/Publish.tsx
@@ -48,7 +48,7 @@ const Publish: React.FC {
- setCurrentMode('notifications');
+ setCurrentPageMode('notifications');
}, []);
return (
diff --git a/Composer/packages/client/src/pages/setting/SettingsPage.tsx b/Composer/packages/client/src/pages/setting/SettingsPage.tsx
index 22dbd8d2cb..23cbde35e8 100644
--- a/Composer/packages/client/src/pages/setting/SettingsPage.tsx
+++ b/Composer/packages/client/src/pages/setting/SettingsPage.tsx
@@ -45,7 +45,7 @@ const SettingPage: React.FC = () => {
addLanguages,
deleteLanguages,
fetchProjectById,
- setCurrentMode,
+ setCurrentPageMode,
} = useRecoilValue(dispatcherState);
const locale = useRecoilValue(localeState(projectId));
const showDelLanguageModal = useRecoilValue(showDelLanguageModalState(projectId));
@@ -59,7 +59,7 @@ const SettingPage: React.FC = () => {
// use cached projectId do fetch.
const cachedProjectId = useProjectIdCache();
useEffect(() => {
- setCurrentMode('settings');
+ setCurrentPageMode('settings');
if (!projectId && cachedProjectId) {
fetchProjectById(cachedProjectId);
}
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/application.ts b/Composer/packages/client/src/recoilModel/dispatchers/application.ts
index 4e5b564df0..9bd22e28f7 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/application.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/application.ts
@@ -75,7 +75,7 @@ export const applicationDispatcher = () => {
set(announcementState, message);
});
- const setCurrentMode = useRecoilCallback(({ set }: CallbackInterface) => (mode: PageMode) => {
+ const setCurrentPageMode = useRecoilCallback(({ set }: CallbackInterface) => (mode: PageMode) => {
set(currentModeState, mode);
});
@@ -119,6 +119,6 @@ export const applicationDispatcher = () => {
onboardingAddCoachMarkRef,
setCreationFlowStatus,
setApplicationLevelError,
- setCurrentMode,
+ setCurrentPageMode,
};
};
From 83c126dadb91329e251973594bccb86275fac6a1 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Tue, 20 Oct 2020 10:14:39 -0700
Subject: [PATCH 16/44] restore multibots to tree
---
.../components/ProjectTree/ProjectTree.tsx | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index fe9a06b5dc..6206c9bf32 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -150,20 +150,20 @@ export const ProjectTree: React.FC = ({
}
}
- const dialogHasWarnings = (dialog: DialogInfo) => {
- notificationMap[currentProjectId][dialog.id].some((diag) => diag.severity === DiagnosticSeverity.Warning);
+ const dialogHasWarnings = (projectId: string) => (dialog: DialogInfo) => {
+ notificationMap[projectId][dialog.id].some((diag) => diag.severity === DiagnosticSeverity.Warning);
};
const botHasWarnings = (bot: BotInProject) => {
- return bot.dialogs.some(dialogHasWarnings);
+ return bot.dialogs.some(dialogHasWarnings(bot.projectId));
};
- const dialogHasErrors = (dialog: DialogInfo) => {
- notificationMap[currentProjectId][dialog.id].some((diag) => diag.severity === DiagnosticSeverity.Error);
+ const dialogHasErrors = (projectId: string) => (dialog: DialogInfo) => {
+ notificationMap[projectId][dialog.id].some((diag) => diag.severity === DiagnosticSeverity.Error);
};
const botHasErrors = (bot: BotInProject) => {
- return bot.dialogs.some(dialogHasErrors);
+ return bot.dialogs.some(dialogHasErrors(bot.projectId));
};
const handleOnSelect = (link: TreeLink) => {
@@ -211,11 +211,11 @@ export const ProjectTree: React.FC = ({
};
const renderDialogHeader = (skillId: string, dialog: DialogInfo) => {
- const warningContent = notificationMap[currentProjectId][dialog.id]
+ const warningContent = notificationMap[skillId][dialog.id]
.filter((diag) => diag.severity === DiagnosticSeverity.Warning)
.map((diag) => diag.message)
.join(',');
- const errorContent = notificationMap[currentProjectId][dialog.id]
+ const errorContent = notificationMap[skillId][dialog.id]
.filter((diag) => diag.severity === DiagnosticSeverity.Error)
.map((diag) => diag.message)
.join(',');
@@ -224,7 +224,7 @@ export const ProjectTree: React.FC = ({
dialogName: dialog.id,
displayName: dialog.displayName,
isRoot: dialog.isRoot,
- projectId: currentProjectId,
+ projectId: skillId,
skillId: null,
errorContent,
warningContent,
@@ -270,7 +270,7 @@ export const ProjectTree: React.FC = ({
trigger: item.index,
dialogName: dialog.id,
isRoot: false,
- projectId: currentProjectId,
+ projectId: projectId,
skillId: null,
};
From e498a2f4e92aece43f544c3d7a60119f1bf03397 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Wed, 21 Oct 2020 11:52:20 -0700
Subject: [PATCH 17/44] fix links from project tree
---
.../components/ProjectTree/ProjectTree.tsx | 37 +++++------
.../src/components/ProjectTree/treeItem.tsx | 6 +-
.../client/src/pages/design/DesignPage.tsx | 64 ++++++++++---------
.../client/src/pages/skills/skill-list.tsx | 2 +-
.../src/recoilModel/dispatchers/navigation.ts | 25 ++++++--
Composer/packages/client/src/router.tsx | 7 ++
.../packages/client/src/shell/useShell.ts | 4 +-
.../packages/client/src/utils/navigation.ts | 6 +-
.../packages/server/src/locales/en-US.json | 19 +++---
9 files changed, 93 insertions(+), 77 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index 6206c9bf32..7a36603614 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -71,8 +71,8 @@ export type TreeLink = {
warningContent?: string;
errorContent?: string;
projectId: string;
- skillId: string | null;
- dialogName?: string;
+ skillId?: string;
+ dialogId?: string;
trigger?: number;
};
@@ -107,7 +107,7 @@ type BotInProject = {
};
type Props = {
- onSelect?: (link: TreeLink) => void;
+ onSelect: (link: TreeLink) => void;
onSelectAllLink?: () => void;
showTriggers?: boolean;
showDialogs?: boolean;
@@ -124,7 +124,7 @@ export const ProjectTree: React.FC = ({
onDeleteTrigger,
onSelect,
}) => {
- const { onboardingAddCoachMarkRef, selectTo, navTo } = useRecoilValue(dispatcherState);
+ const { onboardingAddCoachMarkRef } = useRecoilValue(dispatcherState);
const [filter, setFilter] = useState('');
const [selectedLink, setSelectedLink] = useState();
@@ -168,14 +168,7 @@ export const ProjectTree: React.FC = ({
const handleOnSelect = (link: TreeLink) => {
setSelectedLink(link);
- onSelect?.(link); // if we've defined a custom onSelect, use it
- if (link.dialogName != null) {
- if (link.trigger != null) {
- selectTo(link.projectId, link.skillId, link.dialogName, `triggers[${link.trigger}]`);
- } else {
- navTo(link.projectId, link.skillId, link.dialogName);
- }
- }
+ onSelect(link);
};
const renderBotHeader = (bot: BotInProject) => {
@@ -221,11 +214,11 @@ export const ProjectTree: React.FC = ({
.join(',');
const link: TreeLink = {
- dialogName: dialog.id,
+ dialogId: dialog.id,
displayName: dialog.displayName,
isRoot: dialog.isRoot,
- projectId: skillId,
- skillId: null,
+ projectId: currentProjectId,
+ skillId: skillId,
errorContent,
warningContent,
};
@@ -251,7 +244,7 @@ export const ProjectTree: React.FC = ({
label: formatMessage('Remove this dialog'),
icon: 'Delete',
onClick: (link) => {
- onDeleteDialog(link.dialogName ?? '');
+ onDeleteDialog(link.dialogId ?? '');
},
},
]}
@@ -264,14 +257,14 @@ export const ProjectTree: React.FC = ({
const renderTrigger = (projectId: string, item: any, dialog: DialogInfo): React.ReactNode => {
// NOTE: put the form-dialog detection here when it's ready
const link: TreeLink = {
+ projectId: currentProjectId,
+ skillId: projectId,
+ dialogId: dialog.id,
+ trigger: item.index,
displayName: item.displayName,
warningContent: item.warningContent,
errorContent: item.errorContent,
- trigger: item.index,
- dialogName: dialog.id,
isRoot: false,
- projectId: projectId,
- skillId: null,
};
return (
@@ -287,7 +280,7 @@ export const ProjectTree: React.FC = ({
label: formatMessage('Remove this trigger'),
icon: 'Delete',
onClick: (link) => {
- onDeleteTrigger(link.dialogName ?? '', link.trigger ?? 0);
+ onDeleteTrigger(link.dialogId ?? '', link.trigger ?? 0);
},
},
]}
@@ -404,7 +397,7 @@ export const ProjectTree: React.FC = ({
{onAllSelected != null ? (
) : null}
diff --git a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
index a4ba777ae9..400b22932e 100644
--- a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
@@ -240,7 +240,7 @@ export const TreeItem: React.FC = ({
isActive = false,
icon,
dialogName,
- forceIndent: shiftOut,
+ forceIndent,
onSelect,
menu = [],
}) => {
@@ -248,12 +248,12 @@ export const TreeItem: React.FC = ({
const overflowMenu = menu.map(renderTreeMenuItem(link));
- const linkString = `${link.projectId}_DialogTreeItem${link.dialogName}_${link.trigger ?? ''}`;
+ const linkString = `${link.projectId}_DialogTreeItem${link.dialogId}_${link.trigger ?? ''}`;
return (
{
}
};
-const DesignPage: React.FC
> = (props) => {
- const { location, dialogId, projectId = '' } = props;
+const DesignPage: React.FC> = (
+ props
+) => {
+ const { location, dialogId, projectId = '', skillId = null } = props;
const userSettings = useRecoilValue(userSettingsState);
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
const schemas = useRecoilValue(schemasState(projectId));
- const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId));
+ const dialogs = useRecoilValue(validateDialogSelectorFamily(skillId ?? projectId));
const displaySkillManifest = useRecoilValue(displaySkillManifestState(projectId));
const breadcrumb = useRecoilValue(breadcrumbState(projectId));
const focusPath = useRecoilValue(focusPathState(projectId));
@@ -160,10 +162,10 @@ const DesignPage: React.FC(dialogs[0]);
const [exportSkillModalVisible, setExportSkillModalVisible] = useState(false);
const [warningIsVisible, setWarningIsVisible] = useState(true);
- const shell = useShell('DesignPage', projectId);
- const shellForFlowEditor = useShell('FlowEditor', projectId);
- const shellForPropertyEditor = useShell('PropertyEditor', projectId);
- const triggerApi = useTriggerApi(projectId);
+ const shell = useShell('DesignPage', skillId ?? rootProjectId);
+ const shellForFlowEditor = useShell('FlowEditor', skillId ?? rootProjectId);
+ const shellForPropertyEditor = useShell('PropertyEditor', skillId ?? rootProjectId);
+ const triggerApi = useTriggerApi(skillId ?? rootProjectId);
const { createTrigger } = shell.api;
const defaultQnATriggerData = {
@@ -204,9 +206,8 @@ const DesignPage: React.FC ({ ...acc, [id]: content }), {});
@@ -217,20 +218,20 @@ const DesignPage: React.FC id === dialogId) || dialogs.find(({ isRoot }) => isRoot) || {};
+ const { id: foundId } = dialogs.find(({ id }) => id === dialogId) || dialogs.find(({ isRoot }) => isRoot) || {};
/**
* It's improper to fallback to `dialogId` directly:
- * - If 'action' not exists at `focused` path, fallback to trigger path;
- * - If 'trigger' not exists at `selected` path, fallback to dialog Id;
- * - If 'dialog' not exists at `dialogId` path, fallback to main dialog.
+ * - If 'action' does not exist at `focused` path, fallback to trigger path;
+ * - If 'trigger' does not exist at `selected` path, fallback to dialog Id;
+ * - If 'dialog' does not exist at `dialogId` path, fallback to main dialog.
*/
- if (id) {
- navTo(rootProjectId, null, id);
+ if (foundId != null) {
+ navTo(projectId, skillId, foundId);
}
return;
}
- setDesignPageLocation(projectId, {
+ setDesignPageLocation(skillId, {
dialogId,
selected,
focused,
@@ -265,18 +266,19 @@ const DesignPage: React.FC {
- if (newDialog) {
- navTo(projectId, null, newDialog, []);
+ const onCreateDialogComplete = (dialogId) => {
+ if (dialogId) {
+ navTo(projectId, null, dialogId, []);
}
};
@@ -462,7 +464,7 @@ const DesignPage: React.FC= 0) {
//if the deleted node is selected and the selected one is not the first one, navTo the previous trigger;
- selectTo(projectId, null, dialogId, createSelectedPath(currentIdx - 1));
+ selectTo(projectId, skillId, dialogId, createSelectedPath(currentIdx - 1));
} else {
//if the deleted node is selected and the selected one is the first one, navTo the first trigger;
- navTo(projectId, null, dialogId, []);
+ navTo(projectId, skillId, dialogId, []);
}
} else if (index < currentIdx) {
//if the deleted node is at the front, navTo the current one;
- selectTo(projectId, null, dialogId, createSelectedPath(currentIdx - 1));
+ selectTo(projectId, skillId, dialogId, createSelectedPath(currentIdx - 1));
}
}
}
@@ -599,7 +601,7 @@ const DesignPage: React.FC handleSelect(projectId, ...props)}
+ onSelect={handleSelect}
/>
diff --git a/Composer/packages/client/src/pages/skills/skill-list.tsx b/Composer/packages/client/src/pages/skills/skill-list.tsx
index 43424dd313..63435f0e8d 100644
--- a/Composer/packages/client/src/pages/skills/skill-list.tsx
+++ b/Composer/packages/client/src/pages/skills/skill-list.tsx
@@ -131,7 +131,7 @@ const SkillList: React.FC
= ({ projectId }) => {
}
};
- const handleEditSkill = (projectId, skillId) => (skillData) => {
+ const handleEditSkill = (projectId: string, skillId: string) => (skillData) => {
updateSkill(projectId, skillId, skillData);
};
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index 95ee4d4598..ab0d82d6f1 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -73,7 +73,7 @@ export const navigationDispatcher = () => {
const currentUri = convertPathToUrl(projectId, skillId, dialogId, path);
- if (checkUrl(currentUri, projectId, designPageLocation)) return;
+ if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
}
@@ -99,20 +99,28 @@ export const navigationDispatcher = () => {
const encodedSelectPath = encodeArrayPathToDesignerPath(currentDialog?.content, selectPath);
const currentUri = convertPathToUrl(projectId, skillId, dialogId, encodedSelectPath);
- if (checkUrl(currentUri, projectId, designPageLocation)) return;
+ if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected) } });
}
);
const focusTo = useRecoilCallback(
- ({ snapshot, set }: CallbackInterface) => async (projectId: string, focusPath: string, fragment: string) => {
+ ({ snapshot, set }: CallbackInterface) => async (
+ projectId: string,
+ skillId: string | null,
+ focusPath: string,
+ fragment: string
+ ) => {
set(currentProjectIdState, projectId);
const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
const breadcrumb = await snapshot.getPromise(breadcrumbState(projectId));
let updatedBreadcrumb = [...breadcrumb];
const { dialogId, selected } = designPageLocation;
- let currentUri = `/bot/${projectId}/dialogs/${dialogId}`;
+ let currentUri =
+ skillId == null
+ ? `/bot/${projectId}/dialogs/${dialogId}`
+ : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}`;
if (focusPath) {
const dialogs = await snapshot.getPromise(dialogsState(projectId));
@@ -134,7 +142,7 @@ export const navigationDispatcher = () => {
if (fragment && typeof fragment === 'string') {
currentUri += `#${fragment}`;
}
- if (checkUrl(currentUri, projectId, designPageLocation)) return;
+ if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
}
);
@@ -157,9 +165,12 @@ export const navigationDispatcher = () => {
const search = getUrlSearch(encodedSelectPath, encodedFocusPath);
const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
if (search) {
- const currentUri = `/bot/${projectId}/dialogs/${dialogId}${search}`;
+ const currentUri =
+ skillId == null
+ ? `/bot/${projectId}/dialogs/${dialogId}${search}`
+ : `/bot/${projectId}/skill/{skillId}/dialogs/${dialogId}${search}`;
- if (checkUrl(currentUri, projectId, designPageLocation)) return;
+ if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb } });
} else {
navTo(projectId, skillId, dialogId, breadcrumb);
diff --git a/Composer/packages/client/src/router.tsx b/Composer/packages/client/src/router.tsx
index 037bc9eff8..9488066c4d 100644
--- a/Composer/packages/client/src/router.tsx
+++ b/Composer/packages/client/src/router.tsx
@@ -70,6 +70,13 @@ const Routes = (props) => {
/>
))}
+
+
+
+
+
+
+
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index 624cfc90e1..e47a8207ae 100644
--- a/Composer/packages/client/src/shell/useShell.ts
+++ b/Composer/packages/client/src/shell/useShell.ts
@@ -27,6 +27,7 @@ import {
lgFilesState,
luFilesState,
rateInfoState,
+ rootBotProjectIdSelector,
} from '../recoilModel';
import { undoFunctionState } from '../recoilModel/undo/history';
@@ -78,6 +79,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
const botName = useRecoilValue(botDisplayNameState(projectId));
const settings = useRecoilValue(settingsState(projectId));
const flowZoomRate = useRecoilValue(rateInfoState);
+ const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector);
const userSettings = useRecoilValue(userSettingsState);
const clipboardActions = useRecoilValue(clipboardActionsState);
@@ -154,7 +156,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
}
}
- focusTo(projectId, dataPath, fragment ?? '');
+ focusTo(rootBotProjectId ?? projectId, projectId, dataPath, fragment ?? '');
}
function updateFlowZoomRate(currentRate) {
diff --git a/Composer/packages/client/src/utils/navigation.ts b/Composer/packages/client/src/utils/navigation.ts
index 773e5b0c6b..49e76b7dd1 100644
--- a/Composer/packages/client/src/utils/navigation.ts
+++ b/Composer/packages/client/src/utils/navigation.ts
@@ -69,9 +69,13 @@ export function getUrlSearch(selected: string, focused: string): string {
export function checkUrl(
currentUri: string,
projectId: string,
+ skillId: string | null,
{ dialogId, selected, focused, promptTab }: DesignPageLocation
) {
- let lastUri = `/bot/${projectId}/dialogs/${dialogId}${getUrlSearch(selected, focused)}`;
+ let lastUri =
+ skillId == null
+ ? `/bot/${projectId}/dialogs/${dialogId}${getUrlSearch(selected, focused)}`
+ : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}${getUrlSearch(selected, focused)}`;
if (promptTab) {
lastUri += `#${promptTab}`;
}
diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json
index ec843ebc05..bdbccaef8d 100644
--- a/Composer/packages/server/src/locales/en-US.json
+++ b/Composer/packages/server/src/locales/en-US.json
@@ -758,9 +758,6 @@
"debugging_options_20e2e9da": {
"message": "Debugging options"
},
- "decrement_by_step_9b6c2fa3": {
- "message": "decrement by { step }"
- },
"default_language_486a558d": {
"message": "Default language"
},
@@ -1280,9 +1277,6 @@
"in_the_b_create_a_trigger_b_wizard_set_the_trigger_f9b23519": {
"message": "In the Create a trigger wizard, set the trigger type to Activities in the dropdown. Then set the Activity Type to Greeting (ConversationUpdate activity), and click the Submit button."
},
- "increment_by_step_1cf3a88": {
- "message": "increment by { step }"
- },
"input_1d1d9b8e": {
"message": "Input"
},
@@ -1754,9 +1748,6 @@
"number_or_expression_55c7f9f": {
"message": "Number or expression"
},
- "numeric_field_c2564f69": {
- "message": "numeric field"
- },
"oauth_login_b6aa9534": {
"message": "OAuth login"
},
@@ -2204,8 +2195,8 @@
"select_options_9ee7b227": {
"message": "Select options"
},
- "select_property_type_5f3ab685": {
- "message": "select property type"
+ "select_property_type_45c6e68e": {
+ "message": "Select property type"
},
"select_runtime_version_to_add_d63d383b": {
"message": "Select runtime version to add"
@@ -2339,6 +2330,9 @@
"start_command_a085f2ec": {
"message": "Start command"
},
+ "start_typing_kind_or_b0c305da": {
+ "message": "Start typing { kind } or"
+ },
"state_is_state_a2b8943": {
"message": "State is { state }"
},
@@ -2795,6 +2789,9 @@
"which_event_6e655d2b": {
"message": "Which event?"
},
+ "write_an_expression_8773ea5c": {
+ "message": "Write an expression"
+ },
"yes_dde87d5": {
"message": "Yes"
},
From bf75528302fa7fe7ba0bb937c2a62dfc3e224f13 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Wed, 21 Oct 2020 13:40:58 -0700
Subject: [PATCH 18/44] fix wrong IDs in URL
---
.../components/ProjectTree/ProjectTree.tsx | 22 ++++++++++++-------
.../client/src/pages/design/DesignPage.tsx | 2 +-
.../dispatchers/__tests__/navigation.test.tsx | 8 +++----
3 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index 7a36603614..f1fabbd091 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -14,7 +14,12 @@ import { useRecoilValue } from 'recoil';
import { ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import isEqual from 'lodash/isEqual';
-import { dispatcherState, currentProjectIdState, botProjectSpaceSelector } from '../../recoilModel';
+import {
+ dispatcherState,
+ rootBotProjectIdSelector,
+ botProjectSpaceSelector,
+ currentProjectIdState,
+} from '../../recoilModel';
import { getFriendlyName } from '../../utils/dialogUtil';
import { triggerNotSupported } from '../../utils/dialogValidator';
@@ -134,7 +139,8 @@ export const ProjectTree: React.FC = ({
...bot,
hasWarnings: false,
}));
- const currentProjectId = useRecoilValue(currentProjectIdState);
+ // if we're in a single-bot setting, the root will be undefined, so we fall back to current
+ const rootProjectId = useRecoilValue(rootBotProjectIdSelector) ?? useRecoilValue(currentProjectIdState);
const botProjectSpace = useRecoilValue(botProjectSpaceSelector);
const notificationMap: { [projectId: string]: { [dialogId: string]: Diagnostic[] } } = {};
@@ -174,7 +180,7 @@ export const ProjectTree: React.FC = ({
const renderBotHeader = (bot: BotInProject) => {
const link: TreeLink = {
displayName: bot.name,
- projectId: currentProjectId,
+ projectId: rootProjectId,
skillId: bot.projectId,
isRoot: true,
warningContent: botHasWarnings(bot) ? formatMessage('This bot has warnings') : undefined,
@@ -195,7 +201,7 @@ export const ProjectTree: React.FC = ({
showProps
forceIndent={bot.isRemote ? SUMMARY_ARROW_SPACE : 0}
icon={bot.isRemote ? icons.EXTERNAL_SKILL : icons.BOT}
- isSubItemActive={isEqual(link, selectedLink)}
+ isActive={isEqual(link, selectedLink)}
link={link}
menu={[{ label: formatMessage('Create/edit skill manifest'), onClick: () => {} }]}
/>
@@ -217,7 +223,7 @@ export const ProjectTree: React.FC = ({
dialogId: dialog.id,
displayName: dialog.displayName,
isRoot: dialog.isRoot,
- projectId: currentProjectId,
+ projectId: rootProjectId,
skillId: skillId,
errorContent,
warningContent,
@@ -237,7 +243,7 @@ export const ProjectTree: React.FC = ({
showProps
forceIndent={showTriggers ? 0 : SUMMARY_ARROW_SPACE}
icon={icons.DIALOG}
- isSubItemActive={isEqual(link, selectedLink)}
+ isActive={isEqual(link, selectedLink)}
link={link}
menu={[
{
@@ -257,7 +263,7 @@ export const ProjectTree: React.FC = ({
const renderTrigger = (projectId: string, item: any, dialog: DialogInfo): React.ReactNode => {
// NOTE: put the form-dialog detection here when it's ready
const link: TreeLink = {
- projectId: currentProjectId,
+ projectId: rootProjectId,
skillId: projectId,
dialogId: dialog.id,
trigger: item.index,
@@ -397,7 +403,7 @@ export const ProjectTree: React.FC = ({
{onAllSelected != null ? (
) : null}
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index 7939d0d482..a05842bd7d 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -625,7 +625,7 @@ const DesignPage: React.FC {
- updateDialog({ id: currentDialog.id, content: data, projectId });
+ updateDialog({ id: currentDialog.id, content: data, projectId: skillId ?? projectId });
}}
/>
) : withWarning ? (
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..d3b6a5d51c 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
@@ -230,7 +230,7 @@ describe('navigation dispatcher', () => {
describe('focusTo', () => {
it('goes to the same page with no arguments', async () => {
await act(async () => {
- await dispatcher.focusTo(projectId, '', '');
+ await dispatcher.focusTo(projectId, null, '', '');
});
expectNavTo(`/bot/${projectId}/dialogs/dialogId?selected=a`);
});
@@ -238,7 +238,7 @@ describe('navigation dispatcher', () => {
it('goes to a focused page', async () => {
mockGetSelected.mockReturnValueOnce('select');
await act(async () => {
- await dispatcher.focusTo(projectId, 'focus', '');
+ await dispatcher.focusTo(projectId, null, 'focus', '');
});
expectNavTo(`/bot/${projectId}/dialogs/dialogId?selected=select&focused=focus`);
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
@@ -248,7 +248,7 @@ describe('navigation dispatcher', () => {
it('goes to a focused page with fragment', async () => {
mockGetSelected.mockReturnValueOnce('select');
await act(async () => {
- await dispatcher.focusTo(projectId, 'focus', 'fragment');
+ await dispatcher.focusTo(projectId, null, 'focus', 'fragment');
});
expectNavTo(`/bot/${projectId}/dialogs/dialogId?selected=select&focused=focus#fragment`);
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
@@ -259,7 +259,7 @@ describe('navigation dispatcher', () => {
mockCheckUrl.mockReturnValue(true);
mockGetSelected.mockReturnValueOnce('select');
await act(async () => {
- await dispatcher.focusTo(projectId, 'focus', 'fragment');
+ await dispatcher.focusTo(projectId, null, 'focus', 'fragment');
});
expect(mockNavigateTo).not.toBeCalled();
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
From b32dffffbf7a1b283e64cfa3103d6b598e14c2c4 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 22 Oct 2020 10:56:16 -0700
Subject: [PATCH 19/44] yarn.lock rebuild
---
.../src/components/ProjectTree/ProjectTree.tsx | 1 +
extensions/azurePublish/yarn.lock | 13 +++----------
extensions/localPublish/yarn.lock | 3 +++
3 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index f1fabbd091..eb8e11acf3 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -139,6 +139,7 @@ export const ProjectTree: React.FC = ({
...bot,
hasWarnings: false,
}));
+
// if we're in a single-bot setting, the root will be undefined, so we fall back to current
const rootProjectId = useRecoilValue(rootBotProjectIdSelector) ?? useRecoilValue(currentProjectIdState);
const botProjectSpace = useRecoilValue(botProjectSpaceSelector);
diff --git a/extensions/azurePublish/yarn.lock b/extensions/azurePublish/yarn.lock
index 2770d6c502..bc45364ad0 100644
--- a/extensions/azurePublish/yarn.lock
+++ b/extensions/azurePublish/yarn.lock
@@ -898,11 +898,6 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
-base64-js@^1.0.2:
- version "1.3.1"
- resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
- integrity sha1-WOzoy3XdB+ce0IxzarxfrE2/jfE=
-
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
@@ -969,6 +964,9 @@ buffer@^5.1.0, buffer@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
cardinal@^2.1.1:
version "2.1.1"
@@ -1652,11 +1650,6 @@ hyperlinker@^1.0.0:
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==
-ieee754@^1.1.4:
- version "1.1.13"
- resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
- integrity sha1-7BaFWOlaoYH9h9N/VcMrvLZwi4Q=
-
ignore@^5.1.1, ignore@^5.1.4:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
diff --git a/extensions/localPublish/yarn.lock b/extensions/localPublish/yarn.lock
index e2b9dd013c..00ded2c015 100644
--- a/extensions/localPublish/yarn.lock
+++ b/extensions/localPublish/yarn.lock
@@ -212,6 +212,9 @@ buffer@^5.1.0, buffer@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
chownr@^2.0.0:
version "2.0.0"
From bb61b65447310b4641d72ebfd92f25ed908085fd Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 22 Oct 2020 13:10:27 -0700
Subject: [PATCH 20/44] update yarn.lock files
---
extensions/azurePublish/yarn.lock | 10 ++++++++++
extensions/localPublish/yarn.lock | 10 ++++++++++
2 files changed, 20 insertions(+)
diff --git a/extensions/azurePublish/yarn.lock b/extensions/azurePublish/yarn.lock
index bc45364ad0..0c7da6c2ae 100644
--- a/extensions/azurePublish/yarn.lock
+++ b/extensions/azurePublish/yarn.lock
@@ -898,6 +898,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-js@^1.0.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+ integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
@@ -1650,6 +1655,11 @@ hyperlinker@^1.0.0:
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==
+ieee754@^1.1.4:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+ integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+
ignore@^5.1.1, ignore@^5.1.4:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
diff --git a/extensions/localPublish/yarn.lock b/extensions/localPublish/yarn.lock
index 00ded2c015..6564cb95f9 100644
--- a/extensions/localPublish/yarn.lock
+++ b/extensions/localPublish/yarn.lock
@@ -179,6 +179,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-js@^1.0.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+ integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
bl@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489"
@@ -373,6 +378,11 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+ieee754@^1.1.4:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+ integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+
ignore@^5.1.4:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
From 72ce95b125cdcdd9efb483f379e509eb0e5d1c2e Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 22 Oct 2020 16:33:00 -0700
Subject: [PATCH 21/44] fix action card links
---
Composer/packages/client/src/pages/design/DesignPage.tsx | 1 +
.../client/src/recoilModel/dispatchers/navigation.ts | 6 +++---
Composer/packages/client/src/shell/useShell.ts | 4 ++--
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index dd1f9d28f5..bc563d10cb 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -217,6 +217,7 @@ const DesignPage: React.FC id === dialogId) || dialogs.find(({ isRoot }) => isRoot) || {};
/**
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index ab0d82d6f1..7fc4cf1979 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -112,8 +112,8 @@ export const navigationDispatcher = () => {
fragment: string
) => {
set(currentProjectIdState, projectId);
- const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
- const breadcrumb = await snapshot.getPromise(breadcrumbState(projectId));
+ const designPageLocation = await snapshot.getPromise(designPageLocationState(skillId ?? projectId));
+ const breadcrumb = await snapshot.getPromise(breadcrumbState(skillId ?? projectId));
let updatedBreadcrumb = [...breadcrumb];
const { dialogId, selected } = designPageLocation;
@@ -123,7 +123,7 @@ export const navigationDispatcher = () => {
: `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}`;
if (focusPath) {
- const dialogs = await snapshot.getPromise(dialogsState(projectId));
+ const dialogs = await snapshot.getPromise(dialogsState(skillId ?? projectId));
const currentDialog = dialogs.find(({ id }) => id === dialogId);
const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog?.content, focusPath);
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index e47a8207ae..d38e4f23ce 100644
--- a/Composer/packages/client/src/shell/useShell.ts
+++ b/Composer/packages/client/src/shell/useShell.ts
@@ -137,11 +137,11 @@ export function useShell(source: EventSource, projectId: string): Shell {
}
function navigationTo(path) {
- navTo(projectId, null, path, breadcrumb);
+ navTo(rootBotProjectId ?? projectId, projectId, path, breadcrumb);
}
function focusEvent(subPath) {
- selectTo(projectId, null, null, subPath);
+ selectTo(rootBotProjectId ?? projectId, projectId, dialogId, subPath);
}
function focusSteps(subPaths: string[] = [], fragment?: string) {
From 0d13f7873529b8502787b6328d97c969f6f5988d Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Fri, 23 Oct 2020 12:58:48 -0700
Subject: [PATCH 22/44] Update DesignPage.tsx
---
.../client/src/pages/design/DesignPage.tsx | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index bc563d10cb..12b50ec624 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -116,17 +116,17 @@ const DesignPage: React.FC
Date: Fri, 23 Oct 2020 13:40:56 -0700
Subject: [PATCH 23/44] fix unit test
---
.../client/__tests__/utils/navigation.test.ts | 27 +++++++++++++++++--
.../__tests__/botProjectFile.test.tsx | 4 ++-
.../bot1/recognizers/cross-train.config.json | 1 +
3 files changed, 29 insertions(+), 3 deletions(-)
create mode 100644 Composer/packages/server/src/__mocks__/samplebots/bot1/recognizers/cross-train.config.json
diff --git a/Composer/packages/client/__tests__/utils/navigation.test.ts b/Composer/packages/client/__tests__/utils/navigation.test.ts
index c28892c8aa..100e4c12d3 100644
--- a/Composer/packages/client/__tests__/utils/navigation.test.ts
+++ b/Composer/packages/client/__tests__/utils/navigation.test.ts
@@ -73,10 +73,11 @@ describe('composer url util', () => {
expect(result1).toEqual('?selected=triggers[0]&focused=triggers[0].actions[0]');
});
- it('check url', () => {
+ it('checks a URL without a skill', () => {
const result1 = checkUrl(
`/bot/${projectId}/dialogs/a?selected=triggers[0]&focused=triggers[0].actions[0]#botAsks`,
projectId,
+ null,
{
dialogId: 'a',
selected: 'triggers[0]',
@@ -85,7 +86,29 @@ describe('composer url util', () => {
}
);
expect(result1).toEqual(true);
- const result2 = checkUrl(`test`, projectId, {
+ const result2 = checkUrl(`test`, projectId, skillId, {
+ dialogId: 'a',
+ selected: 'triggers[0]',
+ focused: 'triggers[0].actions[0]',
+ promptTab: PromptTab.BOT_ASKS,
+ });
+ expect(result2).toEqual(false);
+ });
+
+ it('checks a URL with a skill', () => {
+ const result1 = checkUrl(
+ `/bot/${projectId}/skill/${skillId}/dialogs/a?selected=triggers[0]&focused=triggers[0].actions[0]#botAsks`,
+ projectId,
+ skillId,
+ {
+ dialogId: 'a',
+ selected: 'triggers[0]',
+ focused: 'triggers[0].actions[0]',
+ promptTab: PromptTab.BOT_ASKS,
+ }
+ );
+ expect(result1).toEqual(true);
+ const result2 = checkUrl(`test`, projectId, skillId, {
dialogId: 'a',
selected: 'triggers[0]',
focused: 'triggers[0].actions[0]',
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/botProjectFile.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/botProjectFile.test.tsx
index 8d24ddeb21..f6e49a076a 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/botProjectFile.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/botProjectFile.test.tsx
@@ -127,7 +127,9 @@ describe('Bot Project File dispatcher', () => {
dispatcher.addLocalSkillToBotProjectFile(testSkillId);
});
- expect(renderedComponent.current.botProjectFile.content.skills.todoSkill.workspace).toBe('../Todo-Skill');
+ expect(renderedComponent.current.botProjectFile.content.skills.todoSkill.workspace).toMatch(
+ /\.\.(\/|\\)Todo-Skill/
+ );
expect(renderedComponent.current.botProjectFile.content.skills.todoSkill.remote).toBeFalsy();
});
diff --git a/Composer/packages/server/src/__mocks__/samplebots/bot1/recognizers/cross-train.config.json b/Composer/packages/server/src/__mocks__/samplebots/bot1/recognizers/cross-train.config.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/Composer/packages/server/src/__mocks__/samplebots/bot1/recognizers/cross-train.config.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
From faaa7caae4ec0c245f9688723815f4c8a77e456a Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Mon, 26 Oct 2020 14:43:40 -0700
Subject: [PATCH 24/44] add unit test to ExpandableNode
---
.../components/expandableNode.test.tsx | 33 +++++++++++++++++++
.../components/ProjectTree/ExpandableNode.tsx | 9 ++++-
extensions/vacore/yarn.lock | 3 ++
3 files changed, 44 insertions(+), 1 deletion(-)
create mode 100644 Composer/packages/client/__tests__/components/expandableNode.test.tsx
diff --git a/Composer/packages/client/__tests__/components/expandableNode.test.tsx b/Composer/packages/client/__tests__/components/expandableNode.test.tsx
new file mode 100644
index 0000000000..4818afff10
--- /dev/null
+++ b/Composer/packages/client/__tests__/components/expandableNode.test.tsx
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import React from 'react';
+import { render, fireEvent } from '@botframework-composer/test-utils';
+
+import { ExpandableNode } from '../../src/components/ProjectTree/ExpandableNode';
+
+function isShown(details: HTMLElement) {
+ if (details == null) return false;
+ return details.attributes.getNamedItem('open') != null;
+}
+
+describe('', () => {
+ let component;
+ beforeEach(() => {
+ component = render({'details'});
+ });
+
+ it('closes and opens on click', async () => {
+ const triangle = await component.findByTestId('summaryTag');
+ let details = await component.findAllByText('details');
+ expect(isShown(details[0])).toEqual(true);
+
+ fireEvent.click(triangle);
+ details = await component.findAllByText('details');
+ expect(isShown(details[0])).toEqual(false);
+
+ fireEvent.click(triangle);
+ details = await component.findAllByText('details');
+ expect(isShown(details[0])).toEqual(true);
+ });
+});
diff --git a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
index cf9d64f5df..56df97ed41 100644
--- a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
@@ -41,7 +41,14 @@ export const ExpandableNode = ({ children, summary, detailsRef, depth = 0 }: Pro
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */}
-
+
{summary}
{children}
diff --git a/extensions/vacore/yarn.lock b/extensions/vacore/yarn.lock
index d1cbceaef8..260f60503c 100644
--- a/extensions/vacore/yarn.lock
+++ b/extensions/vacore/yarn.lock
@@ -112,6 +112,9 @@ buffer@^5.1.0, buffer@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
compress-commons@^2.1.1:
version "2.1.1"
From 399479c53072ef5cbf5a216b46c9f882caebd34a Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Mon, 26 Oct 2020 16:28:40 -0700
Subject: [PATCH 25/44] update ProjectTree unit tests
---
.../__tests__/components/projecttree.test.tsx | 22 +++++++-
.../client/__tests__/mocks/sampleDialog.ts | 54 +++++++++++++++++++
.../components/ProjectTree/ProjectTree.tsx | 3 +-
.../packages/server/src/locales/en-US.json | 6 +--
extensions/vacore/yarn.lock | 10 ++++
5 files changed, 89 insertions(+), 6 deletions(-)
diff --git a/Composer/packages/client/__tests__/components/projecttree.test.tsx b/Composer/packages/client/__tests__/components/projecttree.test.tsx
index c287e5e39a..9412f1e955 100644
--- a/Composer/packages/client/__tests__/components/projecttree.test.tsx
+++ b/Composer/packages/client/__tests__/components/projecttree.test.tsx
@@ -6,10 +6,11 @@ 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 { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../mocks/sampleDialog';
import { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel';
const projectId = '12345.6789';
+const projectId2 = '56789.1234';
const dialogs = [SAMPLE_DIALOG];
const initRecoilState = ({ set }) => {
@@ -19,6 +20,15 @@ const initRecoilState = ({ set }) => {
set(schemasState(projectId), { sdk: { content: {} } });
};
+const initRecoilStateMulti = ({ set }) => {
+ set(currentProjectIdState, projectId);
+ set(botProjectIdsState, [projectId, projectId2]);
+ set(dialogsSelectorFamily(projectId), dialogs);
+ set(dialogsSelectorFamily(projectId2), [SAMPLE_DIALOG, SAMPLE_DIALOG_2]);
+ set(schemasState(projectId), { sdk: { content: {} } });
+ set(schemasState(projectId2), { sdk: { content: {} } });
+};
+
describe('', () => {
it('should render the projecttree', async () => {
const { findByText } = renderWithRecoil(
@@ -29,6 +39,16 @@ describe('', () => {
await findByText('EchoBot-1');
});
+ it('should render the projecttree with multiple bots', async () => {
+ const { findAllByText, findByText } = renderWithRecoil(
+ {}} onDeleteTrigger={() => {}} onSelect={() => {}} />,
+ initRecoilStateMulti
+ );
+
+ await findAllByText('EchoBot-1');
+ await findByText('EchoBot-1b');
+ });
+
it('should handle project tree item click', async () => {
const mockFileSelect = jest.fn(() => null);
const component = renderWithRecoil(
diff --git a/Composer/packages/client/__tests__/mocks/sampleDialog.ts b/Composer/packages/client/__tests__/mocks/sampleDialog.ts
index a63b624a70..bd3cb1f1e6 100644
--- a/Composer/packages/client/__tests__/mocks/sampleDialog.ts
+++ b/Composer/packages/client/__tests__/mocks/sampleDialog.ts
@@ -91,3 +91,57 @@ export const SAMPLE_DIALOG = {
],
skills: [],
};
+
+export const SAMPLE_DIALOG_2 = {
+ isRoot: false,
+ displayName: 'EchoBot-1b',
+ id: 'echobot-1b',
+ content: {
+ $kind: 'Microsoft.AdaptiveDialog',
+ $designer: { id: '433224', description: '', name: 'EchoBot-1' },
+ autoEndDialog: true,
+ defaultResultProperty: 'dialog.result',
+ triggers: [
+ {
+ $kind: 'Microsoft.OnUnknownIntent',
+ $designer: { id: '821845' },
+ actions: [
+ { $kind: 'Microsoft.SendActivity', $designer: { id: '003038' }, activity: '${SendActivity_003038()}' },
+ ],
+ },
+ { $kind: 'Microsoft.OnError', $designer: { id: 'XVSGCI' } },
+ {
+ $kind: 'Microsoft.OnIntent',
+ $designer: { id: 'QIgTMy', name: 'more errors' },
+ intent: 'test',
+ actions: [{ $kind: 'Microsoft.SetProperty', $designer: { id: 'VyWC7G' }, value: '=[' }],
+ },
+ ],
+ generator: 'echobot-1b.lg',
+ $schema:
+ 'https://raw.githubusercontent.com/microsoft/BotFramework-Composer/stable/Composer/packages/server/schemas/sdk.schema',
+ id: 'EchoBot-1',
+ recognizer: 'echobot-1b.lu.qna',
+ },
+ diagnostics: [],
+ referredDialogs: [],
+ lgTemplates: [
+ { name: 'SendActivity_003038', path: 'echobot-1.triggers[0].actions[0]' },
+ { name: 'SendActivity_Welcome', path: 'echobot-1.triggers[1].actions[0].actions[0].actions[0]' },
+ ],
+ referredLuIntents: [{ name: 'test', path: 'echobot-1.triggers[3]#Microsoft.OnIntent' }],
+ luFile: 'echobot-1',
+ qnaFile: 'echobot-1',
+ lgFile: 'echobot-1',
+ triggers: [
+ { id: 'triggers[0]', displayName: '', type: 'Microsoft.OnUnknownIntent', isIntent: false },
+ { id: 'triggers[1]', displayName: '', type: 'Microsoft.OnConversationUpdateActivity', isIntent: false },
+ { id: 'triggers[2]', displayName: '', type: 'Microsoft.OnError', isIntent: false },
+ { id: 'triggers[3]', displayName: 'more errors', type: 'Microsoft.OnIntent', isIntent: true },
+ ],
+ intentTriggers: [
+ { intent: 'test', dialogs: [] },
+ { intent: 'test', dialogs: [] },
+ ],
+ skills: [],
+};
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index ba97fe1cd4..a737d32985 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -8,7 +8,6 @@ import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox';
import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone';
import cloneDeep from 'lodash/cloneDeep';
import formatMessage from 'format-message';
-import { extractSchemaProperties, groupTriggersByPropertyReference } from '@bfc/indexers';
import { DialogInfo, ITrigger, Diagnostic, DiagnosticSeverity } from '@bfc/shared';
import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
@@ -370,7 +369,7 @@ export const ProjectTree: React.FC = ({
const renderTriggerGroupHeader = (displayName: string, dialog: DialogInfo, projectId: string) => {
const link: TreeLink = {
- dialogName: dialog.id,
+ dialogId: dialog.id,
displayName,
isRoot: false,
projectId: projectId,
diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json
index d5235726b6..ffd9da57ad 100644
--- a/Composer/packages/server/src/locales/en-US.json
+++ b/Composer/packages/server/src/locales/en-US.json
@@ -611,9 +611,6 @@
"could_not_connect_to_storage_50411de0": {
"message": "Could not connect to storage."
},
- "could_not_init_plugin_1f1c29cd": {
- "message": "Could not init plugin"
- },
"couldn_t_complete_the_update_a337a359": {
"message": "Couldn''t complete the update:"
},
@@ -1163,6 +1160,9 @@
"form_title_baf85c7e": {
"message": "form title"
},
+ "form_wide_operations_1c1a73eb": {
+ "message": "form-wide operations"
+ },
"forms_380ab2ae": {
"message": "Forms"
},
diff --git a/extensions/vacore/yarn.lock b/extensions/vacore/yarn.lock
index 260f60503c..9bdde5a153 100644
--- a/extensions/vacore/yarn.lock
+++ b/extensions/vacore/yarn.lock
@@ -79,6 +79,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-js@^1.0.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+ integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
bl@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
@@ -272,6 +277,11 @@ graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+ieee754@^1.1.4:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+ integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+
ignore@^5.1.4:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
From 398988fe25a1ee6f45c03b3b763a466b328512d5 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Tue, 27 Oct 2020 09:08:53 -0700
Subject: [PATCH 26/44] Update ExpandableNode.tsx
---
.../src/components/ProjectTree/ExpandableNode.tsx | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
index 56df97ed41..fb7bae5a9e 100644
--- a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
@@ -23,6 +23,18 @@ const nodeStyle = (depth: number) => css`
margin-left: ${depth * 16}px;
`;
+const TRIANGLE_SCALE = 0.6;
+
+const detailsStyle = css`
+ details:not([open]) > summary::-webkit-details-marker {
+ transform: scaleX(${TRIANGLE_SCALE});
+ }
+
+ details[open] > summary::-webkit-details-marker {
+ transform: scaleY(${TRIANGLE_SCALE});
+ }
+`;
+
export const ExpandableNode = ({ children, summary, detailsRef, depth = 0 }: Props) => {
const [isExpanded, setExpanded] = useState(true);
@@ -39,7 +51,7 @@ export const ExpandableNode = ({ children, summary, detailsRef, depth = 0 }: Pro
return (
-
+
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */}
Date: Tue, 27 Oct 2020 10:48:56 -0700
Subject: [PATCH 27/44] add rootProjectId to triggerApi
---
.../packages/client/src/shell/triggerApi.ts | 19 +++++++++++++------
extensions/pvaPublish/yarn.lock | 6 ++++++
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/Composer/packages/client/src/shell/triggerApi.ts b/Composer/packages/client/src/shell/triggerApi.ts
index 44c51ce5d2..f7a59a87bc 100644
--- a/Composer/packages/client/src/shell/triggerApi.ts
+++ b/Composer/packages/client/src/shell/triggerApi.ts
@@ -18,7 +18,13 @@ import get from 'lodash/get';
import { useResolvers } from '../hooks/useResolver';
import { onChooseIntentKey, generateNewDialog, intentTypeKey, qnaMatcherKey } from '../utils/dialogUtil';
-import { schemasState, lgFilesState, dialogsSelectorFamily, localeState } from '../recoilModel';
+import {
+ schemasState,
+ lgFilesState,
+ dialogsSelectorFamily,
+ localeState,
+ rootBotProjectIdSelector,
+} from '../recoilModel';
import { Dispatcher } from '../recoilModel/dispatchers';
import { dispatcherState } from './../recoilModel/DispatcherWrapper';
@@ -35,7 +41,7 @@ const defaultQnATriggerData = {
};
function createTriggerApi(
- state: { projectId; schemas; dialogs; locale; lgFiles },
+ state: { rootProjectId; projectId; schemas; dialogs; locale; lgFiles },
dispatchers: Dispatcher, //TODO
luFileResolver: (id: string) => LuFile | undefined,
lgFileResolver: (id: string) => LgFile | undefined,
@@ -55,7 +61,7 @@ function createTriggerApi(
const lgFile = lgFileResolver(id);
const dialog = dialogResolver(id);
const { createLuIntent, createLgTemplates, updateDialog, selectTo } = dispatchers;
- const { projectId, schemas, dialogs, locale, lgFiles } = state;
+ const { rootProjectId, projectId, schemas, dialogs, locale, lgFiles } = state;
if (!luFile) throw new Error(`lu file ${id} not found`);
if (!lgFile) throw new Error(`lg file ${id} not found`);
if (!dialog) throw new Error(`dialog ${id} not found`);
@@ -112,7 +118,7 @@ function createTriggerApi(
};
await updateDialog(dialogPayload);
if (autoSelected) {
- selectTo(projectId, null, null, `triggers[${index}]`);
+ selectTo(rootProjectId, projectId, newDialog.id, `triggers[${index}]`);
}
};
@@ -155,6 +161,7 @@ export function useTriggerApi(projectId: string) {
const lgFiles = useRecoilValue(lgFilesState(projectId));
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const locale = useRecoilValue(localeState(projectId));
+ const rootProjectId = useRecoilValue(rootBotProjectIdSelector);
const { deleteActions } = useActionApi(projectId);
const { removeLuIntent } = useLuApi(projectId);
@@ -162,7 +169,7 @@ export function useTriggerApi(projectId: string) {
const { luFileResolver, lgFileResolver, dialogResolver } = useResolvers(projectId);
const [api, setApi] = useState(
createTriggerApi(
- { projectId, schemas, dialogs, locale, lgFiles },
+ { rootProjectId, projectId, schemas, dialogs, locale, lgFiles },
dispatchers,
luFileResolver,
lgFileResolver,
@@ -174,7 +181,7 @@ export function useTriggerApi(projectId: string) {
useEffect(() => {
const newApi = createTriggerApi(
- { projectId, schemas, dialogs, locale, lgFiles },
+ { rootProjectId, projectId, schemas, dialogs, locale, lgFiles },
dispatchers,
luFileResolver,
lgFileResolver,
diff --git a/extensions/pvaPublish/yarn.lock b/extensions/pvaPublish/yarn.lock
index 1c4fcd7cb0..de99390695 100644
--- a/extensions/pvaPublish/yarn.lock
+++ b/extensions/pvaPublish/yarn.lock
@@ -989,6 +989,7 @@
version "1.0.0"
dependencies:
"@botframework-composer/types" "*"
+ "@types/debug" "^4.1.5"
"@types/passport" "^1.0.3"
debug "^4.1.1"
fs-extra "^9.0.1"
@@ -1410,6 +1411,11 @@
dependencies:
"@types/node" "*"
+"@types/debug@^4.1.5":
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
+ integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
+
"@types/express-serve-static-core@*":
version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084"
From f7ee2c6de762c9ba93804b5366f88be3eeeead41 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Tue, 27 Oct 2020 13:23:16 -0700
Subject: [PATCH 28/44] fix creation and deletion bugs
---
.../components/ProjectTree/ProjectTree.tsx | 27 ++++++++++++-------
.../client/src/pages/design/DesignPage.tsx | 15 ++++++++++-
2 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index a737d32985..fefe9dcecc 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
/** @jsx jsx */
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useState, useEffect } from 'react';
import { jsx, css } from '@emotion/core';
import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox';
import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone';
@@ -12,8 +12,8 @@ import { DialogInfo, ITrigger, Diagnostic, DiagnosticSeverity } from '@bfc/share
import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
import { ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
-import isEqual from 'lodash/isEqual';
import { extractSchemaProperties, groupTriggersByPropertyReference, NoGroupingTriggerGroupName } from '@bfc/indexers';
+import isMatch from 'lodash/isMatch';
import {
dispatcherState,
@@ -43,7 +43,6 @@ const root = css`
width: 100%;
height: 100%;
box-sizing: border-box;
- overflow-y: auto;
overflow-x: hidden;
.ms-List-cell {
min-height: 36px;
@@ -62,9 +61,6 @@ const icons = {
};
const tree = css`
- height: 100%;
- overflow-x: hidden;
- overflow-y: auto;
height: 100%;
label: tree;
`;
@@ -135,6 +131,7 @@ type Props = {
navLinks?: TreeLink[];
onDeleteTrigger: (id: string, index: number) => void;
onDeleteDialog: (id: string) => void;
+ defaultSelected?: Partial;
};
export const ProjectTree: React.FC = ({
@@ -144,12 +141,13 @@ export const ProjectTree: React.FC = ({
onDeleteDialog,
onDeleteTrigger,
onSelect,
+ defaultSelected,
}) => {
const { onboardingAddCoachMarkRef, navigateToFormDialogSchema } = useRecoilValue(dispatcherState);
const [filter, setFilter] = useState('');
const formDialogComposerFeatureEnabled = useFeatureFlag('FORM_DIALOG');
- const [selectedLink, setSelectedLink] = useState();
+ const [selectedLink, setSelectedLink] = useState | undefined>(defaultSelected);
const delayedSetFilter = debounce((newValue) => setFilter(newValue), 1000);
const addMainDialogRef = useCallback((mainDialog) => onboardingAddCoachMarkRef({ mainDialog }), []);
const projectCollection = useRecoilValue(botProjectSpaceSelector).map((bot) => ({
@@ -157,6 +155,10 @@ export const ProjectTree: React.FC = ({
hasWarnings: false,
}));
+ useEffect(() => {
+ setSelectedLink(defaultSelected);
+ }, [defaultSelected]);
+
// if we're in a single-bot setting, the root will be undefined, so we fall back to current
const rootProjectId = useRecoilValue(rootBotProjectIdSelector) ?? useRecoilValue(currentProjectIdState);
const botProjectSpace = useRecoilValue(botProjectSpaceSelector);
@@ -204,6 +206,11 @@ export const ProjectTree: React.FC = ({
return bot.dialogs.some(dialogHasErrors(bot.projectId));
};
+ const isTriggerMatch = (link1?: Partial, link2?: Partial) => {
+ if (link1 == null || link2 == null) return false;
+ return isMatch(link1, link2);
+ };
+
const handleOnSelect = (link: TreeLink) => {
setSelectedLink(link);
onSelect(link);
@@ -233,7 +240,7 @@ export const ProjectTree: React.FC = ({
showProps
forceIndent={bot.isRemote ? SUMMARY_ARROW_SPACE : 0}
icon={bot.isRemote ? icons.EXTERNAL_SKILL : icons.BOT}
- isActive={isEqual(link, selectedLink)}
+ isActive={isTriggerMatch(link, selectedLink)}
link={link}
menu={[{ label: formatMessage('Create/edit skill manifest'), onClick: () => {} }]}
/>
@@ -279,7 +286,7 @@ export const ProjectTree: React.FC = ({
showProps
forceIndent={showTriggers ? 0 : SUMMARY_ARROW_SPACE}
icon={isFormDialog ? icons.FORM_DIALOG : icons.DIALOG}
- isActive={isEqual(link, selectedLink)}
+ isActive={isTriggerMatch(link, selectedLink)}
link={link}
menu={[
{
@@ -325,7 +332,7 @@ export const ProjectTree: React.FC = ({
dialogName={dialog.displayName}
forceIndent={48}
icon={icons.TRIGGER}
- isActive={isEqual(link, selectedLink)}
+ isActive={isTriggerMatch(link, selectedLink)}
link={link}
menu={[
{
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index 581e722c0f..9ab8e10179 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -536,7 +536,7 @@ const DesignPage: React.FC triggerApi.deleteTrigger(dialogId, trigger));
if (content) {
- updateDialog({ id: dialogId, content, projectId });
+ updateDialog({ id: dialogId, content, projectId: skillId ?? projectId });
const match = /\[(\d+)\]/g.exec(selected);
const current = match && match[1];
if (!current) return;
@@ -584,11 +584,24 @@ const DesignPage: React.FC t.id === selected);
const withWarning = triggerNotSupported(currentDialog, selectedTrigger);
+ const parseTriggerId = (triggerId: string | undefined): number | undefined => {
+ if (triggerId == null) return undefined;
+ const indexString = triggerId.match(/\d+/)?.[0];
+ if (indexString == null) return undefined;
+ return parseInt(indexString);
+ };
+
return (
Date: Tue, 27 Oct 2020 14:04:32 -0700
Subject: [PATCH 29/44] change mocks folder name
---
.../client/__tests__/{mocks => __mocks__}/sampleDialog.ts | 0
.../packages/client/__tests__/{mocks => __mocks__}/worker.ts | 0
Composer/packages/client/__tests__/components/design.test.tsx | 2 +-
.../packages/client/__tests__/components/projecttree.test.tsx | 2 +-
4 files changed, 2 insertions(+), 2 deletions(-)
rename Composer/packages/client/__tests__/{mocks => __mocks__}/sampleDialog.ts (100%)
rename Composer/packages/client/__tests__/{mocks => __mocks__}/worker.ts (100%)
diff --git a/Composer/packages/client/__tests__/mocks/sampleDialog.ts b/Composer/packages/client/__tests__/__mocks__/sampleDialog.ts
similarity index 100%
rename from Composer/packages/client/__tests__/mocks/sampleDialog.ts
rename to Composer/packages/client/__tests__/__mocks__/sampleDialog.ts
diff --git a/Composer/packages/client/__tests__/mocks/worker.ts b/Composer/packages/client/__tests__/__mocks__/worker.ts
similarity index 100%
rename from Composer/packages/client/__tests__/mocks/worker.ts
rename to Composer/packages/client/__tests__/__mocks__/worker.ts
diff --git a/Composer/packages/client/__tests__/components/design.test.tsx b/Composer/packages/client/__tests__/components/design.test.tsx
index aa4ad6edf5..278106ef5f 100644
--- a/Composer/packages/client/__tests__/components/design.test.tsx
+++ b/Composer/packages/client/__tests__/components/design.test.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { fireEvent } from '@botframework-composer/test-utils';
import { renderWithRecoil } from '../testUtils';
-import { SAMPLE_DIALOG } from '../mocks/sampleDialog';
+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';
diff --git a/Composer/packages/client/__tests__/components/projecttree.test.tsx b/Composer/packages/client/__tests__/components/projecttree.test.tsx
index 9412f1e955..0603440de4 100644
--- a/Composer/packages/client/__tests__/components/projecttree.test.tsx
+++ b/Composer/packages/client/__tests__/components/projecttree.test.tsx
@@ -6,7 +6,7 @@ import { fireEvent } from '@botframework-composer/test-utils';
import { ProjectTree } from '../../src/components/ProjectTree/ProjectTree';
import { renderWithRecoil } from '../testUtils';
-import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../mocks/sampleDialog';
+import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../__mocks__/sampleDialog';
import { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel';
const projectId = '12345.6789';
From 3406a933f017a301c524886fc8b6a7bb06ffdcc4 Mon Sep 17 00:00:00 2001
From: Srinaath Ravichandran
Date: Tue, 27 Oct 2020 15:27:17 -0700
Subject: [PATCH 30/44] Load design page only after skill project is loaded
Signed-off-by: Srinaath Ravichandran
---
Composer/packages/client/src/router.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Composer/packages/client/src/router.tsx b/Composer/packages/client/src/router.tsx
index 9488066c4d..fda81b41a4 100644
--- a/Composer/packages/client/src/router.tsx
+++ b/Composer/packages/client/src/router.tsx
@@ -104,7 +104,7 @@ const projectStyle = css`
label: ProjectRouter;
`;
-const ProjectRouter: React.FC> = (props) => {
+const ProjectRouter: React.FC> = (props) => {
const { projectId = '' } = props;
const schemas = useRecoilValue(schemasState(projectId));
const { fetchProjectById } = useRecoilValue(dispatcherState);
@@ -126,7 +126,11 @@ const ProjectRouter: React.FC> = (pro
}, [schemas, projectId]);
if (props.projectId && botProjects.includes(props.projectId)) {
- return {props.children}
;
+ if (props.skillId && !botProjects.includes(props.skillId)) {
+ return ;
+ } else {
+ return {props.children}
;
+ }
}
return ;
};
From 8f3573223fbdb035623ec11bdf4428c884b84d85 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Tue, 27 Oct 2020 15:42:11 -0700
Subject: [PATCH 31/44] fixes from CR
---
.../src/components/ProjectTree/ExpandableNode.tsx | 4 ++--
.../client/src/recoilModel/dispatchers/navigation.ts | 12 +++++++-----
Composer/packages/client/src/shell/useShell.ts | 6 ++++--
3 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
index fb7bae5a9e..bd3baefe10 100644
--- a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
@@ -26,11 +26,11 @@ const nodeStyle = (depth: number) => css`
const TRIANGLE_SCALE = 0.6;
const detailsStyle = css`
- details:not([open]) > summary::-webkit-details-marker {
+ &:not([open]) > summary::-webkit-details-marker {
transform: scaleX(${TRIANGLE_SCALE});
}
- details[open] > summary::-webkit-details-marker {
+ &[open] > summary::-webkit-details-marker {
transform: scaleY(${TRIANGLE_SCALE});
}
`;
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index fd9dddfc04..2f0955cbc1 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -9,7 +9,7 @@ import cloneDeep from 'lodash/cloneDeep';
import { currentProjectIdState } from '../atoms';
import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designerPathEncoder';
-import { dialogsSelectorFamily } from '../selectors';
+import { dialogsSelectorFamily, rootBotProjectIdSelector } from '../selectors';
import { createSelectedPath, getSelected } from './../../utils/dialogUtil';
import { BreadcrumbItem } from './../../recoilModel/types';
@@ -51,10 +51,12 @@ export const navigationDispatcher = () => {
const navTo = useRecoilCallback(
({ snapshot, set }: CallbackInterface) => async (
projectId: string,
- skillId: string | null,
dialogId: string,
breadcrumb: BreadcrumbItem[] = []
) => {
+ const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
+ if (rootBotProjectId == null) return;
+
const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId));
const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
const updatedBreadcrumb = cloneDeep(breadcrumb);
@@ -72,9 +74,9 @@ export const navigationDispatcher = () => {
}
}
- const currentUri = convertPathToUrl(projectId, skillId, dialogId, path);
+ const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId, path);
- if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
+ if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
}
@@ -174,7 +176,7 @@ export const navigationDispatcher = () => {
if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb } });
} else {
- navTo(projectId, skillId, dialogId, breadcrumb);
+ navTo(skillId ?? projectId, dialogId, breadcrumb);
}
}
);
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index f047288bd5..61f23afeae 100644
--- a/Composer/packages/client/src/shell/useShell.ts
+++ b/Composer/packages/client/src/shell/useShell.ts
@@ -137,11 +137,13 @@ export function useShell(source: EventSource, projectId: string): Shell {
}
function navigationTo(path) {
- navTo(rootBotProjectId ?? projectId, projectId, path, breadcrumb);
+ if (rootBotProjectId == null) return;
+ navTo(projectId, path, breadcrumb);
}
function focusEvent(subPath) {
- selectTo(rootBotProjectId ?? projectId, projectId, dialogId, subPath);
+ if (rootBotProjectId == null) return;
+ selectTo(rootBotProjectId, projectId, dialogId, subPath);
}
function focusSteps(subPaths: string[] = [], fragment?: string) {
From e3fa82bf641d06a7ad5e6e0e9095c6f2bf67b9e2 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Wed, 28 Oct 2020 09:06:47 -0700
Subject: [PATCH 32/44] fix more tests
---
.../__tests__/components/design.test.tsx | 2 +-
.../__tests__/components/projecttree.test.tsx | 2 +-
.../{__mocks__ => mocks}/sampleDialog.ts | 0
.../__tests__/{__mocks__ => mocks}/worker.ts | 0
.../client/src/pages/design/DesignPage.tsx | 14 ++---
.../dispatchers/__tests__/navigation.test.tsx | 60 +++++++++++++++----
.../src/recoilModel/dispatchers/navigation.ts | 14 +++--
.../packages/client/src/shell/triggerApi.ts | 19 ++----
.../packages/client/src/shell/useShell.ts | 2 +-
9 files changed, 75 insertions(+), 38 deletions(-)
rename Composer/packages/client/__tests__/{__mocks__ => mocks}/sampleDialog.ts (100%)
rename Composer/packages/client/__tests__/{__mocks__ => mocks}/worker.ts (100%)
diff --git a/Composer/packages/client/__tests__/components/design.test.tsx b/Composer/packages/client/__tests__/components/design.test.tsx
index 278106ef5f..aa4ad6edf5 100644
--- a/Composer/packages/client/__tests__/components/design.test.tsx
+++ b/Composer/packages/client/__tests__/components/design.test.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { fireEvent } from '@botframework-composer/test-utils';
import { renderWithRecoil } from '../testUtils';
-import { SAMPLE_DIALOG } from '../__mocks__/sampleDialog';
+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';
diff --git a/Composer/packages/client/__tests__/components/projecttree.test.tsx b/Composer/packages/client/__tests__/components/projecttree.test.tsx
index 0603440de4..9412f1e955 100644
--- a/Composer/packages/client/__tests__/components/projecttree.test.tsx
+++ b/Composer/packages/client/__tests__/components/projecttree.test.tsx
@@ -6,7 +6,7 @@ import { fireEvent } from '@botframework-composer/test-utils';
import { ProjectTree } from '../../src/components/ProjectTree/ProjectTree';
import { renderWithRecoil } from '../testUtils';
-import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../__mocks__/sampleDialog';
+import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../mocks/sampleDialog';
import { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel';
const projectId = '12345.6789';
diff --git a/Composer/packages/client/__tests__/__mocks__/sampleDialog.ts b/Composer/packages/client/__tests__/mocks/sampleDialog.ts
similarity index 100%
rename from Composer/packages/client/__tests__/__mocks__/sampleDialog.ts
rename to Composer/packages/client/__tests__/mocks/sampleDialog.ts
diff --git a/Composer/packages/client/__tests__/__mocks__/worker.ts b/Composer/packages/client/__tests__/mocks/worker.ts
similarity index 100%
rename from Composer/packages/client/__tests__/__mocks__/worker.ts
rename to Composer/packages/client/__tests__/mocks/worker.ts
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index 9ab8e10179..cbc008a9ad 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -217,7 +217,7 @@ const DesignPage: React.FC {
if (dialogId) {
- navTo(projectId, null, dialogId, []);
+ navTo(projectId, dialogId, []);
}
};
@@ -544,14 +544,14 @@ const DesignPage: React.FC= 0) {
//if the deleted node is selected and the selected one is not the first one, navTo the previous trigger;
- selectTo(projectId, skillId, dialogId, createSelectedPath(currentIdx - 1));
+ selectTo(skillId ?? projectId, dialogId, createSelectedPath(currentIdx - 1));
} else {
//if the deleted node is selected and the selected one is the first one, navTo the first trigger;
- navTo(projectId, skillId, dialogId, []);
+ navTo(skillId ?? projectId, dialogId, []);
}
} else if (index < currentIdx) {
//if the deleted node is at the front, navTo the current one;
- selectTo(projectId, skillId, dialogId, createSelectedPath(currentIdx - 1));
+ selectTo(skillId ?? projectId, dialogId, createSelectedPath(currentIdx - 1));
}
}
}
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 d58ae40785..a08cf64459 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
@@ -21,7 +21,7 @@ import {
} from '../../../utils/navigation';
import { createSelectedPath, getSelected } from '../../../utils/dialogUtil';
import { BreadcrumbItem } from '../../../recoilModel/types';
-import { currentProjectIdState } from '../../atoms';
+import { currentProjectIdState, botProjectIdsState, botProjectFileState, projectMetaDataState } from '../../atoms';
jest.mock('../../../utils/navigation');
jest.mock('../../../utils/dialogUtil');
@@ -35,6 +35,7 @@ const mockConvertPathToUrl = convertPathToUrl as jest.Mock;
const mockCreateSelectedPath = createSelectedPath as jest.Mock;
const projectId = '12345.678';
+const skillId = '98765.4321';
function expectNavTo(location: string, state: {} | null = null) {
expect(mockNavigateTo).toHaveBeenLastCalledWith(location, state == null ? expect.anything() : state);
@@ -85,6 +86,18 @@ describe('navigation dispatcher', () => {
recoilState: dialogsSelectorFamily(projectId),
initialValue: [{ id: 'newDialogId', triggers: [{ type: SDKKinds.OnBeginDialog }] }],
},
+ {
+ recoilState: botProjectIdsState,
+ initialValue: [projectId],
+ },
+ {
+ recoilState: botProjectFileState(projectId),
+ initialValue: { foo: 'bar' },
+ },
+ {
+ recoilState: projectMetaDataState(projectId),
+ initialValue: { isRootBot: true },
+ },
],
dispatcher: {
recoilState: dispatcherState,
@@ -176,27 +189,27 @@ describe('navigation dispatcher', () => {
it('navigates to a destination', async () => {
mockConvertPathToUrl.mockReturnValue(`/bot/${projectId}/dialogs/dialogId`);
await act(async () => {
- await dispatcher.navTo(projectId, null, 'dialogId', []);
+ await dispatcher.navTo(projectId, 'dialogId', []);
});
expectNavTo(`/bot/${projectId}/dialogs/dialogId`);
- expect(mockConvertPathToUrl).toBeCalledWith(projectId, null, 'dialogId', undefined);
+ expect(mockConvertPathToUrl).toBeCalledWith(projectId, projectId, 'dialogId', undefined);
});
it('redirects to the begin dialog trigger', async () => {
mockConvertPathToUrl.mockReturnValue(`/bot/${projectId}/dialogs/newDialogId?selection=triggers[0]`);
mockCreateSelectedPath.mockReturnValue('triggers[0]');
await act(async () => {
- await dispatcher.navTo(projectId, null, 'newDialogId', []);
+ await dispatcher.navTo(projectId, 'newDialogId', []);
});
expectNavTo(`/bot/${projectId}/dialogs/newDialogId?selection=triggers[0]`);
- expect(mockConvertPathToUrl).toBeCalledWith(projectId, null, 'newDialogId', 'triggers[0]');
+ expect(mockConvertPathToUrl).toBeCalledWith(projectId, projectId, 'newDialogId', 'triggers[0]');
expect(mockCreateSelectedPath).toBeCalledWith(0);
});
it("doesn't navigate to a destination where we already are", async () => {
mockCheckUrl.mockReturnValue(true);
await act(async () => {
- await dispatcher.navTo(projectId, null, 'dialogId', []);
+ await dispatcher.navTo(projectId, 'dialogId', []);
});
expect(mockNavigateTo).not.toBeCalled();
});
@@ -205,7 +218,7 @@ describe('navigation dispatcher', () => {
describe('selectTo', () => {
it("doesn't go anywhere without a selection", async () => {
await act(async () => {
- await dispatcher.selectTo(projectId, null, null, '');
+ await dispatcher.selectTo(null, null, '');
});
expect(mockNavigateTo).not.toBeCalled();
});
@@ -213,16 +226,25 @@ describe('navigation dispatcher', () => {
it('navigates to a default URL with selected path', async () => {
mockConvertPathToUrl.mockReturnValue(`/bot/${projectId}/dialogs/dialogId?selected=selection`);
await act(async () => {
- await dispatcher.selectTo(projectId, null, null, 'selection');
+ await dispatcher.selectTo(null, null, 'selection');
});
expectNavTo(`/bot/${projectId}/dialogs/dialogId?selected=selection`);
expect(mockConvertPathToUrl).toBeCalledWith(projectId, null, 'dialogId', 'selection');
});
+ it('navigates to a default URL with skillId and selected path', async () => {
+ mockConvertPathToUrl.mockReturnValue(`/bot/${projectId}/skill/${skillId}/dialogs/dialogId?selected=selection`);
+ await act(async () => {
+ await dispatcher.selectTo(skillId, 'dialogId', 'selection');
+ });
+ expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogId?selected=selection`);
+ expect(mockConvertPathToUrl).toBeCalledWith(projectId, skillId, 'dialogId', 'selection');
+ });
+
it("doesn't go anywhere if we're already there", async () => {
mockCheckUrl.mockReturnValue(true);
await act(async () => {
- await dispatcher.selectTo(projectId, null, null, 'selection');
+ await dispatcher.selectTo(null, null, 'selection');
});
expect(mockNavigateTo).not.toBeCalled();
});
@@ -246,6 +268,16 @@ describe('navigation dispatcher', () => {
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused);
});
+ it('goes to a focused page with skill', async () => {
+ mockGetSelected.mockReturnValueOnce('select');
+ await act(async () => {
+ await dispatcher.focusTo(projectId, skillId, 'focus', '');
+ });
+ expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogId?selected=select&focused=focus`);
+ expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
+ expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused);
+ });
+
it('goes to a focused page with fragment', async () => {
mockGetSelected.mockReturnValueOnce('select');
await act(async () => {
@@ -269,7 +301,7 @@ describe('navigation dispatcher', () => {
});
describe('selectAndFocus', () => {
- it('sets selection and focus with a valud search', async () => {
+ it('sets selection and focus with a valid search', async () => {
mockGetUrlSearch.mockReturnValue('?foo=bar&baz=quux');
await act(async () => {
await dispatcher.selectAndFocus(projectId, null, 'dialogId', 'select', 'focus');
@@ -277,6 +309,14 @@ describe('navigation dispatcher', () => {
expectNavTo(`/bot/${projectId}/dialogs/dialogId?foo=bar&baz=quux`);
});
+ it('sets selection and focus with a valid search and skillId', async () => {
+ mockGetUrlSearch.mockReturnValue('?foo=bar&baz=quux');
+ await act(async () => {
+ await dispatcher.selectAndFocus(projectId, skillId, 'dialogId', 'select', 'focus');
+ });
+ expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogId?foo=bar&baz=quux`);
+ });
+
it("doesn't go anywhere if we're already there", async () => {
mockCheckUrl.mockReturnValue(true);
await act(async () => {
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index 2f0955cbc1..d5549102e2 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -75,7 +75,7 @@ export const navigationDispatcher = () => {
}
const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId, path);
-
+ console.log('navigate to', currentUri);
if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
@@ -84,12 +84,16 @@ export const navigationDispatcher = () => {
const selectTo = useRecoilCallback(
({ snapshot, set }: CallbackInterface) => async (
- projectId: string,
skillId: string | null,
destinationDialogId: string | null,
selectPath: string
) => {
if (!selectPath) return;
+ const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
+ if (rootBotProjectId == null) return;
+
+ const projectId = skillId ?? rootBotProjectId;
+
set(currentProjectIdState, projectId);
const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
const breadcrumb = await snapshot.getPromise(breadcrumbState(projectId));
@@ -100,9 +104,9 @@ export const navigationDispatcher = () => {
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);
+ const currentUri = convertPathToUrl(rootBotProjectId, skillId, dialogId, encodedSelectPath);
- if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
+ if (checkUrl(currentUri, rootBotProjectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected) } });
}
);
@@ -171,7 +175,7 @@ export const navigationDispatcher = () => {
const currentUri =
skillId == null
? `/bot/${projectId}/dialogs/${dialogId}${search}`
- : `/bot/${projectId}/skill/{skillId}/dialogs/${dialogId}${search}`;
+ : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}${search}`;
if (checkUrl(currentUri, projectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb } });
diff --git a/Composer/packages/client/src/shell/triggerApi.ts b/Composer/packages/client/src/shell/triggerApi.ts
index f7a59a87bc..c1d6ceb6c5 100644
--- a/Composer/packages/client/src/shell/triggerApi.ts
+++ b/Composer/packages/client/src/shell/triggerApi.ts
@@ -18,13 +18,7 @@ import get from 'lodash/get';
import { useResolvers } from '../hooks/useResolver';
import { onChooseIntentKey, generateNewDialog, intentTypeKey, qnaMatcherKey } from '../utils/dialogUtil';
-import {
- schemasState,
- lgFilesState,
- dialogsSelectorFamily,
- localeState,
- rootBotProjectIdSelector,
-} from '../recoilModel';
+import { schemasState, lgFilesState, dialogsSelectorFamily, localeState } from '../recoilModel';
import { Dispatcher } from '../recoilModel/dispatchers';
import { dispatcherState } from './../recoilModel/DispatcherWrapper';
@@ -41,7 +35,7 @@ const defaultQnATriggerData = {
};
function createTriggerApi(
- state: { rootProjectId; projectId; schemas; dialogs; locale; lgFiles },
+ state: { projectId; schemas; dialogs; locale; lgFiles },
dispatchers: Dispatcher, //TODO
luFileResolver: (id: string) => LuFile | undefined,
lgFileResolver: (id: string) => LgFile | undefined,
@@ -61,7 +55,7 @@ function createTriggerApi(
const lgFile = lgFileResolver(id);
const dialog = dialogResolver(id);
const { createLuIntent, createLgTemplates, updateDialog, selectTo } = dispatchers;
- const { rootProjectId, projectId, schemas, dialogs, locale, lgFiles } = state;
+ const { projectId, schemas, dialogs, locale, lgFiles } = state;
if (!luFile) throw new Error(`lu file ${id} not found`);
if (!lgFile) throw new Error(`lg file ${id} not found`);
if (!dialog) throw new Error(`dialog ${id} not found`);
@@ -118,7 +112,7 @@ function createTriggerApi(
};
await updateDialog(dialogPayload);
if (autoSelected) {
- selectTo(rootProjectId, projectId, newDialog.id, `triggers[${index}]`);
+ selectTo(projectId, newDialog.id, `triggers[${index}]`);
}
};
@@ -161,7 +155,6 @@ export function useTriggerApi(projectId: string) {
const lgFiles = useRecoilValue(lgFilesState(projectId));
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const locale = useRecoilValue(localeState(projectId));
- const rootProjectId = useRecoilValue(rootBotProjectIdSelector);
const { deleteActions } = useActionApi(projectId);
const { removeLuIntent } = useLuApi(projectId);
@@ -169,7 +162,7 @@ export function useTriggerApi(projectId: string) {
const { luFileResolver, lgFileResolver, dialogResolver } = useResolvers(projectId);
const [api, setApi] = useState(
createTriggerApi(
- { rootProjectId, projectId, schemas, dialogs, locale, lgFiles },
+ { projectId, schemas, dialogs, locale, lgFiles },
dispatchers,
luFileResolver,
lgFileResolver,
@@ -181,7 +174,7 @@ export function useTriggerApi(projectId: string) {
useEffect(() => {
const newApi = createTriggerApi(
- { rootProjectId, projectId, schemas, dialogs, locale, lgFiles },
+ { projectId, schemas, dialogs, locale, lgFiles },
dispatchers,
luFileResolver,
lgFileResolver,
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index 61f23afeae..3e44f3ee05 100644
--- a/Composer/packages/client/src/shell/useShell.ts
+++ b/Composer/packages/client/src/shell/useShell.ts
@@ -143,7 +143,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
function focusEvent(subPath) {
if (rootBotProjectId == null) return;
- selectTo(rootBotProjectId, projectId, dialogId, subPath);
+ selectTo(projectId, dialogId, subPath);
}
function focusSteps(subPaths: string[] = [], fragment?: string) {
From 3d328571be8072e68d34a5d4d1ba230d177c98ed Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Wed, 28 Oct 2020 11:31:08 -0700
Subject: [PATCH 33/44] fix more unit tests
---
.../dispatchers/__tests__/navigation.test.tsx | 22 ++++++++++++++++++-
.../src/recoilModel/dispatchers/navigation.ts | 2 +-
2 files changed, 22 insertions(+), 2 deletions(-)
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 a08cf64459..3ebdd821e4 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
@@ -81,6 +81,14 @@ describe('navigation dispatcher', () => {
focused: 'b',
},
},
+ {
+ recoilState: designPageLocationState(skillId),
+ initialValue: {
+ dialogId: 'dialogInSkillId',
+ selected: 'a',
+ focused: 'b',
+ },
+ },
{ recoilState: currentProjectIdState, initialValue: projectId },
{
recoilState: dialogsSelectorFamily(projectId),
@@ -273,7 +281,7 @@ describe('navigation dispatcher', () => {
await act(async () => {
await dispatcher.focusTo(projectId, skillId, 'focus', '');
});
- expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogId?selected=select&focused=focus`);
+ expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogInSkillId?selected=select&focused=focus`);
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused);
});
@@ -288,6 +296,18 @@ describe('navigation dispatcher', () => {
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused);
});
+ it('goes to a focused page with skill and fragment', async () => {
+ mockGetSelected.mockReturnValueOnce('select');
+ await act(async () => {
+ await dispatcher.focusTo(projectId, skillId, 'focus', 'fragment');
+ });
+ expectNavTo(
+ `/bot/${projectId}/skillId/${skillId}/dialogs/dialogInSkillId?selected=select&focused=focus#fragment`
+ );
+ expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
+ expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused);
+ });
+
it('stays on the same page but updates breadcrumbs with a checked URL', async () => {
mockCheckUrl.mockReturnValue(true);
mockGetSelected.mockReturnValueOnce('select');
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index d5549102e2..82726649c5 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -75,7 +75,6 @@ export const navigationDispatcher = () => {
}
const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId, path);
- console.log('navigate to', currentUri);
if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
@@ -131,6 +130,7 @@ export const navigationDispatcher = () => {
if (focusPath) {
const dialogs = await snapshot.getPromise(dialogsSelectorFamily(skillId ?? projectId));
+ console.log('in focusTo', projectId, skillId, focusPath, fragment, 'dialogs=', dialogs);
const currentDialog = dialogs.find(({ id }) => id === dialogId);
const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog?.content, focusPath);
From 3c106df7e883211603a186c8f8f0241ec4665ebe Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Wed, 28 Oct 2020 11:59:12 -0700
Subject: [PATCH 34/44] fix test typol
---
.../src/recoilModel/dispatchers/__tests__/navigation.test.tsx | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
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 3ebdd821e4..6e4f4ebfd8 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
@@ -301,9 +301,7 @@ describe('navigation dispatcher', () => {
await act(async () => {
await dispatcher.focusTo(projectId, skillId, 'focus', 'fragment');
});
- expectNavTo(
- `/bot/${projectId}/skillId/${skillId}/dialogs/dialogInSkillId?selected=select&focused=focus#fragment`
- );
+ expectNavTo(`/bot/${projectId}/skill/${skillId}/dialogs/dialogInSkillId?selected=select&focused=focus#fragment`);
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Selected);
expect(mockUpdateBreadcrumb).toHaveBeenCalledWith(expect.anything(), BreadcrumbUpdateType.Focused);
});
From 5662dcff54c3238ac6bba0af2ff47911c7c56f53 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 10:46:16 -0700
Subject: [PATCH 35/44] handle clicks on bots as bot/id/skill/id
---
.../components/ProjectTree/ProjectTree.tsx | 2 ++
.../client/src/pages/design/DesignPage.tsx | 9 ++++++---
.../src/recoilModel/dispatchers/navigation.ts | 19 +++++++++++--------
.../packages/client/src/utils/navigation.ts | 19 ++++++++++++++-----
4 files changed, 33 insertions(+), 16 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index 4a72f5c047..a56163cc7a 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -14,6 +14,7 @@ import { useRecoilValue } from 'recoil';
import { ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import { extractSchemaProperties, groupTriggersByPropertyReference, NoGroupingTriggerGroupName } from '@bfc/indexers';
import isMatch from 'lodash/isMatch';
+import isEqual from 'lodash/isEqual';
import {
dispatcherState,
@@ -250,6 +251,7 @@ export const ProjectTree: React.FC = ({
isActive={isTriggerMatch(link, selectedLink)}
link={link}
menu={[{ label: formatMessage('Create/edit skill manifest'), onClick: () => {} }]}
+ onSelect={handleOnSelect}
/>
);
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index cbc008a9ad..bbe2e9058d 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -264,6 +264,9 @@ const DesignPage: React.FC {
const navTo = useRecoilCallback(
({ snapshot, set }: CallbackInterface) => async (
projectId: string,
- dialogId: string,
+ dialogId: string | null,
breadcrumb: BreadcrumbItem[] = []
) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
@@ -64,13 +64,16 @@ export const navigationDispatcher = () => {
let path;
if (dialogId !== designPageLocation.dialogId) {
- const currentDialog = dialogs.find(({ id }) => id === dialogId);
- const beginDialogIndex = currentDialog?.triggers.findIndex(({ type }) => type === SDKKinds.OnBeginDialog);
-
- if (typeof beginDialogIndex !== 'undefined' && beginDialogIndex >= 0) {
- path = createSelectedPath(beginDialogIndex);
- path = encodeArrayPathToDesignerPath(currentDialog?.content, path);
- updatedBreadcrumb.push({ dialogId, selected: '', focused: '' });
+ if (dialogId == null) {
+ } else {
+ const currentDialog = dialogs.find(({ id }) => id === dialogId);
+ const beginDialogIndex = currentDialog?.triggers.findIndex(({ type }) => type === SDKKinds.OnBeginDialog);
+
+ if (typeof beginDialogIndex !== 'undefined' && beginDialogIndex >= 0) {
+ path = createSelectedPath(beginDialogIndex);
+ path = encodeArrayPathToDesignerPath(currentDialog?.content, path);
+ updatedBreadcrumb.push({ dialogId, selected: '', focused: '' });
+ }
}
}
diff --git a/Composer/packages/client/src/utils/navigation.ts b/Composer/packages/client/src/utils/navigation.ts
index 49e76b7dd1..dcc7b19fe4 100644
--- a/Composer/packages/client/src/utils/navigation.ts
+++ b/Composer/packages/client/src/utils/navigation.ts
@@ -87,14 +87,23 @@ export interface NavigationState {
qnaKbUrls?: string[];
}
-export function convertPathToUrl(projectId: string, skillId: string | null, dialogId: string, path?: string): string {
+export function convertPathToUrl(
+ projectId: string,
+ skillId: string | null,
+ dialogId: string | null,
+ path?: string
+): string {
//path is like main.triggers[0].actions[0]
//uri = id?selected=triggers[0]&focused=triggers[0].actions[0]
- let uri =
- skillId == null
- ? `/bot/${projectId}/dialogs/${dialogId}`
- : `/bot/${projectId}/skill/${skillId}/dialogs/${dialogId}`;
+ let uri = `/bot/${projectId}`;
+ if (skillId != null) {
+ uri += `/skill/${skillId}`;
+ }
+ if (dialogId != null) {
+ uri += `/dialogs/${dialogId}`;
+ }
+
if (!path) return uri;
const items = path.split('#');
From ead752b7791d8bfa5b443447fc5bec8782dcd2de Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 12:41:27 -0700
Subject: [PATCH 36/44] always go to dialog on click (not to a trigger in it)
---
.../client/src/pages/design/DesignPage.tsx | 7 +++---
.../src/recoilModel/dispatchers/navigation.ts | 22 ++++---------------
2 files changed, 7 insertions(+), 22 deletions(-)
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index bbe2e9058d..6a33b5e3e4 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -257,11 +257,10 @@ const DesignPage: React.FC {
const navTo = useRecoilCallback(
({ snapshot, set }: CallbackInterface) => async (
- projectId: string,
+ skillId: string | null,
dialogId: string | null,
breadcrumb: BreadcrumbItem[] = []
) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
if (rootBotProjectId == null) return;
- const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId));
+ const projectId = skillId ?? rootBotProjectId;
+
const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
const updatedBreadcrumb = cloneDeep(breadcrumb);
set(currentProjectIdState, projectId);
- let path;
- if (dialogId !== designPageLocation.dialogId) {
- if (dialogId == null) {
- } else {
- const currentDialog = dialogs.find(({ id }) => id === dialogId);
- const beginDialogIndex = currentDialog?.triggers.findIndex(({ type }) => type === SDKKinds.OnBeginDialog);
-
- if (typeof beginDialogIndex !== 'undefined' && beginDialogIndex >= 0) {
- path = createSelectedPath(beginDialogIndex);
- path = encodeArrayPathToDesignerPath(currentDialog?.content, path);
- updatedBreadcrumb.push({ dialogId, selected: '', focused: '' });
- }
- }
- }
-
- const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId, path);
+ const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId);
if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
From 90a0a3f6cc4833d3e776557c1bd62b14222aa7b1 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 13:19:22 -0700
Subject: [PATCH 37/44] fix unit tests
---
.../dispatchers/__tests__/navigation.test.tsx | 13 +------------
.../src/recoilModel/dispatchers/navigation.ts | 4 ++--
2 files changed, 3 insertions(+), 14 deletions(-)
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 6e4f4ebfd8..9661ff113f 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
@@ -200,18 +200,7 @@ describe('navigation dispatcher', () => {
await dispatcher.navTo(projectId, 'dialogId', []);
});
expectNavTo(`/bot/${projectId}/dialogs/dialogId`);
- expect(mockConvertPathToUrl).toBeCalledWith(projectId, projectId, 'dialogId', undefined);
- });
-
- it('redirects to the begin dialog trigger', async () => {
- mockConvertPathToUrl.mockReturnValue(`/bot/${projectId}/dialogs/newDialogId?selection=triggers[0]`);
- mockCreateSelectedPath.mockReturnValue('triggers[0]');
- await act(async () => {
- await dispatcher.navTo(projectId, 'newDialogId', []);
- });
- expectNavTo(`/bot/${projectId}/dialogs/newDialogId?selection=triggers[0]`);
- expect(mockConvertPathToUrl).toBeCalledWith(projectId, projectId, 'newDialogId', 'triggers[0]');
- expect(mockCreateSelectedPath).toBeCalledWith(0);
+ expect(mockConvertPathToUrl).toBeCalledWith(projectId, projectId, 'dialogId');
});
it("doesn't navigate to a destination where we already are", async () => {
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index 179728f834..b0c78e4b24 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -4,14 +4,14 @@
//TODO: refactor the router to use one-way data flow
import { useRecoilCallback, CallbackInterface } from 'recoil';
-import { PromptTab, SDKKinds } from '@bfc/shared';
+import { PromptTab } from '@bfc/shared';
import cloneDeep from 'lodash/cloneDeep';
import { currentProjectIdState } from '../atoms';
import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designerPathEncoder';
import { dialogsSelectorFamily, rootBotProjectIdSelector } from '../selectors';
-import { createSelectedPath, getSelected } from './../../utils/dialogUtil';
+import { getSelected } from './../../utils/dialogUtil';
import { BreadcrumbItem } from './../../recoilModel/types';
import { breadcrumbState, designPageLocationState, focusPathState } from './../atoms/botState';
import {
From a5bd2ee96102c176b43146fb4e5f14834a313c4d Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 14:04:42 -0700
Subject: [PATCH 38/44] fix link highlight for dialogs
---
.../src/components/ProjectTree/ProjectTree.tsx | 17 ++++++++++-------
.../client/src/pages/design/DesignPage.tsx | 2 +-
.../packages/client/src/recoilModel/types.ts | 1 +
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index a56163cc7a..ab6bd48da6 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -13,7 +13,6 @@ import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
import { ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import { extractSchemaProperties, groupTriggersByPropertyReference, NoGroupingTriggerGroupName } from '@bfc/indexers';
-import isMatch from 'lodash/isMatch';
import isEqual from 'lodash/isEqual';
import {
@@ -211,9 +210,13 @@ export const ProjectTree: React.FC = ({
return bot.dialogs.some(dialogHasErrors(bot.projectId));
};
- const isTriggerMatch = (link1?: Partial, link2?: Partial) => {
- if (link1 == null || link2 == null) return false;
- return isMatch(link1, link2);
+ const doesLinkMatch = (linkInTree?: Partial, selectedLink?: Partial) => {
+ if (linkInTree == null || selectedLink == null) return false;
+ return (
+ linkInTree.skillId === selectedLink.skillId &&
+ linkInTree.dialogId === selectedLink.dialogId &&
+ linkInTree.trigger === selectedLink.trigger
+ );
};
const handleOnSelect = (link: TreeLink) => {
@@ -248,7 +251,7 @@ export const ProjectTree: React.FC = ({
showProps
forceIndent={bot.isRemote ? SUMMARY_ARROW_SPACE : 0}
icon={bot.isRemote ? icons.EXTERNAL_SKILL : icons.BOT}
- isActive={isTriggerMatch(link, selectedLink)}
+ isActive={doesLinkMatch(link, selectedLink)}
link={link}
menu={[{ label: formatMessage('Create/edit skill manifest'), onClick: () => {} }]}
onSelect={handleOnSelect}
@@ -295,7 +298,7 @@ export const ProjectTree: React.FC = ({
showProps
forceIndent={showTriggers ? 0 : SUMMARY_ARROW_SPACE}
icon={isFormDialog ? icons.FORM_DIALOG : icons.DIALOG}
- isActive={isTriggerMatch(link, selectedLink)}
+ isActive={doesLinkMatch(link, selectedLink)}
link={link}
menu={[
{
@@ -340,7 +343,7 @@ export const ProjectTree: React.FC = ({
dialogName={dialog.displayName}
forceIndent={48}
icon={icons.TRIGGER}
- isActive={isTriggerMatch(link, selectedLink)}
+ isActive={doesLinkMatch(link, selectedLink)}
link={link}
menu={[
{
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index 6a33b5e3e4..dfd8f56b33 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -222,7 +222,7 @@ const DesignPage: React.FC
Date: Thu, 29 Oct 2020 14:59:57 -0700
Subject: [PATCH 39/44] attempt to fix breadcrumbs (will finish in separate PR)
---
Composer/packages/client/src/pages/design/DesignPage.tsx | 6 ++++--
.../client/src/recoilModel/dispatchers/navigation.ts | 4 +---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx
index dfd8f56b33..e838c9a3d2 100644
--- a/Composer/packages/client/src/pages/design/DesignPage.tsx
+++ b/Composer/packages/client/src/pages/design/DesignPage.tsx
@@ -262,7 +262,9 @@ const DesignPage: React.FC {
if (dialogId) {
- navTo(projectId, dialogId, []);
+ navTo(projectId, dialogId, [{ dialogId, selected: '', focused: '' }]);
}
};
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index b0c78e4b24..c7492d5108 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -60,13 +60,12 @@ export const navigationDispatcher = () => {
const projectId = skillId ?? rootBotProjectId;
const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
- const updatedBreadcrumb = cloneDeep(breadcrumb);
set(currentProjectIdState, projectId);
const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId);
if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return;
- navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
+ navigateTo(currentUri, { state: { breadcrumb } });
}
);
@@ -119,7 +118,6 @@ export const navigationDispatcher = () => {
if (focusPath) {
const dialogs = await snapshot.getPromise(dialogsSelectorFamily(skillId ?? projectId));
- console.log('in focusTo', projectId, skillId, focusPath, fragment, 'dialogs=', dialogs);
const currentDialog = dialogs.find(({ id }) => id === dialogId);
const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog?.content, focusPath);
From 3989f1eda46c891d91edeac5a960765b9f4cc017 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 15:32:50 -0700
Subject: [PATCH 40/44] fixes from CR
---
.../packages/client/src/recoilModel/dispatchers/navigation.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
index c7492d5108..e18df80e4d 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -5,7 +5,6 @@
//TODO: refactor the router to use one-way data flow
import { useRecoilCallback, CallbackInterface } from 'recoil';
import { PromptTab } from '@bfc/shared';
-import cloneDeep from 'lodash/cloneDeep';
import { currentProjectIdState } from '../atoms';
import { encodeArrayPathToDesignerPath } from '../../utils/convertUtils/designerPathEncoder';
@@ -105,7 +104,7 @@ export const navigationDispatcher = () => {
focusPath: string,
fragment: string
) => {
- set(currentProjectIdState, projectId);
+ set(currentProjectIdState, skillId ?? projectId);
const designPageLocation = await snapshot.getPromise(designPageLocationState(skillId ?? projectId));
const breadcrumb = await snapshot.getPromise(breadcrumbState(skillId ?? projectId));
let updatedBreadcrumb = [...breadcrumb];
From 0dcc8e1b70e4ce1054ad38bd9135e30e2eeb4124 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 16:13:24 -0700
Subject: [PATCH 41/44] Update ProjectTree.tsx
---
.../packages/client/src/components/ProjectTree/ProjectTree.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index ab6bd48da6..8b2cbd7970 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -163,8 +163,7 @@ export const ProjectTree: React.FC = ({
setSelectedLink(defaultSelected);
}, [defaultSelected]);
- // if we're in a single-bot setting, the root will be undefined, so we fall back to current
- const rootProjectId = useRecoilValue(rootBotProjectIdSelector) ?? useRecoilValue(currentProjectIdState);
+ const rootProjectId = useRecoilValue(rootBotProjectIdSelector);
const botProjectSpace = useRecoilValue(botProjectSpaceSelector);
const jsonSchemaFilesByProjectId = useRecoilValue(jsonSchemaFilesByProjectIdSelector);
From 502fe70ec91bf772b5238384bfb315d7e02f4e1c Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 16:28:09 -0700
Subject: [PATCH 42/44] Update ProjectTree.tsx
---
.../packages/client/src/components/ProjectTree/ProjectTree.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index 8b2cbd7970..7450615cc5 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -18,7 +18,6 @@ import isEqual from 'lodash/isEqual';
import {
dispatcherState,
rootBotProjectIdSelector,
- currentProjectIdState,
botProjectSpaceSelector,
jsonSchemaFilesByProjectIdSelector,
} from '../../recoilModel';
From e17819774b07962c57b0acad7b60c6929efdaf46 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 17:06:10 -0700
Subject: [PATCH 43/44] Update ProjectTree.tsx
---
.../client/src/components/ProjectTree/ProjectTree.tsx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index 7450615cc5..5de75b7a78 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -24,6 +24,7 @@ import {
import { getFriendlyName } from '../../utils/dialogUtil';
import { triggerNotSupported } from '../../utils/dialogValidator';
import { useFeatureFlag } from '../../utils/hooks';
+import { LoadingSpinner } from '../LoadingSpinner';
import { TreeItem } from './treeItem';
import { ExpandableNode } from './ExpandableNode';
@@ -167,6 +168,11 @@ export const ProjectTree: React.FC = ({
const jsonSchemaFilesByProjectId = useRecoilValue(jsonSchemaFilesByProjectIdSelector);
+ if (rootProjectId == null) {
+ // this should only happen before a project is loaded in, so it won't last very long
+ return ;
+ }
+
const notificationMap: { [projectId: string]: { [dialogId: string]: Diagnostic[] } } = {};
for (const bot of projectCollection) {
From 70119c1596a4bc6833047906b45ae259e8e9a6e1 Mon Sep 17 00:00:00 2001
From: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Date: Thu, 29 Oct 2020 17:59:46 -0700
Subject: [PATCH 44/44] fix unit tests to add correct multibot Recoil stuff
---
.../client/__tests__/components/design.test.tsx | 11 ++++++++++-
.../__tests__/components/projecttree.test.tsx | 13 ++++++++++++-
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/Composer/packages/client/__tests__/components/design.test.tsx b/Composer/packages/client/__tests__/components/design.test.tsx
index aa4ad6edf5..f439076d51 100644
--- a/Composer/packages/client/__tests__/components/design.test.tsx
+++ b/Composer/packages/client/__tests__/components/design.test.tsx
@@ -9,7 +9,14 @@ 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 { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel';
+import {
+ dialogsSelectorFamily,
+ currentProjectIdState,
+ botProjectIdsState,
+ schemasState,
+ projectMetaDataState,
+ botProjectFileState,
+} from '../../src/recoilModel';
jest.mock('@bfc/code-editor', () => {
return {
@@ -24,6 +31,8 @@ const initRecoilState = ({ set }) => {
set(botProjectIdsState, [projectId]);
set(dialogsSelectorFamily(projectId), dialogs);
set(schemasState(projectId), { sdk: { content: {} } });
+ set(projectMetaDataState(projectId), { isRootBot: true });
+ set(botProjectFileState(projectId), { foo: 'bar' });
};
describe('', () => {
diff --git a/Composer/packages/client/__tests__/components/projecttree.test.tsx b/Composer/packages/client/__tests__/components/projecttree.test.tsx
index 9412f1e955..1185e61d9c 100644
--- a/Composer/packages/client/__tests__/components/projecttree.test.tsx
+++ b/Composer/packages/client/__tests__/components/projecttree.test.tsx
@@ -7,7 +7,14 @@ import { fireEvent } from '@botframework-composer/test-utils';
import { ProjectTree } from '../../src/components/ProjectTree/ProjectTree';
import { renderWithRecoil } from '../testUtils';
import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../mocks/sampleDialog';
-import { dialogsSelectorFamily, currentProjectIdState, botProjectIdsState, schemasState } from '../../src/recoilModel';
+import {
+ dialogsSelectorFamily,
+ currentProjectIdState,
+ botProjectIdsState,
+ projectMetaDataState,
+ schemasState,
+ botProjectFileState,
+} from '../../src/recoilModel';
const projectId = '12345.6789';
const projectId2 = '56789.1234';
@@ -18,6 +25,8 @@ const initRecoilState = ({ set }) => {
set(botProjectIdsState, [projectId]);
set(dialogsSelectorFamily(projectId), dialogs);
set(schemasState(projectId), { sdk: { content: {} } });
+ set(projectMetaDataState(projectId), { isRootBot: true });
+ set(botProjectFileState(projectId), { foo: 'bar' });
};
const initRecoilStateMulti = ({ set }) => {
@@ -27,6 +36,8 @@ const initRecoilStateMulti = ({ set }) => {
set(dialogsSelectorFamily(projectId2), [SAMPLE_DIALOG, SAMPLE_DIALOG_2]);
set(schemasState(projectId), { sdk: { content: {} } });
set(schemasState(projectId2), { sdk: { content: {} } });
+ set(projectMetaDataState(projectId), { isRootBot: true });
+ set(botProjectFileState(projectId), { foo: 'bar' });
};
describe('', () => {