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('', () => {