Initial Commit. Setup powerbi-router project with build and tests
This commit is contained in:
Родитель
209117875a
Коммит
98085b0e3a
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
typings
|
||||
tmp
|
||||
dist
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
typings
|
||||
test
|
||||
tmp
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "0.1.0",
|
||||
"command": "tsc",
|
||||
"isShellCommand": true,
|
||||
"args": ["-p", "."],
|
||||
"showOutput": "silent",
|
||||
"problemMatcher": "$tsc"
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
# Contributing
|
||||
|
||||
## Setup
|
||||
|
||||
Clone the repository:
|
||||
```
|
||||
git clone https://pbix.visualstudio.com/DefaultCollection/PaaS/_git/powerbi-router
|
||||
```
|
||||
|
||||
Install global dependencies:
|
||||
```
|
||||
npm install -g typescript gulp typings
|
||||
```
|
||||
|
||||
Install local dependencies:
|
||||
```
|
||||
npm install
|
||||
typings install
|
||||
```
|
||||
|
||||
## Building
|
||||
```
|
||||
tsc -p .
|
||||
```
|
||||
Or if using VSCode: `Ctrl + Shift + B`
|
||||
|
||||
## Testing
|
||||
```
|
||||
npm test
|
||||
```
|
||||
or use gulp directly:
|
||||
```
|
||||
gulp test
|
||||
```
|
||||
|
||||
Run tests with Chrome and close when finished
|
||||
```
|
||||
gulp test --debug
|
||||
```
|
||||
|
||||
Run tests with Chrome and remain open for debugging
|
||||
```
|
||||
gulp test --debug --watch
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Microsoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
36
README.md
36
README.md
|
@ -1,2 +1,36 @@
|
|||
# powerbi-router
|
||||
A lightweight router for the Power BI JavaScript SDK which allows a hosting application to register routes to extend and integrate with Power BI embedded components.
|
||||
|
||||
Router for Microsoft Power BI. Given a pattern, call handler. Patterns will only be http-like objects so syntax matches known libraries such as express and restify.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Installation:
|
||||
```
|
||||
npm install --save powerbi-router
|
||||
```
|
||||
|
||||
Usage:
|
||||
```
|
||||
import * as wpmp from 'window-post-message-proxy';
|
||||
import * as pbiRouter from 'powerbi-router';
|
||||
|
||||
const wpmp1 = new wpmp.WindowPostMessageProxy();
|
||||
const router = new pbiRouter.Router(wpmp1);
|
||||
|
||||
router.get('/report/pages', (request, response) => {
|
||||
return app.getPages()
|
||||
.then(pages => {
|
||||
response.send(200, pages);
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/report/pages/active', (request, response) => {
|
||||
app.setPage(request.body)
|
||||
.then(page => {
|
||||
host.sendEvent('pageChanged', page);
|
||||
});
|
||||
|
||||
response.send(202);
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
var gulp = require('gulp-help')(require('gulp'));
|
||||
var karma = require('karma'),
|
||||
webpack = require('webpack-stream'),
|
||||
webpackConfig = require('./webpack.config'),
|
||||
runSequence = require('run-sequence'),
|
||||
argv = require('yargs').argv
|
||||
;
|
||||
|
||||
gulp.task('test', 'Run unit tests', function (done) {
|
||||
return runSequence(
|
||||
'compile:spec',
|
||||
'test:spec',
|
||||
done
|
||||
)
|
||||
});
|
||||
|
||||
gulp.task('compile:spec', 'Compile typescript for tests', function () {
|
||||
return gulp.src(['typings/**/*.d.ts', './src/**/*.ts', './test/**/*.spec.ts'])
|
||||
.pipe(webpack(webpackConfig))
|
||||
.pipe(gulp.dest('./tmp'));
|
||||
});
|
||||
|
||||
gulp.task('test:spec', 'Runs spec tests', function(done) {
|
||||
new karma.Server.start({
|
||||
configFile: __dirname + '/karma.conf.js',
|
||||
singleRun: argv.watch ? false : true,
|
||||
captureTimeout: argv.timeout || 20000
|
||||
}, done);
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
var argv = require('yargs').argv;
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
frameworks: ['jasmine'],
|
||||
files: [
|
||||
'./node_modules/es6-promise/dist/es6-promise.js',
|
||||
'./tmp/**/*.js'
|
||||
],
|
||||
exclude: [],
|
||||
reporters: argv.debug ? ['spec'] : ['spec', 'coverage'],
|
||||
autoWatch: true,
|
||||
browsers: [argv.debug ? 'Chrome' : 'PhantomJS'],
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-jasmine',
|
||||
'karma-spec-reporter',
|
||||
'karma-sourcemap-loader',
|
||||
'karma-phantomjs-launcher',
|
||||
'karma-coverage'
|
||||
],
|
||||
preprocessors: {
|
||||
'./tmp/**/*.js': ['sourcemap']
|
||||
},
|
||||
coverageReporter: {
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
logLevel: argv.debug ? config.LOG_DEBUG : config.LOG_INFO
|
||||
});
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "powerbi-router",
|
||||
"version": "0.1.0",
|
||||
"description": "Router for Microsoft Power BI. Given a pattern, call handler. Patterns will only be http-like objects so syntax matches known libraries such as express and restify.",
|
||||
"main": "dist/router.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://pbix.visualstudio.com/DefaultCollection/PaaS/_git/powerbi-router"
|
||||
},
|
||||
"keywords": [
|
||||
"microsoft",
|
||||
"powerbi",
|
||||
"router"
|
||||
],
|
||||
"author": "Microsoft Power BI Team",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-help": "^1.6.1",
|
||||
"jasmine-core": "^2.4.1",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma-coverage": "^1.0.0",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
"phantomjs-prebuilt": "^2.1.7",
|
||||
"run-sequence": "^1.2.0",
|
||||
"ts-loader": "^0.8.2",
|
||||
"webpack-stream": "^3.2.0",
|
||||
"yargs": "^4.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"es6-promise": "^3.2.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
export interface IAddHandler {
|
||||
addHandler(handler: any): void;
|
||||
}
|
||||
|
||||
export interface IRouterHandler {
|
||||
(request: IRequest, response: Response): void | Promise<void>;
|
||||
}
|
||||
|
||||
export class Router {
|
||||
handlers: IAddHandler;
|
||||
|
||||
constructor(handlers: IAddHandler) {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
get(url: string, handler: IRouterHandler) {
|
||||
this.registerHandler("GET", url, handler);
|
||||
}
|
||||
|
||||
post(url: string, handler: IRouterHandler) {
|
||||
this.registerHandler("POST", url, handler);
|
||||
}
|
||||
|
||||
put(url: string, handler: IRouterHandler) {
|
||||
this.registerHandler("PUT", url, handler);
|
||||
}
|
||||
|
||||
delete(url: string, handler: IRouterHandler) {
|
||||
this.registerHandler("DELETE", url, handler);
|
||||
}
|
||||
|
||||
private registerHandler(method: string, url: string, handler: IRouterHandler) {
|
||||
const internalHandler = {
|
||||
test(request: IRequest): boolean {
|
||||
return (request.method === method
|
||||
&& request.url === url);
|
||||
},
|
||||
handle(request: IRequest): Promise<IResponse> {
|
||||
const response = new Response();
|
||||
return Promise.resolve(handler(request, response))
|
||||
.then(x => response);
|
||||
}
|
||||
};
|
||||
|
||||
this.handlers.addHandler(internalHandler);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRequest {
|
||||
method: "GET" | "POST" | "PUT" | "DELETE";
|
||||
url: string,
|
||||
headers: { [key: string]: string };
|
||||
body: any;
|
||||
}
|
||||
|
||||
export interface IResponse {
|
||||
statusCode: number;
|
||||
headers?: { [key: string]: string };
|
||||
body: any;
|
||||
}
|
||||
|
||||
export class Response implements IResponse {
|
||||
statusCode: number;
|
||||
headers: any;
|
||||
body: any;
|
||||
|
||||
constructor() {
|
||||
this.statusCode = 200;
|
||||
this.headers = {};
|
||||
this.body = null;
|
||||
}
|
||||
|
||||
send(statusCode: number, body?: any) {
|
||||
this.statusCode = statusCode;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
import * as pbiRouter from '../src/router';
|
||||
|
||||
describe('router', function () {
|
||||
let router: pbiRouter.Router;
|
||||
let wpmpStub = {
|
||||
addHandler: jasmine.createSpy("spyHandler")
|
||||
};
|
||||
|
||||
beforeAll(function () {
|
||||
router = new pbiRouter.Router(wpmpStub);
|
||||
});
|
||||
|
||||
describe('common', function () {
|
||||
// Arrange
|
||||
const testUrl = '/report/pages';
|
||||
const testHandler: pbiRouter.IRouterHandler = (request: pbiRouter.IRequest, response: pbiRouter.Response) => {
|
||||
response.send(400);
|
||||
};
|
||||
let internalHandler: any;
|
||||
|
||||
beforeEach(function () {
|
||||
router.get(testUrl, testHandler);
|
||||
internalHandler = wpmpStub.addHandler.calls.mostRecent().args[0];
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
wpmpStub.addHandler.calls.reset();
|
||||
});
|
||||
|
||||
it('the handle method will always return a promise', function () {
|
||||
expect(internalHandler.handle({ method: "GET", url: testUrl }).then).toBeDefined();
|
||||
});
|
||||
|
||||
it('the handle method will call the given handler with request and response object so the response may be modified', function (done) {
|
||||
internalHandler.handle({ method: "GET", url: testUrl })
|
||||
.then((response: pbiRouter.Response) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe('get', function () {
|
||||
// Arrange
|
||||
const testUrl = '/report/pages';
|
||||
const testHandler: pbiRouter.IRouterHandler = (request: pbiRouter.IRequest, response: pbiRouter.Response) => {
|
||||
response.send(400);
|
||||
};
|
||||
let internalHandler: any;
|
||||
|
||||
beforeEach(function () {
|
||||
router.get(testUrl, testHandler);
|
||||
internalHandler = wpmpStub.addHandler.calls.mostRecent().args[0];
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
wpmpStub.addHandler.calls.reset();
|
||||
});
|
||||
|
||||
it('calling get registers handler on the handlers object', function () {
|
||||
expect(wpmpStub.addHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('the test method will return true if the method is GET and the url match exactly', function () {
|
||||
expect(internalHandler.test({ method: "POST", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "GET", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "GET", url: testUrl })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('post', function () {
|
||||
// Arrange
|
||||
const testUrl = '/report/filters';
|
||||
const testHandler: pbiRouter.IRouterHandler = (request: pbiRouter.IRequest, response: pbiRouter.Response) => {
|
||||
response.send(202);
|
||||
};
|
||||
let internalHandler: any;
|
||||
|
||||
beforeEach(function () {
|
||||
router.post(testUrl, testHandler);
|
||||
internalHandler = wpmpStub.addHandler.calls.mostRecent().args[0];
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
wpmpStub.addHandler.calls.reset();
|
||||
});
|
||||
|
||||
it('calling post registers handler on the handlers object', function () {
|
||||
expect(wpmpStub.addHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('the test method will return true if the method is POST and the url match exactly', function () {
|
||||
expect(internalHandler.test({ method: "GET", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "POST", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "POST", url: testUrl })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('put', function () {
|
||||
// Arrange
|
||||
const testUrl = '/report/pages/active';
|
||||
const testHandler: pbiRouter.IRouterHandler = (request: pbiRouter.IRequest, response: pbiRouter.Response) => {
|
||||
response.send(202);
|
||||
};
|
||||
let internalHandler: any;
|
||||
|
||||
beforeEach(function () {
|
||||
router.put(testUrl, testHandler);
|
||||
internalHandler = wpmpStub.addHandler.calls.mostRecent().args[0];
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
wpmpStub.addHandler.calls.reset();
|
||||
});
|
||||
|
||||
it('calling put registers handler on the handlers object', function () {
|
||||
expect(wpmpStub.addHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('the test method will return true if the method is PUT and the url match exactly', function () {
|
||||
expect(internalHandler.test({ method: "POST", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "PUT", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "PUT", url: testUrl })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', function () {
|
||||
// Arrange
|
||||
const testUrl = '/report/filters/myFilter';
|
||||
const testHandler: pbiRouter.IRouterHandler = (request: pbiRouter.IRequest, response: pbiRouter.Response) => {
|
||||
response.send(202);
|
||||
};
|
||||
let internalHandler: any;
|
||||
|
||||
beforeEach(function () {
|
||||
router.delete(testUrl, testHandler);
|
||||
internalHandler = wpmpStub.addHandler.calls.mostRecent().args[0];
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
wpmpStub.addHandler.calls.reset();
|
||||
});
|
||||
|
||||
it('calling delete registers handler on the handlers object', function () {
|
||||
expect(wpmpStub.addHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('the test method will return true if the method is PUT and the url match exactly', function () {
|
||||
expect(internalHandler.test({ method: "POST", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "DELETE", url: "xyz" })).toBe(false);
|
||||
expect(internalHandler.test({ method: "DELETE", url: testUrl })).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"typings/globals/es6-promise",
|
||||
"typings/index.d.ts",
|
||||
"dist",
|
||||
"test",
|
||||
"tmp"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"version": false,
|
||||
"dependencies": {},
|
||||
"globalDependencies": {
|
||||
"es6-promise": "registry:dt/es6-promise#0.0.0+20160423074304",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
|
||||
"karma-jasmine": "registry:dt/karma-jasmine#0.0.0+20160316155526"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
entry: './test/router.spec.ts',
|
||||
output: {
|
||||
path: __dirname + "/tmp",
|
||||
filename: 'router.spec.js'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.ts$/, loader: 'ts-loader' }
|
||||
]
|
||||
},
|
||||
ts: {
|
||||
configFileName: "webpack.tsconfig.json"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"declaration": false,
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"typings/main",
|
||||
"typings/main.d.ts",
|
||||
"test",
|
||||
"tmp",
|
||||
"dist"
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче