@@ -612,7 +632,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/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/__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/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/navigation.test.tsx
index f931e55c2f..9661ff113f 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);
@@ -80,11 +81,31 @@ describe('navigation dispatcher', () => {
focused: 'b',
},
},
+ {
+ recoilState: designPageLocationState(skillId),
+ initialValue: {
+ dialogId: 'dialogInSkillId',
+ selected: 'a',
+ focused: 'b',
+ },
+ },
{ recoilState: currentProjectIdState, initialValue: projectId },
{
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 +197,16 @@ 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);
- });
-
- 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', []);
- });
- expectNavTo(`/bot/${projectId}/dialogs/newDialogId?selection=triggers[0]`);
- expect(mockConvertPathToUrl).toBeCalledWith(projectId, null, '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 () => {
mockCheckUrl.mockReturnValue(true);
await act(async () => {
- await dispatcher.navTo(projectId, null, 'dialogId', []);
+ await dispatcher.navTo(projectId, 'dialogId', []);
});
expect(mockNavigateTo).not.toBeCalled();
});
@@ -205,7 +215,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 +223,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();
});
@@ -231,7 +250,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`);
});
@@ -239,28 +258,48 @@ 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);
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/dialogInSkillId?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 () => {
- 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);
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}/skill/${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');
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);
@@ -269,7 +308,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 +316,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 23126d1946..e18df80e4d 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/navigation.ts
@@ -4,14 +4,13 @@
//TODO: refactor the router to use one-way data flow
import { useRecoilCallback, CallbackInterface } from 'recoil';
-import { PromptTab, SDKKinds } from '@bfc/shared';
-import cloneDeep from 'lodash/cloneDeep';
+import { PromptTab } from '@bfc/shared';
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 { getSelected } from './../../utils/dialogUtil';
import { BreadcrumbItem } from './../../recoilModel/types';
import { breadcrumbState, designPageLocationState, focusPathState } from './../atoms/botState';
import {
@@ -50,44 +49,37 @@ export const navigationDispatcher = () => {
const navTo = useRecoilCallback(
({ snapshot, set }: CallbackInterface) => async (
- projectId: string,
skillId: string | null,
- dialogId: string,
+ dialogId: string | null,
breadcrumb: BreadcrumbItem[] = []
) => {
- const dialogs = await snapshot.getPromise(dialogsSelectorFamily(projectId));
- const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
- const updatedBreadcrumb = cloneDeep(breadcrumb);
- set(currentProjectIdState, projectId);
-
- let path;
- if (dialogId !== designPageLocation.dialogId) {
- const currentDialog = dialogs.find(({ id }) => id === dialogId);
- const beginDialogIndex = currentDialog?.triggers.findIndex(({ type }) => type === SDKKinds.OnBeginDialog);
+ const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
+ if (rootBotProjectId == null) return;
- if (typeof beginDialogIndex !== 'undefined' && beginDialogIndex >= 0) {
- path = createSelectedPath(beginDialogIndex);
- path = encodeArrayPathToDesignerPath(currentDialog?.content, path);
- updatedBreadcrumb.push({ dialogId, selected: '', focused: '' });
- }
- }
+ const projectId = skillId ?? rootBotProjectId;
- const currentUri = convertPathToUrl(projectId, skillId, dialogId, path);
+ const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
+ set(currentProjectIdState, projectId);
- if (checkUrl(currentUri, projectId, designPageLocation)) return;
+ const currentUri = convertPathToUrl(rootBotProjectId, projectId, dialogId);
+ if (checkUrl(currentUri, rootBotProjectId, projectId, designPageLocation)) return;
- navigateTo(currentUri, { state: { breadcrumb: updatedBreadcrumb } });
+ navigateTo(currentUri, { state: { breadcrumb } });
}
);
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));
@@ -98,25 +90,33 @@ 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, designPageLocation)) return;
+ if (checkUrl(currentUri, rootBotProjectId, skillId, designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb: updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected) } });
}
);
const focusTo = useRecoilCallback(
- ({ snapshot, set }: CallbackInterface) => async (projectId: string, focusPath: string, fragment: string) => {
- set(currentProjectIdState, projectId);
- const designPageLocation = await snapshot.getPromise(designPageLocationState(projectId));
- const breadcrumb = await snapshot.getPromise(breadcrumbState(projectId));
+ ({ snapshot, set }: CallbackInterface) => async (
+ projectId: string,
+ skillId: string | null,
+ focusPath: string,
+ fragment: string
+ ) => {
+ set(currentProjectIdState, skillId ?? projectId);
+ const designPageLocation = await snapshot.getPromise(designPageLocationState(skillId ?? projectId));
+ const breadcrumb = await snapshot.getPromise(breadcrumbState(skillId ?? 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(dialogsSelectorFamily(projectId));
+ const dialogs = await snapshot.getPromise(dialogsSelectorFamily(skillId ?? projectId));
const currentDialog = dialogs.find(({ id }) => id === dialogId);
const encodedFocusPath = encodeArrayPathToDesignerPath(currentDialog?.content, focusPath);
@@ -135,7 +135,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 } });
}
);
@@ -158,12 +158,15 @@ 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);
+ navTo(skillId ?? projectId, dialogId, breadcrumb);
}
}
);
diff --git a/Composer/packages/client/src/recoilModel/types.ts b/Composer/packages/client/src/recoilModel/types.ts
index 8f59646139..453eca41b0 100644
--- a/Composer/packages/client/src/recoilModel/types.ts
+++ b/Composer/packages/client/src/recoilModel/types.ts
@@ -77,6 +77,7 @@ export interface AppUpdateState {
}
export interface BreadcrumbItem {
+ skillId?: string;
dialogId: string;
selected: string;
focused: string;
diff --git a/Composer/packages/client/src/router.tsx b/Composer/packages/client/src/router.tsx
index 037bc9eff8..fda81b41a4 100644
--- a/Composer/packages/client/src/router.tsx
+++ b/Composer/packages/client/src/router.tsx
@@ -70,6 +70,13 @@ const Routes = (props) => {
/>
))}
+
+
+
+
+
+
+
@@ -97,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);
@@ -119,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 ;
};
diff --git a/Composer/packages/client/src/shell/triggerApi.ts b/Composer/packages/client/src/shell/triggerApi.ts
index 44c51ce5d2..c1d6ceb6c5 100644
--- a/Composer/packages/client/src/shell/triggerApi.ts
+++ b/Composer/packages/client/src/shell/triggerApi.ts
@@ -112,7 +112,7 @@ function createTriggerApi(
};
await updateDialog(dialogPayload);
if (autoSelected) {
- selectTo(projectId, null, null, `triggers[${index}]`);
+ selectTo(projectId, newDialog.id, `triggers[${index}]`);
}
};
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index 13c6d19a00..3e44f3ee05 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);
@@ -135,11 +137,13 @@ export function useShell(source: EventSource, projectId: string): Shell {
}
function navigationTo(path) {
- navTo(projectId, null, path, breadcrumb);
+ if (rootBotProjectId == null) return;
+ navTo(projectId, path, breadcrumb);
}
function focusEvent(subPath) {
- selectTo(projectId, null, null, subPath);
+ if (rootBotProjectId == null) return;
+ selectTo(projectId, dialogId, subPath);
}
function focusSteps(subPaths: string[] = [], fragment?: string) {
@@ -154,7 +158,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..dcc7b19fe4 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}`;
}
@@ -83,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('#');
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