Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add endpoint dropdown to the add skill modal #4163

Merged
merged 8 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
460 changes: 394 additions & 66 deletions Composer/packages/client/__tests__/components/skill.test.tsx

Large diffs are not rendered by default.

410 changes: 212 additions & 198 deletions Composer/packages/client/src/components/CreateSkillModal.tsx

Large diffs are not rendered by default.

19 changes: 2 additions & 17 deletions Composer/packages/client/src/pages/design/DesignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import {
visualEditorSelectionState,
focusPathState,
showAddSkillDialogModalState,
skillsState,
actionsSeedState,
userSettingsState,
localeState,
Expand Down Expand Up @@ -121,13 +120,13 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
const showCreateDialogModal = useRecoilValue(showCreateDialogModalState);
const showAddSkillDialogModal = useRecoilValue(showAddSkillDialogModalState);
const { undo, redo, canRedo, canUndo, commitChanges, clearUndo } = useRecoilValue(undoFunctionState);
const skills = useRecoilValue(skillsState);
const actionsSeed = useRecoilValue(actionsSeedState);
const userSettings = useRecoilValue(userSettingsState);
const qnaFiles = useRecoilValue(qnaFilesState);
const locale = useRecoilValue(localeState);
const undoVersion = useRecoilValue(undoVersionState);
const {
addSkill,
removeDialog,
updateDialog,
createDialogCancel,
Expand All @@ -140,7 +139,6 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
selectAndFocus,
addSkillDialogCancel,
createQnAFile,
updateSkill,
exportToZip,
onboardingAddCoachMarkRef,
importQnAFromUrls,
Expand Down Expand Up @@ -499,10 +497,6 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
);
}, [dialogs, breadcrumb, dialogJsonVisible]);

function handleAddSkillDialogSubmit(skillData: { manifestUrl: string }) {
updateSkill({ projectId, targetId: -1, skillData });
}

async function handleCreateDialogSubmit(data: { name: string; description: string }) {
const seededContent = new DialogFactory(schemas.sdk?.content).create(SDKKinds.AdaptiveDialog, {
$designer: { name: data.name, description: data.description },
Expand Down Expand Up @@ -676,16 +670,7 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
onSubmit={handleCreateDialogSubmit}
/>
)}
{showAddSkillDialogModal && (
<CreateSkillModal
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we pass the projectId along with the CreateSkillModal. The reason being when we support multiple bots we are calling the dispatcher function with the project ID rather than fetching the current project ID asynchronously in the dispatcher.

Copy link
Contributor

Choose a reason for hiding this comment

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

editIndex={-1}
isOpen={showAddSkillDialogModal}
projectId={projectId}
skills={skills}
onDismiss={() => addSkillDialogCancel()}
onSubmit={handleAddSkillDialogSubmit}
/>
)}
{showAddSkillDialogModal && <CreateSkillModal onDismiss={addSkillDialogCancel} onSubmit={addSkill} />}
{exportSkillModalVisible && (
<ExportSkillModal
isOpen={exportSkillModalVisible}
Expand Down
48 changes: 12 additions & 36 deletions Composer/packages/client/src/pages/skills/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import { RouteComponentProps } from '@reach/router';
import React, { useCallback, useState } from 'react';
import formatMessage from 'format-message';
import { useRecoilValue } from 'recoil';
import { Skill } from '@bfc/shared';

import { skillsState, botNameState, settingsState, projectIdState, dispatcherState } from '../../recoilModel';
import { botNameState, settingsState, projectIdState, dispatcherState } from '../../recoilModel';
import { Toolbar, IToolbarItem } from '../../components/Toolbar';
import { TestController } from '../../components/TestController/TestController';
import { CreateSkillModal, ISkillFormData } from '../../components/CreateSkillModal';
import { CreateSkillModal } from '../../components/CreateSkillModal';

import { ContainerStyle, ContentHeaderStyle, HeaderText } from './styles';
import SkillSettings from './skill-settings';
import SkillList from './skill-list';

const Skills: React.FC<RouteComponentProps> = () => {
const [editIndex, setEditIndex] = useState<number | undefined>();
const [showAddSkillDialogModal, setShowAddSkillDialogModal] = useState(false);

const botName = useRecoilValue(botNameState);
const settings = useRecoilValue(settingsState);
const projectId = useRecoilValue(projectIdState);
const skills = useRecoilValue(skillsState);
const { setSettings, updateSkill } = useRecoilValue(dispatcherState);
const { addSkill, setSettings } = useRecoilValue(dispatcherState);

const toolbarItems: IToolbarItem[] = [
{
Expand All @@ -35,7 +35,7 @@ const Skills: React.FC<RouteComponentProps> = () => {
iconName: 'Add',
},
onClick: () => {
setEditIndex(-1);
setShowAddSkillDialogModal(true);
},
},
align: 'left',
Expand All @@ -47,33 +47,16 @@ const Skills: React.FC<RouteComponentProps> = () => {
},
];

const onItemDelete = useCallback(
(index) => {
const payload = {
projectId,
targetId: index,
skillData: null,
};
updateSkill(payload);
},
[projectId]
);

const onSubmitForm = useCallback(
(submitFormData: ISkillFormData, editIndex: number) => {
const payload = {
projectId,
targetId: editIndex,
skillData: submitFormData,
};
updateSkill(payload);
setEditIndex(undefined);
(skill: Skill) => {
addSkill(skill);
setShowAddSkillDialogModal(false);
},
[projectId]
);

const onDismissForm = useCallback(() => {
setEditIndex(undefined);
setShowAddSkillDialogModal(false);
}, []);

return (
Expand All @@ -93,15 +76,8 @@ const Skills: React.FC<RouteComponentProps> = () => {
skillHostEndpoint={settings.skillHostEndpoint as string | undefined}
/>
</div>
<SkillList projectId={projectId} skills={skills} onDelete={onItemDelete} onEdit={(idx) => setEditIndex(idx)} />
<CreateSkillModal
editIndex={editIndex}
isOpen={typeof editIndex === 'number'}
projectId={projectId}
skills={skills}
onDismiss={onDismissForm}
onSubmit={onSubmitForm}
/>
<SkillList />
{showAddSkillDialogModal && <CreateSkillModal onDismiss={onDismissForm} onSubmit={onSubmitForm} />}
</div>
);
};
Expand Down
157 changes: 81 additions & 76 deletions Composer/packages/client/src/pages/skills/skill-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,22 @@ import {
CheckboxVisibility,
IColumn,
} from 'office-ui-fabric-react/lib/DetailsList';
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane';
import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky';
import { Stack } from 'office-ui-fabric-react/lib/Stack';
import { FontSizes } from '@uifabric/fluent-theme';
import { useRecoilValue } from 'recoil';
import formatMessage from 'format-message';
import { Skill } from '@bfc/shared';

import { DisplayManifestModal } from '../../components/Modal/DisplayManifestModal';
import { dispatcherState, skillsState } from '../../recoilModel';

import { TableView, TableCell } from './styles';

export interface ISkillListProps {
skills: Skill[];
projectId: string;
onEdit: (index?: number) => void;
onDelete: (index?: number) => void;
}

const columns: IColumn[] = [
{
key: 'name',
Expand All @@ -40,20 +35,8 @@ const columns: IColumn[] = [
maxWidth: 150,
isResizable: true,
data: 'string',
onRender: (item: Skill) => {
return <div css={TableCell}>{item.name}</div>;
},
},
{
key: 'msAppId',
name: formatMessage('App Id'),
fieldName: 'msAppId',
minWidth: 150,
maxWidth: 280,
isResizable: true,
data: 'string',
onRender: (item: Skill) => {
return <div css={TableCell}>{item.msAppId}</div>;
onRender: ({ skill: { name } }) => {
return <div css={TableCell}>{name}</div>;
},
},
{
Expand All @@ -64,8 +47,26 @@ const columns: IColumn[] = [
maxWidth: 400,
isResizable: true,
data: 'string',
onRender: (item: Skill) => {
return <div css={TableCell}>{item.endpointUrl}</div>;
onRender: ({ skill, onEditSkill }) => {
const { endpoints, endpointUrl: selectedEndpointUrl } = skill;

const options = (endpoints || []).map(({ name, endpointUrl, msAppId }, key) => ({
key,
text: name,
data: {
endpointUrl,
msAppId,
},
selected: endpointUrl === selectedEndpointUrl,
}));

const handleChange = (_, option?: IDropdownOption) => {
if (option) {
onEditSkill({ ...skill, ...option.data });
}
};

return <Dropdown options={options} onChange={handleChange} />;
},
},
{
Expand All @@ -76,71 +77,75 @@ const columns: IColumn[] = [
maxWidth: 400,
isResizable: true,
data: 'string',
onRender: (item: Skill) => {
return <div css={TableCell}>{item.description}</div>;
onRender: ({ skill: { description } }) => {
return <div css={TableCell}>{description}</div>;
},
},
{
key: 'buttons',
name: '',
minWidth: 120,
maxWidth: 120,
fieldName: 'buttons',
data: 'string',
onRender: ({ onDelete, onViewManifest }) => {
return (
<div>
<Stack horizontal tokens={{ childrenGap: 8 }}>
<IconButton
ariaLabel={formatMessage('Delete')}
data-testid="DeleteSkill"
iconProps={{
iconName: 'Delete',
}}
title={formatMessage('Delete')}
onClick={() => onDelete()}
/>
<IconButton
ariaLabel={formatMessage('View')}
data-testid="ViewManifest"
iconProps={{ iconName: 'ContextMenu' }}
title={formatMessage('View')}
onClick={() => onViewManifest()}
/>
</Stack>
</div>
);
},
},
];

const SkillList: React.FC<ISkillListProps> = (props) => {
const { skills, projectId, onEdit, onDelete } = props;
const SkillList: React.FC = () => {
const { removeSkill, updateSkill } = useRecoilValue(dispatcherState);
const skills = useRecoilValue(skillsState);

const [selectedSkillUrl, setSelectedSkillUrl] = useState<string | null>(null);

const onViewManifest = (item) => {
const handleViewManifest = (item) => {
if (item && item.name && item.body) {
setSelectedSkillUrl(item.manifestUrl);
}
};

const handleEditSkill = (targetId) => (skillData) => {
updateSkill({ skillData, targetId });
};

const items = useMemo(
() =>
skills.map((skill, index) => ({
skill,
onDelete: () => removeSkill(skill.manifestUrl),
onViewManifest: () => handleViewManifest(skill),
onEditSkill: handleEditSkill(index),
})),
[skills]
);

const onDismissManifest = () => {
setSelectedSkillUrl(null);
};

const getColumns = useCallback(() => {
return columns.concat({
key: 'buttons',
name: '',
minWidth: 120,
maxWidth: 120,
fieldName: 'buttons',
data: 'string',
onRender: (item, index) => {
return (
<div>
<Stack horizontal tokens={{ childrenGap: 8 }}>
<IconButton
ariaLabel={formatMessage('Edit')}
data-testid="EditSkill"
iconProps={{
iconName: 'Edit',
}}
title={formatMessage('Edit')}
onClick={() => onEdit(index)}
/>
<IconButton
ariaLabel={formatMessage('Delete')}
data-testid="DeleteSkill"
iconProps={{
iconName: 'Delete',
}}
title={formatMessage('Delete')}
onClick={() => onDelete(index)}
/>
<IconButton
ariaLabel={formatMessage('View')}
data-testid="ViewManifest"
iconProps={{ iconName: 'ContextMenu' }}
title={formatMessage('View')}
onClick={() => onViewManifest(item)}
/>
</Stack>
</div>
);
},
});
}, [projectId]);

const onRenderDetailsHeader = useCallback((props, defaultRender) => {
return (
<div data-testid="tableHeader">
Expand All @@ -161,8 +166,8 @@ const SkillList: React.FC<ISkillListProps> = (props) => {
<DetailsList
isHeaderVisible
checkboxVisibility={CheckboxVisibility.hidden}
columns={getColumns()}
items={skills}
columns={columns}
items={items}
layoutMode={DetailsListLayoutMode.justified}
selectionMode={SelectionMode.single}
styles={{ contentWrapper: { fontSize: FontSizes.size16 } }}
Expand Down
Loading