(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 (
-
-
- onEdit(index)}
- />
- onDelete(index)}
- />
- onViewManifest(item)}
- />
-
-
- );
- },
- });
- }, [projectId]);
-
const onRenderDetailsHeader = useCallback((props, defaultRender) => {
return (
@@ -161,8 +170,8 @@ const SkillList: React.FC = (props) => {
{
const mockDialogComplete = jest.fn();
const makeTestSkill: (number) => Skill = (n) => ({
- manifestUrl: 'url',
+ manifestUrl: 'url' + n,
name: 'skill' + n,
protocol: 'GET',
description: 'test skill' + n,
@@ -48,6 +49,7 @@ describe('skill dispatcher', () => {
mockDialogComplete.mockClear();
const useRecoilTestHook = () => {
+ const projectId = useRecoilValue(projectIdState);
const skillManifests = useRecoilValue(skillManifestsState);
const onAddSkillDialogComplete = useRecoilValue(onAddSkillDialogCompleteState);
const skills: Skill[] = useRecoilValue(skillsState);
@@ -58,6 +60,7 @@ describe('skill dispatcher', () => {
const currentDispatcher = useRecoilValue(dispatcherState);
return {
+ projectId,
skillManifests,
onAddSkillDialogComplete,
skills,
@@ -85,6 +88,7 @@ describe('skill dispatcher', () => {
{ recoilState: settingsState, initialValue: {} },
{ recoilState: showAddSkillDialogModalState, initialValue: false },
{ recoilState: displaySkillManifestState, initialValue: undefined },
+ { recoilState: projectIdState, initialValue: '123' },
],
dispatcher: {
recoilState: dispatcherState,
@@ -126,51 +130,32 @@ describe('skill dispatcher', () => {
]);
});
- describe('updateSkill', () => {
- it('adds a skill', async () => {
- await act(async () => {
- dispatcher.updateSkill({
- projectId: 'projectId',
- targetId: -1,
- skillData: makeTestSkill(3),
- });
- });
- expect(renderedComponent.current.showAddSkillDialogModal).toBe(false);
- expect(renderedComponent.current.onAddSkillDialogComplete.func).toBeUndefined();
- // expect(renderedComponent.current.settingsState.skill).toContain({
- // manifestUrl: 'url',
- // name: 'skill3',
- // });
- expect(renderedComponent.current.skills).toContainEqual(makeTestSkill(3));
+ it('addsSkill', async () => {
+ await act(async () => {
+ dispatcher.addSkill('123', makeTestSkill(3));
});
- it('modifies a skill', async () => {
- await act(async () => {
- dispatcher.updateSkill({
- projectId: 'projectId',
- targetId: 0,
- skillData: makeTestSkill(100),
- });
+ expect(renderedComponent.current.showAddSkillDialogModal).toBe(false);
+ expect(renderedComponent.current.onAddSkillDialogComplete.func).toBeUndefined();
+ expect(renderedComponent.current.skills).toContainEqual(makeTestSkill(3));
+ });
+
+ it('updateSkill', async () => {
+ await act(async () => {
+ dispatcher.updateSkill('123', {
+ targetId: 0,
+ skillData: makeTestSkill(100),
});
- expect(renderedComponent.current.showAddSkillDialogModal).toBe(false);
- expect(renderedComponent.current.onAddSkillDialogComplete.func).toBeUndefined();
- // expect(renderedComponent.current.settingsState.skill).toContain({
- // manifestUrl: 'url',
- // name: 'skill100',
- // });
- expect(renderedComponent.current.skills).not.toContain(makeTestSkill(1));
- expect(renderedComponent.current.skills).toContainEqual(makeTestSkill(100));
});
- it('deletes a skill', async () => {
- await act(async () => {
- dispatcher.updateSkill({
- projectId: 'projectId',
- targetId: 0,
- });
- });
- expect(renderedComponent.current.showAddSkillDialogModal).toBe(false);
- expect(renderedComponent.current.onAddSkillDialogComplete.func).toBeUndefined();
- expect(renderedComponent.current.skills).not.toContain(makeTestSkill(1));
+
+ expect(renderedComponent.current.skills).not.toContain(makeTestSkill(1));
+ expect(renderedComponent.current.skills).toContainEqual(makeTestSkill(100));
+ });
+
+ it('removeSkill', async () => {
+ await act(async () => {
+ dispatcher.removeSkill('123', makeTestSkill(1).manifestUrl);
});
+ expect(renderedComponent.current.skills).not.toContain(makeTestSkill(1));
});
it('addSkillDialogBegin', async () => {
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/skill.ts b/Composer/packages/client/src/recoilModel/dispatchers/skill.ts
index 2301a213e3..249c116075 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/skill.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/skill.ts
@@ -3,7 +3,7 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { CallbackInterface, useRecoilCallback } from 'recoil';
-import { SkillManifest, convertSkillsToDictionary } from '@bfc/shared';
+import { SkillManifest, convertSkillsToDictionary, Skill } from '@bfc/shared';
import httpClient from '../../utils/httpUtil';
@@ -39,50 +39,71 @@ export const skillDispatcher = () => {
}
);
- const updateSkill = useRecoilCallback(
- (callbackHelpers: CallbackInterface) => async ({ projectId, targetId, skillData }) => {
+ 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, (settings) => ({
+ ...settings,
+ skill: convertSkillsToDictionary(skills),
+ }));
+ set(skillsState, skills);
+
+ return skills;
+ } catch (error) {
+ logMessage(callbackHelpers, error.message);
+ }
+ };
+
+ const addSkill = useRecoilCallback(
+ (callbackHelpers: CallbackInterface) => async (projectId: string, skillData: Skill) => {
const { set, snapshot } = callbackHelpers;
- const onAddSkillDialogComplete = (await snapshot.getPromise(onAddSkillDialogCompleteState)).func;
+ const { func: onAddSkillDialogComplete } = await snapshot.getPromise(onAddSkillDialogCompleteState);
+ const skills = await updateSkillState(callbackHelpers, projectId, [
+ ...(await snapshot.getPromise(skillsState)),
+ skillData,
+ ]);
+
+ const skill = (skills || []).find(({ manifestUrl }) => manifestUrl === skillData.manifestUrl);
+
+ if (typeof onAddSkillDialogComplete === 'function') {
+ onAddSkillDialogComplete(skill || null);
+ }
+
+ set(showAddSkillDialogModalState, false);
+ set(onAddSkillDialogCompleteState, {});
+ }
+ );
+
+ const removeSkill = useRecoilCallback(
+ (callbackHelpers: CallbackInterface) => async (projectId: string, manifestUrl?: string) => {
+ const { snapshot } = callbackHelpers;
+ const skills = [...(await snapshot.getPromise(skillsState))].filter((skill) => skill.manifestUrl !== manifestUrl);
+ await updateSkillState(callbackHelpers, projectId, skills);
+ }
+ );
+
+ const updateSkill = useRecoilCallback(
+ (callbackHelpers: CallbackInterface) => async (
+ projectId: string,
+ { targetId, skillData }: { targetId: number; skillData?: any }
+ ) => {
+ const { snapshot } = callbackHelpers;
const originSkills = [...(await snapshot.getPromise(skillsState))];
- // add
- if (targetId === -1 && skillData) {
- originSkills.push(skillData);
- } else if (targetId >= 0 && targetId < originSkills.length) {
- // modify
- if (skillData) {
- originSkills.splice(targetId, 1, skillData);
-
- // delete
- } else {
- originSkills.splice(targetId, 1);
- }
- // error
+ if (targetId >= 0 && targetId < originSkills.length && skillData) {
+ originSkills.splice(targetId, 1, skillData);
} else {
throw new Error(`update out of range, skill not found`);
}
- try {
- const response = await httpClient.post(`/projects/${projectId}/skills/`, { skills: originSkills });
-
- if (typeof onAddSkillDialogComplete === 'function') {
- const skill = response.data.find(({ manifestUrl }) => manifestUrl === skillData.manifestUrl);
- onAddSkillDialogComplete(skill ? skill : null);
- }
-
- const skills = response.data;
-
- set(showAddSkillDialogModalState, false);
- set(onAddSkillDialogCompleteState, { func: undefined });
- set(settingsState, (settings) => ({
- ...settings,
- skill: convertSkillsToDictionary(skills),
- }));
- set(skillsState, skills);
- } catch (err) {
- //TODO: error
- logMessage(callbackHelpers, err.message);
- }
+ updateSkillState(callbackHelpers, projectId, originSkills);
}
);
@@ -115,6 +136,7 @@ export const skillDispatcher = () => {
});
return {
+ addSkill,
createSkillManifest,
removeSkillManifest,
updateSkillManifest,
@@ -124,5 +146,6 @@ export const skillDispatcher = () => {
addSkillDialogSuccess,
displayManifestModal,
dismissManifestModal,
+ removeSkill,
};
};
diff --git a/Composer/packages/server/src/models/bot/skillManager.ts b/Composer/packages/server/src/models/bot/skillManager.ts
index f15d3fceab..0fbfefdb06 100644
--- a/Composer/packages/server/src/models/bot/skillManager.ts
+++ b/Composer/packages/server/src/models/bot/skillManager.ts
@@ -17,7 +17,12 @@ 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): Promise => {
+export const getSkillByUrl = async (
+ url: string,
+ name?: string,
+ msAppId?: string,
+ endpointUrl?: string
+): Promise => {
try {
const req: msRest.RequestPrepareOptions = {
url,
@@ -36,9 +41,9 @@ export const getSkillByUrl = async (url: string, name?: string): Promise
name: name || resBody?.name || '',
description: resBody?.description || '',
endpoints: get(resBody, 'endpoints', []),
- endpointUrl: get(resBody, 'endpoints[0].endpointUrl', ''), // needs more invesment on endpoint
+ endpointUrl: endpointUrl || get(resBody, 'endpoints[0].endpointUrl', ''), // needs more investment on endpoint
protocol: get(resBody, 'endpoints[0].protocol', ''),
- msAppId: get(resBody, 'endpoints[0].msAppId', ''),
+ msAppId: msAppId || get(resBody, 'endpoints[0].msAppId', ''),
body: res.bodyAsText,
};
} catch (error) {
@@ -52,9 +57,9 @@ export const extractSkillManifestUrl = async (
const skillsParsed: Skill[] = [];
const diagnostics: Diagnostic[] = [];
for (const skill of skills) {
- const { manifestUrl, name } = skill;
+ const { manifestUrl, name, msAppId, endpointUrl } = skill;
try {
- const parsedSkill = await getSkillByUrl(manifestUrl, name);
+ const parsedSkill = await getSkillByUrl(manifestUrl, name, msAppId, endpointUrl);
skillsParsed.push(parsedSkill);
} catch (error) {
const notify = new Diagnostic(
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 4cb5857c09..df1af439d4 100644
--- a/Composer/packages/ui-plugins/select-skill-dialog/src/BeginSkillDialogField.tsx
+++ b/Composer/packages/ui-plugins/select-skill-dialog/src/BeginSkillDialogField.tsx
@@ -16,6 +16,11 @@ const referBySettings = (skillName: string, property: string) => {
return `=settings.skill['${skillName}'].${property}`;
};
+const settingReferences = (skillName: string) => ({
+ skillEndpoint: referBySettings(skillName, 'endpointUrl'),
+ skillAppId: referBySettings(skillName, 'msAppId'),
+});
+
const handleBackwardCompatibility = (skills: Skill[], value): { name: string; endpointName: string } | undefined => {
const { skillEndpoint } = value;
const foundSkill = skills.find(({ manifestUrl }) => manifestUrl === value.id);
@@ -107,8 +112,9 @@ export const BeginSkillDialogField: React.FC = (props) => {
};
const onSkillSelectionChange = (option: IComboBoxOption | null) => {
- if (option) {
- setSelectedSkill(option?.text);
+ if (option?.text) {
+ setSelectedSkill(option.text);
+ onChange({ ...value, ...settingReferences(option.text) });
}
};