Skip to content

Commit 4268802

Browse files
committed
feature(Issue): add Milestone API
Closes github-tools#68 Closes github-tools#268
1 parent 3d761f5 commit 4268802

File tree

4 files changed

+153
-27
lines changed

4 files changed

+153
-27
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## master
44
### Features
5+
Added Milestone API
6+
* `Issue.listMilestones`
7+
* `Issue.getMilestone`
8+
* `Issue.editMilestone`
9+
* `Issue.deleteMilestone`
510

611
### Fixes
712

lib/Issue.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,62 @@ class Issue extends Requestable {
123123
getIssue(issue, cb) {
124124
return this._request('GET', `/repos/${this.__repository}/issues/${issue}`, null, cb);
125125
}
126+
127+
/**
128+
* List the milestones for the repository
129+
* @see https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
130+
* @param {Object} options - filtering options
131+
* @param {Requestable.callback} [cb] - will receive the array of milestones
132+
* @return {Promise} - the promise for the http request
133+
*/
134+
listMilestones(options, cb) {
135+
return this._request('GET', `/repos/${this.__repository}/milestones`, options, cb);
136+
}
137+
138+
/**
139+
* Get a milestone
140+
* @see https://developer.github.com/v3/issues/milestones/#get-a-single-milestone
141+
* @param {string} milestone - the id of the milestone to fetch
142+
* @param {Requestable.callback} [cb] - will receive the array of milestones
143+
* @return {Promise} - the promise for the http request
144+
*/
145+
getMilestone(milestone, cb) {
146+
return this._request('GET', `/repos/${this.__repository}/milestones/${milestone}`, null, cb);
147+
}
148+
149+
/**
150+
* Create a new milestone
151+
* @see https://developer.github.com/v3/issues/milestones/#create-a-milestone
152+
* @param {Object} milestoneData - the milestone definition
153+
* @param {Requestable.callback} [cb] - will receive the array of milestones
154+
* @return {Promise} - the promise for the http request
155+
*/
156+
createMilestone(milestoneData, cb) {
157+
return this._request('POST', `/repos/${this.__repository}/milestones`, milestoneData, cb);
158+
}
159+
160+
/**
161+
* Edit a milestone
162+
* @see https://developer.github.com/v3/issues/milestones/#update-a-milestone
163+
* @param {string} milestone - the id of the milestone to edit
164+
* @param {Object} milestoneData - the updates to make to the milestone
165+
* @param {Requestable.callback} [cb] - will receive the array of milestones
166+
* @return {Promise} - the promise for the http request
167+
*/
168+
editMilestone(milestone, milestoneData, cb) {
169+
return this._request('PATCH', `/repos/${this.__repository}/milestones/${milestone}`, milestoneData, cb);
170+
}
171+
172+
/**
173+
* Delete a milestone (this is distinct from closing a milestone)
174+
* @see https://developer.github.com/v3/issues/milestones/#delete-a-milestone
175+
* @param {string} milestone - the id of the milestone to delete
176+
* @param {Requestable.callback} [cb] - will receive the array of milestones
177+
* @return {Promise} - the promise for the http request
178+
*/
179+
deleteMilestone(milestone, cb) {
180+
return this._request('DELETE', `/repos/${this.__repository}/milestones/${milestone}`, null, cb);
181+
}
126182
}
127183

128184
module.exports = Issue;

lib/Requestable.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,14 @@ module.exports = Requestable;
228228
// ////////////////////////// //
229229
// Private helper functions //
230230
// ////////////////////////// //
231-
function buildError(path, response) {
232-
return {
233-
path: path,
234-
request: response.config,
235-
response: response,
236-
status: response.status
237-
};
231+
class ResponseError extends Error {
232+
constructor(path, response) {
233+
super(`error making request ${response.config.method} ${response.config.url}`);
234+
this.path = path;
235+
this.request = response.config;
236+
this.response = response;
237+
this.status = response.status;
238+
}
238239
}
239240

240241
const METHODS_WITH_NO_BODY = ['GET', 'HEAD', 'DELETE'];
@@ -256,10 +257,12 @@ function getNextPage(linksHeader = '') {
256257
function callbackErrorOrThrow(cb, path) {
257258
return function handler(response) {
258259
log(`error making request ${response.config.method} ${response.config.url} ${JSON.stringify(response.data)}`);
259-
let error = buildError(path, response);
260+
let error = new ResponseError(path, response);
260261
if (cb) {
262+
log('going to error callback');
261263
cb(error);
262264
} else {
265+
log('throwing error');
263266
throw error;
264267
}
265268
};

test/issue.spec.js

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import {assertSuccessful} from './helpers/callbacks';
77
describe('Issue', function() {
88
let github;
99
let remoteIssues;
10-
let remoteIssueId;
11-
let remoteIssueCommentId;
1210

1311
before(function() {
1412
github = new Github({
@@ -21,6 +19,9 @@ describe('Issue', function() {
2119
});
2220

2321
describe('reading', function() {
22+
let remoteIssueId;
23+
let milestoneId;
24+
2425
it('should list issues', function(done) {
2526
remoteIssues.listIssues({}, assertSuccessful(done, function(err, issues) {
2627
expect(issues).to.be.an.array();
@@ -37,9 +38,31 @@ describe('Issue', function() {
3738
done();
3839
}));
3940
});
41+
42+
it('should get all milestones', function(done) {
43+
remoteIssues.listMilestones()
44+
.then(({data: milestones}) => {
45+
expect(milestones).to.be.an.array();
46+
milestoneId = milestones[0].number;
47+
48+
done();
49+
}).catch(done);
50+
});
51+
52+
it('should get a single milestone', function(done) {
53+
remoteIssues.getMilestone(milestoneId)
54+
.then(({data: milestone}) => {
55+
expect(milestone).to.have.own('title', 'Default Milestone');
56+
done();
57+
}).catch(done);
58+
});
4059
});
4160

42-
describe('creating/modifiying/editing/deleting', function() {
61+
describe('creating/editing/deleting', function() {
62+
let createdIssueId;
63+
let issueCommentId;
64+
let createdMilestoneId;
65+
4366
// 200ms between tests so that Github has a chance to settle
4467
beforeEach(function(done) {
4568
setTimeout(done, 200);
@@ -52,6 +75,7 @@ describe('Issue', function() {
5275
};
5376

5477
remoteIssues.createIssue(newIssue, assertSuccessful(done, function(err, issue) {
78+
createdIssueId = issue.number;
5579
expect(issue).to.have.own('url');
5680
expect(issue).to.have.own('title', newIssue.title);
5781
expect(issue).to.have.own('body', newIssue.body);
@@ -60,56 +84,94 @@ describe('Issue', function() {
6084
}));
6185
});
6286

87+
it('should edit issue', function(done) {
88+
const newProps = {
89+
title: 'Edited title',
90+
state: 'closed'
91+
};
92+
93+
remoteIssues.editIssue(createdIssueId, newProps, assertSuccessful(done, function(err, issue) {
94+
expect(issue).to.have.own('title', newProps.title);
95+
96+
done();
97+
}));
98+
});
99+
63100
it('should post issue comment', function(done) {
64-
remoteIssues.createIssueComment(remoteIssueId, 'Comment test', assertSuccessful(done, function(err, issue) {
101+
remoteIssues.createIssueComment(createdIssueId, 'Comment test', assertSuccessful(done, function(err, issue) {
65102
expect(issue).to.have.own('body', 'Comment test');
66103

67104
done();
68105
}));
69106
});
70107

71108
it('should list issue comments', function(done) {
72-
remoteIssues.listIssueComments(remoteIssueId, assertSuccessful(done, function(err, comments) {
109+
remoteIssues.listIssueComments(createdIssueId, assertSuccessful(done, function(err, comments) {
73110
expect(comments).to.be.an.array();
74111
expect(comments[0]).to.have.own('body', 'Comment test');
75-
remoteIssueCommentId = comments[0].id
112+
issueCommentId = comments[0].id;
76113
done();
77114
}));
78115
});
79116

80117
it('should get a single issue comment', function(done) {
81-
remoteIssues.getIssueComment(remoteIssueCommentId, assertSuccessful(done, function(err, comment) {
118+
remoteIssues.getIssueComment(issueCommentId, assertSuccessful(done, function(err, comment) {
82119
expect(comment).to.have.own('body', 'Comment test');
83120
done();
84121
}));
85122
});
86123

87124
it('should edit issue comment', function(done) {
88-
remoteIssues.editIssueComment(remoteIssueCommentId, 'Comment test edited', assertSuccessful(done, function(err, comment) {
89-
expect(comment).to.have.own('body', 'Comment test edited');
125+
remoteIssues.editIssueComment(issueCommentId, 'Comment test edited',
126+
assertSuccessful(done, function(err, comment) {
127+
expect(comment).to.have.own('body', 'Comment test edited');
90128

91-
done();
92-
}));
129+
done();
130+
}));
93131
});
94132

95133
it('should delete issue comment', function(done) {
96-
remoteIssues.deleteIssueComment(remoteIssueCommentId, assertSuccessful(done, function(err, response) {
134+
remoteIssues.deleteIssueComment(issueCommentId, assertSuccessful(done, function(err, response) {
97135
expect(response).to.be.true;
98136

99137
done();
100138
}));
101139
});
102140

103-
it('should edit issues title', function(done) {
104-
const newProps = {
105-
title: 'Edited title'
141+
it('should create a milestone', function(done) {
142+
let milestone = {
143+
title: 'v42',
144+
description: 'The ultimate version'
106145
};
107146

108-
remoteIssues.editIssue(remoteIssueId, newProps, assertSuccessful(done, function(err, issue) {
109-
expect(issue).to.have.own('title', newProps.title);
147+
remoteIssues.createMilestone(milestone)
148+
.then(({data: createdMilestone}) => {
149+
expect(createdMilestone).to.have.own('number');
150+
expect(createdMilestone).to.have.own('title', milestone.title);
110151

111-
done();
112-
}));
152+
createdMilestoneId = createdMilestone.number;
153+
done();
154+
}).catch(done);
155+
});
156+
it('should update a milestone', function(done) {
157+
let milestone = {
158+
description: 'Version 6 * 7'
159+
};
160+
161+
remoteIssues.editMilestone(createdMilestoneId, milestone)
162+
.then(({data: createdMilestone}) => {
163+
expect(createdMilestone).to.have.own('number', createdMilestoneId);
164+
expect(createdMilestone).to.have.own('describe', milestone.description);
165+
166+
done();
167+
}).catch(done);
168+
});
169+
it('should delete a milestone', function(done) {
170+
remoteIssues.deleteMilestone(createdMilestoneId)
171+
.then(({status}) => {
172+
expect(status).to.equal(204);
173+
done();
174+
}).catch(done);
113175
});
114176
});
115177
});

0 commit comments

Comments
 (0)