Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: manage samples via plugin #2805

Merged
merged 15 commits into from
May 6, 2020
5 changes: 4 additions & 1 deletion Composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
"build:server": "yarn workspace @bfc/server build",
"build:client": "yarn workspace @bfc/client build",
"build:tools": "yarn workspace @bfc/tools build:all",
"build:plugins": "concurrently \"cd plugins/localPublish && yarn install && yarn build\" \"cd plugins/azurePublish && yarn install && yarn build\" ",
"build:plugins": "yarn build:plugins:localpublish && yarn build:plugins:samples && yarn build:plugins:azurePublish",
"build:plugins:localpublish": "cd plugins/localPublish && yarn install && yarn build",
"build:plugins:samples": "cd plugins/samples && yarn install && yarn build",
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can't these just be a part of the yarn workspaces?

Copy link
Contributor Author

@boydc2014 boydc2014 May 6, 2020

Choose a reason for hiding this comment

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

yes, i think those can and should be managed by yarn workspaces, it's just those plugins were not included when we started to use this plugin approach, and i haven't thought about that in this effort.

i created a new ticket to track this work #2901 to keep this one focused, make sense? because this one moved a lot of files, which is very easy to get a conflict.

"build:plugins:azurePublish": "cd plugins/azurePublish && yarn install && yarn build",
"start": "cross-env NODE_ENV=production PORT=3000 yarn start:server",
"startall": "node scripts/update.js && yarn start",
"start:dev": "concurrently \"npm:start:client\" \"npm:start:server:dev\"",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { JSONSchema7 } from 'json-schema';

import { PluginLoader } from './pluginLoader';
import log from './logger';
import { PublishPlugin, RuntimeTemplate } from './types';
import { PublishPlugin, RuntimeTemplate, BotTemplate } from './types';

export class ComposerPluginRegistration {
public loader: PluginLoader;
Expand Down Expand Up @@ -89,6 +89,20 @@ export class ComposerPluginRegistration {
this.loader.extensions.runtimeTemplates.push(plugin);
}

/**************************************************************************************
* Add Bot Template (aka, SampleBot)
*************************************************************************************/
public addBotTemplate(template: BotTemplate) {
this.loader.extensions.botTemplates.push(template);
}

/**************************************************************************************
* Add Base Template (aka, BoilerPlate)
*************************************************************************************/
public addBaseTemplate(template: BotTemplate) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really understand this one. What is meant by boilerplate?

Copy link
Contributor Author

@boydc2014 boydc2014 May 6, 2020

Choose a reason for hiding this comment

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

Thanks, Andy, boilerPlate is introduced by #2837 which works as a baseTemplate for all sample bot. For example, contains as a README.md that could be copied into any created bot.

This PR don't add any features yet, just refactor the structure.

this.loader.extensions.baseTemplates.push(template);
}

/**************************************************************************************
* Express/web related features
*************************************************************************************/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export class PluginLoader {
allowedUrls: [this.loginUri],
},
runtimeTemplates: [],
botTemplates: [],
baseTemplates: [],
};
this._passport = passport;
}
Expand Down
14 changes: 14 additions & 0 deletions Composer/packages/extensions/plugin-loader/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ export interface PublishResponse {
result: PublishResult;
}

export interface BotTemplate {
id: string;
name: string;
description: string;
/* absolute path */
path: string;
/* tags for further grouping and search secenario */
tags?: string[];
/* list of supported runtime versions */
support?: string[];
}

// TODO: Add types for project, metadata
export interface PublishPlugin<Config = any> {
publish: (config: Config, project: any, metadata: any, user?: UserIdentity) => Promise<PublishResponse>;
Expand Down Expand Up @@ -73,4 +85,6 @@ export interface ExtensionCollection {
[key: string]: any;
};
runtimeTemplates: RuntimeTemplate[];
botTemplates: BotTemplate[];
baseTemplates: BotTemplate[];
}
10 changes: 6 additions & 4 deletions Composer/packages/lib/shared/src/types/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ export interface ProjectTemplate {
id: string;
name: string;
description: string;
/** Absolute path of the template */
path?: string;
/** Optional order property */
order?: number;
/* absolute path */
path: string;
/* tags for further grouping and search secenario */
tags?: string[];
/* list of supported runtime versions */
support?: string[];
}
25 changes: 24 additions & 1 deletion Composer/packages/server/__tests__/controllers/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,28 @@

import { Request, Response } from 'express';
import rimraf from 'rimraf';
import { pluginLoader } from '@bfc/plugin-loader';

import { BotProjectService } from '../../src/services/project';
import { ProjectController } from '../../src/controllers/project';
import { Path } from '../../src/utility/path';

jest.mock('@bfc/plugin-loader', () => {
return {
pluginLoader: {
extensions: {
botTemplates: [],
baseTemplates: [],
},
},
PluginLoader: {
getUserFromRequest: jest.fn(),
},
};
});

const mockSampleBotPath = Path.join(__dirname, '../mocks/asset/projects/SampleBot');

let mockRes: Response;

const newBot = Path.resolve(__dirname, '../mocks/samplebots/newBot');
Expand All @@ -34,6 +51,12 @@ beforeEach(() => {
});

beforeAll(async () => {
pluginLoader.extensions.botTemplates.push({
id: 'SampleBot',
name: 'Sample Bot',
description: 'Sample Bot',
path: mockSampleBotPath,
});
const currentProjectId = await BotProjectService.openProject(location1);
const currentProject = await BotProjectService.getProjectById(currentProjectId);
await BotProjectService.saveProjectAs(currentProject, location2);
Expand Down Expand Up @@ -134,7 +157,7 @@ describe('create a Empty Bot project', () => {
const mockReq = {
params: {},
query: {},
body: { storageId: 'default', location: newBotDir, description: '', name: name, templateId: '' },
body: { storageId: 'default', location: newBotDir, description: '', name: name, templateId: 'SampleBot' },
} as Request;
await ProjectController.createProject(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,53 @@
// Licensed under the MIT License.

import rimraf from 'rimraf';
import { pluginLoader } from '@bfc/plugin-loader';

import { Path } from '../../../src/utility/path';
import { AssetManager } from '../../../src/models/asset/assetManager';
jest.mock('azure-storage', () => {
return {};
});
const mockAssetLibraryPath = Path.join(__dirname, '../../mocks/asset');
const mockRuntimeLibraryPath = Path.join(__dirname, '../../mocks/runtimes');

jest.mock('@bfc/plugin-loader', () => {
//const p = require('path');
return {
pluginLoader: {
extensions: {
botTemplates: [],
},
},
};
});

const mockSampleBotPath = Path.join(__dirname, '../../mocks/asset/projects/SampleBot');
const mockCopyToPath = Path.join(__dirname, '../../mocks/new');
const locationRef = {
storageId: 'default',
path: mockCopyToPath,
};

beforeAll(() => {
pluginLoader.extensions.botTemplates.push({
id: 'SampleBot',
name: 'Sample Bot',
description: 'Sample Bot',
path: mockSampleBotPath,
});
});

describe('assetManager', () => {
it('getProjectTemplate', async () => {
const assetManager = new AssetManager(mockAssetLibraryPath, mockRuntimeLibraryPath);
const assetManager = new AssetManager();
const result = await assetManager.getProjectTemplates();
expect(result.length).toBeGreaterThan(0);
expect(result[0].name).toBe('Sample Bot');
expect(result[0].id).toBe('SampleBot');
});

it('copyProjectTemplateTo', async () => {
const assetManager = new AssetManager(mockAssetLibraryPath, mockRuntimeLibraryPath);
const assetManager = new AssetManager();
await assetManager.getProjectTemplates();
await assetManager.getProjectRuntime();

await expect(assetManager.copyProjectTemplateTo('SampleBot', locationRef)).resolves.toBe(locationRef);
// remove the saveas files
Expand Down
Loading