forked from mirrors/probot
Merge pull request #67 from bkeepers/load
Load configuration from another file
This commit is contained in:
commit
249809f9d2
|
@ -77,7 +77,7 @@ Only preform the actions if the function returns `true`. The `event` is passed a
|
|||
.filter(event => event.payload.issue.body.includes("- [ ]"))
|
||||
```
|
||||
|
||||
#### `comment`
|
||||
### `comment`
|
||||
|
||||
Comments can be posted in response to any event performed on an Issue or Pull Request. Comments use [mustache](https://mustache.github.io/) for templates and can use any data from the event payload.
|
||||
|
||||
|
@ -85,7 +85,7 @@ Comments can be posted in response to any event performed on an Issue or Pull Re
|
|||
.comment("Hey @{{ user.login }}, thanks for the contribution!");
|
||||
```
|
||||
|
||||
#### `close`
|
||||
### `close`
|
||||
|
||||
Close an issue or pull request.
|
||||
|
||||
|
@ -93,7 +93,7 @@ Close an issue or pull request.
|
|||
.close();
|
||||
```
|
||||
|
||||
#### `open`
|
||||
### `open`
|
||||
|
||||
Reopen an issue or pull request.
|
||||
|
||||
|
@ -101,7 +101,7 @@ Reopen an issue or pull request.
|
|||
.open();
|
||||
```
|
||||
|
||||
#### `lock`
|
||||
### `lock`
|
||||
|
||||
Lock conversation on an issue or pull request.
|
||||
|
||||
|
@ -109,7 +109,7 @@ Lock conversation on an issue or pull request.
|
|||
.lock();
|
||||
```
|
||||
|
||||
#### `unlock`
|
||||
### `unlock`
|
||||
|
||||
Unlock conversation on an issue or pull request.
|
||||
|
||||
|
@ -117,7 +117,7 @@ Unlock conversation on an issue or pull request.
|
|||
.unlock();
|
||||
```
|
||||
|
||||
#### `label`
|
||||
### `label`
|
||||
|
||||
Add labels
|
||||
|
||||
|
@ -125,7 +125,7 @@ Add labels
|
|||
.label('bug');
|
||||
```
|
||||
|
||||
#### `unlabel`
|
||||
### `unlabel`
|
||||
|
||||
Add labels
|
||||
|
||||
|
@ -133,18 +133,33 @@ Add labels
|
|||
.unlabel('needs-work').label('waiting-for-review');
|
||||
```
|
||||
|
||||
#### `assign`
|
||||
### `assign`
|
||||
|
||||
```
|
||||
.assign('hubot');
|
||||
```
|
||||
|
||||
#### `unassign`
|
||||
### `unassign`
|
||||
|
||||
```
|
||||
.unassign('defunkt');
|
||||
```
|
||||
|
||||
## include
|
||||
|
||||
Loads a configuration from another file.
|
||||
|
||||
```js
|
||||
include('.github/bot/issues.js');
|
||||
include('.github/bot/releases.js');
|
||||
```
|
||||
|
||||
You can also include configuration from another repository.
|
||||
|
||||
```js
|
||||
include('user/repo:path.js#branch');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [examples](examples.md) for ideas of behaviors you can implement by combining these configuration options.
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
const debug = require('debug')('PRobot');
|
||||
const Sandbox = require('./sandbox');
|
||||
const Workflow = require('./workflow');
|
||||
const url = require('./util/github-url');
|
||||
|
||||
module.exports = class Configuration {
|
||||
// Get bot config from target repository
|
||||
static load(github, repository) {
|
||||
debug('Fetching .probot.js from %s', repository.full_name);
|
||||
const parts = repository.full_name.split('/');
|
||||
return github.repos.getContent({
|
||||
owner: parts[0],
|
||||
repo: parts[1],
|
||||
path: '.probot.js'
|
||||
}).then(data => {
|
||||
const content = new Buffer(data.content, 'base64').toString();
|
||||
debug('Configuration fetched', content);
|
||||
return Configuration.parse(content);
|
||||
static load(context, path) {
|
||||
const options = url(path);
|
||||
debug('Fetching %s from %s', path, context.payload.repository.full_name);
|
||||
return context.github.repos.getContent(context.toRepo(options)).then(data => {
|
||||
return new Configuration(context).parse(new Buffer(data.content, 'base64').toString());
|
||||
});
|
||||
}
|
||||
|
||||
static parse(content) {
|
||||
return new Configuration().parse(content);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor(context) {
|
||||
this.context = context;
|
||||
this.workflows = [];
|
||||
|
||||
this.api = {
|
||||
on: this.on.bind(this)
|
||||
on: this.on.bind(this),
|
||||
include: this.include.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,12 +28,24 @@ module.exports = class Configuration {
|
|||
return workflow.api;
|
||||
}
|
||||
|
||||
include(path) {
|
||||
const load = Configuration.load(this.context, path);
|
||||
|
||||
this.workflows.push({
|
||||
execute() {
|
||||
return load.then(config => config.execute(this.context));
|
||||
}
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
parse(content) {
|
||||
new Sandbox(content).execute(this.api);
|
||||
return this;
|
||||
}
|
||||
|
||||
execute(context) {
|
||||
return Promise.all(this.workflows.map(w => w.execute(context)));
|
||||
execute() {
|
||||
return Promise.all(this.workflows.map(w => w.execute(this.context)));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,15 +8,15 @@ module.exports = class Context {
|
|||
}
|
||||
|
||||
toRepo(object) {
|
||||
return Object.assign({}, object, {
|
||||
return Object.assign({
|
||||
owner: this.payload.repository.owner.login,
|
||||
repo: this.payload.repository.name
|
||||
});
|
||||
}, object);
|
||||
}
|
||||
|
||||
toIssue(object) {
|
||||
return Object.assign({}, object, {
|
||||
return Object.assign({
|
||||
number: (this.payload.issue || this.payload.pull_request || this.payload).number
|
||||
}, this.toRepo());
|
||||
}, this.toRepo(), object);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const debug = require('debug')('PRobot');
|
||||
const installations = require('./installations');
|
||||
const Context = require('./configuration');
|
||||
const Context = require('./context');
|
||||
const Configuration = require('./configuration');
|
||||
|
||||
class Robot {
|
||||
|
@ -14,9 +14,8 @@ class Robot {
|
|||
if (event.payload.repository) {
|
||||
installations.auth(event.payload.installation.id).then(github => {
|
||||
const context = new Context(github, event);
|
||||
|
||||
return Configuration.load(github, event.payload.repository).then(config => {
|
||||
return config.execute(context);
|
||||
Configuration.load(context, '.probot.js').then(config => {
|
||||
return config.execute();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const REGEX = /^(?:([\w-]+)\/([\w-]+):)?([^#]*)(?:#(.*))?$/;
|
||||
|
||||
// Parses paths in the form of `owner/repo:path/to/file#ref`
|
||||
module.exports = function (url) {
|
||||
const [, owner, repo, path, ref] = url.match(REGEX);
|
||||
return Object.assign({path}, owner && {owner, repo}, ref && {ref});
|
||||
};
|
|
@ -1,10 +1,12 @@
|
|||
const expect = require('expect');
|
||||
const Configuration = require('../lib/configuration');
|
||||
const config = require('./fixtures/content/probot.json');
|
||||
const Context = require('../lib/context');
|
||||
const content = require('./fixtures/content/probot.json');
|
||||
const payload = require('./fixtures/webhook/comment.created');
|
||||
|
||||
const createSpy = expect.createSpy;
|
||||
|
||||
config.content = new Buffer(`
|
||||
content.content = new Buffer(`
|
||||
on("issues.opened")
|
||||
.comment("Hello World!")
|
||||
.assign("bkeepers");
|
||||
|
@ -14,30 +16,41 @@ config.content = new Buffer(`
|
|||
`).toString('base64');
|
||||
|
||||
describe('Configuration', () => {
|
||||
describe('load', () => {
|
||||
describe('include', () => {
|
||||
let github;
|
||||
|
||||
const repo = JSON.parse('{"full_name": "bkeepers/test"}');
|
||||
let context;
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
github = {
|
||||
repos: {
|
||||
getContent: createSpy().andReturn(Promise.resolve(config))
|
||||
getContent: createSpy().andReturn(Promise.resolve(content))
|
||||
}
|
||||
};
|
||||
context = new Context(github, {payload});
|
||||
config = new Configuration(context);
|
||||
});
|
||||
|
||||
it('loads from the repo', done => {
|
||||
Configuration.load(github, repo).then(config => {
|
||||
expect(github.repos.getContent).toHaveBeenCalledWith({
|
||||
owner: 'bkeepers',
|
||||
repo: 'test',
|
||||
path: '.probot.js'
|
||||
});
|
||||
it('includes from the repo', () => {
|
||||
config.include('foo.js');
|
||||
expect(github.repos.getContent).toHaveBeenCalledWith({
|
||||
owner: 'bkeepers-inc',
|
||||
repo: 'test',
|
||||
path: 'foo.js'
|
||||
});
|
||||
});
|
||||
|
||||
expect(config.workflows.length).toEqual(2);
|
||||
it('returns undefined', () => {
|
||||
expect(config.include('foo.js')).toBe(undefined);
|
||||
});
|
||||
|
||||
done();
|
||||
it('includes from another repository', () => {
|
||||
config.include('atom/configs:foo.js#branch');
|
||||
expect(github.repos.getContent).toHaveBeenCalledWith({
|
||||
owner: 'atom',
|
||||
repo: 'configs',
|
||||
path: 'foo.js',
|
||||
ref: 'branch'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,9 +25,9 @@ describe('Context', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not override repo attributes', () => {
|
||||
it('overrides repo attributes', () => {
|
||||
expect(context.toRepo({owner: 'muahaha'})).toEqual({
|
||||
owner: 'bkeepers', repo:'probot'
|
||||
owner: 'muahaha', repo:'probot'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -43,9 +43,9 @@ describe('Context', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not override repo attributes', () => {
|
||||
it('overrides repo attributes', () => {
|
||||
expect(context.toIssue({owner: 'muahaha', number: 5})).toEqual({
|
||||
owner: 'bkeepers', repo:'probot', number: 4
|
||||
owner: 'muahaha', repo:'probot', number: 5
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,12 +5,12 @@ const payload = require('./fixtures/webhook/comment.created.json');
|
|||
|
||||
const createSpy = expect.createSpy;
|
||||
|
||||
describe('dispatch', () => {
|
||||
describe('integration', () => {
|
||||
const event = {event: 'issues', payload, issue: {}};
|
||||
let context;
|
||||
let github;
|
||||
|
||||
beforeEach(() => {
|
||||
const event = {event: 'issues', payload, issue: {}};
|
||||
github = {
|
||||
issues: {
|
||||
createComment: createSpy().andReturn(Promise.resolve()),
|
||||
|
@ -21,9 +21,13 @@ describe('dispatch', () => {
|
|||
context = new Context(github, event);
|
||||
});
|
||||
|
||||
function configure(content) {
|
||||
return new Configuration(context).parse(content);
|
||||
}
|
||||
|
||||
describe('reply to new issue with a comment', () => {
|
||||
it('posts a coment', () => {
|
||||
const config = Configuration.parse('on("issues").comment("Hello World!")');
|
||||
const config = configure('on("issues").comment("Hello World!")');
|
||||
return config.execute(context).then(() => {
|
||||
expect(github.issues.createComment).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -32,7 +36,7 @@ describe('dispatch', () => {
|
|||
|
||||
describe('reply to new issue with a comment', () => {
|
||||
it('calls the action', () => {
|
||||
const config = Configuration.parse('on("issues.created").comment("Hello World!")');
|
||||
const config = configure('on("issues.created").comment("Hello World!")');
|
||||
|
||||
return config.execute(context).then(() => {
|
||||
expect(github.issues.createComment).toHaveBeenCalled();
|
||||
|
@ -42,7 +46,7 @@ describe('dispatch', () => {
|
|||
|
||||
describe('on an event with a different action', () => {
|
||||
it('does not perform behavior', () => {
|
||||
const config = Configuration.parse('on("issues.labeled").comment("Hello World!")');
|
||||
const config = configure('on("issues.labeled").comment("Hello World!")');
|
||||
|
||||
return config.execute(context).then(() => {
|
||||
expect(github.issues.createComment).toNotHaveBeenCalled();
|
||||
|
@ -59,17 +63,51 @@ describe('dispatch', () => {
|
|||
});
|
||||
|
||||
it('calls action when condition matches', () => {
|
||||
const config = Configuration.parse('on("issues.labeled").filter((e) => e.payload.label.name == "bug").close()');
|
||||
const config = configure('on("issues.labeled").filter((e) => e.payload.label.name == "bug").close()');
|
||||
return config.execute(context).then(() => {
|
||||
expect(github.issues.edit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call action when conditions do not match', () => {
|
||||
const config = Configuration.parse('on("issues.labeled").filter((e) => e.payload.label.name == "foobar").close()');
|
||||
const config = configure('on("issues.labeled").filter((e) => e.payload.label.name == "foobar").close()');
|
||||
return config.execute(context).then(() => {
|
||||
expect(github.issues.edit).toNotHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('include', () => {
|
||||
beforeEach(() => {
|
||||
const content = require('./fixtures/content/probot.json');
|
||||
|
||||
content.content = new Buffer('on("issues").comment("Hello!");').toString('base64');
|
||||
|
||||
github = {
|
||||
repos: {
|
||||
getContent: createSpy().andReturn(Promise.resolve(content))
|
||||
},
|
||||
issues: {
|
||||
createComment: createSpy()
|
||||
}
|
||||
};
|
||||
context = new Context(github, event);
|
||||
});
|
||||
|
||||
it('includes a file in the local repository', () => {
|
||||
configure('include(".github/triage.js");');
|
||||
expect(github.repos.getContent).toHaveBeenCalledWith({
|
||||
owner: 'bkeepers-inc',
|
||||
repo: 'test',
|
||||
path: '.github/triage.js'
|
||||
});
|
||||
});
|
||||
|
||||
it('executes included rules', done => {
|
||||
configure('include(".github/triage.js");').execute().then(() => {
|
||||
expect(github.issues.createComment).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
const expect = require('expect');
|
||||
const url = require('../../lib/util/github-url');
|
||||
|
||||
describe('github-url', () => {
|
||||
const cases = {
|
||||
'path.js': {path: 'path.js'},
|
||||
'owner/repo:path.js': {owner: 'owner', repo: 'repo', path: 'path.js'},
|
||||
'owner/repo:path/to/file.js': {owner: 'owner', repo: 'repo', path: 'path/to/file.js'},
|
||||
'path.js#ref': {path: 'path.js', ref: 'ref'},
|
||||
'owner/repo:path.js#ref': {owner: 'owner', repo: 'repo', path: 'path.js', ref: 'ref'}
|
||||
};
|
||||
|
||||
Object.keys(cases).forEach(path => {
|
||||
it(path, () => {
|
||||
expect(url(path)).toEqual(cases[path]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue