Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
51b33db
Added schema for bot project support
Jul 29, 2020
40f8661
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
Sep 29, 2020
308e392
Add 1.0 botproject schema
Oct 4, 2020
b98e1fa
Fix typo
Oct 4, 2020
b046925
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
Oct 7, 2020
c56e1b1
Updated gitignore
Oct 7, 2020
b78d118
Typo fixed
Oct 7, 2020
828bee2
Merge branch 'main' into srravich/bot-projects-schema
cwhitten Oct 8, 2020
ccf5f88
Merge branch 'main' into srravich/bot-projects-schema
srinaath Oct 8, 2020
e9f525a
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
Oct 8, 2020
c791838
Merge branch 'srravich/bot-projects-schema' of https://github.com/mic…
Oct 8, 2020
5576163
Following sdk.schema approach to specificy object type using ref
Oct 8, 2020
bc20752
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
Oct 16, 2020
0a688c4
Fix schema
Oct 16, 2020
1f8a9c4
Removed workspace references.
Oct 16, 2020
6970cc5
Relative path support
Oct 16, 2020
84f0a50
Update operations on Bot project file
Oct 16, 2020
e6ef1d2
Updated tests for removing workspace reference
Oct 20, 2020
1339b15
Removed manifest update operation
Oct 20, 2020
975952e
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
Oct 20, 2020
64cc847
Cleared logs
Oct 20, 2020
29a7fd2
Merge branch 'main' into srravich/bot-projects-schema
cwhitten Oct 20, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ describe('Bot Project File dispatcher', () => {
content: {
$schema: '',
name: 'TesterBot',
workspace: 'file:///Users/tester/Desktop/LoadedBotProject/TesterBot',
skills: {},
},
},
Expand All @@ -99,6 +98,10 @@ describe('Bot Project File dispatcher', () => {
},
},
{ recoilState: botProjectIdsState, initialValue: [rootBotProjectId] },
{
recoilState: locationState(rootBotProjectId),
initialValue: '/Users/tester/Desktop/LoadedBotProject/RootBot',
},
],
dispatcher: {
recoilState: dispatcherState,
Expand All @@ -115,7 +118,7 @@ describe('Bot Project File dispatcher', () => {
it('should add a local skill to bot project file', async () => {
await act(async () => {
renderedComponent.current.setSkillsData({
location: 'Users/tester/Desktop/LoadedBotProject/Todo-Skill',
location: '/Users/tester/Desktop/LoadedBotProject/Todo-Skill',
botNameIdentifier: 'todoSkill',
});
});
Expand All @@ -124,9 +127,7 @@ describe('Bot Project File dispatcher', () => {
dispatcher.addLocalSkillToBotProjectFile(testSkillId);
});

expect(renderedComponent.current.botProjectFile.content.skills.todoSkill.workspace).toBe(
'file:///Users/tester/Desktop/LoadedBotProject/Todo-Skill'
);
expect(renderedComponent.current.botProjectFile.content.skills.todoSkill.workspace).toBe('../Todo-Skill');
expect(renderedComponent.current.botProjectFile.content.skills.todoSkill.remote).toBeFalsy();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ describe('Project dispatcher', () => {
expect(renderedComponent.current.botStates.echoSkill2.botDisplayName).toBe('Echo-Skill-2');

await act(async () => {
await dispatcher.addRemoteSkillToBotProject('https://test.net/api/manifest/man', 'test-skill', 'remote');
await dispatcher.addRemoteSkillToBotProject('https://test.net/api/manifest/test', 'remote');
});

expect(navigateTo).toHaveBeenLastCalledWith(`/bot/${projectId}/dialogs/emptybot-1`);
Expand Down Expand Up @@ -392,14 +392,12 @@ describe('Project dispatcher', () => {
await act(async () => {
await dispatcher.addRemoteSkillToBotProject(
'https://test-dev.azurewebsites.net/manifests/onenote-2-1-preview-1-manifest.json',
'one-note',
'remote'
);
});

expect(renderedComponent.current.botStates.oneNote).toBeDefined();
expect(renderedComponent.current.botStates.oneNote.botDisplayName).toBe('OneNoteSync');
expect(renderedComponent.current.botStates.oneNote.location).toBe(
expect(renderedComponent.current.botStates.oneNoteSync).toBeDefined();
expect(renderedComponent.current.botStates.oneNoteSync.botDisplayName).toBe('OneNoteSync');
expect(renderedComponent.current.botStates.oneNoteSync.location).toBe(
'https://test-dev.azurewebsites.net/manifests/onenote-2-1-preview-1-manifest.json'
);
expect(navigateTo).toHaveBeenLastCalledWith(`/bot/${projectId}/dialogs/emptybot-1`);
Expand Down Expand Up @@ -429,26 +427,17 @@ describe('Project dispatcher', () => {
await act(async () => {
await dispatcher.addRemoteSkillToBotProject(
'https://test-dev.azurewebsites.net/manifests/onenote-2-1-preview-1-manifest.json',
'one-note',
'remote'
);
});

await act(async () => {
await dispatcher.addRemoteSkillToBotProject(
'https://test-dev.azurewebsites.net/manifests/onenote-second-manifest.json',
'one-note-2',
'remote'
);
});

const oneNoteProjectId = renderedComponent.current.botStates.oneNote.projectId;
const oneNoteProjectId = renderedComponent.current.botStates.oneNoteSync.projectId;
mockImplementation.mockClear();

await act(async () => {
dispatcher.removeSkillFromBotProject(oneNoteProjectId);
});
expect(renderedComponent.current.botStates.oneNote).toBeUndefined();
expect(renderedComponent.current.botStates.oneNoteSync).toBeUndefined();
});

it('should be able to add a new skill to Botproject', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import path from 'path';

import { CallbackInterface, useRecoilCallback } from 'recoil';
import { produce } from 'immer';
import { BotProjectSpaceSkill, convertAbsolutePathToFileProtocol } from '@bfc/shared';
import { BotProjectSpaceSkill } from '@bfc/shared';

import { botNameIdentifierState, botProjectFileState, locationState } from '../atoms';
import { rootBotProjectIdSelector } from '../selectors';

export const botProjectFileDispatcher = () => {
const addLocalSkillToBotProjectFile = useRecoilCallback(
({ set, snapshot }: CallbackInterface) => async (skillId: string) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
if (!rootBotProjectId) {
return;
}
const skillLocation = await snapshot.getPromise(locationState(skillId));
const botName = await snapshot.getPromise(botNameIdentifierState(skillId));
const addLocalSkill = useRecoilCallback(({ set, snapshot }: CallbackInterface) => async (skillId: string) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
if (!rootBotProjectId) {
return;
}
const rootBotLocation = await snapshot.getPromise(locationState(rootBotProjectId));
const skillLocation = await snapshot.getPromise(locationState(skillId));
const botName = await snapshot.getPromise(botNameIdentifierState(skillId));

set(botProjectFileState(rootBotProjectId), (current) => {
const result = produce(current, (draftState) => {
const skill: BotProjectSpaceSkill = {
workspace: convertAbsolutePathToFileProtocol(skillLocation),
remote: false,
};
draftState.content.skills[botName] = skill;
});
return result;
set(botProjectFileState(rootBotProjectId), (current) => {
const result = produce(current, (draftState) => {
const relativePath = path.relative(rootBotLocation, skillLocation);
const skill: BotProjectSpaceSkill = {
workspace: relativePath,
remote: false,
};
draftState.content.skills[botName] = skill;
});
}
);
return result;
});
});

const addRemoteSkillToBotProjectFile = useRecoilCallback(
const addRemoteSkill = useRecoilCallback(
({ set, snapshot }: CallbackInterface) => async (skillId: string, manifestUrl: string, endpointName: string) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
if (!rootBotProjectId) {
return;
}
const botName = await snapshot.getPromise(botNameIdentifierState(skillId));

set(botProjectFileState(rootBotProjectId), (current) => {
const result = produce(current, (draftState) => {
const skill: BotProjectSpaceSkill = {
Expand All @@ -55,26 +56,24 @@ export const botProjectFileDispatcher = () => {
}
);

const removeSkillFromBotProjectFile = useRecoilCallback(
({ set, snapshot }: CallbackInterface) => async (skillId: string) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
if (!rootBotProjectId) {
return;
}
const removeSkill = useRecoilCallback(({ set, snapshot }: CallbackInterface) => async (skillId: string) => {
const rootBotProjectId = await snapshot.getPromise(rootBotProjectIdSelector);
if (!rootBotProjectId) {
return;
}

const botName = await snapshot.getPromise(botNameIdentifierState(skillId));
set(botProjectFileState(rootBotProjectId), (current) => {
const result = produce(current, (draftState) => {
delete draftState.content.skills[botName];
});
return result;
const botName = await snapshot.getPromise(botNameIdentifierState(skillId));
set(botProjectFileState(rootBotProjectId), (current) => {
const result = produce(current, (draftState) => {
delete draftState.content.skills[botName];
});
}
);
return result;
});
});

return {
addLocalSkillToBotProjectFile,
removeSkillFromBotProjectFile,
addRemoteSkillToBotProjectFile,
addLocalSkillToBotProjectFile: addLocalSkill,
removeSkillFromBotProjectFile: removeSkill,
addRemoteSkillToBotProjectFile: addRemoteSkill,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const projectDispatcher = () => {
);

const addRemoteSkillToBotProject = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (manifestUrl: string, name: string, endpointName: string) => {
(callbackHelpers: CallbackInterface) => async (manifestUrl: string, endpointName: string) => {
const { set, snapshot } = callbackHelpers;
try {
const dispatcher = await snapshot.getPromise(dispatcherState);
Expand All @@ -125,9 +125,9 @@ export const projectDispatcher = () => {
formatMessage('This operation cannot be completed. The skill is already part of the Bot Project')
);
}
const skillNameIdentifier: string = await getSkillNameIdentifier(callbackHelpers, name);

set(botOpeningState, true);
const { projectId } = await openRemoteSkill(callbackHelpers, manifestUrl, skillNameIdentifier);
const { projectId } = await openRemoteSkill(callbackHelpers, manifestUrl);
set(botProjectIdsState, (current) => [...current, projectId]);
await dispatcher.addRemoteSkillToBotProjectFile(projectId, manifestUrl, endpointName);
} catch (ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import path from 'path';
Copy link
Contributor

Choose a reason for hiding this comment

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

You sure you can use this module on the browser?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch. @hatpick and me had an offline conversation about this.

#4437 This would be the ticket that should do a cleanup of the path module throughout our codebase in non node context


import { indexer, validateDialog } from '@bfc/indexers';
import {
BotProjectFile,
BotProjectSpace,
BotProjectSpaceSkill,
convertFileProtocolToPath,
convertSkillsToDictionary,
dereferenceDefinitions,
DialogInfo,
Expand Down Expand Up @@ -350,7 +351,7 @@ export const removeRecentProject = async (callbackHelpers: CallbackInterface, pa
export const openRemoteSkill = async (
callbackHelpers: CallbackInterface,
manifestUrl: string,
botNameIdentifier: string
botNameIdentifier?: string
) => {
const { set } = callbackHelpers;

Expand All @@ -366,7 +367,8 @@ export const openRemoteSkill = async (
isRootBot: false,
isRemote: true,
});
set(botNameIdentifierState(projectId), botNameIdentifier);

set(botNameIdentifierState(projectId), botNameIdentifier || camelCase(manifestResponse.data.name));
set(botDisplayNameState(projectId), manifestResponse.data.name);
set(locationState(projectId), manifestUrl);
return { projectId, manifestResponse: manifestResponse.data };
Expand Down Expand Up @@ -458,7 +460,8 @@ const openRootBotAndSkills = async (callbackHelpers: CallbackInterface, data, st

const mainDialog = await initBotState(callbackHelpers, projectData, botFiles);
const rootBotProjectId = projectData.id;
const { name } = projectData;
const { name, location } = projectData;

set(botNameIdentifierState(rootBotProjectId), camelCase(name));

if (botFiles.botProjectSpaceFiles && botFiles.botProjectSpaceFiles.length) {
Expand All @@ -477,8 +480,10 @@ const openRootBotAndSkills = async (callbackHelpers: CallbackInterface, data, st
const skill = skills[nameIdentifier];
let skillPromise;
if (!skill.remote && skill.workspace) {
const skillPath = convertFileProtocolToPath(skill.workspace);
skillPromise = openLocalSkill(callbackHelpers, skillPath, storageId, nameIdentifier);
const rootBotPath = location;
const skillPath = skill.workspace;
const absoluteSkillPath = path.resolve(rootBotPath, skillPath);
skillPromise = openLocalSkill(callbackHelpers, absoluteSkillPath, storageId, nameIdentifier);
} else if (skill.manifest) {
skillPromise = openRemoteSkill(callbackHelpers, skill.manifest, nameIdentifier);
}
Expand Down Expand Up @@ -564,6 +569,7 @@ export const checkIfBotExistsInBotProjectFile = async (
if (!rootBotProjectId) {
throw new Error(formatMessage('The root bot is not a bot project'));
}
const rootBotLocation = await snapshot.getPromise(locationState(rootBotProjectId));
const { content: botProjectFile } = await snapshot.getPromise(botProjectFileState(rootBotProjectId));

for (const uniqueSkillName in botProjectFile.skills) {
Expand All @@ -574,8 +580,8 @@ export const checkIfBotExistsInBotProjectFile = async (
}
} else {
if (workspace) {
const resolvedPath = convertFileProtocolToPath(workspace);
if (pathOrManifest === resolvedPath) {
const absolutePathOfSkill = path.resolve(rootBotLocation, workspace);
if (pathOrManifest === absolutePathOfSkill) {
return true;
}
}
Expand Down
28 changes: 0 additions & 28 deletions Composer/packages/lib/shared/__tests__/fileUtils/index.test.ts

This file was deleted.

22 changes: 0 additions & 22 deletions Composer/packages/lib/shared/src/fileUtils/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion Composer/packages/lib/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@ export * from './schemaUtils';
export * from './viewUtils';
export * from './walkerUtils';
export * from './skillsUtils';
export * from './fileUtils';
export const DialogUtils = dialogUtils;
8 changes: 2 additions & 6 deletions Composer/packages/server/schemas/botproject.schema
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/microsoft/BotFramework-Composer/blob/main/Composer/packages/server/schemas/botproject.schema",
"$version": "1.0.0",
"$version": "0.2.0",
"title": "Root Bot and Skills workspace schema for a environment. Each publishing environment has a Bot Project file associated with it.",
"description": "Schema that captures the relationship between the Root Bot and remote/local skills that the Root Bot consumes.",
"type": "object",
Expand All @@ -16,10 +16,6 @@
"type": "string",
"description": "Name of the Root Bot."
},
"workspace": {
"type": "string",
"description": "Absolute path to the Root Bot. If a workspace is local, we use the file protocol as opposed to http/https for remote workspaces."
},
"skills": {
"description": "List of skills (remote or local) skills that the Root Bot consumes.",
"type": "object",
Expand Down Expand Up @@ -54,7 +50,7 @@
},
"workspace": {
"type": "string",
"description": "Absolute path to a skill workspace. If a workspace is local, we use the file protocol as opposed to http/https protocols for remote workspaces."
"description": "Path to the skill always relative to the Root Bot."
},
"remote": {
"description": "Indication if the skill is remote or local skill.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jest.mock('../../../services/asset', () => {
botProjectFileTemplate: {
$schema: '',
name: '',
workspace: '',
skills: {},
},
},
Expand Down
Loading