'use strict';
/* jshint node:true */
var request = require('request'),
_ = require('underscore'),
querystring = require('querystring'),
fs = require('fs'),
path = require('path'),
Pivotal;
/**
* Pivotal API Interface
* @constructor
* @param {string} apiToken Pivotal API Token
*/
Pivotal = function Pivotal(apiToken) {
this.apiToken = apiToken;
this.baseUrl = 'https://www.pivotaltracker.com/services/v5/';
};
/**
* Update a story
* @param {String} projectId Pivotal project id
* @param {String} storyId Pivotal story id
* @param {Object} [params] Extra parameters
* @param {Function} [callback] function(error, response)
*/
Pivotal.prototype.updateStory = function updateStory(projectId, storyId, params, callback) {
this.api('put', 'projects/' + projectId + '/stories/' + storyId, {
json: params
}, callback);
};
/**
* Obtains a story
* @param {String} storyId Pivotal story id
* @param {Function} [callback] function(error, response)
*/
Pivotal.prototype.getStory = function getStory(storyId, callback) {
this.api('get', '/stories/' + storyId, {}, callback);
};
/**
* Obtains list of projects
* @param {Function} [callback] function(error, projects)
*/
Pivotal.prototype.getProjects = function getProjects(callback) {
this.api('get', '/projects', {}, callback);
};
/**
* Get paginated stories from project
* @param {String} projectId Pivotal project id
* @param {Object} [options] Extra options
* @param {Function} [callback] function(stories, pagination, callback(error or true to stop pagination))
* @param {Function} [completed] function(error)
* @param {Integer} [offset] Initial pagination offset
* @param {Integer} [limit] Pagination limit for each response
*/
Pivotal.prototype.getStories = function getStories(projectId, options, callback, completed, offset, limit) {
this.paginated('projects/' + projectId + '/stories', offset || 0, limit || 128, options, callback, completed);
};
/**
* Get paginated activity from project
* @param {String} projectId Pivotal project id
* @param {Object} [options] Extra options
* @param {Function} [callback] function(events, pagination, callback(error or true to stop pagination))
* @param {Function} [completed] function(error)
* @param {Integer} [offset] Initial pagination offset
* @param {Integer} [limit] Pagination limit for each response
*/
Pivotal.prototype.getActivity = function getActivity(projectId, options, callback, completed, offset, limit) {
this.paginated('projects/' + projectId + '/activity', offset || 0, limit || 128, options, callback, completed);
};
/**
* Get activity for story
* @param {String} projectId Pivotal project id
* @param {String} storyId Pivotal story id
* @param {Function} [callback] function(error, events)
* @param {Object} [options] Extra options
*/
Pivotal.prototype.getStoryActivity = function getStoryActivity(projectId, storyId, callback, options) {
this.api('get', 'projects/' + projectId + '/stories/' + storyId + '/activity', {
qs: options || {}
}, callback);
};
/**
* Get API token owner activity
* @param {Function} [callback] function(error, events)
* @param {Object} [options] Extra options
*/
Pivotal.prototype.getMyActivity = function getMyActivity(callback, options) {
this.api('get', 'my/activity', {
qs: options || {}
}, callback);
};
/**
* Get tasks for story
* @param {String} projectId Pivotal project id
* @param {String} storyId Pivotal story id
* @param {Function} [callback] function(error, tasks)
*/
Pivotal.prototype.getTasks = function getTasks(projectId, storyId, callback) {
this.api('get', 'projects/' + projectId + '/stories/' + storyId + '/tasks', {}, callback);
};
/**
* Get paginated iterations for project
* @param {String} projectId Pivotal project id
* @param {Object} [options] Extra options
* @param {Function} [callback] function(iterations, pagination, callback(error or true to stop pagination))
* @param {Function} [completed] function(error)
* @param {Integer} [offset] Initial pagination offset
* @param {Integer} [limit] Pagination limit for each response
*/
Pivotal.prototype.getIterations = function getIterations(projectId, options, callback, completed, offset, limit) {
this.paginated('projects/' + projectId + '/iterations', offset || 0, limit || 128, options, callback, completed);
};
/**
* Get current iteration stories from project
* @param {String} projectId Pivotal project id
* @param {Function} [callback] function(error, iterations)
*/
Pivotal.prototype.getCurrentIterations = function(projectId, callback) {
this.api('get', 'projects/' + projectId + '/iterations', {
qs: {
scope: 'current',
date_format: 'millis'
}
}, function(err, iterations) {
if (_.isFunction(callback)) {
if (err || !iterations) {
callback(err);
} else {
callback(false, iterations);
}
}
});
};
/**
* Get memberships for project
* @param {String} projectId Pivotal project id
* @param {Function} [callback] function(error, memberships)
*/
Pivotal.prototype.getMemberships = function updateStory(projectId, callback) {
this.api('get', 'projects/' + projectId + '/memberships', {}, callback);
};
/**
* Get comments from story
* @param {String} projectId Pivotal project id
* @param {String} storyId Pivotal story id
* @param {Function} [callback] function(error, comments)
*/
Pivotal.prototype.getComments = function getStories(projectId, storyId, callback) {
this.api('get', 'projects/' + projectId + '/stories/' + storyId + '/comments', {}, callback);
};
/**
* Export stories from Pivotal
* @param {String[]} stories List of story id's
* @param {Function} [callback] function(error, response)
*/
Pivotal.prototype.exportStories = function exportStories(stories, callback) {
this.api('post', 'stories/export', {
body: querystring.stringify({
'ids[]': stories
})
}, callback);
};
/**
* Post attachment to story
* @param {String} projectId Pivotal project id
* @param {String} storyId Pivotal story id
* @param {String} filepath Path of attachment
* @param {String} type Content type of attachment
* @param {String} comment Comment text
* @param {Function} [callback] function(error, response)
*/
Pivotal.prototype.postAttachment = function postAttachment(projectId, storyId, filepath, type, comment, callback) {
var that = this;
fs.readFile(filepath, function(err, contents) {
request.post({
url: "https://www.pivotaltracker.com/services/v5/projects/" + projectId + "/uploads",
multipart: [{
"Content-Disposition": "form-data; name=\"file\"; filename=\"" + path.basename(filepath) + "\"",
"Content-Type": type,
"body": contents
}],
headers: {
"X-TrackerToken": that.apiToken
}
}, function(err, res, upload) {
if (err || upload.kind == 'error') {
callback(err, {
success: false,
error: upload ? upload.error + ' (' + upload.general_problem + ')' : err
});
} else {
that.api('post', 'projects/' + projectId + '/stories/' + storyId + '/comments', {
json: {
text: comment,
file_attachments: [JSON.parse(upload)]
}
}, callback);
}
});
});
};
/**
* Get all labels in project
* @param {String} projectId Pivotal project id
* @param {Function} [callback] function(error, labels)
*/
Pivotal.prototype.getLabels = function getLabels(projectId, callback) {
this.api('get', 'projects/' + projectId + '/labels', {}, callback);
};
/**
* Create label
* @param {String} projectId Pivotal project id
* @param {String} name Name of label
* @param {Function} [callback] function(error, label)
*/
Pivotal.prototype.createLabel = function createStory(projectId, name, callback) {
this.api('post', 'projects/' + projectId + '/labels', {
body: {
name: name
}
}, callback);
};
/**
* Create new Pivotal story
* @param {String} projectId Pivotal project id
* @param {Object} [params] Story parameters
* @param {Function} [callback] function(error, story)
*/
Pivotal.prototype.createStory = function createStory(projectId, params, callback) {
this.api('post', 'projects/' + projectId + '/stories', {
body: params
}, callback);
};
Pivotal.prototype.paginated = function(path, offset, limit, options, callback, completed) {
var that = this;
var _options = {
qs: _.extend({
offset: offset,
limit: limit,
envelope: true
}, options)
};
this.api('get', path, _options, function(err, res) {
if (err || !res.pagination) {
completed && completed(err || res);
} else {
offset += res.pagination.returned;
callback && callback(res.data, res.pagination, function(err) {
if (!err) {
var left = res.pagination.total - offset;
if (left > 0) {
that.paginated(path, offset, Math.min(left, limit), options, callback, completed);
} else {
completed && completed();
}
} else {
completed && completed(err);
}
});
}
});
};
Pivotal.prototype.api = function api(method, path, options, callback) {
var opts = _.extend({
method: method,
url: this.baseUrl + path,
json: true,
headers: {
'X-TrackerToken': this.apiToken
}
}, options);
request(opts, function(err, response, result) {
if (_.isFunction(callback)) {
callback(err, result);
}
});
};
module.exports = Pivotal;