diff --git a/Composer/packages/client/__tests__/components/skill.test.tsx b/Composer/packages/client/__tests__/components/skill.test.tsx
index 1fe175c270..67b0bdf4b1 100644
--- a/Composer/packages/client/__tests__/components/skill.test.tsx
+++ b/Composer/packages/client/__tests__/components/skill.test.tsx
@@ -22,24 +22,24 @@ jest.mock('../../src/components/Modal/dialogStyle', () => ({}));
const skills: Skill[] = [
{
+ id: 'email-skill',
+ content: {},
manifestUrl: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json',
name: 'Email-Skill',
description: 'The Email skill provides email related capabilities and supports Office and Google calendars.',
endpointUrl: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/api/messages',
msAppId: '79432da8-0f7e-4a16-8c23-ddbba30ae85d',
- protocol: '',
endpoints: [],
- body: '',
},
{
+ id: 'point-of-interest-skill',
+ content: {},
manifestUrl: 'https://hualxielearn2-snskill.azurewebsites.net/manifest/manifest-1.0.json',
name: 'Point Of Interest Skill',
description: 'The Point of Interest skill provides PoI search capabilities leveraging Azure Maps and Foursquare.',
endpointUrl: 'https://hualxielearn2-snskill.azurewebsites.net/api/messages',
msAppId: 'e2852590-ea71-4a69-9e44-e74b5b6cbe89',
- protocol: '',
endpoints: [],
- body: '',
},
];
@@ -101,7 +101,7 @@ describe('', () => {
it('should render the skill form, and update skill manifest url', () => {
jest.useFakeTimers();
- (httpClient.post as jest.Mock).mockResolvedValue({ endpoints: [] });
+ (httpClient.get as jest.Mock).mockResolvedValue({ endpoints: [] });
const onDismiss = jest.fn();
const onSubmit = jest.fn();
@@ -203,7 +203,7 @@ describe('', () => {
});
it('should try and retrieve manifest if manifest url meets other criteria', async () => {
- (httpClient.post as jest.Mock) = jest.fn().mockResolvedValue({ data: 'skill manifest' });
+ (httpClient.get as jest.Mock) = jest.fn().mockResolvedValue({ data: 'skill manifest' });
const formData = { manifestUrl: 'https://skill' };
const formDataErrors = { manifestUrl: 'error' };
@@ -223,7 +223,11 @@ describe('', () => {
manifestUrl: 'Validating',
})
);
- expect(httpClient.post).toBeCalledWith(`/projects/${projectId}/skill/check`, { url: formData.manifestUrl });
+ expect(httpClient.get).toBeCalledWith(`/projects/${projectId}/skill/retrieve-skill-manifest`, {
+ params: {
+ url: formData.manifestUrl,
+ },
+ });
expect(setSkillManifest).toBeCalledWith('skill manifest');
expect(setValidationState).toBeCalledWith(
expect.objectContaining({
@@ -238,7 +242,7 @@ describe('', () => {
});
it('should show error when it could not retrieve skill manifest', async () => {
- (httpClient.post as jest.Mock) = jest.fn().mockRejectedValue({ message: 'skill manifest' });
+ (httpClient.get as jest.Mock) = jest.fn().mockRejectedValue({ message: 'skill manifest' });
const formData = { manifestUrl: 'https://skill' };
@@ -257,7 +261,11 @@ describe('', () => {
manifestUrl: 'Validating',
})
);
- expect(httpClient.post).toBeCalledWith(`/projects/${projectId}/skill/check`, { url: formData.manifestUrl });
+ expect(httpClient.get).toBeCalledWith(`/projects/${projectId}/skill/retrieve-skill-manifest`, {
+ params: {
+ url: formData.manifestUrl,
+ },
+ });
expect(setSkillManifest).not.toBeCalled();
expect(setValidationState).toBeCalledWith(
expect.objectContaining({
diff --git a/Composer/packages/client/src/components/CreateSkillModal.tsx b/Composer/packages/client/src/components/CreateSkillModal.tsx
index f52408d152..f42a21b491 100644
--- a/Composer/packages/client/src/components/CreateSkillModal.tsx
+++ b/Composer/packages/client/src/components/CreateSkillModal.tsx
@@ -11,7 +11,7 @@ import { Stack, StackItem } from 'office-ui-fabric-react/lib/Stack';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { useRecoilValue } from 'recoil';
import debounce from 'lodash/debounce';
-import { Skill } from '@bfc/shared';
+import { SkillSetting } from '@bfc/shared';
import { addSkillDialog } from '../constants';
import httpClient from '../utils/httpUtil';
@@ -31,7 +31,7 @@ export const msAppIdRegex = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A
export interface CreateSkillModalProps {
projectId: string;
- onSubmit: (data: Skill) => void;
+ onSubmit: (data: SkillSetting) => void;
onDismiss: () => void;
}
@@ -83,7 +83,11 @@ export const validateManifestUrl = async ({
} else {
try {
setValidationState({ ...validationState, manifestUrl: ValidationState.Validating });
- const { data } = await httpClient.post(`/projects/${projectId}/skill/check`, { url: manifestUrl });
+ const { data } = await httpClient.get(`/projects/${projectId}/skill/retrieve-skill-manifest`, {
+ params: {
+ url: manifestUrl,
+ },
+ });
setFormDataErrors(errors);
setSkillManifest(data);
setValidationState({ ...validationState, manifestUrl: ValidationState.Validated });
@@ -118,7 +122,7 @@ export const validateName = ({
export const CreateSkillModal: React.FC = ({ projectId, onSubmit, onDismiss }) => {
const skills = useRecoilValue(skillsState(projectId));
- const [formData, setFormData] = useState>({});
+ const [formData, setFormData] = useState>({});
const [formDataErrors, setFormDataErrors] = useState({});
const [validationState, setValidationState] = useState({
endpoint: ValidationState.NotValidated,
@@ -208,7 +212,7 @@ export const CreateSkillModal: React.FC = ({ projectId, o
Object.values(validationState).every((validation) => validation === ValidationState.Validated) &&
!Object.values(formDataErrors).some(Boolean)
) {
- onSubmit(formData as Skill);
+ onSubmit({ name: skillManifest.name, ...formData } as SkillSetting);
}
};
diff --git a/Composer/packages/client/src/components/Modal/DisplayManifestModal.tsx b/Composer/packages/client/src/components/Modal/DisplayManifestModal.tsx
index 0f29520e55..70ba9257ba 100644
--- a/Composer/packages/client/src/components/Modal/DisplayManifestModal.tsx
+++ b/Composer/packages/client/src/components/Modal/DisplayManifestModal.tsx
@@ -98,7 +98,7 @@ export const DisplayManifestModal: React.FC = ({
height={'100%'}
id={'modaljsonview'}
options={{ readOnly: true }}
- value={JSON.parse(selectedSkill.body ?? '')}
+ value={selectedSkill.content}
onChange={() => {}}
/>
diff --git a/Composer/packages/client/src/pages/skills/index.tsx b/Composer/packages/client/src/pages/skills/index.tsx
index 901c0ac06e..bdfa8467b9 100644
--- a/Composer/packages/client/src/pages/skills/index.tsx
+++ b/Composer/packages/client/src/pages/skills/index.tsx
@@ -7,7 +7,7 @@ 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 { SkillSetting } from '@bfc/shared';
import { dispatcherState, settingsState, botNameState } from '../../recoilModel';
import { Toolbar, IToolbarItem } from '../../components/Toolbar';
@@ -48,7 +48,7 @@ const Skills: React.FC> = (props) =>
];
const onSubmitForm = useCallback(
- (skill: Skill) => {
+ (skill: SkillSetting) => {
addSkill(projectId, skill);
setShowAddSkillDialogModal(false);
},
diff --git a/Composer/packages/client/src/pages/skills/skill-list.tsx b/Composer/packages/client/src/pages/skills/skill-list.tsx
index 7d19e28522..43424dd313 100644
--- a/Composer/packages/client/src/pages/skills/skill-list.tsx
+++ b/Composer/packages/client/src/pages/skills/skill-list.tsx
@@ -126,22 +126,22 @@ const SkillList: React.FC = ({ projectId }) => {
const [selectedSkillUrl, setSelectedSkillUrl] = useState(null);
const handleViewManifest = (item) => {
- if (item && item.name && item.body) {
+ if (item && item.name && item.content) {
setSelectedSkillUrl(item.manifestUrl);
}
};
- const handleEditSkill = (targetId) => (skillData) => {
- updateSkill(projectId, { skillData, targetId });
+ const handleEditSkill = (projectId, skillId) => (skillData) => {
+ updateSkill(projectId, skillId, skillData);
};
const items = useMemo(
() =>
- skills.map((skill, index) => ({
+ skills.map((skill) => ({
skill,
- onDelete: () => removeSkill(projectId, skill.manifestUrl),
+ onDelete: () => removeSkill(projectId, skill.id),
onViewManifest: () => handleViewManifest(skill),
- onEditSkill: handleEditSkill(index),
+ onEditSkill: handleEditSkill(projectId, skill.id),
})),
[skills, projectId]
);
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/setting.test.tsx b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/setting.test.tsx
index c2f11a5151..d5676354c2 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/setting.test.tsx
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/setting.test.tsx
@@ -5,10 +5,13 @@ import { useRecoilValue } from 'recoil';
import { act } from '@bfc/test-utils/lib/hooks';
import { renderRecoilHook } from '../../../../__tests__/testUtils';
-import { settingsState, currentProjectIdState } from '../../atoms';
+import { settingsState, currentProjectIdState, skillsState } from '../../atoms';
import { dispatcherState } from '../../../recoilModel/DispatcherWrapper';
import { Dispatcher } from '..';
import { settingsDispatcher } from '../setting';
+import httpClient from '../../../utils/httpUtil';
+
+jest.mock('../../../utils/httpUtil');
const projectId = '1235a.2341';
@@ -65,6 +68,7 @@ const settings = {
maxImbalanceRatio: 10,
maxUtteranceAllowed: 15000,
},
+ skill: {},
};
describe('setting dispatcher', () => {
@@ -72,10 +76,11 @@ describe('setting dispatcher', () => {
beforeEach(() => {
const useRecoilTestHook = () => {
const settings = useRecoilValue(settingsState(projectId));
+ const skills = useRecoilValue(skillsState(projectId));
const currentDispatcher = useRecoilValue(dispatcherState);
-
return {
settings,
+ skills,
currentDispatcher,
};
};
@@ -84,6 +89,7 @@ describe('setting dispatcher', () => {
states: [
{ recoilState: settingsState(projectId), initialValue: settings },
{ recoilState: currentProjectIdState, initialValue: projectId },
+ { recoilState: skillsState(projectId), initialValue: [] },
],
dispatcher: {
recoilState: dispatcherState,
@@ -151,4 +157,38 @@ describe('setting dispatcher', () => {
});
expect(renderedComponent.current.settings.runtime.customRuntime).toBeTruthy();
});
+
+ it('should update skills state', async () => {
+ (httpClient.get as jest.Mock).mockResolvedValue({
+ data: { description: 'description', endpoints: [{ endpointUrl: 'https://test' }] },
+ });
+
+ await act(async () => {
+ await dispatcher.setSettings(projectId, {
+ skill: {
+ foo: {
+ msAppId: '00000000-0000',
+ endpointUrl: 'https://skill-manifest/api/messages',
+ name: 'foo',
+ manifestUrl: 'https://skill-manifest',
+ },
+ },
+ } as any);
+ });
+
+ expect(renderedComponent.current.skills).toEqual(
+ expect.arrayContaining([
+ {
+ id: 'foo',
+ name: 'foo',
+ manifestUrl: 'https://skill-manifest',
+ msAppId: '00000000-0000',
+ endpointUrl: 'https://skill-manifest/api/messages',
+ description: 'description',
+ endpoints: expect.any(Array),
+ content: expect.any(Object),
+ },
+ ])
+ );
+ });
});
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/skill.test.ts b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/skill.test.ts
index 39d6078de4..61ebcd3c70 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/__tests__/skill.test.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/__tests__/skill.test.ts
@@ -35,14 +35,17 @@ const mockDialogComplete = jest.fn();
const projectId = '42345.23432';
const makeTestSkill: (number) => Skill = (n) => ({
+ id: 'id' + n,
manifestUrl: 'url' + n,
name: 'skill' + n,
- protocol: 'GET',
description: 'test skill' + n,
endpointUrl: 'url',
endpoints: [{ test: 'foo' }],
msAppId: 'ID',
- body: 'body',
+ content: {
+ description: 'test skill' + n,
+ endpoints: [{ test: 'foo' }],
+ },
});
describe('skill dispatcher', () => {
@@ -143,19 +146,30 @@ describe('skill dispatcher', () => {
it('updateSkill', async () => {
await act(async () => {
- dispatcher.updateSkill(projectId, {
- targetId: 0,
- skillData: makeTestSkill(100),
+ dispatcher.updateSkill(projectId, 'id1', {
+ msAppId: 'test',
+ manifestUrl: 'test',
+ endpointUrl: 'test',
+ name: 'test',
});
});
- expect(renderedComponent.current.skills).not.toContain(makeTestSkill(1));
- expect(renderedComponent.current.skills).toContainEqual(makeTestSkill(100));
+ expect(renderedComponent.current.skills[0]).toEqual(
+ expect.objectContaining({
+ id: 'id1',
+ content: {},
+ name: 'test',
+ msAppId: 'test',
+ manifestUrl: 'test',
+ endpointUrl: 'test',
+ endpoints: [],
+ })
+ );
});
it('removeSkill', async () => {
await act(async () => {
- dispatcher.removeSkill(projectId, makeTestSkill(1).manifestUrl);
+ dispatcher.removeSkill(projectId, makeTestSkill(1).id);
});
expect(renderedComponent.current.skills).not.toContain(makeTestSkill(1));
});
@@ -176,32 +190,6 @@ describe('skill dispatcher', () => {
expect(renderedComponent.current.onAddSkillDialogComplete.func).toBe(undefined);
});
- describe('addSkillDialogSuccess', () => {
- it('with a function in onAddSkillDialogComplete', async () => {
- await act(async () => {
- dispatcher.addSkillDialogBegin(mockDialogComplete, projectId);
- });
- await act(async () => {
- dispatcher.addSkillDialogSuccess(projectId);
- });
- expect(mockDialogComplete).toHaveBeenCalledWith(null);
- expect(renderedComponent.current.showAddSkillDialogModal).toBe(false);
- expect(renderedComponent.current.onAddSkillDialogComplete.func).toBe(undefined);
- });
-
- it('with nothing in onAddSkillDialogComplete', async () => {
- await act(async () => {
- dispatcher.addSkillDialogCancel(projectId);
- });
- await act(async () => {
- dispatcher.addSkillDialogSuccess(projectId);
- });
- expect(mockDialogComplete).not.toHaveBeenCalled();
- expect(renderedComponent.current.showAddSkillDialogModal).toBe(false);
- expect(renderedComponent.current.onAddSkillDialogComplete.func).toBe(undefined);
- });
- });
-
it('displayManifestModal', async () => {
await act(async () => {
dispatcher.displayManifestModal('foo', projectId);
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/project.ts
index c5136d015a..506b2a5ff9 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/project.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/project.ts
@@ -143,6 +143,7 @@ const initQnaFilesStatus = (projectId: string, qnaFiles: QnAFile[], dialogs: Dia
);
return updateQnaFilesStatus(projectId, qnaFiles);
};
+
export const projectDispatcher = () => {
const initBotState = async (
callbackHelpers: CallbackInterface,
@@ -152,7 +153,17 @@ export const projectDispatcher = () => {
qnaKbUrls?: string[]
) => {
const { snapshot, gotoSnapshot, set } = callbackHelpers;
- const { files, botName, botEnvironment, location, schemas, settings, id: projectId, diagnostics, skills } = data;
+ const {
+ files,
+ botName,
+ botEnvironment,
+ location,
+ schemas,
+ settings,
+ id: projectId,
+ diagnostics,
+ skills: skillContent,
+ } = data;
const curLocation = await snapshot.getPromise(locationState(projectId));
const storedLocale = languageStorage.get(botName)?.locale;
const locale = settings.languages.includes(storedLocale) ? storedLocale : settings.defaultLanguage;
@@ -160,6 +171,12 @@ export const projectDispatcher = () => {
// cache current projectId in session, resolve page refresh caused state lost.
projectIdCache.set(projectId);
+ const mergedSettings = mergeLocalStorage(projectId, settings);
+ if (Array.isArray(mergedSettings.skill)) {
+ const skillsArr = mergedSettings.skill.map((skillData) => ({ ...skillData }));
+ mergedSettings.skill = convertSkillsToDictionary(skillsArr);
+ }
+
try {
schemas.sdk.content = processSchema(projectId, schemas.sdk.content);
} catch (err) {
@@ -169,10 +186,12 @@ export const projectDispatcher = () => {
}
try {
- const { dialogs, dialogSchemas, luFiles, lgFiles, qnaFiles, skillManifestFiles } = indexer.index(
+ const { dialogs, dialogSchemas, luFiles, lgFiles, qnaFiles, skillManifestFiles, skills } = indexer.index(
files,
botName,
- locale
+ locale,
+ skillContent,
+ mergedSettings
);
let mainDialog = '';
@@ -207,15 +226,6 @@ export const projectDispatcher = () => {
set(botDiagnosticsState(projectId), diagnostics);
refreshLocalStorage(projectId, settings);
- const mergedSettings = mergeLocalStorage(projectId, settings);
- if (Array.isArray(mergedSettings.skill)) {
- const skillsArr = mergedSettings.skill.map((skillData) => {
- return {
- ...skillData,
- };
- });
- mergedSettings.skill = convertSkillsToDictionary(skillsArr);
- }
set(settingsState(projectId), mergedSettings);
set(filePersistenceState(projectId), new FilePersistence(projectId));
set(undoHistoryState(projectId), new UndoHistory(projectId));
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/setting.ts b/Composer/packages/client/src/recoilModel/dispatchers/setting.ts
index b8fc5f344c..d13761e46b 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/setting.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/setting.ts
@@ -4,26 +4,66 @@
import { CallbackInterface, useRecoilCallback } from 'recoil';
import { SensitiveProperties, DialogSetting, PublishTarget, Skill } from '@bfc/shared';
+import { skillIndexer } from '@bfc/indexers';
import get from 'lodash/get';
import has from 'lodash/has';
+import isEqual from 'lodash/isEqual';
+import keys from 'lodash/keys';
import settingStorage from '../../utils/dialogSettingStorage';
-import { settingsState } from '../atoms/botState';
+import { settingsState, skillsState } from '../atoms/botState';
import httpClient from './../../utils/httpUtil';
import { setError } from './shared';
+export const setSettingState = async (
+ callbackHelpers: CallbackInterface,
+ projectId: string,
+ settings: DialogSetting
+) => {
+ const { set, snapshot } = callbackHelpers;
+ const previousSettings = await snapshot.getPromise(settingsState(projectId));
+
+ if (!isEqual(settings.skill, previousSettings.skill)) {
+ const skills = await snapshot.getPromise(skillsState(projectId));
+ const skillContent = await Promise.all(
+ keys(settings.skill).map(async (id) => {
+ if (settings?.skill?.[id]?.manifestUrl !== previousSettings?.skill?.[id]?.manifestUrl) {
+ try {
+ const { data: content } = await httpClient.get(`/projects/${projectId}/skill/retrieve-skill-manifest`, {
+ params: {
+ url: settings?.skill?.[id]?.manifestUrl,
+ },
+ });
+ return { id, content };
+ } catch (error) {
+ return { id };
+ }
+ }
+
+ const { content = {} } = skills.find(({ id: key }) => id === key) || ({} as Skill);
+
+ return { id, content };
+ })
+ );
+
+ set(skillsState(projectId), skillIndexer.index(skillContent, settings.skill));
+ }
+
+ // set value in local storage
+ for (const property of SensitiveProperties) {
+ if (has(settings, property)) {
+ const propertyValue = get(settings, property, '');
+ settingStorage.setField(projectId, property, propertyValue);
+ }
+ }
+ set(settingsState(projectId), settings);
+};
+
export const settingsDispatcher = () => {
const setSettings = useRecoilCallback<[string, DialogSetting], Promise>(
- ({ set }: CallbackInterface) => async (projectId: string, settings: DialogSetting) => {
- // set value in local storage
- for (const property of SensitiveProperties) {
- if (has(settings, property)) {
- const propertyValue = get(settings, property, '');
- settingStorage.setField(projectId, property, propertyValue);
- }
- }
- set(settingsState(projectId), settings);
+ (callbackHelpers: CallbackInterface) => async (projectId: string, settings: DialogSetting) => {
+ setSettingState(callbackHelpers, projectId, settings);
}
);
@@ -89,29 +129,6 @@ export const settingsDispatcher = () => {
}
);
- const updateSkillsInSetting = useRecoilCallback(
- ({ set, snapshot }: CallbackInterface) => async (
- skillName: string,
- skillInfo: Partial,
- projectId: string
- ) => {
- const currentSettings: DialogSetting = await snapshot.getPromise(settingsState(projectId));
- const matchedSkill = get(currentSettings, `skill[${skillName}]`, undefined);
- if (matchedSkill) {
- set(settingsState(projectId), {
- ...currentSettings,
- skill: {
- ...currentSettings.skill,
- [skillName]: {
- ...matchedSkill,
- ...skillInfo,
- },
- },
- });
- }
- }
- );
-
return {
setSettings,
setRuntimeSettings,
@@ -119,6 +136,5 @@ export const settingsDispatcher = () => {
setRuntimeField,
setCustomRuntime,
setQnASettings,
- updateSkillsInSetting,
};
};
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/skill.ts b/Composer/packages/client/src/recoilModel/dispatchers/skill.ts
index d728fa46a6..c7938aacbd 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/skill.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/skill.ts
@@ -3,19 +3,17 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { CallbackInterface, useRecoilCallback } from 'recoil';
-import { SkillManifest, convertSkillsToDictionary, Skill } from '@bfc/shared';
-
-import httpClient from '../../utils/httpUtil';
+import { SkillManifest, SkillSetting } from '@bfc/shared';
+import produce from 'immer';
import {
skillManifestsState,
onAddSkillDialogCompleteState,
- skillsState,
- settingsState,
showAddSkillDialogModalState,
displaySkillManifestState,
+ settingsState,
} from './../atoms/botState';
-import { logMessage } from './shared';
+import { setSettingState } from './setting';
export const skillDispatcher = () => {
const createSkillManifest = ({ set }, { id, content, projectId }) => {
@@ -41,38 +39,19 @@ export const skillDispatcher = () => {
}
);
- const updateSkillState = async (
- callbackHelpers: CallbackInterface,
- projectId: string,
- updatedSkills: Skill[]
- ): Promise => {
- try {
- const { set } = callbackHelpers;
-
- const { data: skills } = await httpClient.post(`/projects/${projectId}/skills/`, { skills: updatedSkills });
-
- set(settingsState(projectId), (settings) => ({
- ...settings,
- skill: convertSkillsToDictionary(skills),
- }));
- set(skillsState(projectId), skills);
-
- return skills;
- } catch (error) {
- logMessage(callbackHelpers, error.message);
- }
- };
-
const addSkill = useRecoilCallback(
- (callbackHelpers: CallbackInterface) => async (projectId: string, skillData: Skill) => {
+ (callbackHelpers: CallbackInterface) => async (projectId: string, skill: SkillSetting) => {
const { set, snapshot } = callbackHelpers;
const { func: onAddSkillDialogComplete } = await snapshot.getPromise(onAddSkillDialogCompleteState(projectId));
- const skills = await updateSkillState(callbackHelpers, projectId, [
- ...(await snapshot.getPromise(skillsState(projectId))),
- skillData,
- ]);
-
- const skill = (skills || []).find(({ manifestUrl }) => manifestUrl === skillData.manifestUrl);
+ const settings = await snapshot.getPromise(settingsState(projectId));
+
+ setSettingState(
+ callbackHelpers,
+ projectId,
+ produce(settings, (updateSettings) => {
+ updateSettings.skill = { ...(updateSettings.skill || {}), [skill.name]: skill };
+ })
+ );
if (typeof onAddSkillDialogComplete === 'function') {
onAddSkillDialogComplete(skill || null);
@@ -84,29 +63,44 @@ export const skillDispatcher = () => {
);
const removeSkill = useRecoilCallback(
- (callbackHelpers: CallbackInterface) => async (projectId: string, manifestUrl?: string) => {
+ (callbackHelpers: CallbackInterface) => async (projectId: string, key: string) => {
const { snapshot } = callbackHelpers;
- const currentSkills = await snapshot.getPromise(skillsState(projectId));
- const skills = currentSkills.filter((skill) => skill.manifestUrl !== manifestUrl);
- await updateSkillState(callbackHelpers, projectId, skills);
+ const settings = await snapshot.getPromise(settingsState(projectId));
+
+ setSettingState(
+ callbackHelpers,
+ projectId,
+ produce(settings, (updateSettings) => {
+ delete updateSettings.skill?.[key];
+ })
+ );
}
);
const updateSkill = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (
projectId: string,
- { targetId, skillData }: { targetId: number; skillData?: any }
+ key: string,
+ { endpointUrl, manifestUrl, msAppId, name }: SkillSetting
) => {
const { snapshot } = callbackHelpers;
- const originSkills = [...(await snapshot.getPromise(skillsState(projectId)))];
-
- if (targetId >= 0 && targetId < originSkills.length && skillData) {
- originSkills.splice(targetId, 1, skillData);
- } else {
- throw new Error(`update out of range, skill not found`);
- }
-
- updateSkillState(callbackHelpers, projectId, originSkills);
+ const settings = await snapshot.getPromise(settingsState(projectId));
+
+ setSettingState(
+ callbackHelpers,
+ projectId,
+ produce(settings, (updateSettings) => {
+ updateSettings.skill = {
+ ...(updateSettings.skill || {}),
+ [key]: {
+ endpointUrl,
+ manifestUrl,
+ msAppId,
+ name,
+ },
+ };
+ })
+ );
}
);
@@ -117,21 +111,9 @@ export const skillDispatcher = () => {
const addSkillDialogCancel = useRecoilCallback(({ set }: CallbackInterface) => (projectId: string) => {
set(showAddSkillDialogModalState(projectId), false);
- set(onAddSkillDialogCompleteState(projectId), { func: undefined });
+ set(onAddSkillDialogCompleteState(projectId), {});
});
- const addSkillDialogSuccess = useRecoilCallback(
- ({ set, snapshot }: CallbackInterface) => async (projectId: string) => {
- const onAddSkillDialogComplete = (await snapshot.getPromise(onAddSkillDialogCompleteState(projectId))).func;
- if (typeof onAddSkillDialogComplete === 'function') {
- onAddSkillDialogComplete(null);
- }
-
- set(showAddSkillDialogModalState(projectId), false);
- set(onAddSkillDialogCompleteState(projectId), { func: undefined });
- }
- );
-
const displayManifestModal = useRecoilCallback(({ set }: CallbackInterface) => (id: string, projectId: string) => {
set(displaySkillManifestState(projectId), id);
});
@@ -142,15 +124,14 @@ export const skillDispatcher = () => {
return {
addSkill,
+ removeSkill,
+ updateSkill,
createSkillManifest,
removeSkillManifest,
updateSkillManifest,
- updateSkill,
addSkillDialogBegin,
addSkillDialogCancel,
- addSkillDialogSuccess,
displayManifestModal,
dismissManifestModal,
- removeSkill,
};
};
diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts
index f80bacaafc..de01dcbee3 100644
--- a/Composer/packages/client/src/shell/useShell.ts
+++ b/Composer/packages/client/src/shell/useShell.ts
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
import { useMemo, useRef } from 'react';
-import { ShellApi, ShellData, Shell, fetchFromSettings, DialogSchemaFile, Skill } from '@bfc/shared';
+import { ShellApi, ShellData, Shell, fetchFromSettings, DialogSchemaFile, SkillSetting } from '@bfc/shared';
import { useRecoilValue } from 'recoil';
import formatMessage from 'format-message';
@@ -73,7 +73,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
updateUserSettings,
setMessage,
displayManifestModal,
- updateSkillsInSetting,
+ updateSkill,
} = useRecoilValue(dispatcherState);
const lgApi = useLgApi(projectId);
@@ -197,15 +197,15 @@ export function useShell(source: EventSource, projectId: string): Shell {
redo,
commitChanges,
addCoachMarkRef: onboardingAddCoachMarkRef,
- updateUserSettings: updateUserSettings,
+ updateUserSettings,
announce: setMessage,
displayManifestModal: (skillId) => displayManifestModal(skillId, projectId),
updateDialogSchema: async (dialogSchema: DialogSchemaFile) => {
updateDialogSchema(dialogSchema, projectId);
},
- skillsInSettings: {
+ skillsSettings: {
get: (path: string) => fetchFromSettings(path, settings),
- set: (skillName: string, skillInfo: Partial) => updateSkillsInSetting(skillName, skillInfo, projectId),
+ set: (id: string, skill: SkillSetting) => updateSkill(projectId, id, skill),
},
};
diff --git a/Composer/packages/lib/indexers/src/index.ts b/Composer/packages/lib/indexers/src/index.ts
index f2b9345a06..72ae1a51d2 100644
--- a/Composer/packages/lib/indexers/src/index.ts
+++ b/Composer/packages/lib/indexers/src/index.ts
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-import { FileInfo, importResolverGenerator } from '@bfc/shared';
+import { DialogSetting, FileInfo, importResolverGenerator } from '@bfc/shared';
import { dialogIndexer } from './dialogIndexer';
import { dialogSchemaIndexer } from './dialogSchemaIndexer';
import { lgIndexer } from './lgIndexer';
import { luIndexer } from './luIndexer';
import { qnaIndexer } from './qnaIndexer';
+import { skillIndexer } from './skillIndexer';
import { skillManifestIndexer } from './skillManifestIndexer';
import { FileExtensions } from './utils/fileExtensions';
import { getExtension, getBaseName } from './utils/help';
@@ -43,7 +44,7 @@ class Indexer {
return importResolverGenerator(lgFiles, '.lg', locale);
};
- public index(files: FileInfo[], botName: string, locale: string) {
+ public index(files: FileInfo[], botName: string, locale: string, skillContent: any, settings: DialogSetting) {
const result = this.classifyFile(files);
return {
dialogs: dialogIndexer.index(result[FileExtensions.Dialog], botName),
@@ -52,6 +53,7 @@ class Indexer {
luFiles: luIndexer.index(result[FileExtensions.Lu]),
qnaFiles: qnaIndexer.index(result[FileExtensions.QnA]),
skillManifestFiles: skillManifestIndexer.index(result[FileExtensions.Manifest]),
+ skills: skillIndexer.index(skillContent, settings.skill),
};
}
}
@@ -66,3 +68,4 @@ export * from './luIndexer';
export * from './qnaIndexer';
export * from './utils';
export * from './validations';
+export * from './skillIndexer';
diff --git a/Composer/packages/lib/indexers/src/skillIndexer.ts b/Composer/packages/lib/indexers/src/skillIndexer.ts
new file mode 100644
index 0000000000..67e71e81b9
--- /dev/null
+++ b/Composer/packages/lib/indexers/src/skillIndexer.ts
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { Skill, SkillSetting } from '@bfc/shared';
+import pickBy from 'lodash/pickBy';
+import toPairs from 'lodash/toPairs';
+
+const index = (skillContent: any[], skillSettings: { [name: string]: SkillSetting } = {}): Skill[] => {
+ return toPairs(skillSettings).map(
+ ([id, settings]): Skill => {
+ const { content = {} } = skillContent.find(({ id: key }) => key === id) || {};
+ const { description, endpoints = [] } = content;
+
+ return pickBy({
+ id,
+ description,
+ endpoints,
+ content,
+ ...settings,
+ }) as Skill;
+ }
+ );
+};
+
+export const skillIndexer = {
+ index,
+};
diff --git a/Composer/packages/lib/shared/src/types/indexers.ts b/Composer/packages/lib/shared/src/types/indexers.ts
index cd8a68b2a1..9416d09c0e 100644
--- a/Composer/packages/lib/shared/src/types/indexers.ts
+++ b/Composer/packages/lib/shared/src/types/indexers.ts
@@ -142,14 +142,14 @@ export interface LgFile {
}
export interface Skill {
- manifestUrl: string;
- name: string;
- protocol: string;
- description: string;
- endpoints: { [key: string]: any }[];
+ id: string;
+ content: any;
+ description?: string;
+ endpoints: any[];
endpointUrl: string;
+ manifestUrl: string;
msAppId: string;
- body: string | null | undefined;
+ name: string;
}
export interface TextFile {
diff --git a/Composer/packages/lib/shared/src/types/settings.ts b/Composer/packages/lib/shared/src/types/settings.ts
index 6a12c2efd2..0a549c0fb9 100644
--- a/Composer/packages/lib/shared/src/types/settings.ts
+++ b/Composer/packages/lib/shared/src/types/settings.ts
@@ -20,6 +20,13 @@ export interface AppUpdaterSettings {
useNightly: boolean;
}
+export interface SkillSetting {
+ name: string;
+ manifestUrl: string;
+ msAppId: string;
+ endpointUrl: string;
+}
+
export interface DialogSetting {
MicrosoftAppId?: string;
MicrosoftAppPassword?: string;
@@ -34,12 +41,7 @@ export interface DialogSetting {
defaultLanguage: string;
languages: string[];
skill?: {
- [skillName: string]: {
- name: string;
- manifestUrl: string;
- msAppId: string;
- endpointUrl: string;
- };
+ [skillName: string]: SkillSetting;
};
botId?: string;
skillHostEndpoint?: string;
diff --git a/Composer/packages/lib/shared/src/types/shell.ts b/Composer/packages/lib/shared/src/types/shell.ts
index cca88829fa..acfb580fb0 100644
--- a/Composer/packages/lib/shared/src/types/shell.ts
+++ b/Composer/packages/lib/shared/src/types/shell.ts
@@ -2,8 +2,8 @@
// Licensed under the MIT License.
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { DialogInfo, LuFile, LgFile, QnAFile, LuIntentSection, LgTemplate, DialogSchemaFile, Skill } from './indexers';
-import { UserSettings } from './settings';
+import { DialogInfo, LuFile, LgFile, QnAFile, LuIntentSection, LgTemplate, DialogSchemaFile } from './indexers';
+import { SkillSetting, UserSettings } from './settings';
import { OBISchema } from './schema';
/** Recursively marks all properties as optional. */
@@ -100,9 +100,9 @@ export interface ShellApi {
displayManifestModal: (manifestId: string) => void;
updateDialogSchema: (_: DialogSchemaFile) => Promise;
createTrigger: (id: string, formData, url?: string) => void;
- skillsInSettings: {
+ skillsSettings: {
get: (path: string) => any;
- set: (skillName: string, skillsData: Partial) => Promise;
+ set: (skillId: string, skillsData: SkillSetting) => Promise;
};
}
diff --git a/Composer/packages/server/__tests__/controllers/project.test.ts b/Composer/packages/server/__tests__/controllers/project.test.ts
index 10f67abbaa..5ed15daf4c 100644
--- a/Composer/packages/server/__tests__/controllers/project.test.ts
+++ b/Composer/packages/server/__tests__/controllers/project.test.ts
@@ -331,45 +331,17 @@ describe('skill operation', () => {
projectId = await BotProjectService.openProject(location2);
});
- it('should check skill url', async () => {
+ it('should retrieve skill manifest', async () => {
const mockReq = {
params: { projectId },
- query: {},
- body: {
+ query: {
url: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json',
},
+ body: {},
} as Request;
await ProjectController.getSkill(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
}, 10000);
-
- it('should update skill', async () => {
- const mockReq = {
- params: { projectId },
- query: {},
- body: {
- skills: [
- {
- manifestUrl: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json',
- },
- ],
- },
- } as Request;
- await ProjectController.updateSkill(mockReq, mockRes);
- expect(mockRes.status).toHaveBeenCalledWith(200);
- }, 5000);
-
- it('should update skill, remove', async () => {
- const mockReq = {
- params: { projectId },
- query: {},
- body: {
- skills: [],
- },
- } as Request;
- await ProjectController.updateSkill(mockReq, mockRes);
- expect(mockRes.status).toHaveBeenCalledWith(200);
- });
});
// TODO: add a success publish test.
diff --git a/Composer/packages/server/src/controllers/project.ts b/Composer/packages/server/src/controllers/project.ts
index 6f6c23331e..61956e4727 100644
--- a/Composer/packages/server/src/controllers/project.ts
+++ b/Composer/packages/server/src/controllers/project.ts
@@ -11,7 +11,7 @@ import log from '../logger';
import { BotProjectService } from '../services/project';
import AssetService from '../services/asset';
import { LocationRef } from '../models/bot/interface';
-import { getSkillByUrl } from '../models/bot/skillManager';
+import { getSkillManifest } from '../models/bot/skillManager';
import StorageService from '../services/storage';
import settings from '../settings';
@@ -252,27 +252,6 @@ async function removeFile(req: Request, res: Response) {
}
}
-async function updateSkill(req: Request, res: Response) {
- const projectId = req.params.projectId;
- const user = await PluginLoader.getUserFromRequest(req);
-
- const currentProject = await BotProjectService.getProjectById(projectId, user);
- if (currentProject !== undefined) {
- try {
- const skills = await currentProject.updateSkill(req.body.skills);
- res.status(200).json(skills);
- } catch (err) {
- res.status(404).json({
- message: err.message,
- });
- }
- } else {
- res.status(404).json({
- message: 'No such bot project opened',
- });
- }
-}
-
async function getSkill(req: Request, res: Response) {
const projectId = req.params.projectId;
const user = await PluginLoader.getUserFromRequest(req);
@@ -280,8 +259,8 @@ async function getSkill(req: Request, res: Response) {
const currentProject = await BotProjectService.getProjectById(projectId, user);
if (currentProject !== undefined) {
try {
- const skill = await getSkillByUrl(req.body.url);
- res.status(200).json(skill);
+ const content = await getSkillManifest(req.query.url);
+ res.status(200).json(content);
} catch (err) {
res.status(404).json({
message: err.message,
@@ -422,7 +401,6 @@ export const ProjectController = {
updateFile,
createFile,
removeFile,
- updateSkill,
getSkill,
build,
setQnASettings,
diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts
index f214875e75..abc5938af2 100644
--- a/Composer/packages/server/src/models/bot/botProject.ts
+++ b/Composer/packages/server/src/models/bot/botProject.ts
@@ -6,20 +6,10 @@ import fs from 'fs';
import axios from 'axios';
import { autofixReferInDialog } from '@bfc/indexers';
-import {
- getNewDesigner,
- FileInfo,
- Skill,
- Diagnostic,
- convertSkillsToDictionary,
- IBotProject,
- DialogSetting,
- FileExtensions,
-} from '@bfc/shared';
+import { getNewDesigner, FileInfo, Skill, Diagnostic, IBotProject, DialogSetting, FileExtensions } from '@bfc/shared';
import merge from 'lodash/merge';
import { UserIdentity, pluginLoader } from '@bfc/extension';
import { FeedbackType, generate } from '@microsoft/bf-generate-library';
-import values from 'lodash/values';
import { Path } from '../../utility/path';
import { copyDir } from '../../utility/storage';
@@ -33,7 +23,7 @@ import { BotProjectService } from '../../services/project';
import { Builder } from './builder';
import { IFileStorage } from './../storage/interface';
import { LocationRef, IBuildConfig } from './interface';
-import { extractSkillManifestUrl } from './skillManager';
+import { retrieveSkillManifests } from './skillManager';
import { defaultFilePath, serializeFiles, parseFileName } from './botStructure';
const debug = log.extend('bot-project');
@@ -149,9 +139,8 @@ export class BotProject implements IBotProject {
public init = async () => {
this.diagnostics = [];
this.settings = await this.getEnvSettings(false);
- const skillsCollection = values(this.settings?.skill);
- const { skillsParsed, diagnostics } = await extractSkillManifestUrl(skillsCollection || ([] as any));
- this.skills = skillsParsed;
+ const { skillManifests, diagnostics } = await retrieveSkillManifests(this.settings?.skill);
+ this.skills = skillManifests;
this.diagnostics.push(...diagnostics);
this.files = await this._getFiles();
};
@@ -216,16 +205,6 @@ export class BotProject implements IBotProject {
this.settings = config;
};
- // update skill in settings
- public updateSkill = async (config: any[]) => {
- const settings = await this.getEnvSettings(false);
- const { skillsParsed } = await extractSkillManifestUrl(config);
- const mapped = convertSkillsToDictionary(skillsParsed);
- settings.skill = await this.settingManager.set(mapped);
- this.skills = skillsParsed;
- return skillsParsed;
- };
-
public exportToZip = (cb) => {
try {
this.fileStorage.zip(this.dataDir, cb);
diff --git a/Composer/packages/server/src/models/bot/skillManager.ts b/Composer/packages/server/src/models/bot/skillManager.ts
index 0fbfefdb06..88a5576af4 100644
--- a/Composer/packages/server/src/models/bot/skillManager.ts
+++ b/Composer/packages/server/src/models/bot/skillManager.ts
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-import get from 'lodash/get';
import * as msRest from '@azure/ms-rest-js';
-import { Skill, Diagnostic, DiagnosticSeverity } from '@bfc/shared';
+import { SkillSetting, Diagnostic, DiagnosticSeverity } from '@bfc/shared';
+import toPairs from 'lodash/toPairs';
import logger from './../../logger';
@@ -17,50 +17,26 @@ const token = process.env.ACCESS_TOKEN || 'token';
const creds = new msRest.TokenCredentials(token);
const client = new msRest.ServiceClient(creds, clientOptions);
-export const getSkillByUrl = async (
- url: string,
- name?: string,
- msAppId?: string,
- endpointUrl?: string
-): Promise => {
- try {
- const req: msRest.RequestPrepareOptions = {
- url,
- method: 'GET',
- };
+export const getSkillManifest = async (url: string): Promise => {
+ const { bodyAsText: content } = await client.sendRequest({
+ url,
+ method: 'GET',
+ });
- const res: msRest.HttpOperationResponse = await client.sendRequest(req);
-
- if (res.status >= 400) {
- throw new Error('Manifest url can not be accessed.');
- }
-
- const resBody = typeof res.bodyAsText === 'string' && JSON.parse(res.bodyAsText);
- return {
- manifestUrl: url,
- name: name || resBody?.name || '',
- description: resBody?.description || '',
- endpoints: get(resBody, 'endpoints', []),
- endpointUrl: endpointUrl || get(resBody, 'endpoints[0].endpointUrl', ''), // needs more investment on endpoint
- protocol: get(resBody, 'endpoints[0].protocol', ''),
- msAppId: msAppId || get(resBody, 'endpoints[0].msAppId', ''),
- body: res.bodyAsText,
- };
- } catch (error) {
- throw new Error('Manifest url can not be accessed.');
- }
+ return typeof content === 'string' ? JSON.parse(content) : {};
};
-export const extractSkillManifestUrl = async (
- skills: any[]
-): Promise<{ skillsParsed: Skill[]; diagnostics: Diagnostic[] }> => {
- const skillsParsed: Skill[] = [];
+export const retrieveSkillManifests = async (skillSettings?: { [name: string]: SkillSetting } | SkillSetting[]) => {
+ const skills = toPairs(skillSettings);
+
const diagnostics: Diagnostic[] = [];
- for (const skill of skills) {
- const { manifestUrl, name, msAppId, endpointUrl } = skill;
+ const skillManifests: any = [];
+
+ for (const [id, { manifestUrl }] of skills) {
try {
- const parsedSkill = await getSkillByUrl(manifestUrl, name, msAppId, endpointUrl);
- skillsParsed.push(parsedSkill);
+ const content = await getSkillManifest(manifestUrl);
+
+ skillManifests.push({ content, id, manifestUrl });
} catch (error) {
const notify = new Diagnostic(
`Accessing skill manifest url error, ${manifestUrl}`,
@@ -68,7 +44,9 @@ export const extractSkillManifestUrl = async (
DiagnosticSeverity.Warning
);
diagnostics.push(notify);
+ skillManifests.push({ id, manifestUrl });
}
}
- return { skillsParsed, diagnostics };
+
+ return { diagnostics, skillManifests };
};
diff --git a/Composer/packages/server/src/router/api.ts b/Composer/packages/server/src/router/api.ts
index 9b2b90aa67..e28ee00cef 100644
--- a/Composer/packages/server/src/router/api.ts
+++ b/Composer/packages/server/src/router/api.ts
@@ -27,8 +27,7 @@ router.delete('/projects/:projectId', ProjectController.removeProject);
router.put('/projects/:projectId/files/:name', ProjectController.updateFile);
router.delete('/projects/:projectId/files/:name', ProjectController.removeFile);
router.post('/projects/:projectId/files', ProjectController.createFile);
-router.post('/projects/:projectId/skills', ProjectController.updateSkill);
-router.post('/projects/:projectId/skill/check', ProjectController.getSkill);
+router.get('/projects/:projectId/skill/retrieve-skill-manifest', ProjectController.getSkill);
router.post('/projects/:projectId/build', ProjectController.build);
router.post('/projects/:projectId/qnaSettings/set', ProjectController.setQnASettings);
router.post('/projects/:projectId/project/saveAs', ProjectController.saveProjectAs);
diff --git a/Composer/packages/ui-plugins/select-skill-dialog/src/BeginSkillDialogField.tsx b/Composer/packages/ui-plugins/select-skill-dialog/src/BeginSkillDialogField.tsx
index df1af439d4..5e46060a83 100644
--- a/Composer/packages/ui-plugins/select-skill-dialog/src/BeginSkillDialogField.tsx
+++ b/Composer/packages/ui-plugins/select-skill-dialog/src/BeginSkillDialogField.tsx
@@ -36,7 +36,7 @@ const handleBackwardCompatibility = (skills: Skill[], value): { name: string; en
export const BeginSkillDialogField: React.FC = (props) => {
const { depth, id, schema, uiOptions, value, onChange, definitions } = props;
const { projectId, shellApi, skills = [] } = useShellApi();
- const { displayManifestModal, skillsInSettings } = shellApi;
+ const { displayManifestModal, skillsSettings } = shellApi;
const [selectedSkill, setSelectedSkill] = useState('');
const [oldEndpoint, loadEndpointForOldBots] = useState('');
@@ -57,30 +57,29 @@ export const BeginSkillDialogField: React.FC = (props) => {
}
}, []);
- const matchedSkill: Skill | undefined = useMemo(() => {
- return skills.find(({ name }) => name === selectedSkill);
+ const matchedSkill = useMemo(() => {
+ return skills.find(({ id }) => id === selectedSkill) || ({} as Skill);
}, [skills, selectedSkill]);
const endpointOptions = useMemo(() => {
- return (matchedSkill?.endpoints || []).map(({ name }) => name);
+ return (matchedSkill.endpoints || []).map(({ name }) => name);
}, [matchedSkill]);
const handleEndpointChange = async (skillEndpoint) => {
- if (matchedSkill) {
+ if (matchedSkill.id) {
const { msAppId, endpointUrl } =
(matchedSkill.endpoints || []).find(({ name }) => name === skillEndpoint) || ({} as any);
const schemaUpdate: any = {};
- const settingsUpdate: any = {};
+ const settingsUpdate: any = { ...matchedSkill };
if (endpointUrl) {
- skillsInSettings.set(matchedSkill.name, { endpointUrl });
- schemaUpdate.skillEndpoint = referBySettings(matchedSkill?.name, 'endpointUrl');
+ schemaUpdate.skillEndpoint = referBySettings(matchedSkill.name, 'endpointUrl');
settingsUpdate.endpointUrl = endpointUrl;
}
if (msAppId) {
- schemaUpdate.skillAppId = referBySettings(matchedSkill?.name, 'msAppId');
+ schemaUpdate.skillAppId = referBySettings(matchedSkill.name, 'msAppId');
settingsUpdate.msAppId = msAppId;
}
- skillsInSettings.set(matchedSkill.name, { ...settingsUpdate });
+ skillsSettings.set(matchedSkill.id, { ...settingsUpdate });
onChange({
...value,
...schemaUpdate,
@@ -101,7 +100,7 @@ export const BeginSkillDialogField: React.FC = (props) => {
const skillEndpointUiSchema = uiOptions.properties?.skillEndpoint || {};
skillEndpointUiSchema.serializer = {
get: (value) => {
- const url: any = skillsInSettings.get(value);
+ const url: any = skillsSettings.get(value);
const endpoint = (matchedSkill?.endpoints || []).find(({ endpointUrl }) => endpointUrl === url);
return endpoint?.name;
},
@@ -122,7 +121,7 @@ export const BeginSkillDialogField: React.FC = (props) => {
diff --git a/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/BeginSkillDialogField.test.tsx b/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/BeginSkillDialogField.test.tsx
index 4208e87867..3caaab819c 100644
--- a/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/BeginSkillDialogField.test.tsx
+++ b/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/BeginSkillDialogField.test.tsx
@@ -15,6 +15,8 @@ import { schema } from './constants';
const skills: any = [
{
+ id: 'yuesuemailskill0207',
+ content: {},
manifestUrl: 'https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json',
name: 'yuesuemailskill0207',
endpoints: [
@@ -52,7 +54,7 @@ const renderBeginSkillDialog = ({ value = {}, onChange = jest.fn() } = {}) => {
const shell: any = {
addSkillDialog,
- skillsInSettings: {
+ skillsSettings: {
get: (path: string) => fetchFromSettings(path, setting),
set: () => {},
},
diff --git a/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/SelectSkillDialog.test.tsx b/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/SelectSkillDialog.test.tsx
index 7b28cc1adc..09db7f078d 100644
--- a/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/SelectSkillDialog.test.tsx
+++ b/Composer/packages/ui-plugins/select-skill-dialog/src/__tests__/SelectSkillDialog.test.tsx
@@ -49,7 +49,7 @@ const renderSelectSkillDialog = ({ addSkillDialog, onChange } = {}) => {
const shell = {
addSkillDialog,
- skillsInSettings: {
+ skillsSettings: {
get: (path: string) =>
fetchFromSettings(path, {
skill: convertSkillsToDictionary(skills),
diff --git a/Composer/plugins/azurePublish/src/deploy.ts b/Composer/plugins/azurePublish/src/deploy.ts
index aa94f8a621..fb95666546 100644
--- a/Composer/plugins/azurePublish/src/deploy.ts
+++ b/Composer/plugins/azurePublish/src/deploy.ts
@@ -42,7 +42,6 @@ export class BotProjectDeploy {
profileName: string,
name: string,
environment: string,
- language?: string,
hostname?: string,
luisResource?: string
) {
@@ -55,6 +54,7 @@ export class BotProjectDeploy {
// STEP 2: UPDATE LUIS
// Do the LUIS build if LUIS settings are present
+ let language = settings.defaultLanguage || settings.luis.defaultLanguage;
if (!language) {
language = 'en-us';
}
diff --git a/Composer/plugins/azurePublish/src/index.ts b/Composer/plugins/azurePublish/src/index.ts
index 70fce38471..759d0370dd 100644
--- a/Composer/plugins/azurePublish/src/index.ts
+++ b/Composer/plugins/azurePublish/src/index.ts
@@ -28,7 +28,6 @@ interface CreateAndDeployResources {
hostname?: string;
luisResource?: string;
subscriptionID: string;
- language?: string;
}
interface PublishConfig {
@@ -192,15 +191,7 @@ export default async (composer: any): Promise => {
resourcekey: string,
customizeConfiguration: CreateAndDeployResources
) => {
- const {
- subscriptionID,
- accessToken,
- name,
- environment,
- hostname,
- luisResource,
- language,
- } = customizeConfiguration;
+ const { subscriptionID, accessToken, name, environment, hostname, luisResource } = customizeConfiguration;
try {
// Create the BotProjectDeploy object, which is used to carry out the deploy action.
const azDeployer = new BotProjectDeploy({
@@ -221,7 +212,7 @@ export default async (composer: any): Promise => {
});
// Perform the deploy
- await azDeployer.deploy(project, settings, profileName, name, environment, language, hostname, luisResource);
+ await azDeployer.deploy(project, settings, profileName, name, environment, hostname, luisResource);
// update status and history
const status = this.getLoadingStatus(botId, profileName, jobId);
@@ -322,7 +313,7 @@ export default async (composer: any): Promise => {
environment,
hostname,
luisResource,
- language,
+ defaultLanguage,
settings,
accessToken,
} = config;
@@ -360,7 +351,6 @@ export default async (composer: any): Promise => {
environment,
hostname,
luisResource,
- language,
};
await this.performDeploymentAction(
project,