Skip to content

Commit

Permalink
feat: Add skill configuration to bot project settings (#6522)
Browse files Browse the repository at this point in the history
* feat: Add skill configuration to bot project settings

* hide remove button

* Move IsSkill.tsx to SkillToggle.tsx

* requested chagnes

* lint

Co-authored-by: Soroush <[email protected]>
Co-authored-by: Chris Whitten <[email protected]>
  • Loading branch information
3 people authored Mar 26, 2021
1 parent d0ce8fe commit 1b44b16
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RuntimeSettings } from './RuntimeSettings';
import { PublishTargets } from './PublishTargets';
import { DeleteBotButton } from './DeleteBotButton';
import AdapterSection from './adapters/AdapterSection';
import { SkillConfiguration } from './skill-configuration';

// -------------------- Styles -------------------- //

Expand Down Expand Up @@ -55,6 +56,7 @@ export const BotProjectSettingsTableView: React.FC<RouteComponentProps<{
<div css={publishTargetsWrap(!isRootBot)}>
<PublishTargets projectId={projectId} scrollToSectionId={scrollToSectionId} />
</div>
<SkillConfiguration projectId={projectId} />
{isRootBot && <DeleteBotButton projectId={projectId} scrollToSectionId={scrollToSectionId} />}
</div>
);
Expand Down
14 changes: 2 additions & 12 deletions Composer/packages/client/src/pages/botProject/PublishTargets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { AuthDialog } from '../../components/Auth/AuthDialog';
import { isShowAuthDialog } from '../../utils/auth';

import { PublishProfileDialog } from './create-publish-profile/PublishProfileDialog';
import { title, tableRow, tableRowItem, tableColumnHeader, columnSizes } from './styles';
import { title, tableRow, tableRowItem, tableColumnHeader, columnSizes, addNewButton } from './styles';

// -------------------- Styles -------------------- //

Expand All @@ -33,16 +33,6 @@ const publishTargetsHeader = css`
height: 42px;
`;

const addPublishProfile = {
root: {
fontSize: 12,
fontWeight: FontWeights.regular,
color: SharedColors.cyanBlue10,
paddingLeft: 0,
marginLeft: 5,
},
};

const editPublishProfile = {
root: {
fontSize: 12,
Expand Down Expand Up @@ -149,7 +139,7 @@ export const PublishTargets: React.FC<PublishTargetsProps> = (props) => {
})}
<ActionButton
data-testid={'addNewPublishProfile'}
styles={addPublishProfile}
styles={addNewButton}
onClick={() => {
if (isShowAuthDialog(true)) {
setShowAuthDialog(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { ActionButton, IconButton } from 'office-ui-fabric-react/lib/components/Button';
import { FluentTheme } from '@uifabric/fluent-theme';
import { Stack } from 'office-ui-fabric-react/lib/components/Stack';
import { ITextField, TextField } from 'office-ui-fabric-react/lib/components/TextField';
import cloneDeep from 'lodash/cloneDeep';
import formatMessage from 'format-message';

import { dispatcherState, rootBotProjectIdSelector, settingsState } from '../../../recoilModel';
import { mergePropertiesManagedByRootBot } from '../../../recoilModel/dispatchers/utils/project';
import { addNewButton, tableColumnHeader } from '../styles';

const Input = styled(TextField)({
width: '100%',
position: 'relative',
'& .ms-TextField-fieldGroup:focus::after': {
content: '""',
position: 'absolute',
left: -1,
top: -1,
right: -1,
bottom: -1,
pointerEvents: 'none',
borderRadius: 2,
border: `2px solid ${FluentTheme.palette.themePrimary}`,
zIndex: 1,
},
});

const textFieldStyles = {
fieldGroup: {
borderColor: 'transparent',
transition: 'border-color 0.1s linear',
selectors: {
':hover': {
borderColor: FluentTheme.palette.neutralLight,
},
},
},
};

const ItemContainer = styled.div({
borderTop: `1px solid ${FluentTheme.palette.neutralLight}`,
marginTop: '4px',
});

const Row = styled(Stack)({
borderBottom: `1px solid ${FluentTheme.palette.neutralLight}`,
padding: '8px 0 8px 4px',
'& .ms-Button:not(:focus) i': {
visibility: 'hidden',
},
'&:hover .ms-Button i': {
visibility: 'visible',
},
});

type ItemProps = {
value: string;
onBlur: () => void;
onChange: (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void;
onRemove: () => void;
};

const Item = React.memo(({ value, onBlur, onChange, onRemove }: ItemProps) => {
const itemRef = React.useRef<ITextField | null>(null);
const didMount = React.useRef<boolean>(false);

React.useEffect(() => {
if (!value && !didMount.current) {
itemRef.current?.focus();
}
didMount.current = true;
}, []);

return (
<Row horizontal>
<Input
componentRef={(ref) => (itemRef.current = ref)}
styles={textFieldStyles}
value={value}
onBlur={onBlur}
onChange={onChange}
/>
<IconButton aria-label={formatMessage('Remove item')} iconProps={{ iconName: 'Trash' }} onClick={onRemove} />
</Row>
);
});

type Props = {
projectId: string;
};

export const AllowedCallers: React.FC<Props> = ({ projectId }) => {
const { setSettings } = useRecoilValue(dispatcherState);
const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector) || '';
const settings = useRecoilValue(settingsState(projectId));
const mergedSettings = mergePropertiesManagedByRootBot(projectId, rootBotProjectId, settings);
const { skillConfiguration } = mergedSettings;

const updateAllowedCallers = React.useCallback(
(allowedCallers: string[] = []) => {
const updatedSetting = {
...cloneDeep(mergedSettings),
skillConfiguration: { ...skillConfiguration, allowedCallers },
};
setSettings(projectId, updatedSetting);
},
[mergedSettings, projectId, skillConfiguration]
);

const onBlur = React.useCallback(() => {
updateAllowedCallers(skillConfiguration?.allowedCallers?.filter(Boolean));
}, [skillConfiguration?.allowedCallers, updateAllowedCallers]);

const onChange = React.useCallback(
(index: number) => (_: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue = '') => {
const updatedAllowedCallers = [...(skillConfiguration?.allowedCallers || [])];
updatedAllowedCallers[index] = newValue;
updateAllowedCallers(updatedAllowedCallers);
},
[skillConfiguration?.allowedCallers, updateAllowedCallers]
);

const onRemove = React.useCallback(
(index: number) => () => {
const updatedAllowedCallers = skillConfiguration?.allowedCallers?.filter((_, itemIndex) => itemIndex !== index);
updateAllowedCallers(updatedAllowedCallers);
},
[skillConfiguration?.allowedCallers, updateAllowedCallers]
);

const onAddNewAllowedCaller = React.useCallback(() => {
updateAllowedCallers([...skillConfiguration?.allowedCallers, '']);
}, [skillConfiguration?.allowedCallers, updateAllowedCallers]);

return (
<React.Fragment>
<div css={tableColumnHeader()}>{formatMessage('Allowed callers')} </div>
<ItemContainer>
{skillConfiguration?.allowedCallers?.map((caller, index) => {
return (
<Item key={index} value={caller} onBlur={onBlur} onChange={onChange(index)} onRemove={onRemove(index)} />
);
})}
</ItemContainer>
<ActionButton data-testid={'addNewAllowedCaller'} styles={addNewButton} onClick={onAddNewAllowedCaller}>
{formatMessage('Add new')}
</ActionButton>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { css, jsx } from '@emotion/core';
import React from 'react';
import { useRecoilValue } from 'recoil';
import { Toggle } from 'office-ui-fabric-react/lib/components/Toggle';
import cloneDeep from 'lodash/cloneDeep';
import formatMessage from 'format-message';

import { dispatcherState, rootBotProjectIdSelector, settingsState } from '../../../recoilModel';
import { mergePropertiesManagedByRootBot } from '../../../recoilModel/dispatchers/utils/project';

const toggle = css`
display: flex;
flex-direction: column;
box-sizing: border-box;
`;

type Props = {
projectId: string;
};

export const SkillToggle: React.FC<Props> = ({ projectId }) => {
const { setSettings } = useRecoilValue(dispatcherState);
const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector) || '';
const settings = useRecoilValue(settingsState(projectId));
const mergedSettings = mergePropertiesManagedByRootBot(projectId, rootBotProjectId, settings);
const { skillConfiguration } = mergedSettings;

const updateIsSkill = React.useCallback(
(isSkill: boolean) => {
const updatedSetting = {
...cloneDeep(mergedSettings),
skillConfiguration: { ...skillConfiguration, isSkill },
};
setSettings(projectId, updatedSetting);
},
[mergedSettings, projectId, skillConfiguration]
);

const toggleIsSKill = React.useCallback(
(event, checked?: boolean) => {
updateIsSkill(!!checked);
},
[updateIsSkill]
);

return (
<div css={toggle}>
<Toggle
inlineLabel
checked={!!skillConfiguration?.isSkill}
label={formatMessage('Allow bot to be called as skill')}
onChange={toggleIsSKill}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React from 'react';
import formatMessage from 'format-message';

import { CollapsableWrapper } from '../../../components/CollapsableWrapper';
import { title } from '../styles';

import { AllowedCallers } from './AllowedCallers';
import { SkillToggle } from './SkillToggle';

type Props = {
projectId: string;
};

export const SkillConfiguration: React.FC<Props> = ({ projectId }) => {
return (
<CollapsableWrapper title={formatMessage('Skill configuration')} titleStyle={title}>
<SkillToggle projectId={projectId} />
<AllowedCallers projectId={projectId} />
</CollapsableWrapper>
);
};
10 changes: 10 additions & 0 deletions Composer/packages/client/src/pages/botProject/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,13 @@ export const unknownIconStyle = (required: boolean) => {
};

export const columnSizes = ['300px', '150px', '150px'];

export const addNewButton = {
root: {
fontSize: 12,
fontWeight: FontWeights.regular,
color: SharedColors.cyanBlue10,
paddingLeft: 0,
marginLeft: 5,
},
};
12 changes: 12 additions & 0 deletions Composer/packages/server/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,12 @@
"all_4321c3a1": {
"message": "All"
},
"allow_bot_to_be_called_as_skill_d3496fd8": {
"message": "Allow bot to be called as skill"
},
"allowed_callers_fe0f5bfe": {
"message": "Allowed callers"
},
"an_authoring_key_is_created_automatically_when_you_21cf77aa": {
"message": "An authoring key is created automatically when you create a LUIS account."
},
Expand Down Expand Up @@ -2780,6 +2786,9 @@
"remove_f47dc62a": {
"message": "Remove"
},
"remove_item_5877e701": {
"message": "Remove item"
},
"remove_this_dialog_6146716c": {
"message": "Remove this dialog"
},
Expand Down Expand Up @@ -3059,6 +3068,9 @@
"skill_9b084d2e": {
"message": "Skill"
},
"skill_configuration_5e4bfbcd": {
"message": "Skill configuration"
},
"skill_dialog_name_1bbf0eff": {
"message": "Skill Dialog Name"
},
Expand Down

0 comments on commit 1b44b16

Please sign in to comment.