Skip to content

Commit

Permalink
feat: Add endpoint dropdown to the add skill modal (#4163)
Browse files Browse the repository at this point in the history
* feat: Add endpoint dropdown to the add skill modal

* update duplicate criteria

* prject id

* updated dispatchers

* fix test
  • Loading branch information
tdurnford committed Sep 17, 2020
1 parent 5f3d153 commit f4ba9e8
Show file tree
Hide file tree
Showing 9 changed files with 765 additions and 474 deletions.
414 changes: 348 additions & 66 deletions Composer/packages/client/__tests__/components/skill.test.tsx

Large diffs are not rendered by default.

406 changes: 209 additions & 197 deletions Composer/packages/client/src/components/CreateSkillModal.tsx

Large diffs are not rendered by default.

15 changes: 3 additions & 12 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 @@ -678,12 +672,9 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
)}
{showAddSkillDialogModal && (
<CreateSkillModal
editIndex={-1}
isOpen={showAddSkillDialogModal}
projectId={projectId}
skills={skills}
onDismiss={() => addSkillDialogCancel()}
onSubmit={handleAddSkillDialogSubmit}
onDismiss={addSkillDialogCancel}
onSubmit={(skill) => addSkill(projectId, skill)}
/>
)}
{exportSkillModalVisible && (
Expand Down
50 changes: 14 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(projectId, skill);
setShowAddSkillDialogModal(false);
},
[projectId]
);

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

return (
Expand All @@ -93,15 +76,10 @@ 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 projectId={projectId} />
{showAddSkillDialogModal && (
<CreateSkillModal projectId={projectId} onDismiss={onDismissForm} onSubmit={onSubmitForm} />
)}
</div>
);
};
Expand Down
161 changes: 85 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,79 @@ 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;
interface SkillListProps {
projectId: string;
}

const SkillList: React.FC<SkillListProps> = ({ projectId }) => {
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(projectId, { skillData, targetId });
};

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

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 +170,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

0 comments on commit f4ba9e8

Please sign in to comment.