Initial Commit. Setup powerbi-router project with build and tests

This commit is contained in:
Matt Mazzola 2016-05-23 15:28:57 -07:00
Родитель 209117875a
Коммит 98085b0e3a
15 изменённых файлов: 512 добавлений и 1 удалений

4
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
node_modules
typings
tmp
dist

4
.npmignore Normal file
Просмотреть файл

@ -0,0 +1,4 @@
node_modules
typings
test
tmp

10
.vscode/tasks.json поставляемый Normal file
Просмотреть файл

@ -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"
}

44
CONTRIBUTING.md Normal file
Просмотреть файл

@ -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
```

21
LICENSE Normal file
Просмотреть файл

@ -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.

Просмотреть файл

@ -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);
});
```

29
gulpfile.js Normal file
Просмотреть файл

@ -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);
});

33
karma.conf.js Normal file
Просмотреть файл

@ -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
});
};

40
package.json Normal file
Просмотреть файл

@ -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"
}
}

77
src/router.ts Normal file
Просмотреть файл

@ -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;
}
}

154
test/router.spec.ts Normal file
Просмотреть файл

@ -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);
});
});
});

17
tsconfig.json Normal file
Просмотреть файл

@ -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"
]
}

9
typings.json Normal file
Просмотреть файл

@ -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"
}
}

19
webpack.config.js Normal file
Просмотреть файл

@ -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"
}
}

16
webpack.tsconfig.json Normal file
Просмотреть файл

@ -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"
]
}