Skip to content

Commit

Permalink
Bot proj breadcrumbs (microsoft#4)
Browse files Browse the repository at this point in the history
* remove old breadcrumbs and start making new ones

* Update DesignPage.tsx

* Update DesignPage.tsx

* update unit tests to remove breadcrumb things

* fix duplicate key bug in breadcrumbs

* fix e2e test

* detect and display action names in breadcrumb

* rewrite to make typechecker happy

* make new DesignPage unit tests

* Update publisher.ts

* Update publisher.ts

* restore navigation in undo

* retrieve breadcrumb from URL on location change

* read double-nested $designer fields

* navigate to trigger[0] on OpenDialog node events

* fix typo and unit tests

* Update validateDialogName.test.ts

* better error-checking for invalid URLs

* make special "beginDialog" trigger

* Update en-US.json

* Update DesignPage.tsx

Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com>
Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
  • Loading branch information
3 people committed Nov 5, 2020
1 parent d5b2950 commit d558846
Show file tree
Hide file tree
Showing 20 changed files with 337 additions and 348 deletions.
4 changes: 2 additions & 2 deletions Composer/cypress/integration/Breadcrumb.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ context('breadcrumb', () => {
hasBreadcrumbItems(cy, ['__TestTodoSample']);
});

it('can show event name in breadcrumb', () => {
it('can show dialog and trigger name in breadcrumb', () => {
cy.findByTestId('ProjectTree').within(() => {
cy.findByTestId('addtodo_Dialog started').click();
});

hasBreadcrumbItems(cy, ['__TestTodoSample', 'Dialog started']);
hasBreadcrumbItems(cy, ['addtodo', 'Dialog started']);
});

it('can show action name in breadcrumb', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const useEditorEventApi = (
onFocusSteps,
onFocusEvent,
onCopy: onClipboardChange,
navTo: onOpen,
navTo,
saveData: onChange,
undo,
redo,
Expand Down Expand Up @@ -153,7 +153,7 @@ export const useEditorEventApi = (
break;
case NodeEventTypes.OpenDialog:
handler = ({ callee }) => {
onOpen(callee);
navTo(callee, '"beginDialog"');
announce(ScreenReaderMessage.DialogOpened);
};
break;
Expand Down
65 changes: 65 additions & 0 deletions Composer/packages/client/__tests__/pages/design/Design.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React from 'react';

import { renderWithRecoil } from '../../testUtils';
import {
botProjectIdsState,
currentProjectIdState,
dialogsSelectorFamily,
schemasState,
projectMetaDataState,
botProjectFileState,
} from '../../../src/recoilModel';
import { undoFunctionState } from '../../../src/recoilModel/undo/history';
import mockProjectResponse from '../../../src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json';
import DesignPage from '../../../src/pages/design/DesignPage';
import { SAMPLE_DIALOG, SAMPLE_DIALOG_2 } from '../../mocks/sampleDialog';

const projectId = '12345.6789';
const skillId = '56789.1234';
const dialogId = SAMPLE_DIALOG.id;

const initRecoilState = ({ set }) => {
set(currentProjectIdState, projectId);
set(botProjectIdsState, [projectId]);
set(dialogsSelectorFamily(projectId), [SAMPLE_DIALOG]);
set(schemasState(projectId), mockProjectResponse.schemas);
set(projectMetaDataState(projectId), { isRootBot: true });
set(botProjectFileState(projectId), { foo: 'bar' });
set(undoFunctionState(projectId), { canUndo: () => false, canRedo: () => false });
};

const initRecoilStateMulti = ({ set }) => {
set(currentProjectIdState, projectId);
set(botProjectIdsState, [projectId, skillId]);
set(dialogsSelectorFamily(projectId), [SAMPLE_DIALOG]);
set(dialogsSelectorFamily(skillId), [SAMPLE_DIALOG, SAMPLE_DIALOG_2]);
set(schemasState(projectId), mockProjectResponse.schemas);
set(schemasState(skillId), mockProjectResponse.schemas);
set(projectMetaDataState(projectId), { isRootBot: true });
set(botProjectFileState(projectId), { foo: 'bar' });
set(undoFunctionState(projectId), { canUndo: () => false, canRedo: () => false });
set(undoFunctionState(skillId), { canUndo: () => false, canRedo: () => false });
};

describe('publish page', () => {
it('should render the design page (no skill)', () => {
const { getAllByText, getByText } = renderWithRecoil(
<DesignPage dialogId={dialogId} projectId={projectId} />,
initRecoilState
);
getAllByText(SAMPLE_DIALOG.displayName);
getByText('Start Bot');
});

it('should render the design page (with skill)', () => {
const { getAllByText, getByText } = renderWithRecoil(
<DesignPage dialogId={dialogId} projectId={projectId} skillId={skillId} />,
initRecoilStateMulti
);
getAllByText(SAMPLE_DIALOG.displayName);
getAllByText(SAMPLE_DIALOG_2.displayName);
getByText('Start Bot');
});
});
50 changes: 1 addition & 49 deletions Composer/packages/client/__tests__/utils/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@

import { PromptTab } from '@bfc/shared';

import {
BreadcrumbUpdateType,
getUrlSearch,
checkUrl,
getFocusPath,
clearBreadcrumb,
updateBreadcrumb,
convertPathToUrl,
} from './../../src/utils/navigation';
import { getUrlSearch, checkUrl, getFocusPath, convertPathToUrl } from './../../src/utils/navigation';

const projectId = '123a-sdf123';
const skillId = '98765.4321';
Expand All @@ -27,46 +19,6 @@ describe('getFocusPath', () => {
});
});

describe('Breadcrumb Util', () => {
it('return focus path', () => {
const breadcrumb = [
{ dialogId: `1`, selected: `1`, focused: `1` },
{ dialogId: `2`, selected: `2`, focused: `2` },
{ dialogId: `3`, selected: `3`, focused: `3` },
];
const result1 = clearBreadcrumb(breadcrumb);
expect(result1).toEqual([]);
const result2 = clearBreadcrumb(breadcrumb, 0);
expect(result2).toEqual([]);
const result3 = clearBreadcrumb(breadcrumb, 1);
expect(result3.length).toEqual(1);
expect(result3[0].dialogId).toEqual('1');
const result4 = clearBreadcrumb(breadcrumb, 4);
expect(result4.length).toEqual(3);
});

it('update breadcrumb', () => {
const result1 = updateBreadcrumb([], BreadcrumbUpdateType.Selected);
expect(result1).toEqual([]);
let breadcrumb = [
{ dialogId: `1`, selected: `1`, focused: `1` },
{ dialogId: `2`, selected: `2`, focused: `2` },
{ dialogId: `3`, selected: `3`, focused: `3` },
];
const result2 = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected);
expect(result2.length).toEqual(1);
expect(result2[0].dialogId).toEqual('1');
breadcrumb = [
{ dialogId: `1`, selected: `1`, focused: `` },
{ dialogId: `2`, selected: `2`, focused: `` },
{ dialogId: `3`, selected: `3`, focused: `3` },
];
const result3 = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Focused);
expect(result3.length).toEqual(2);
expect(result3[1].dialogId).toEqual('2');
});
});

describe('composer url util', () => {
it('create url', () => {
const result1 = getUrlSearch('triggers[0]', 'triggers[0].actions[0]');
Expand Down
109 changes: 58 additions & 51 deletions Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type TreeLink = {
skillId?: string;
dialogId?: string;
trigger?: number;
parentLink?: TreeLink;
};

export type TreeMenuItem = {
Expand Down Expand Up @@ -274,7 +275,7 @@ export const ProjectTree: React.FC<Props> = ({
.map((diag) => diag.message)
.join(',');

const link: TreeLink = {
const dialogLink: TreeLink = {
dialogId: dialog.id,
displayName: dialog.displayName,
isRoot: dialog.isRoot,
Expand All @@ -287,53 +288,56 @@ export const ProjectTree: React.FC<Props> = ({
const isFormDialog = dialogIsFormDialog(dialog);
const showEditSchema = formDialogSchemaExists(skillId, dialog);

return (
<span
key={dialog.id}
ref={dialog.isRoot ? addMainDialogRef : null}
css={css`
margin-top: -6px;
width: 100%;
label: dialog-header;
`}
role="grid"
>
<TreeItem
showProps
forceIndent={showTriggers ? 0 : SUMMARY_ARROW_SPACE}
icon={isFormDialog ? icons.FORM_DIALOG : icons.DIALOG}
isActive={doesLinkMatch(link, selectedLink)}
link={link}
menu={[
...(!dialog.isRoot
? [
{
label: formatMessage('Remove this dialog'),
icon: 'Delete',
onClick: (link) => {
onDeleteDialog(link.dialogId ?? '');
return {
summaryElement: (
<span
key={dialog.id}
ref={dialog.isRoot ? addMainDialogRef : null}
css={css`
margin-top: -6px;
width: 100%;
label: dialog-header;
`}
role="grid"
>
<TreeItem
showProps
forceIndent={showTriggers ? 0 : SUMMARY_ARROW_SPACE}
icon={isFormDialog ? icons.FORM_DIALOG : icons.DIALOG}
isActive={doesLinkMatch(dialogLink, selectedLink)}
link={dialogLink}
menu={[
...(!dialog.isRoot
? [
{
label: formatMessage('Remove this dialog'),
icon: 'Delete',
onClick: (link) => {
onDeleteDialog(link.dialogId ?? '');
},
},
},
]
: []),
...(showEditSchema
? [
{
label: formatMessage('Edit schema'),
icon: 'Edit',
onClick: (link) =>
navigateToFormDialogSchema({ projectId: link.skillId, schemaId: link.dialogName }),
},
]
: []),
]}
onSelect={handleOnSelect}
/>
</span>
);
]
: []),
...(showEditSchema
? [
{
label: formatMessage('Edit schema'),
icon: 'Edit',
onClick: (link) =>
navigateToFormDialogSchema({ projectId: link.skillId, schemaId: link.dialogName }),
},
]
: []),
]}
onSelect={handleOnSelect}
/>
</span>
),
dialogLink,
};
};

const renderTrigger = (item: any, dialog: DialogInfo, projectId: string): React.ReactNode => {
const renderTrigger = (item: any, dialog: DialogInfo, projectId: string, dialogLink?: TreeLink): React.ReactNode => {
const link: TreeLink = {
projectId: rootProjectId,
skillId: projectId === rootProjectId ? undefined : projectId,
Expand All @@ -343,6 +347,7 @@ export const ProjectTree: React.FC<Props> = ({
warningContent: item.warningContent,
errorContent: item.errorContent,
isRoot: false,
parentLink: dialogLink,
};

return (
Expand Down Expand Up @@ -377,7 +382,7 @@ export const ProjectTree: React.FC<Props> = ({
return scope.toLowerCase().includes(filter.toLowerCase());
};

const renderTriggerList = (triggers: ITrigger[], dialog: DialogInfo, projectId: string) => {
const renderTriggerList = (triggers: ITrigger[], dialog: DialogInfo, projectId: string, dialogLink?: TreeLink) => {
return triggers
.filter((tr) => filterMatch(dialog.displayName) || filterMatch(getTriggerName(tr)))
.map((tr) => {
Expand All @@ -389,7 +394,8 @@ export const ProjectTree: React.FC<Props> = ({
return renderTrigger(
{ ...tr, index, displayName: getTriggerName(tr), warningContent, errorContent },
dialog,
projectId
projectId,
dialogLink
);
});
};
Expand Down Expand Up @@ -451,10 +457,10 @@ export const ProjectTree: React.FC<Props> = ({
});
};

const renderDialogTriggers = (dialog: DialogInfo, projectId: string, startDepth: number) => {
const renderDialogTriggers = (dialog: DialogInfo, projectId: string, startDepth: number, dialogLink?: TreeLink) => {
return dialogIsFormDialog(dialog)
? renderDialogTriggersByProperty(dialog, projectId, startDepth)
: renderTriggerList(dialog.triggers, dialog, projectId);
: renderTriggerList(dialog.triggers, dialog, projectId, dialogLink);
};

const createDetailsTree = (bot: BotInProject, startDepth: number) => {
Expand All @@ -471,14 +477,15 @@ export const ProjectTree: React.FC<Props> = ({

if (showTriggers) {
return filteredDialogs.map((dialog: DialogInfo) => {
const { summaryElement, dialogLink } = renderDialogHeader(projectId, dialog);
return (
<ExpandableNode
key={dialog.id}
depth={startDepth}
detailsRef={dialog.isRoot ? addMainDialogRef : undefined}
summary={renderDialogHeader(projectId, dialog)}
summary={summaryElement}
>
<div>{renderDialogTriggers(dialog, projectId, startDepth + 1)}</div>
<div>{renderDialogTriggers(dialog, projectId, startDepth + 1, dialogLink)}</div>
</ExpandableNode>
);
});
Expand Down
Loading

0 comments on commit d558846

Please sign in to comment.